Starter Strategy
The Starter Strategy is a conversion of the MetaTrader 5 expert advisor "Starter (barabashkakvn's edition)". The system waits for the Commodity Channel Index (CCI) to rebound from extreme oversold or overbought territory and confirms the move with the slope of a long-term moving average. When the momentum agrees with the trend filter, the strategy opens a single market position whose size is determined by a configurable risk percentage of the portfolio. Protective stops and an optional trailing mechanism reproduce the money-management rules from the original expert.
Trading Logic
- Trend filter – a configurable moving average (MA) must be rising faster than
MaDeltato allow long trades and falling faster thanMaDeltato allow short trades. The strategy supports the same smoothing methods as the MQL version (simple, exponential, smoothed, linear weighted). - CCI confirmation – the Commodity Channel Index must cross back above
-CciLevelfrom below to trigger long entries and cross back belowCciLevelfrom above to trigger shorts. The indicator is evaluated on finished candles only, mirroring the original bar-by-bar processing. - Single position model – the algorithm keeps at most one open position. New signals are ignored until the current trade is closed, matching the MetaTrader logic that filters by magic number and symbol.
Entry Rules
- Wait for the close of a candle.
- Calculate the latest and previous values of the moving average at the configured shifts.
- Calculate the current and previous CCI readings.
- Go long when:
- The moving average slope exceeds
MaDelta(current MA minus previous MA). - The current CCI value is greater than the previous one.
- The CCI crosses upward through
-CciLevel(previous below the threshold, current above).
- The moving average slope exceeds
- Go short when:
- The moving average slope is below
-MaDelta. - The current CCI value is smaller than the previous one.
- The CCI crosses downward through
CciLevel(previous above the threshold, current below).
- The moving average slope is below
Exit Rules
- Initial stop-loss – if
StopLossPipsis greater than zero, the filled entry price is offset byStopLossPips * PriceStepto compute an initial protective stop. - Trailing stop – when both
TrailingStopPipsandTrailingStepPipsare positive, the stop is advanced whenever price improves by at least the configured step. Long trades move the stop toClose - TrailingStop, shorts toClose + TrailingStop. - Manual exit – if price touches the stop level inside the candle range, the strategy closes the position with a market order and resets the protection state.
Risk Management
- Position sizing – base volume is
Portfolio.CurrentValue * MaximumRisk / price. When the broker or back-end reports an invalid equity value, the strategy falls back to the manualVolumeproperty (default 1). - Loss streak reduction – after two or more consecutive losing trades the volume is reduced by
volume * losses / DecreaseFactor, mimicking the originalDecreaseFactorrule. Any profitable trade resets the loss counter.
Parameters
| Parameter | Default | Description |
|---|---|---|
MaximumRisk |
0.02 |
Fraction of equity risked per trade when sizing the position. |
DecreaseFactor |
3 |
Lot reduction divisor applied after two or more consecutive losing trades. |
CciPeriod |
14 |
Number of bars used by the Commodity Channel Index. |
CciLevel |
100 |
Oversold/overbought threshold for CCI crossings. |
CciCurrentBar |
0 |
Shift of the current CCI value (0 = latest candle). |
CciPreviousBar |
1 |
Shift of the previous CCI value. |
MaPeriod |
120 |
Period of the moving average trend filter. |
MaMethod |
Simple |
Moving average smoothing method (Simple, Exponential, Smoothed, LinearWeighted). |
MaCurrentBar |
0 |
Shift applied to the moving average value. |
MaDelta |
0.001 |
Minimum slope difference between current and previous MA readings. |
StopLossPips |
0 |
Initial stop-loss distance in pips (0 disables the stop). |
TrailingStopPips |
5 |
Base trailing stop distance in pips (0 disables trailing). |
TrailingStepPips |
5 |
Minimum pip improvement before the trailing stop is advanced. |
CandleType |
30m time frame |
Primary candle subscription processed by the strategy. |
Implementation Notes
- Indicator buffers are cached internally so the strategy can access historical values for arbitrary shifts, replicating the MQL approach of indexing indicator arrays.
- The pip size is derived from
Security.PriceStep. If the instrument does not report a valid price step the stop and trailing distances are treated as zero. - All comments inside the code are written in English per repository guidelines.
- The Python version is intentionally omitted as requested.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Conversion of the MetaTrader 5 expert advisor "Starter".
/// Uses the Commodity Channel Index and a moving average slope filter to open trades with adaptive position sizing and trailing protection.
/// </summary>
public class StarterStrategy : Strategy
{
private readonly StrategyParam<decimal> _maximumRisk;
private readonly StrategyParam<decimal> _decreaseFactor;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _cciLevel;
private readonly StrategyParam<int> _cciCurrentBar;
private readonly StrategyParam<int> _cciPreviousBar;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<MovingAverageMethods> _maMethod;
private readonly StrategyParam<int> _maCurrentBar;
private readonly StrategyParam<decimal> _maDelta;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<DataType> _candleType;
private CommodityChannelIndex _cci = null!;
private DecimalLengthIndicator _movingAverage = null!;
private readonly List<decimal> _cciHistory = new();
private readonly List<decimal> _maHistory = new();
private decimal _pipSize;
private int _historyCapacity;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longStop;
private decimal? _shortStop;
private decimal _signedPosition;
private Sides? _lastEntrySide;
private decimal _lastEntryPrice;
private int _consecutiveLosses;
/// <summary>
/// Initializes a new instance of the <see cref="StarterStrategy"/> class.
/// </summary>
public StarterStrategy()
{
_maximumRisk = Param(nameof(MaximumRisk), 0.02m)
.SetNotNegative()
.SetDisplay("Maximum Risk", "Fraction of portfolio equity risked per trade", "Risk Management");
_decreaseFactor = Param(nameof(DecreaseFactor), 3m)
.SetNotNegative()
.SetDisplay("Decrease Factor", "Lot reduction factor after consecutive losses", "Risk Management");
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Number of bars for the Commodity Channel Index", "Indicators")
.SetOptimize(5, 60, 1);
_cciLevel = Param(nameof(CciLevel), 100m)
.SetGreaterThanZero()
.SetDisplay("CCI Level", "Threshold used for oversold/overbought detection", "Indicators")
.SetOptimize(50m, 200m, 10m);
_cciCurrentBar = Param(nameof(CciCurrentBar), 0)
.SetNotNegative()
.SetDisplay("CCI Current Bar", "Shift for the current CCI value", "Indicators");
_cciPreviousBar = Param(nameof(CciPreviousBar), 1)
.SetNotNegative()
.SetDisplay("CCI Previous Bar", "Shift for the previous CCI value", "Indicators");
_maPeriod = Param(nameof(MaPeriod), 120)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Number of bars for the moving average", "Indicators")
.SetOptimize(20, 200, 5);
_maMethod = Param(nameof(MaMethod), MovingAverageMethods.Simple)
.SetDisplay("MA Method", "Smoothing method applied to the moving average", "Indicators");
_maCurrentBar = Param(nameof(MaCurrentBar), 0)
.SetNotNegative()
.SetDisplay("MA Current Bar", "Shift for the moving average", "Indicators");
_maDelta = Param(nameof(MaDelta), 0.001m)
.SetNotNegative()
.SetDisplay("MA Delta", "Minimum slope difference between current and previous MA", "Signals")
.SetOptimize(0.0001m, 0.01m, 0.0001m);
_stopLossPips = Param(nameof(StopLossPips), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Initial protective stop distance in pips", "Risk Management")
.SetOptimize(0m, 200m, 10m);
_trailingStopPips = Param(nameof(TrailingStopPips), 5m)
.SetNotNegative()
.SetDisplay("Trailing Stop (pips)", "Base trailing distance in pips", "Risk Management")
.SetOptimize(0m, 200m, 5m);
_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step (pips)", "Minimum improvement required before moving the trailing stop", "Risk Management")
.SetOptimize(0m, 200m, 5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe processed by the strategy", "General");
}
/// <summary>
/// Risk per trade expressed as a fraction of portfolio equity.
/// </summary>
public decimal MaximumRisk
{
get => _maximumRisk.Value;
set => _maximumRisk.Value = value;
}
/// <summary>
/// Lot reduction factor applied after consecutive losing trades.
/// </summary>
public decimal DecreaseFactor
{
get => _decreaseFactor.Value;
set => _decreaseFactor.Value = value;
}
/// <summary>
/// Period for the Commodity Channel Index indicator.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Overbought/oversold CCI threshold.
/// </summary>
public decimal CciLevel
{
get => _cciLevel.Value;
set => _cciLevel.Value = value;
}
/// <summary>
/// Index of the bar considered "current" for CCI comparisons.
/// </summary>
public int CciCurrentBar
{
get => _cciCurrentBar.Value;
set => _cciCurrentBar.Value = value;
}
/// <summary>
/// Index of the bar considered "previous" for CCI comparisons.
/// </summary>
public int CciPreviousBar
{
get => _cciPreviousBar.Value;
set => _cciPreviousBar.Value = value;
}
/// <summary>
/// Period for the trend filter moving average.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Moving average smoothing method.
/// </summary>
public MovingAverageMethods MaMethod
{
get => _maMethod.Value;
set => _maMethod.Value = value;
}
/// <summary>
/// Shift for the moving average value considered "current".
/// </summary>
public int MaCurrentBar
{
get => _maCurrentBar.Value;
set => _maCurrentBar.Value = value;
}
/// <summary>
/// Minimum slope difference between current and previous moving average values.
/// </summary>
public decimal MaDelta
{
get => _maDelta.Value;
set => _maDelta.Value = value;
}
/// <summary>
/// Initial stop-loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimum improvement before advancing the trailing stop in pips.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Candle data type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cci?.Reset();
_movingAverage?.Reset();
_cci = null!;
_movingAverage = null!;
_cciHistory.Clear();
_maHistory.Clear();
_pipSize = 0m;
_historyCapacity = 0;
_longEntryPrice = null;
_shortEntryPrice = null;
_longStop = null;
_shortStop = null;
_signedPosition = 0m;
_lastEntrySide = null;
_lastEntryPrice = 0m;
_consecutiveLosses = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = GetPipSize();
_historyCapacity = CalculateHistoryCapacity();
_cciHistory.Clear();
_maHistory.Clear();
_longEntryPrice = null;
_shortEntryPrice = null;
_longStop = null;
_shortStop = null;
_signedPosition = 0m;
_lastEntrySide = null;
_lastEntryPrice = 0m;
_consecutiveLosses = 0;
_cci = new CommodityChannelIndex { Length = CciPeriod };
_movingAverage = CreateMovingAverage(MaMethod, MaPeriod);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_cci, _movingAverage, OnProcessCandle)
.Start();
}
private void OnProcessCandle(ICandleMessage candle, decimal cciValue, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_cci.IsFormed || !_movingAverage.IsFormed)
return;
// Store the latest indicator values so we can access shifted history like in MetaTrader.
AddHistory(_cciHistory, cciValue);
AddHistory(_maHistory, maValue);
if (Position != 0)
{
// Manage trailing stop and protective exits for open positions before evaluating new entries.
UpdateTrailing(candle);
CheckProtectiveStops(candle);
}
if (Position != 0)
// The original expert only opens a new position when no trades are active.
return;
if (!TryGetHistoryValue(_maHistory, MaCurrentBar, out var maCurrent) ||
!TryGetHistoryValue(_maHistory, MaCurrentBar + 1, out var maPrevious))
return;
if (!TryGetHistoryValue(_cciHistory, CciCurrentBar, out var cciCurrent) ||
!TryGetHistoryValue(_cciHistory, CciPreviousBar, out var cciPrevious))
return;
// Compare the moving average slope and CCI swings to detect breakout conditions.
var maSlope = maCurrent - maPrevious;
if (maSlope > MaDelta && cciCurrent > cciPrevious &&
cciCurrent > -CciLevel && cciPrevious < -CciLevel)
{
TryEnterLong(candle.ClosePrice);
}
else if (maSlope < -MaDelta && cciCurrent < cciPrevious &&
cciCurrent < CciLevel && cciPrevious > CciLevel)
{
TryEnterShort(candle.ClosePrice);
}
}
private void TryEnterLong(decimal price)
{
var volume = CalculateTradeVolume(price);
if (volume <= 0m)
return;
BuyMarket(volume);
LogInfo($"Opening long position at {price} with volume {volume}.");
}
private void TryEnterShort(decimal price)
{
var volume = CalculateTradeVolume(price);
if (volume <= 0m)
return;
SellMarket(volume);
LogInfo($"Opening short position at {price} with volume {volume}.");
}
private void CheckProtectiveStops(ICandleMessage candle)
{
if (Position > 0 && _longStop.HasValue && candle.LowPrice <= _longStop.Value)
{
var volume = Math.Abs(Position);
if (volume > 0)
{
SellMarket(volume);
LogInfo($"Long stop-loss triggered at {_longStop.Value}.");
}
ResetLongProtection();
return;
}
if (Position < 0 && _shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
{
var volume = Math.Abs(Position);
if (volume > 0)
{
BuyMarket(volume);
LogInfo($"Short stop-loss triggered at {_shortStop.Value}.");
}
ResetShortProtection();
}
}
private void UpdateTrailing(ICandleMessage candle)
{
if (TrailingStopPips <= 0m || _pipSize <= 0m)
return;
var offset = TrailingStopPips * _pipSize;
var step = TrailingStepPips * _pipSize;
if (Position > 0 && _longEntryPrice.HasValue)
{
// Advance the long stop only when price improves by at least the configured step.
var targetStop = candle.ClosePrice - offset;
var threshold = candle.ClosePrice - (offset + step);
if (!_longStop.HasValue || _longStop.Value < threshold)
{
_longStop = targetStop;
LogInfo($"Trailing long stop moved to {_longStop.Value}.");
}
}
else if (Position < 0 && _shortEntryPrice.HasValue)
{
// Mirror the trailing logic for short positions.
var targetStop = candle.ClosePrice + offset;
var threshold = candle.ClosePrice + (offset + step);
if (!_shortStop.HasValue || _shortStop.Value > threshold)
{
_shortStop = targetStop;
LogInfo($"Trailing short stop moved to {_shortStop.Value}.");
}
}
}
private decimal CalculateTradeVolume(decimal price)
{
// Start from the configured strategy volume; fall back to 1 if undefined.
var baseVolume = Volume > 0 ? Volume : 1m;
if (price <= 0m)
return NormalizeVolume(baseVolume);
var equity = Portfolio?.CurrentValue ?? 0m;
if (equity <= 0m || MaximumRisk <= 0m)
return NormalizeVolume(baseVolume);
// Position size equals equity * risk percent divided by price, mimicking the original risk formula.
var volume = equity * MaximumRisk / price;
if (DecreaseFactor > 0m && _consecutiveLosses > 1)
{
// Reduce the lot size after two or more losses, replicating MetaTrader's "DecreaseFactor" behavior.
var reduction = volume * _consecutiveLosses / DecreaseFactor;
volume -= reduction;
}
if (volume <= 0m)
volume = baseVolume;
return NormalizeVolume(volume);
}
private decimal NormalizeVolume(decimal volume)
{
var security = Security;
if (security != null)
{
var step = security.VolumeStep ?? 1m;
if (step <= 0m)
step = 1m;
if (volume < step)
volume = step;
var steps = Math.Floor(volume / step);
if (steps < 1m)
steps = 1m;
volume = steps * step;
}
if (volume <= 0m)
volume = 1m;
return volume;
}
private void AddHistory(List<decimal> history, decimal value)
{
history.Add(value);
if (history.Count > _historyCapacity)
history.RemoveRange(0, history.Count - _historyCapacity);
}
private static bool TryGetHistoryValue(List<decimal> history, int shift, out decimal value)
{
value = default;
if (shift < 0)
return false;
var index = history.Count - 1 - shift;
if (index < 0 || index >= history.Count)
return false;
value = history[index];
return true;
}
private void ResetLongProtection()
{
_longEntryPrice = null;
_longStop = null;
}
private void ResetShortProtection()
{
_shortEntryPrice = null;
_shortStop = null;
}
private decimal GetPipSize()
{
var security = Security;
if (security == null)
return 0m;
var step = security.PriceStep ?? 0m;
if (step <= 0m)
return 0m;
return step;
}
private int CalculateHistoryCapacity()
{
var cciRequirement = Math.Max(CciCurrentBar, CciPreviousBar) + CciPeriod + 5;
var maRequirement = MaCurrentBar + MaPeriod + 5;
return Math.Max(cciRequirement, maRequirement);
}
private static DecimalLengthIndicator CreateMovingAverage(MovingAverageMethods method, int period)
{
return method switch
{
MovingAverageMethods.Simple => new SMA { Length = period },
MovingAverageMethods.Exponential => new EMA { Length = period },
MovingAverageMethods.Smoothed => new SmoothedMovingAverage { Length = period },
MovingAverageMethods.LinearWeighted => new WeightedMovingAverage { Length = period },
_ => new SMA { Length = period }
};
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
// Track executed volume to update position state and the loss streak counter.
var volume = trade.Trade.Volume;
if (volume <= 0m)
return;
var delta = trade.Order.Side == Sides.Buy ? volume : -volume;
var previousPosition = _signedPosition;
_signedPosition += delta;
if (previousPosition == 0m && _signedPosition != 0m)
{
_lastEntrySide = trade.Order.Side;
_lastEntryPrice = trade.Trade.Price;
if (_lastEntrySide == Sides.Buy)
{
_longEntryPrice = trade.Trade.Price;
_longStop = StopLossPips > 0m && _pipSize > 0m ? _lastEntryPrice - (StopLossPips * _pipSize) : null;
ResetShortProtection();
}
else if (_lastEntrySide == Sides.Sell)
{
_shortEntryPrice = trade.Trade.Price;
_shortStop = StopLossPips > 0m && _pipSize > 0m ? _lastEntryPrice + (StopLossPips * _pipSize) : null;
ResetLongProtection();
}
}
else if (previousPosition != 0m && _signedPosition == 0m)
{
var exitPrice = trade.Trade.Price;
if (_lastEntrySide != null && _lastEntryPrice != 0m)
{
var profit = _lastEntrySide == Sides.Buy
? exitPrice - _lastEntryPrice
: _lastEntryPrice - exitPrice;
if (profit > 0m)
{
_consecutiveLosses = 0;
}
else if (profit < 0m)
{
_consecutiveLosses++;
}
}
_lastEntrySide = null;
_lastEntryPrice = 0m;
ResetLongProtection();
ResetShortProtection();
}
}
public enum MovingAverageMethods
{
/// <summary>
/// Simple moving average (equivalent to MODE_SMA in MetaTrader).
/// </summary>
Simple,
/// <summary>
/// Exponential moving average (MODE_EMA).
/// </summary>
Exponential,
/// <summary>
/// Smoothed moving average (MODE_SMMA).
/// </summary>
Smoothed,
/// <summary>
/// Linear weighted moving average (MODE_LWMA).
/// </summary>
LinearWeighted
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Indicators import (
CommodityChannelIndex, SimpleMovingAverage,
ExponentialMovingAverage, SmoothedMovingAverage,
WeightedMovingAverage
)
from StockSharp.Algo.Strategies import Strategy
class starter_strategy(Strategy):
def __init__(self):
super(starter_strategy, self).__init__()
self._maximum_risk = self.Param("MaximumRisk", 0.02) \
.SetDisplay("Maximum Risk", "Fraction of portfolio equity risked per trade", "Risk Management")
self._decrease_factor = self.Param("DecreaseFactor", 3.0) \
.SetDisplay("Decrease Factor", "Lot reduction factor after consecutive losses", "Risk Management")
self._cci_period = self.Param("CciPeriod", 14) \
.SetDisplay("CCI Period", "Number of bars for the Commodity Channel Index", "Indicators")
self._cci_level = self.Param("CciLevel", 100.0) \
.SetDisplay("CCI Level", "Threshold used for oversold/overbought detection", "Indicators")
self._cci_current_bar = self.Param("CciCurrentBar", 0) \
.SetDisplay("CCI Current Bar", "Shift for the current CCI value", "Indicators")
self._cci_previous_bar = self.Param("CciPreviousBar", 1) \
.SetDisplay("CCI Previous Bar", "Shift for the previous CCI value", "Indicators")
self._ma_period = self.Param("MaPeriod", 120) \
.SetDisplay("MA Period", "Number of bars for the moving average", "Indicators")
self._ma_current_bar = self.Param("MaCurrentBar", 0) \
.SetDisplay("MA Current Bar", "Shift for the moving average", "Indicators")
self._ma_delta = self.Param("MaDelta", 0.001) \
.SetDisplay("MA Delta", "Minimum slope difference between current and previous MA", "Signals")
self._stop_loss_pips = self.Param("StopLossPips", 0.0) \
.SetDisplay("Stop Loss (pips)", "Initial protective stop distance in pips", "Risk Management")
self._trailing_stop_pips = self.Param("TrailingStopPips", 5.0) \
.SetDisplay("Trailing Stop (pips)", "Base trailing distance in pips", "Risk Management")
self._trailing_step_pips = self.Param("TrailingStepPips", 5.0) \
.SetDisplay("Trailing Step (pips)", "Minimum improvement required before moving the trailing stop", "Risk Management")
self._cci = None
self._moving_average = None
self._cci_history = []
self._ma_history = []
self._pip_size = 0.0
self._history_capacity = 0
self._long_entry_price = None
self._short_entry_price = None
self._long_stop = None
self._short_stop = None
self._signed_position = 0.0
self._last_entry_side = None
self._last_entry_price = 0.0
self._consecutive_losses = 0
@property
def maximum_risk(self):
return self._maximum_risk.Value
@property
def decrease_factor(self):
return self._decrease_factor.Value
@property
def cci_period(self):
return self._cci_period.Value
@property
def cci_level(self):
return self._cci_level.Value
@property
def cci_current_bar(self):
return self._cci_current_bar.Value
@property
def cci_previous_bar(self):
return self._cci_previous_bar.Value
@property
def ma_period(self):
return self._ma_period.Value
@property
def ma_current_bar(self):
return self._ma_current_bar.Value
@property
def ma_delta(self):
return self._ma_delta.Value
@property
def stop_loss_pips(self):
return self._stop_loss_pips.Value
@property
def trailing_stop_pips(self):
return self._trailing_stop_pips.Value
@property
def trailing_step_pips(self):
return self._trailing_step_pips.Value
def OnReseted(self):
super(starter_strategy, self).OnReseted()
self._cci = None
self._moving_average = None
self._cci_history = []
self._ma_history = []
self._pip_size = 0.0
self._history_capacity = 0
self._long_entry_price = None
self._short_entry_price = None
self._long_stop = None
self._short_stop = None
self._signed_position = 0.0
self._last_entry_side = None
self._last_entry_price = 0.0
self._consecutive_losses = 0
def OnStarted2(self, time):
super(starter_strategy, self).OnStarted2(time)
self._pip_size = self._get_pip_size()
self._history_capacity = self._calc_history_capacity()
self._cci_history = []
self._ma_history = []
self._long_entry_price = None
self._short_entry_price = None
self._long_stop = None
self._short_stop = None
self._signed_position = 0.0
self._last_entry_side = None
self._last_entry_price = 0.0
self._consecutive_losses = 0
self._cci = CommodityChannelIndex()
self._cci.Length = self.cci_period
self._moving_average = SimpleMovingAverage()
self._moving_average.Length = self.ma_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(30)))
subscription.Bind(self._cci, self._moving_average, self._on_process_candle)
subscription.Start()
def _on_process_candle(self, candle, cci_value, ma_value):
if candle.State != CandleStates.Finished:
return
if not self._cci.IsFormed or not self._moving_average.IsFormed:
return
cci_val = float(cci_value)
ma_val = float(ma_value)
self._add_history(self._cci_history, cci_val)
self._add_history(self._ma_history, ma_val)
if self.Position != 0:
self._update_trailing(candle)
self._check_protective_stops(candle)
if self.Position != 0:
return
ma_current = self._try_get_history(self._ma_history, self.ma_current_bar)
ma_previous = self._try_get_history(self._ma_history, self.ma_current_bar + 1)
if ma_current is None or ma_previous is None:
return
cci_current = self._try_get_history(self._cci_history, self.cci_current_bar)
cci_previous = self._try_get_history(self._cci_history, self.cci_previous_bar)
if cci_current is None or cci_previous is None:
return
ma_slope = ma_current - ma_previous
cci_lev = float(self.cci_level)
ma_d = float(self.ma_delta)
if (ma_slope > ma_d and cci_current > cci_previous and
cci_current > -cci_lev and cci_previous < -cci_lev):
self._try_enter_long(float(candle.ClosePrice))
elif (ma_slope < -ma_d and cci_current < cci_previous and
cci_current < cci_lev and cci_previous > cci_lev):
self._try_enter_short(float(candle.ClosePrice))
def _try_enter_long(self, price):
volume = self._calculate_trade_volume(price)
if volume <= 0.0:
return
self.BuyMarket(volume)
def _try_enter_short(self, price):
volume = self._calculate_trade_volume(price)
if volume <= 0.0:
return
self.SellMarket(volume)
def _check_protective_stops(self, candle):
if self.Position > 0 and self._long_stop is not None and float(candle.LowPrice) <= self._long_stop:
vol = abs(float(self.Position))
if vol > 0:
self.SellMarket(vol)
self._reset_long_protection()
return
if self.Position < 0 and self._short_stop is not None and float(candle.HighPrice) >= self._short_stop:
vol = abs(float(self.Position))
if vol > 0:
self.BuyMarket(vol)
self._reset_short_protection()
def _update_trailing(self, candle):
trail_pips = float(self.trailing_stop_pips)
if trail_pips <= 0.0 or self._pip_size <= 0.0:
return
offset = trail_pips * self._pip_size
step_pips = float(self.trailing_step_pips)
step = step_pips * self._pip_size
close = float(candle.ClosePrice)
if self.Position > 0 and self._long_entry_price is not None:
target_stop = close - offset
threshold = close - (offset + step)
if self._long_stop is None or self._long_stop < threshold:
self._long_stop = target_stop
elif self.Position < 0 and self._short_entry_price is not None:
target_stop = close + offset
threshold = close + (offset + step)
if self._short_stop is None or self._short_stop > threshold:
self._short_stop = target_stop
def _calculate_trade_volume(self, price):
base_volume = float(self.Volume) if self.Volume > 0 else 1.0
if price <= 0.0:
return self._normalize_volume(base_volume)
equity = float(self.Portfolio.CurrentValue) if self.Portfolio is not None and self.Portfolio.CurrentValue is not None else 0.0
max_risk = float(self.maximum_risk)
if equity <= 0.0 or max_risk <= 0.0:
return self._normalize_volume(base_volume)
volume = equity * max_risk / price
dec_factor = float(self.decrease_factor)
if dec_factor > 0.0 and self._consecutive_losses > 1:
reduction = volume * self._consecutive_losses / dec_factor
volume -= reduction
if volume <= 0.0:
volume = base_volume
return self._normalize_volume(volume)
def _normalize_volume(self, volume):
if self.Security is not None and self.Security.VolumeStep is not None:
step = float(self.Security.VolumeStep)
if step <= 0.0:
step = 1.0
if volume < step:
volume = step
steps = math.floor(volume / step)
if steps < 1.0:
steps = 1.0
volume = steps * step
if volume <= 0.0:
volume = 1.0
return volume
def _add_history(self, history, value):
history.append(value)
if len(history) > self._history_capacity:
del history[:len(history) - self._history_capacity]
def _try_get_history(self, history, shift):
if shift < 0:
return None
index = len(history) - 1 - shift
if index < 0 or index >= len(history):
return None
return history[index]
def _reset_long_protection(self):
self._long_entry_price = None
self._long_stop = None
def _reset_short_protection(self):
self._short_entry_price = None
self._short_stop = None
def _get_pip_size(self):
if self.Security is None:
return 0.0
step = float(self.Security.PriceStep) if self.Security.PriceStep is not None else 0.0
if step <= 0.0:
return 0.0
return step
def _calc_history_capacity(self):
cci_req = max(self.cci_current_bar, self.cci_previous_bar) + self.cci_period + 5
ma_req = self.ma_current_bar + self.ma_period + 5
return max(cci_req, ma_req)
def OnOwnTradeReceived(self, trade):
super(starter_strategy, self).OnOwnTradeReceived(trade)
volume = float(trade.Trade.Volume)
if volume <= 0.0:
return
delta = volume if trade.Order.Side == Sides.Buy else -volume
previous_position = self._signed_position
self._signed_position += delta
if previous_position == 0.0 and self._signed_position != 0.0:
self._last_entry_side = trade.Order.Side
self._last_entry_price = float(trade.Trade.Price)
sl_pips = float(self.stop_loss_pips)
if self._last_entry_side == Sides.Buy:
self._long_entry_price = self._last_entry_price
self._long_stop = (self._last_entry_price - sl_pips * self._pip_size) if (sl_pips > 0.0 and self._pip_size > 0.0) else None
self._reset_short_protection()
else:
self._short_entry_price = self._last_entry_price
self._short_stop = (self._last_entry_price + sl_pips * self._pip_size) if (sl_pips > 0.0 and self._pip_size > 0.0) else None
self._reset_long_protection()
elif previous_position != 0.0 and self._signed_position == 0.0:
exit_price = float(trade.Trade.Price)
if self._last_entry_side is not None and self._last_entry_price != 0.0:
if self._last_entry_side == Sides.Buy:
profit = exit_price - self._last_entry_price
else:
profit = self._last_entry_price - exit_price
if profit > 0.0:
self._consecutive_losses = 0
elif profit < 0.0:
self._consecutive_losses += 1
self._last_entry_side = None
self._last_entry_price = 0.0
self._reset_long_protection()
self._reset_short_protection()
def CreateClone(self):
return starter_strategy()