The Rapid Doji strategy replicates the logic of the original "Rapid Doji EA" expert advisor. It scans finished candles of a configurable timeframe (daily by default) and places stop entry orders above and below every doji candle. Protective stops are positioned using an Average True Range (ATR) multiplier, while an auxiliary trailing stop keeps the risk distance fixed in raw points after a position becomes profitable.
Trading Logic
Data subscription – the strategy listens to finished candles of the selected timeframe and maintains an ATR indicator with a configurable period.
Doji detection – a candle is treated as a doji when the absolute body size is at most 3% of the full candle range. Only completed candles are evaluated.
Order placement – when a valid doji is found:
A buy stop order is placed at the doji high.
A sell stop order is placed at the doji low.
Each entry remembers a protective stop price equal to the opposite extreme minus/plus ATR × multiplier.
Risk management – once a position is opened the remaining pending order is cancelled, the memorized stop is registered as a protective stop, and the trailing logic takes control.
Trailing stop – on each new candle the stop level is moved to keep a fixed distance (in points converted through the instrument price step) from the latest closing price, but only when the position is already profitable.
The strategy never uses take-profit targets; exits happen through the protective stop or manual intervention.
Parameters
Parameter
Description
CandleType
Candle data type used for pattern detection (daily timeframe by default).
AtrPeriod
Lookback length of the ATR indicator.
AtrMultiplier
Multiplier applied to the ATR value for stop-loss calculation.
TrailingDistancePoints
Fixed distance in raw points used when trailing the stop.
All parameters support optimisation inside the StockSharp environment.
Implementation Notes
The code relies on the high-level candle subscription API (SubscribeCandles) combined with indicator binding (Bind) to avoid manual history handling.
Orders are normalised through Security.ShrinkPrice to respect exchange tick size.
Protective stops are managed explicitly to mimic the behaviour of the original MetaTrader expert advisor.
The project intentionally omits a Python implementation as per the task requirements.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Rapid Doji strategy: detects doji candles and trades the breakout direction.
/// Buys on next candle if it closes above doji high, sells if below doji low.
/// Uses ATR for volatility confirmation.
/// </summary>
public class RapidDojiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _dojiThreshold;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal _prevHigh;
private decimal _prevLow;
private bool _prevWasDoji;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal DojiThreshold { get => _dojiThreshold.Value; set => _dojiThreshold.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public RapidDojiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators");
_dojiThreshold = Param(nameof(DojiThreshold), 0.15m)
.SetDisplay("Doji Threshold", "Max body/range ratio for doji detection", "Pattern");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between breakouts", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0m;
_prevLow = 0m;
_prevWasDoji = false;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevWasDoji = false;
_candlesSinceTrade = SignalCooldownCandles;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
if (_prevWasDoji && atr > 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
var close = candle.ClosePrice;
if (close > _prevHigh + atr * 0.2m && Position <= 0)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (close < _prevLow - atr * 0.2m && Position >= 0)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
var range = candle.HighPrice - candle.LowPrice;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
_prevWasDoji = range > 0 && body <= DojiThreshold * range;
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class rapid_doji_strategy(Strategy):
def __init__(self):
super(rapid_doji_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators")
self._doji_threshold = self.Param("DojiThreshold", 0.15) \
.SetDisplay("Doji Threshold", "Max body/range ratio for doji detection", "Pattern")
self._signal_cooldown = self.Param("SignalCooldownCandles", 6) \
.SetDisplay("Signal Cooldown", "Bars to wait between breakouts", "Trading")
self._atr = None
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._candles_since_trade = 0
@property
def atr_period(self):
return self._atr_period.Value
@property
def doji_threshold(self):
return self._doji_threshold.Value
@property
def signal_cooldown(self):
return self._signal_cooldown.Value
def OnReseted(self):
super(rapid_doji_strategy, self).OnReseted()
self._atr = None
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._candles_since_trade = self.signal_cooldown
def OnStarted2(self, time):
super(rapid_doji_strategy, self).OnStarted2(time)
self._atr = AverageTrueRange()
self._atr.Length = self.atr_period
self._prev_was_doji = False
self._candles_since_trade = self.signal_cooldown
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(15)))
subscription.Bind(self._atr, self._process_candle)
subscription.Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
if not self._atr.IsFormed:
return
atr_val = float(atr_value)
if self._candles_since_trade < self.signal_cooldown:
self._candles_since_trade += 1
if self._prev_was_doji and atr_val > 0 and self._candles_since_trade >= self.signal_cooldown:
close = float(candle.ClosePrice)
if close > self._prev_high + atr_val * 0.2 and self.Position <= 0:
self.BuyMarket()
self._candles_since_trade = 0
elif close < self._prev_low - atr_val * 0.2 and self.Position >= 0:
self.SellMarket()
self._candles_since_trade = 0
high = float(candle.HighPrice)
low = float(candle.LowPrice)
range_size = high - low
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
self._prev_was_doji = range_size > 0 and body <= self.doji_threshold * range_size
self._prev_high = high
self._prev_low = low
def CreateClone(self):
return rapid_doji_strategy()