The CCI MACD Scalper ports the MetaTrader 5 expert advisor "CCI + MACD Scalper" to the StockSharp high-level strategy API. The conversion keeps the original indicator stack—an EMA trend filter, a CCI zero-line trigger, and a MACD divergence check—while translating the money-management logic into StockSharp conventions. Orders size themselves from portfolio equity, stops are rejected when the distance is too tight, and an optional trailing stop can secure profits by partially closing positions after the first adjustment. A five-candle cooldown prevents the strategy from re-entering immediately after an execution, replicating the MQL timer behaviour.
Strategy logic
Indicators and data processing
Candles – one configurable timeframe drives every calculation. Signals are evaluated exclusively on completed candles to avoid repainting.
EMA(34) – closing price exponential moving average acts as directional filter. Longs require the latest close to sit above the previous EMA value, shorts require a close below it.
CCI(50) – used as a momentum trigger. The strategy waits for a zero-line cross that occurred on the two most recent finished candles (the current candle confirms the setup but does not participate in the logical comparison).
MACD(12,26,9) – the MACD main and signal lines must both stay on the same side of zero for the previous two candles. Entry requires the MACD signal line to cross the main line in favour of the position between those two bars (bullish crossover for longs, bearish crossover for shorts).
Swing buffers – the last five finished candle highs and lows form the stop-loss reference. Longs anchor to the lowest low, shorts to the highest high, exactly matching the MetaTrader iLowest/iHighest calls with a shift of one bar.
Entry rules
Session control – trading is allowed only when the candle close time falls within [MinHour, MaxHour] in local terminal time.
Cooldown – after each filled entry the system waits for five candle durations before allowing a new trade, mirroring EventSetTimer from the original code.
Long setup
No active long position (Position <= 0).
Close price above the previous EMA value.
CCI crossed from negative to positive on the two most recent closed candles.
MACD crossover occurred below zero during the same two bars (signal rose above MACD).
Stop loss positioned at the most recent swing low satisfies the minimal distance constraint.
Short setup
No active short position (Position >= 0).
Close price below the previous EMA value.
CCI crossed from positive to negative across the last two completed candles.
MACD crossover occurred above zero (signal fell below MACD).
Stop loss at the swing high respects the minimal distance requirement.
Risk and trade management
Dynamic position sizing – trade size is derived from the configured RiskPercent of the portfolio equity. The risk per contract is computed from the stop-loss distance, security price step and step value. The result is snapped to the instrument's volume step and clamped between the minimum and maximum volume.
Stop loss / take profit – stop loss uses the chosen swing extreme and is rejected when the distance is below MinimalStopLossPoints. Take profit equals entry ± RiskReward × stopDistance, matching the EA's reward-to-risk calculation.
Trailing stop (optional) – when enabled, the stop moves by TrailingStopPoints once the price closes far enough beyond the previous stop. The first trailing adjustment triggers a partial exit that closes half of the original volume, faithfully mirroring the MetaTrader implementation.
Protective exits – for longs the position closes if price pierces the stop level (candle low) or reaches the take-profit level (candle high). Shorts mirror the logic using candle highs and lows respectively.
Parameters
Name
Description
Default
CandleType
Timeframe driving the indicator calculations.
15-minute candles
RiskPercent
Percentage of portfolio equity risked on each trade.
2%
RiskReward
Reward-to-risk multiplier for the take-profit level.
1.5
EmaPeriod
Length of the EMA trend filter.
34
CciPeriod
Length of the Commodity Channel Index.
50
MinHour
Earliest hour (inclusive) when new trades may be opened.
0
MaxHour
Latest hour (inclusive) when new trades may be opened.
24
MinimalStopLossPoints
Minimal allowed distance between entry and stop loss expressed in price points.
100
UseTrailingStop
Enables the trailing stop module and partial take profit.
Disabled
TrailingStopPoints
Trailing stop distance measured in price points.
100
Additional notes
The price-point conversion relies on the security's PriceStep. Symbols without a valid step fall back to a distance of one price unit.
Portfolio equity is obtained from Portfolio.CurrentValue and falls back to BeginValue when the current valuation is not available. If both are missing, the strategy reverts to the base Volume property.
There is no Python port for this strategy; only the C# version is included in the API package.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "CCI MACD Scalper" MetaTrader expert.
/// Uses CCI zero-line crossover with EMA trend filter for scalping entries.
/// </summary>
public class CciMacdScalperStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _cciPeriod;
private ExponentialMovingAverage _ema;
private CommodityChannelIndex _cci;
private decimal? _prevCci;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
public CciMacdScalperStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for scalping", "General");
_emaPeriod = Param(nameof(EmaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI period for zero-line crosses", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = null;
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
_cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, _cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed || !_cci.IsFormed)
{
_prevCci = cciValue;
return;
}
if (_prevCci is null)
{
_prevCci = cciValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var close = candle.ClosePrice;
// CCI crosses back above the oversold zone with trend confirmation -> buy
var cciCrossUp = _prevCci.Value <= -50m && cciValue > -50m;
// CCI crosses back below the overbought zone with trend confirmation -> sell
var cciCrossDown = _prevCci.Value >= 50m && cciValue < 50m;
if (cciCrossUp && close > emaValue)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (cciCrossDown && close < emaValue)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevCci = cciValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_ema = null;
_cci = null;
_prevCci = null;
base.OnReseted();
}
}
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 ExponentialMovingAverage, CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_macd_scalper_strategy(Strategy):
def __init__(self):
super(cci_macd_scalper_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._ema_period = self.Param("EmaPeriod", 21)
self._cci_period = self.Param("CciPeriod", 14)
self._prev_cci = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def CciPeriod(self):
return self._cci_period.Value
@CciPeriod.setter
def CciPeriod(self, value):
self._cci_period.Value = value
def OnReseted(self):
super(cci_macd_scalper_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(cci_macd_scalper_strategy, self).OnStarted2(time)
self._prev_cci = None
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, cci, self._process_candle).Start()
def _process_candle(self, candle, ema_value, cci_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
cci_val = float(cci_value)
close = float(candle.ClosePrice)
if self._prev_cci is None:
self._prev_cci = cci_val
return
# CCI crosses back above oversold zone with trend confirmation -> buy
cci_cross_up = self._prev_cci <= -50.0 and cci_val > -50.0
# CCI crosses back below overbought zone with trend confirmation -> sell
cci_cross_down = self._prev_cci >= 50.0 and cci_val < 50.0
if cci_cross_up and close > ema_val:
if self.Position <= 0:
self.BuyMarket()
elif cci_cross_down and close < ema_val:
if self.Position >= 0:
self.SellMarket()
self._prev_cci = cci_val
def CreateClone(self):
return cci_macd_scalper_strategy()