Ver en GitHub

Estrategia de Flechas Doji

Concepto

La estrategia de Flechas Doji convierte el asesor experto original "Doji Arrows" de MetaTrader a la API de alto nivel de StockSharp. La idea es esperar a una vela doji genuina y luego operar un rompimiento de su rango. Una vela doji representa indecisión, por lo tanto, un cierre por encima del máximo del doji sugiere fortaleza alcista mientras que un cierre por debajo del mínimo del doji indica control bajista.

  1. La estrategia procesa solo velas completadas de la suscripción CandleType configurada.
  2. La vela anterior se analiza para determinar si es un doji. La vela se clasifica como doji cuando la diferencia absoluta entre la apertura y el cierre es menor o igual que DojiBodyPoints multiplicado por el paso de precio del instrumento. Si el parámetro se establece en 0, se usa un único paso de precio como tolerancia, lo que coincide con la verificación de igualdad estricta en la versión MQL5.
  3. Cuando la siguiente vela cierra por encima del máximo del doji, la estrategia envía una orden de compra a mercado. Cuando la siguiente vela cierra por debajo del mínimo del doji, se emite una orden de venta a mercado. Las posiciones opuestas existentes se aplanan automáticamente por el volumen de la orden de mercado.

Esta secuencia refleja el asesor experto original que reaccionaba una vez en la apertura de cada nueva barra.

Gestión de Riesgo

La implementación convertida mantiene el comportamiento protector del script MQL:

  • Stop loss: StopLossPoints controla cuán lejos, en pasos de precio, se coloca el stop loss inicial desde el precio de entrada. Establezca en cero para deshabilitar el stop fijo.
  • Take profit: TakeProfitPoints define la distancia al objetivo de beneficio en pasos de precio. Establezca en cero para omitir el objetivo.
  • Trailing stop: TrailingStopPoints y TrailingStepPoints reproducen el mecanismo de trailing. Una vez que la operación gana más de TrailingStopPoints + TrailingStepPoints, el nivel de stop se mueve a TrailingStopPoints desde el último cierre (cierre más alto para largos, cierre más bajo para cortos). El trailing es opcional y se activa solo cuando TrailingStopPoints es mayor que cero.

Los stops y objetivos se evalúan en cada vela terminada. Cuando se viola cualquier nivel (usando el máximo/mínimo de la vela), la estrategia sale de la posición con una orden de mercado y reinicia el estado de protección.

Parámetros

Parámetro Predeterminado Descripción
StopLossPoints 30 Distancia del stop loss inicial en pasos de precio.
TakeProfitPoints 90 Distancia del take profit en pasos de precio.
TrailingStopPoints 15 Distancia usada por el trailing stop en pasos de precio.
TrailingStepPoints 5 Beneficio adicional requerido antes de ajustar el trailing stop, en pasos de precio.
DojiBodyPoints 1 Tamaño máximo permitido del cuerpo de la vela anterior en pasos de precio para tratarla como un doji. 0 usa un paso de precio como tolerancia.
CandleType 1 hora Tipo de vela suscrita para la generación de señales.

Notas de Implementación

  • La estrategia se suscribe a velas mediante SubscribeCandles(CandleType).Bind(ProcessCandle) y mantiene solo la última vela completada en memoria.
  • El paso de precio del instrumento se obtiene mediante Security?.PriceStep. Cuando no está disponible, se usa un valor de respaldo de 1 para que la estrategia pueda operar igualmente con datos sintéticos o históricos.
  • Los niveles protectores se recalculan después de cada entrada, y la lógica de trailing puede crear un stop incluso cuando el stop loss fijo está deshabilitado (coincidiendo con el comportamiento MQL donde el trailing stop podía comenzar desde cero).
  • Todas las acciones se ejecutan con órdenes de mercado para mantenerse alineadas con el asesor original que dependía de ejecución inmediata al mercado.

Consejos de Uso

  1. Configure las propiedades Security, Portfolio y Volume antes de iniciar la estrategia.
  2. Ajuste los parámetros basados en puntos de acuerdo con el instrumento operado. Para instrumentos cotizados con pips fraccionarios, aumente los valores para que coincidan con el tamaño del tick del broker.
  3. Combine la estrategia con controles de riesgo o módulos de análisis de StockSharp si se requiere dimensionamiento de posición más avanzado, porque la conversión mantiene la lógica de volumen fijo del código original.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Doji breakout strategy with optional fixed and trailing protection.
/// </summary>
public class DojiArrowsStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _trailingStepPoints;
	private readonly StrategyParam<decimal> _dojiBodyPoints;
	private readonly StrategyParam<DataType> _candleType;

	private bool _hasPreviousCandle;
	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _prevHigh;
	private decimal _prevLow;

	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	public decimal TrailingStepPoints
	{
		get => _trailingStepPoints.Value;
		set => _trailingStepPoints.Value = value;
	}

	public decimal DojiBodyPoints
	{
		get => _dojiBodyPoints.Value;
		set => _dojiBodyPoints.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public DojiArrowsStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 30m)
			.SetNotNegative()
			.SetDisplay("Stop Loss Points", "Stop loss distance in price steps.", "Risk")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 90m)
			.SetNotNegative()
			.SetDisplay("Take Profit Points", "Take profit distance in price steps.", "Risk")
			;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop Points", "Trailing distance in price steps.", "Risk")
			;

		_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m)
			.SetNotNegative()
			.SetDisplay("Trailing Step Points", "Minimum profit before the trailing stop moves.", "Risk")
			;

		_dojiBodyPoints = Param(nameof(DojiBodyPoints), 1m)
			.SetNotNegative()
			.SetDisplay("Doji Body Points", "Maximum difference between open and close to treat the candle as a doji.", "Pattern")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for signal generation.", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_hasPreviousCandle = false;
		_prevOpen = 0m;
		_prevClose = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		ResetProtection();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		ManageActivePosition(candle);

		if (!_hasPreviousCandle)
		{
			CachePreviousCandle(candle);
			return;
		}

		var step = Security?.PriceStep ?? 1m;
		var tolerance = DojiBodyPoints <= 0m ? step : DojiBodyPoints * step;
		var bodySize = Math.Abs(_prevOpen - _prevClose);
		var isDoji = bodySize <= tolerance;

		var breakoutUp = isDoji && candle.ClosePrice > _prevHigh;
		var breakoutDown = isDoji && candle.ClosePrice < _prevLow;

		if (breakoutUp && Position == 0)
		{
			BuyMarket();
		}
		else if (breakoutDown && Position == 0)
		{
			SellMarket();
		}

		CachePreviousCandle(candle);
	}

	private void ManageActivePosition(ICandleMessage candle)
	{
		if (Position == 0)
			return;

		var step = Security?.PriceStep ?? 1m;
		var trailingDistance = TrailingStopPoints > 0m ? TrailingStopPoints * step : 0m;
		var trailingStep = TrailingStepPoints > 0m ? TrailingStepPoints * step : 0m;

		if (Position > 0)
		{
			if (trailingDistance > 0m && _entryPrice.HasValue)
			{
				var gain = candle.ClosePrice - _entryPrice.Value;

				if (gain > trailingDistance + trailingStep)
				{
					var newStop = candle.ClosePrice - trailingDistance;

					if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}
		}
		else if (Position < 0)
		{
			if (trailingDistance > 0m && _entryPrice.HasValue)
			{
				var gain = _entryPrice.Value - candle.ClosePrice;

				if (gain > trailingDistance + trailingStep)
				{
					var newStop = candle.ClosePrice + trailingDistance;

					if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}
		}
	}

	private void InitializeProtection(decimal price, bool isLong, decimal step)
	{
		_entryPrice = price;

		if (StopLossPoints > 0m)
		{
			var offset = StopLossPoints * step;
			_stopPrice = isLong ? price - offset : price + offset;
		}
		else
		{
			_stopPrice = null;
		}

		if (TakeProfitPoints > 0m)
		{
			var offset = TakeProfitPoints * step;
			_takePrice = isLong ? price + offset : price - offset;
		}
		else
		{
			_takePrice = null;
		}
	}

	private void ResetProtection()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}

	private void CachePreviousCandle(ICandleMessage candle)
	{
		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPreviousCandle = true;
	}
}