Las velas Doji reflejan un equilibrio temporal entre compradores y vendedores. Cuando un doji aparece tras un movimiento direccional fuerte, puede preceder a una reversión a medida que el impulso se desvanece. Esta estrategia mide el cuerpo de la vela en relación con su rango para determinar si se ha formado un verdadero doji.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 103%. Funciona mejor en el mercado de acciones.
Una vez detectado un doji, se verifican las velas anteriores para identificar una tendencia alcista o bajista. Un doji tras una caída puede activar una entrada larga, mientras que uno tras una subida puede abrir una posición corta. Los stops se colocan a una distancia porcentual del precio de entrada y las salidas ocurren si el precio supera los extremos del doji.
El método busca capturar la primera reacción alejándose del doji y es más adecuado para gráficos intradía donde los giros rápidos suelen producirse.
Detalles
Criterios de entrada: Vela Doji tras un movimiento direccional.
Largo/Corto: Ambos.
Criterios de salida: Precio que se mueve más allá del máximo/mínimo del doji o stop-loss.
Stops: Sí, basados en porcentaje.
Valores predeterminados:
CandleType = 5 minute
DojiThreshold = 0.1
StopLossPercent = 1
Filtros:
Categoría: Patrón
Dirección: Ambos
Indicadores: Candlestick
Stops: Sí
Complejidad: Intermedio
Marco temporal: Intradía
Estacionalidad: No
Redes neuronales: No
Divergencia: No
Nivel de riesgo: Medio
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Doji Reversal strategy.
/// Looks for doji candlestick patterns after a trend and takes a reversal position.
/// Doji after downtrend = buy, doji after uptrend = sell.
/// Uses SMA for exit signals.
/// </summary>
public class DojiReversalStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _dojiThreshold;
private readonly StrategyParam<int> _cooldownBars;
private ICandleMessage _bar1;
private ICandleMessage _bar2;
private int _cooldown;
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Doji threshold as fraction of candle range.
/// </summary>
public decimal DojiThreshold
{
get => _dojiThreshold.Value;
set => _dojiThreshold.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DojiReversalStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for SMA", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_dojiThreshold = Param(nameof(DojiThreshold), 0.1m)
.SetNotNegative()
.SetDisplay("Doji Threshold", "Max body/range ratio for doji", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bar1 = null;
_bar2 = null;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bar1 = null;
_bar2 = null;
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
_bar1 = _bar2;
_bar2 = candle;
return;
}
if (_bar1 != null && _bar2 != null)
{
var isDoji = IsDoji(candle);
if (isDoji)
{
var isDowntrend = _bar2.ClosePrice < _bar1.ClosePrice;
var isUptrend = _bar2.ClosePrice > _bar1.ClosePrice;
if (Position == 0 && isDowntrend)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && isUptrend)
{
SellMarket();
_cooldown = CooldownBars;
}
}
// Exit on SMA cross
if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
_bar1 = _bar2;
_bar2 = candle;
}
private bool IsDoji(ICandleMessage candle)
{
var bodySize = Math.Abs(candle.OpenPrice - candle.ClosePrice);
var totalRange = candle.HighPrice - candle.LowPrice;
if (totalRange == 0)
return false;
return bodySize / totalRange < DojiThreshold;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class doji_reversal_strategy(Strategy):
"""
Doji Reversal strategy.
Looks for doji candlestick patterns after a trend and takes a reversal position.
Doji after downtrend = buy, doji after uptrend = sell.
Uses SMA for exit signals.
"""
def __init__(self):
super(doji_reversal_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for SMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._doji_threshold = self.Param("DojiThreshold", 0.1).SetDisplay("Doji Threshold", "Max body/range ratio for doji", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._bar1 = None
self._bar2 = None
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(doji_reversal_strategy, self).OnReseted()
self._bar1 = None
self._bar2 = None
self._cooldown = 0
def OnStarted2(self, time):
super(doji_reversal_strategy, self).OnStarted2(time)
self._bar1 = None
self._bar2 = None
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _is_doji(self, candle):
body_size = abs(float(candle.OpenPrice) - float(candle.ClosePrice))
total_range = float(candle.HighPrice) - float(candle.LowPrice)
if total_range == 0:
return False
return body_size / total_range < float(self._doji_threshold.Value)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
self._bar1 = self._bar2
self._bar2 = candle
return
if self._bar1 is not None and self._bar2 is not None:
is_doji = self._is_doji(candle)
if is_doji:
is_downtrend = self._bar2.ClosePrice < self._bar1.ClosePrice
is_uptrend = self._bar2.ClosePrice > self._bar1.ClosePrice
cd = self._cooldown_bars.Value
if self.Position == 0 and is_downtrend:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and is_uptrend:
self.SellMarket()
self._cooldown = cd
# Exit on SMA cross
sv = float(sma_val)
close = float(candle.ClosePrice)
cd = self._cooldown_bars.Value
if self.Position > 0 and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > sv:
self.BuyMarket()
self._cooldown = cd
self._bar1 = self._bar2
self._bar2 = candle
def CreateClone(self):
return doji_reversal_strategy()