Donchian Scalper is a StockSharp port of the MetaTrader 4 expert advisor DonchianScalperEA. The strategy monitors Donchian channel boundaries and the exponential moving average (EMA) of the same length. A pending stop order is armed only after price pulls back through the EMA, signalling that momentum has reset before a potential breakout. Entries are executed with stop orders placed at the current Donchian extremes and protected by the opposite band. Profits are managed either by a fixed take-profit distance or by adaptive trailing stops that track the chosen market structure.
Strategy logic
Entry preparation
Pullback validation – the strategy waits until one of the two previously closed candles crosses below the EMA (for longs) or above the EMA (for shorts). The crossing level is offset by the configurable Cross Anchor distance to ensure the pullback is meaningful.
Breakout arming – once the pullback condition is satisfied and the cooldown timer has expired, a stop order is submitted at the most recent Donchian boundary (upper band for longs, lower band for shorts). The opposite band defines the initial protective stop. Existing pending orders are automatically realigned when the Donchian levels flatten for at least two candles.
Trade management
Initial protection – when a breakout order fills, the strategy places a protective stop order using the precomputed stop price. The stop level equals the opposite Donchian band and can be shifted inward by the Stop Loss (points) setting.
Profit control – two management modes are available:
Close At Profit – closes the position once the net movement from the average entry price exceeds the configured take-profit distance.
Trailing – keeps the trade open and periodically tightens the protective stop. The trailing engine can follow the Donchian boundary, the EMA, or an ATR-based volatility band.
Cooldown – after all positions are closed, the strategy waits for the specified number of finished candles before arming new breakout orders. This reproduces the MetaTrader logic that requires at least three bars between trades.
Parameters
Volume – order volume used for stop entries and market exits.
Channel Period – Donchian channel length, also used for the EMA filter.
Cross Anchor – additional distance (in points) that the pullback must exceed before the breakout order is armed.
Stop Loss (points) – distance added to the opposite Donchian band for the initial protective stop; set to 0 to place the stop directly on the band.
Take Profit (points) – profit target used by the Close At Profit mode. Ignored when the trailing mode is active.
Candle Type – timeframe driving indicator calculations.
Profit Mode – selects between the fixed take-profit exit and adaptive trailing stops.
Trailing Mode – trailing engine used in the Trailing profit mode. Choices are Donchian boundary, EMA, or ATR-based trailing.
Cooldown Bars – minimum number of finished candles that must pass after the position becomes flat before new orders may be placed.
ATR Period / ATR Multiplier – parameters for the ATR trailing engine. The multiplier defines how many ATRs are subtracted (long) or added (short) to compute the trailing stop.
Additional notes
The strategy aligns every stop and entry price to the instrument’s price step to ensure exchange compliance.
When both long and short stop orders are active, filling one side will automatically cancel the opposite pending order to avoid hedging.
If Take Profit (points) is set to zero while the profit mode remains Close At Profit, the strategy will keep positions open until the protective stop is hit.
The conversion focuses on the high-level StockSharp API: indicator binding, candle subscriptions, and helper methods (BuyStop, SellStop, SellMarket, etc.). Python implementation is not included in this 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>
/// Donchian Channel scalper with EMA filter.
/// Buys on upper channel breakout above EMA, sells on lower breakout below EMA.
/// Exits at middle band.
/// </summary>
public class DonchianScalperStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
public int ChannelPeriod
{
get => _channelPeriod.Value;
set => _channelPeriod.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DonchianScalperStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Channel Period", "Donchian channel lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var donchian = new DonchianChannels { Length = ChannelPeriod };
var ema = new ExponentialMovingAverage { Length = ChannelPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(donchian, ema, (candle, donchianVal, emaVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (donchianVal is not DonchianChannelsValue dcValue)
return;
if (dcValue.UpperBand is not decimal upper ||
dcValue.LowerBand is not decimal lower ||
dcValue.Middle is not decimal middle)
return;
if (emaVal.IsEmpty)
return;
var emaValue = emaVal.GetValue<decimal>();
var close = candle.ClosePrice;
// Long: close breaks above upper Donchian and is above EMA
if (Position == 0 && close >= upper && close > emaValue)
BuyMarket();
// Short: close breaks below lower Donchian and is below EMA
else if (Position == 0 && close <= lower && close < emaValue)
SellMarket();
})
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class donchian_scalper_strategy(Strategy):
def __init__(self):
super(donchian_scalper_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._channel_period = self.Param("ChannelPeriod", 50)
self._highs = []
self._lows = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def ChannelPeriod(self):
return self._channel_period.Value
@ChannelPeriod.setter
def ChannelPeriod(self, value):
self._channel_period.Value = value
def OnReseted(self):
super(donchian_scalper_strategy, self).OnReseted()
self._highs = []
self._lows = []
def OnStarted2(self, time):
super(donchian_scalper_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
ema = ExponentialMovingAverage()
ema.Length = self.ChannelPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self._process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
def _process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
ema_val = float(ema_value)
period = self.ChannelPeriod
self._highs.append(high)
self._lows.append(low)
while len(self._highs) > period:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < period:
return
upper = max(self._highs)
lower = min(self._lows)
# Long: close breaks above upper Donchian and is above EMA
if self.Position == 0 and close >= upper and close > ema_val:
self.BuyMarket()
# Short: close breaks below lower Donchian and is below EMA
elif self.Position == 0 and close <= lower and close < ema_val:
self.SellMarket()
def CreateClone(self):
return donchian_scalper_strategy()