Ver en GitHub

Estrategia de Reversión con Doji

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;
	}
}