La estrategia Rapid Doji replica la lógica del asesor experto original "Rapid Doji EA". Escanea velas terminadas de un período de tiempo configurable (diariamente de forma predeterminada) y coloca órdenes de parada de entrada encima y debajo de cada vela doji. Los stop de protección se colocan utilizando un multiplicador de rango verdadero promedio (ATR), mientras que un trailing stop auxiliar mantiene la distancia de riesgo fija en puntos brutos después de que una posición se vuelve rentable.
Lógica de trading
Suscripción de datos: la estrategia escucha las velas terminadas del período de tiempo seleccionado y mantiene un indicador ATR con un período configurable.
Detección de Doji: una vela se trata como un doji cuando el tamaño absoluto del cuerpo es como máximo el 3% del rango total de la vela. Sólo se evalúan velas completas.
Realización de pedidos – cuando se encuentra un doji válido:
Se coloca una orden de compra stop en el máximo del doji.
Se coloca una orden de venta stop en el mínimo de doji.
Cada entrada recuerda un precio stop de protección igual al extremo opuesto menos/más ATR × multiplicador.
Gestión de riesgos: una vez que se abre una posición, la orden pendiente restante se cancela, el stop memorizado se registra como un stop de protección y la lógica de seguimiento toma el control.
Trailing stop: en cada nueva vela, el nivel de stop se mueve para mantener una distancia fija (en puntos convertidos a través del paso del precio del instrumento) desde el último precio de cierre, pero solo cuando la posición ya es rentable.
La estrategia nunca utiliza objetivos de obtención de beneficios; las salidas se realizan mediante el tope de protección o intervención manual.
Parámetros
Parámetro
Descripción
CandleType
Tipo de datos de vela utilizado para la detección de patrones (período diario de forma predeterminada).
AtrPeriod
Longitud retrospectiva del indicador ATR.
AtrMultiplier
Multiplicador aplicado al valor ATR para el cálculo del stop-loss.
TrailingDistancePoints
Distancia fija en puntos brutos utilizados al seguir la parada.
Todos los parámetros admiten la optimización dentro del entorno StockSharp.
Notas de implementación
El código se basa en la suscripción de vela de alto nivel API (SubscribeCandles) combinada con el enlace de indicador (Bind) para evitar el manejo manual del historial.
Las órdenes se normalizan a través de Security.ShrinkPrice para respetar el tamaño del tick de intercambio.
Las paradas de protección se gestionan explícitamente para imitar el comportamiento del asesor experto MetaTrader original.
El proyecto omite intencionalmente una implementación de Python según los requisitos de la tarea.
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()