The CCI and Martin strategy searches for sharp reversals after a short bearish or bullish sequence and confirms the move with the Commodity Channel Index. The logic replicates the original MetaTrader 5 expert advisor while using StockSharp's high-level API. The strategy works on finished candles only and can operate on any instrument for which CCI values and price steps are available.
Trading Rules
Bullish setup
Candle -2 and candle -1 must both be bearish (open greater than close).
Candle 0 must close above its open and above the open of candle -1.
CCI on candle -1 must be below +5, below the value of candle -2, and both -2 and -3 must show a decreasing sequence. The current CCI (candle 0) must turn upward above the previous value.
When all conditions hold and no position is open, the strategy enters a long trade.
Bearish setup
Candle -2 and candle -1 must both be bullish (open less than close).
Candle 0 must close below its open and below the open of candle -1.
CCI on candle -1 must be above -5, above the value of candle -2, and both -2 and -3 must form an increasing sequence. The current CCI (candle 0) must turn downward below the previous value.
When all conditions hold and no position is open, the strategy enters a short trade.
The algorithm monitors only completed candles. The original MQL implementation waited 40 seconds after the minute open to avoid premature signals; using finished candles makes this filter unnecessary.
Risk Management
Stop-loss and take-profit distances are defined in pips. They are converted to price offsets by multiplying the instrument's price step by ten when the step corresponds to a 3- or 5-digit quote, mirroring the original pip calculation.
Trailing stop becomes active after the price advances by the trailing stop distance plus the trailing step. The stop is then moved to maintain the trailing distance and only advances when price improvement exceeds the configured step.
If stop-loss or take-profit is set to zero the respective exit is disabled. Trailing requires both stop distance and step to be positive.
Volume Management
Two optional position-sizing engines can alter the lot size after each trade.
Martingale scaling multiplies the current volume by the martingale coefficient once the number of consecutive losses reaches the trigger. Scaling stops after the configured number of martingale steps. Any profitable trade resets the volume to the initial value.
Step adjustments increment the volume by a fixed amount either after losses or after profits, depending on the selected mode. The increment is normalised to the instrument's volume step and capped by the maximum volume parameter. When the limit is exceeded or a trade does not meet the trigger condition, the volume falls back to the initial size.
The original expert advisor forbids enabling martingale and step logic simultaneously; the C# port enforces the same restriction.
Parameters
CandleType – candle series used for analysis.
CciPeriod – averaging length for the Commodity Channel Index.
InitialVolume – base order size before any scaling.
StopLossPips – stop-loss distance expressed in pips.
TakeProfitPips – take-profit distance expressed in pips.
TrailingStopPips – trailing stop distance in pips (0 disables trailing).
TrailingStepPips – minimum price improvement required before the trailing stop moves.
EnableMartingale – activates martingale-style scaling after losses.
MartingaleCoefficient – multiplier applied to the current volume for martingale trades.
MartingaleTriggerLosses – number of consecutive losing trades needed before scaling.
MartingaleMaxSteps – maximum number of martingale multiplications.
StepVolumeIncrement – absolute increment applied when the step rule triggers.
StepVolumeMax – upper bound for the step-based volume.
StepAdjustmentMode – selects whether the step increment fires after a loss or after a profit.
Notes
The strategy assumes market orders fill close to the requested price. Protective logic recalculates stops on every finished candle to emulate the tick-based trailing in the original EA.
If the instrument's price step does not correspond to classic FX quoting the pip conversion still works, but pip-based distances may represent different monetary values.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// CCI based strategy with martingale-style entry.
/// Buys when CCI crosses above oversold level, sells when CCI crosses below overbought level.
/// </summary>
public class CCIAndMartinStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<decimal> _oversold;
private decimal? _prevCci;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
public CCIAndMartinStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_cciPeriod = Param(nameof(CciPeriod), 27)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI indicator length", "Indicators");
_overbought = Param(nameof(Overbought), 100m)
.SetDisplay("Overbought", "CCI overbought level", "Indicators");
_oversold = Param(nameof(Oversold), -100m)
.SetDisplay("Oversold", "CCI oversold level", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = null;
var cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, cci);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevCci = cciValue;
return;
}
if (_prevCci == null)
{
_prevCci = cciValue;
return;
}
var prev = _prevCci.Value;
_prevCci = cciValue;
// Buy signal: CCI crosses above oversold level from below
if (prev < Oversold && cciValue >= Oversold && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Sell signal: CCI crosses below overbought level from above
else if (prev > Overbought && cciValue <= Overbought && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
import clr
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
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_and_martin_strategy(Strategy):
def __init__(self):
super(cci_and_martin_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._cci_period = self.Param("CciPeriod", 27) \
.SetDisplay("CCI Period", "CCI indicator length", "Indicators")
self._overbought = self.Param("Overbought", 100.0) \
.SetDisplay("Overbought", "CCI overbought level", "Indicators")
self._oversold = self.Param("Oversold", -100.0) \
.SetDisplay("Oversold", "CCI oversold level", "Indicators")
self._prev_cci = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def CciPeriod(self):
return self._cci_period.Value
@property
def Overbought(self):
return self._overbought.Value
@property
def Oversold(self):
return self._oversold.Value
def OnReseted(self):
super(cci_and_martin_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(cci_and_martin_strategy, self).OnStarted2(time)
self._prev_cci = None
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cci)
self.DrawOwnTrades(area)
def _on_process(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
cv = float(cci_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_cci = cv
return
if self._prev_cci is None:
self._prev_cci = cv
return
prev = self._prev_cci
self._prev_cci = cv
ob = float(self.Overbought)
os_level = float(self.Oversold)
# Buy signal: CCI crosses above oversold level from below
if prev < os_level and cv >= os_level and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Sell signal: CCI crosses below overbought level from above
elif prev > ob and cv <= ob and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return cci_and_martin_strategy()