Ver no GitHub

Estratégia de Reversão com Doji

As velas Doji refletem um equilíbrio temporário entre compradores e vendedores. Quando um doji aparece após um movimento direcional forte, pode preceder uma reversão à medida que o momentum se dissipa. Esta estratégia mede o corpo da vela em relação ao seu intervalo para determinar se um verdadeiro doji foi formado.

Os testes indicam um retorno anual médio de aproximadamente 103%. Funciona melhor no mercado de ações.

Uma vez detectado um doji, as velas anteriores são verificadas para identificar uma tendência de alta ou de baixa. Um doji após uma queda pode acionar uma entrada comprada, enquanto um após uma alta pode abrir uma posição vendida. Os stops são colocados a uma distância percentual da entrada e as saídas ocorrem se o preço romper além dos extremos do doji.

O método busca capturar a primeira reação a partir do doji e é mais adequado para gráficos intradiários, onde reversões rápidas frequentemente se desenvolvem.

Detalhes

  • Critérios de entrada: Vela Doji após um movimento direcional.
  • Comprado/Vendido: Ambos.
  • Critérios de saída: Preço se movendo além da máxima/mínima do doji ou stop-loss.
  • Stops: Sim, baseados em percentual.
  • Valores padrão:
    • CandleType = 5 minute
    • DojiThreshold = 0.1
    • StopLossPercent = 1
  • Filtros:
    • Categoria: Padrão
    • Direção: Ambos
    • Indicadores: Candlestick
    • Stops: Sim
    • Complexidade: Intermediário
    • Período: Intradiário
    • Sazonalidade: Não
    • Redes neurais: Não
    • Divergência: Não
    • Nível de risco: Médio
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Enters against the preceding two-close move on a finished doji.
/// Exits at the opposite doji extreme or native actual-fill percent protection.
/// </summary>
public class DojiReversalStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _dojiThreshold;
	private readonly StrategyParam<decimal> _stopLossPercent;

	private ICandleMessage _older;
	private ICandleMessage _previous;
	private decimal? _targetHigh;
	private decimal? _targetLow;
	private Order _pendingOrder;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public decimal DojiThreshold { get => _dojiThreshold.Value; set => _dojiThreshold.Value = value; }
	public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }

	public DojiReversalStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Doji and prior-close timeframe", "General");
		_dojiThreshold = Param(nameof(DojiThreshold), 0.1m).SetRange(0m, 1m)
			.SetDisplay("Doji Threshold", "Strict upper bound for absolute body divided by high-low range", "Pattern");
		_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetNotNegative()
			.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it", "Protection");
		OrderRegistering += order =>
		{
			_pendingOrder = order;
			if (Position > 0m && order.Side == Sides.Sell ||
				Position < 0m && order.Side == Sides.Buy)
				_targetHigh = _targetLow = null;
		};
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType), (Security, DataType.Level1)];

	protected override void OnReseted()
	{
		base.OnReseted();
		ClearState();
	}

	private void ClearState()
	{
		_older = _previous = null;
		_targetHigh = _targetLow = null;
		_pendingOrder = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		ClearState();
		StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
		foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
		{
			var quotes = new Subscription(DataType.Level1, Security);
			quotes.MarketData.BuildField = field;
			SubscribeLevel1(quotes).Bind(ProcessQuote).Start();
		}
		var candles = SubscribeCandles(CandleType);
		candles.Bind(ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, candles);
			DrawOwnTrades(area);
		}
	}

	private bool Pending => _pendingOrder is not null &&
		_pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);

	private void ProcessQuote(Level1ChangeMessage message)
	{
		if (Pending)
			return;
		if (Position > 0m && _targetHigh is decimal high &&
			message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid &&
			bid > high)
			SellMarket(Position);
		else if (Position < 0m && _targetLow is decimal low &&
			message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask &&
			ask > 0m && ask < low)
			BuyMarket(Math.Abs(Position));
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;
		if (!Pending && Position > 0m && _targetHigh is decimal high && candle.HighPrice > high)
			SellMarket(Position);
		else if (!Pending && Position < 0m && _targetLow is decimal low && candle.LowPrice < low)
			BuyMarket(Math.Abs(Position));
		else if (!Pending && Position == 0m && _older is not null && _previous is not null &&
			IsDoji(candle) && IsFormedAndOnlineAndAllowTrading())
		{
			if (_previous.ClosePrice < _older.ClosePrice)
			{
				_targetHigh = candle.HighPrice;
				_targetLow = null;
				BuyMarket(Volume);
			}
			else if (_previous.ClosePrice > _older.ClosePrice)
			{
				_targetLow = candle.LowPrice;
				_targetHigh = null;
				SellMarket(Volume);
			}
		}
		_older = _previous;
		_previous = candle;
	}

	private bool IsDoji(ICandleMessage candle)
	{
		var range = candle.HighPrice - candle.LowPrice;
		return range > 0m && Math.Abs(candle.ClosePrice - candle.OpenPrice) / range < DojiThreshold;
	}
}