View on GitHub

Rapid Doji Strategy

Overview

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

  1. Data subscription – the strategy listens to finished candles of the selected timeframe and maintains an ATR indicator with a configurable period.
  2. 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.
  3. 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.
  4. 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.
  5. 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;
	}
}