This strategy converts the MQL4 "DojiTrader" expert advisor into a StockSharp C# sample. It searches for recent doji candles and trades a breakout of the doji range during the main European and U.S. sessions.
Trading logic
The strategy processes only finished candles from the selected timeframe (30-minute candles by default).
Trading is allowed only between 08:00 and 17:00 platform time.
While flat, it looks back up to three completed candles and remembers the most recent doji (open price equals close price).
When the candle immediately following the doji closes above the doji high, a long breakout is armed. If it closes below the doji low, a short breakout is armed.
As soon as a subsequent candle closes beyond the arming price, the strategy sends a market order in the breakout direction.
After entry the doji range is kept for exit control. The position is closed when:
The previous candle closes back inside the range (long: close below the doji low, short: close above the doji high).
The candle extremes reach the synthetic stop loss or take profit levels that mimic the original MQL4 fixed-point exits.
Parameters
Order volume – volume used for market orders.
Take profit (steps) – distance to the profit target measured in price steps.
Stop loss (steps) – distance to the protective stop in price steps.
Candle type – timeframe of candles used for signal detection.
The stop-loss and take-profit calculations rely on the security price step, emulating the original EA that used fixed pip distances. When no valid doji is present within the last three candles, the breakout state is cleared and the search restarts.
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>
/// Doji Trader breakout strategy.
/// Detects doji candles and enters on breakout of doji range.
/// Uses SMA as trend filter for direction.
/// </summary>
public class DojiTraderBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<decimal> _dojiRatio;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevHigh;
private decimal _prevLow;
private bool _prevWasDoji;
private bool _hasPrev;
public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
public decimal DojiRatio { get => _dojiRatio.Value; set => _dojiRatio.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DojiTraderBreakoutStrategy()
{
_smaPeriod = Param(nameof(SmaPeriod), 20)
.SetDisplay("SMA Period", "SMA period for trend filter", "Indicators");
_dojiRatio = Param(nameof(DojiRatio), 0.25m)
.SetDisplay("Doji Ratio", "Max body/range ratio for doji detection", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevHigh = 0m; _prevLow = 0m; _prevWasDoji = false; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
_prevWasDoji = false;
var sma = new SimpleMovingAverage { Length = SmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal sma)
{
if (candle.State != CandleStates.Finished)
return;
var range = candle.HighPrice - candle.LowPrice;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var isDoji = range > 0 && body / range < DojiRatio;
if (_hasPrev && _prevWasDoji)
{
// Bullish breakout above doji high with SMA confirmation
if (candle.ClosePrice > _prevHigh && candle.ClosePrice > sma && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Bearish breakout below doji low with SMA confirmation
else if (candle.ClosePrice < _prevLow && candle.ClosePrice < sma && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevWasDoji = isDoji;
_hasPrev = true;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class doji_trader_breakout_strategy(Strategy):
def __init__(self):
super(doji_trader_breakout_strategy, self).__init__()
self._sma_period = self.Param("SmaPeriod", 20) \
.SetDisplay("SMA Period", "SMA period for trend filter", "Indicators")
self._doji_ratio = self.Param("DojiRatio", 0.25) \
.SetDisplay("Doji Ratio", "Max body/range ratio for doji detection", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._has_prev = False
@property
def sma_period(self):
return self._sma_period.Value
@property
def doji_ratio(self):
return self._doji_ratio.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(doji_trader_breakout_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._has_prev = False
def OnStarted2(self, time):
super(doji_trader_breakout_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_was_doji = False
sma = SimpleMovingAverage()
sma.Length = self.sma_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.process_candle).Start()
def process_candle(self, candle, sma):
if candle.State != CandleStates.Finished:
return
sma_val = float(sma)
rng = float(candle.HighPrice) - float(candle.LowPrice)
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
is_doji = rng > 0 and body / rng < self.doji_ratio
if self._has_prev and self._prev_was_doji:
close = float(candle.ClosePrice)
if close > self._prev_high and close > sma_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close < self._prev_low and close < sma_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._prev_was_doji = is_doji
self._has_prev = True
def CreateClone(self):
return doji_trader_breakout_strategy()