Ver en GitHub

Regularidades de la estrategia de tipos de cambio

Esta estrategia StockSharp es una conversión fiel de C# del asesor experto MetaTrader 4 Strategy_of_Regularities_of_Exchange_Rates.mq4. El sistema fue diseñado como un sistema de ruptura diaria: coloca entre paréntesis el mercado con órdenes de parada cuando llega una hora específica y mantiene esas órdenes activas hasta la hora de cierre nocturno. Cualquier posición ocupada es supervisada tanto por un stop-loss del corredor como por un organismo de control de toma de ganancias intradiario para que las operaciones no se prolonguen más allá de la sesión de negociación definida.

A diferencia de los sistemas basados en indicadores, la lógica se centra únicamente en el tiempo y la distancia. Cuando el cronograma dice que el mercado debería estar listo, la estrategia mide una compensación fija en puntos de corredor (pips) de la oferta y demanda actual y coloca un par de órdenes stop simétricas. El código adapta automáticamente el cálculo de puntos a símbolos con comillas de 3 o 5 dígitos, coincidiendo con el comportamiento de la versión original MQL.

Lógica de trading

  1. Hora de apertura: una vez que una vela terminada informa OpeningHour, la estrategia cancela cualquier orden pendiente sobrante y envía un stop de compra por encima de la demanda actual y un stop de venta por debajo de la oferta actual. La distancia es EntryOffsetPoints * point, donde el valor point se deriva del instrumento PriceStep y se ajusta para cotizaciones fraccionarias.
  2. Órdenes de protección: inmediatamente después del inicio, la estrategia habilita StartProtection con el StopLossPoints configurado. Por lo tanto, cualquier operación ejecutada recibe un stop-loss del lado del corredor idéntico al EA original.
  3. Supervisión de obtención de beneficios: en cada vela completa, el algoritmo comprueba si el beneficio actual supera TakeProfitPoints * point. Si es así, cierra la posición al mercado. Esto refleja el bucle OrderClose original que salió cuando las ganancias alcanzaron el umbral.
  4. Hora de cierre: cuando el reloj llega a ClosingHour, la estrategia cierra con fuerza cualquier posición abierta y cancela las órdenes stop, asegurando que el libro se mantenga plano para la siguiente sesión.
  5. Reinicio diario: se envía un nuevo lote de órdenes pendientes solo una vez por día de negociación, lo que evita duplicados y al mismo tiempo respeta la intención original de una única configuración por sesión.

Parámetros

Parámetro Predeterminado Descripción
OpeningHour 9 Hora (0–23) en la que se coloca el par de órdenes stop.
ClosingHour 2 Hora (0–23) en la que se eliminan las órdenes pendientes y se aplanan las operaciones abiertas.
EntryOffsetPoints 20 Distancia en puntos del corredor desde la oferta/demanda actual hasta las órdenes de parada.
TakeProfitPoints 20 Objetivo de beneficio en puntos del corredor que desencadena una salida del mercado. Establezca en 0 para deshabilitar la obtención de ganancias manual.
StopLossPoints 500 Distancia en puntos de intermediario para la parada de protección conectada a través de StartProtection.
OrderVolume 0.1 Volumen de cada orden stop.
CandleType 30 minute time frame Serie de velas utilizadas para evaluar el cronograma. Cualquier período de tiempo ≤ 1 hora mantiene el comportamiento coherente con el script MQL.

Notas de conversión

  • El asesor experto original trabajó en eventos de ticks y hizo referencia a Hour() directamente. En StockSharp la estrategia escucha las velas terminadas y usa su hora de apertura, lo que preserva la lógica de una vez por hora mientras se mantiene dentro de las pautas del repositorio sobre los estados de las velas.
  • Las órdenes pendientes se normalizan con Security.ShrinkPrice para que los precios generados siempre coincidan con el tamaño del tick del instrumento.
  • Detenga la administración de delegados a StartProtection, recreando el stop-loss generado por la plataforma que MetaTrader adjuntó durante OrderSend.
  • El código rastrea la última fecha de negociación para evitar volver a enviar el mismo grupo varias veces dentro del mismo día, algo que podría suceder en períodos de tiempo inferiores a una hora en el EA original.
  • Amplios comentarios en línea aclaran cada paso del flujo de trabajo para futuros mantenimiento o experimentación.
using System;
using System.Linq;
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>
/// Time-based breakout strategy converted from the "Strategy of Regularities of Exchange Rates" MQL expert advisor.
/// At a scheduled hour captures reference price, then enters on breakout above/below offset levels.
/// Exits at a closing hour or on take-profit/stop-loss hit.
/// </summary>
public class RegularitiesOfExchangeRatesStrategy : Strategy
{
	private readonly StrategyParam<int> _openingHour;
	private readonly StrategyParam<int> _closingHour;
	private readonly StrategyParam<decimal> _entryOffsetPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _dummySma;
	private decimal _pointSize;
	private DateTime? _lastEntryDate;
	private decimal _referencePrice;
	private decimal _entryPrice;
	private bool _waitingForBreakout;

	public int OpeningHour
	{
		get => _openingHour.Value;
		set => _openingHour.Value = value;
	}

	public int ClosingHour
	{
		get => _closingHour.Value;
		set => _closingHour.Value = value;
	}

	public decimal EntryOffsetPoints
	{
		get => _entryOffsetPoints.Value;
		set => _entryOffsetPoints.Value = value;
	}

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

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

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

	public RegularitiesOfExchangeRatesStrategy()
	{
		_openingHour = Param(nameof(OpeningHour), 9)
			.SetDisplay("Opening Hour", "Hour (0-23) when breakout levels are set", "Schedule")
			.SetRange(0, 23);

		_closingHour = Param(nameof(ClosingHour), 2)
			.SetDisplay("Closing Hour", "Hour (0-23) when the strategy exits", "Schedule")
			.SetRange(0, 23);

		_entryOffsetPoints = Param(nameof(EntryOffsetPoints), 20m)
			.SetDisplay("Entry Offset (points)", "Distance from reference price for breakout", "Orders")
			.SetGreaterThanZero();

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
			.SetDisplay("Take Profit (points)", "Profit target distance in points", "Risk")
			.SetNotNegative();

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetDisplay("Stop Loss (points)", "Stop-loss distance in points", "Risk")
			.SetGreaterThanZero();

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used to evaluate trading hours", "General");
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_pointSize = 0m;
		_lastEntryDate = null;
		_referencePrice = 0m;
		_entryPrice = 0m;
		_waitingForBreakout = false;
	}

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

		_pointSize = Security?.PriceStep ?? 0.01m;
		if (_pointSize <= 0m)
			_pointSize = 0.01m;

		_dummySma = new SimpleMovingAverage { Length = 2 };

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

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

		var hour = candle.OpenTime.Hour;
		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		// At closing hour: flatten position and cancel breakout watch
		if (hour == ClosingHour)
		{
			if (Position > 0)
				SellMarket(Position);
			else if (Position < 0)
				BuyMarket(-Position);

			_waitingForBreakout = false;
			_entryPrice = 0m;
		}

		// Manage take-profit and stop-loss for existing position
		if (Position != 0 && _entryPrice > 0m)
		{
			var tp = TakeProfitPoints * _pointSize;
			var sl = StopLossPoints * _pointSize;

			if (Position > 0)
			{
				if ((tp > 0m && close - _entryPrice >= tp) || (sl > 0m && _entryPrice - close >= sl))
				{
					SellMarket(Position);
					_entryPrice = 0m;
					_waitingForBreakout = false;
				}
			}
			else if (Position < 0)
			{
				if ((tp > 0m && _entryPrice - close >= tp) || (sl > 0m && close - _entryPrice >= sl))
				{
					BuyMarket(-Position);
					_entryPrice = 0m;
					_waitingForBreakout = false;
				}
			}
		}

		// At opening hour: set reference price for breakout
		if (hour == OpeningHour && Position == 0)
		{
			var date = candle.OpenTime.Date;
			if (!_lastEntryDate.HasValue || _lastEntryDate.Value != date)
			{
				_referencePrice = close;
				_waitingForBreakout = true;
				_lastEntryDate = date;
			}
		}

		// Check for breakout entry
		if (_waitingForBreakout && Position == 0 && _referencePrice > 0m)
		{
			var offset = EntryOffsetPoints * _pointSize;
			var buyLevel = _referencePrice + offset;
			var sellLevel = _referencePrice - offset;

			if (high >= buyLevel)
			{
				BuyMarket();
				_entryPrice = close;
				_waitingForBreakout = false;
			}
			else if (low <= sellLevel)
			{
				SellMarket();
				_entryPrice = close;
				_waitingForBreakout = false;
			}
		}
	}
}