Ver en GitHub

Estrategia Twenty 200 Pips

Descripción general

La estrategia replica el experto original 20/200 pips de MQL5. Examina velas horarias y compara dos precios de apertura históricos (Open[t1] y Open[t2]). Cuando la diferencia entre estas aperturas supera un delta configurable durante una hora específica, la estrategia entra en una única operación para la sesión y se basa en niveles fijos de take-profit y stop-loss.

Lógica de trading

  1. Suscribirse a velas horarias (configurable) y alimentar el precio de apertura a dos indicadores Shift para recuperar las aperturas en los índices requeridos.
  2. Durante cada vela completada, restablecer la bandera "puede operar" una vez que la hora actual es mayor que la hora de trading configurada. Esto refleja el restablecimiento diario en el asesor experto original.
  3. Cuando la hora coincide con la hora de trading configurada y no hay posición abierta, comparar los precios de apertura almacenados:
    • Si Open[t1] > Open[t2] + delta, enviar una orden de venta de mercado.
    • Si Open[t1] + delta < Open[t2], enviar una orden de compra de mercado.
  4. Después de enviar una orden, la estrategia prohíbe nuevas entradas hasta el próximo restablecimiento diario. Las órdenes de take-profit y stop-loss de protección se gestionan mediante StartProtection.

Parámetros

  • TakeProfit – distancia en puntos de precio para la orden de take-profit (por defecto 200 puntos).
  • StopLoss – distancia en puntos de precio para la orden de stop-loss (por defecto 2000 puntos).
  • TradeHour – hora del día en que se realiza la verificación de entrada (por defecto 18).
  • FirstOffset – índice del precio de apertura más antiguo (corresponde a Open[t1] en el script MQL, por defecto 7).
  • SecondOffset – índice del precio de apertura más reciente (Open[t2], por defecto 2).
  • DeltaPoints – diferencia mínima en puntos entre las dos aperturas para activar una operación (por defecto 70).
  • Volume – tamaño de la orden usado para entradas de mercado (por defecto 0.1).
  • CandleType – período de tiempo usado para los cálculos (por defecto velas de 1 hora).

Notas de implementación

  • Los indicadores Shift se procesan manualmente para acceder a los precios de apertura históricos sin mantener colecciones personalizadas.
  • La estrategia llama a StartProtection una vez durante OnStarted para emular los niveles de stop-loss/take-profit definidos en el experto MQL.
  • Los comentarios en inglés se incluyen directamente en el código para facilitar el mantenimiento y la revisión.
  • Solo se permite una operación por día porque _canTrade se limpia justo después de colocar una orden y se restaura solo después de que haya pasado la hora de trading configurada.

Uso

  1. Adjuntar la estrategia a un instrumento y configurar los parámetros según el instrumento objetivo.
  2. Asegurarse de que el instrumento tenga un PriceStep válido; se usa para convertir parámetros basados en puntos en distancias de precio absolutas.
  3. Iniciar la estrategia. Esperará hasta la hora configurada y actuará en la próxima vela completada si se cumplen las condiciones del precio de apertura.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Daily breakout strategy derived from the "20/200 pips" MQL5 expert.
/// Compares open prices from different bar offsets and trades the breakout.
/// </summary>
public class Twenty200PipsStrategy : Strategy
{
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _firstOffset;
	private readonly StrategyParam<int> _secondOffset;
	private readonly StrategyParam<int> _deltaPoints;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _opens = new();
	private decimal _pointValue;

	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	public int FirstOffset
	{
		get => _firstOffset.Value;
		set => _firstOffset.Value = value;
	}

	public int SecondOffset
	{
		get => _secondOffset.Value;
		set => _secondOffset.Value = value;
	}

	public int DeltaPoints
	{
		get => _deltaPoints.Value;
		set => _deltaPoints.Value = value;
	}

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

	public Twenty200PipsStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 200)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (points)", "Take profit distance in points", "Risk")
			.SetOptimize(50, 500, 50);

		_stopLoss = Param(nameof(StopLoss), 2000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (points)", "Stop loss distance in points", "Risk")
			.SetOptimize(200, 4000, 100);

		_firstOffset = Param(nameof(FirstOffset), 7)
			.SetGreaterThanZero()
			.SetDisplay("First Offset", "Older bar index", "Signal")
			.SetOptimize(1, 12, 1);

		_secondOffset = Param(nameof(SecondOffset), 2)
			.SetGreaterThanZero()
			.SetDisplay("Second Offset", "Newer bar index", "Signal")
			.SetOptimize(1, 6, 1);

		_deltaPoints = Param(nameof(DeltaPoints), 1)
			.SetGreaterThanZero()
			.SetDisplay("Delta (points)", "Minimum difference between opens", "Signal")
			.SetOptimize(10, 200, 10);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_opens.Clear();
		_pointValue = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pointValue = Security?.PriceStep ?? 1m;
		if (_pointValue <= 0m)
			_pointValue = 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}

		StartProtection(
			takeProfit: new Unit(TakeProfit * _pointValue, UnitTypes.Absolute),
			stopLoss: new Unit(StopLoss * _pointValue, UnitTypes.Absolute),
			useMarketOrders: true);
	}

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

		_opens.Add(candle.OpenPrice);

		var maxOffset = Math.Max(FirstOffset, SecondOffset);
		if (_opens.Count <= maxOffset)
			return;

		// Keep buffer limited
		if (_opens.Count > maxOffset + 100)
			_opens.RemoveRange(0, _opens.Count - maxOffset - 50);

		if (Position != 0)
			return;

		var openFirst = _opens[_opens.Count - 1 - FirstOffset];
		var openSecond = _opens[_opens.Count - 1 - SecondOffset];
		var threshold = DeltaPoints * _pointValue;

		if (openFirst > openSecond + threshold)
		{
			SellMarket();
		}
		else if (openFirst + threshold < openSecond)
		{
			BuyMarket();
		}
	}
}