Ver en GitHub

Estrategia de Planificador de Cobertura Múltiple

Descripción general

La Estrategia de Planificador de Cobertura Múltiple es una conversión directa de StockSharp del asesor experto original MetaTrader 5 MultiHedg_1.mq5. La estrategia está diseñada para cuentas que permiten cobertura y puede gestionar hasta diez instrumentos diferentes simultáneamente. Abre posiciones de la misma dirección durante una ventana de trading configurable y proporciona lógica de salida a nivel de portafolio basada en tiempo o umbrales de porcentaje de equity.

En lugar de depender de indicadores, la estrategia usa un flujo de velas de un minuto (configurable) puramente como fuente de temporización. Cada vela terminada activa verificaciones para abrir operaciones, cerrar todo cuando la ventana de trading expira y hacer cumplir reglas de riesgo basadas en equity. La estrategia es por lo tanto adecuada para portafolios donde la ejecución está impulsada por horario en lugar de patrones de precio.

Lógica de trading

  1. Selección de instrumentos – Se pueden habilitar hasta diez símbolos. Para cada entrada habilitada la estrategia resuelve el ticker a través del SecurityProvider, se suscribe a velas del tipo configurado y usa la misma lógica en todos los instrumentos.
  2. Ventana de trading – cuando la marca de tiempo de la vela entra en la ventana TradeStartTime (que dura TradeDuration), la estrategia abre una posición de mercado en la dirección configurada (TradeDirection) para cada símbolo habilitado que no tenga ya una posición abierta en esa dirección. Si existe una posición opuesta, el volumen se aumenta para voltear al lado deseado.
  3. Protección de equity – si CloseByEquityPercent está habilitado y el equity del portafolio se desvía del saldo inicial en PercentProfit o PercentLoss, cada posición abierta gestionada por la estrategia se cierra.
  4. Salida basada en tiempo – si UseTimeClose está habilitado, la estrategia cierra todas las posiciones rastreadas cuando el reloj alcanza la ventana CloseTime (que dura TradeDuration).
  5. Registro – acciones como entradas, salidas basadas en equity y salidas basadas en tiempo se registran a través de llamadas LogInfo para trazabilidad.

Parámetros

Parámetro Descripción Predeterminado
TradeDirection Dirección de todas las órdenes (Buy o Sell). Buy
TradeStartTime Hora local cuando se abre la ventana de entrada. 19:51
TradeDuration Duración de las ventanas de entrada y cierre. 00:05:00
UseTimeClose Habilita la ventana de cierre basada en tiempo. true
CloseTime Hora local cuando se abre la ventana de cierre. 20:50
CloseByEquityPercent Habilita el cierre de todas las posiciones en umbrales de equity. true
PercentProfit Porcentaje de ganancia en equity que activa un cierre global. 1.0
PercentLoss Porcentaje de drawdown en equity que activa un cierre global. 55.0
CandleType Tipo de vela usado como controlador de programación. Marco temporal de 1 minuto
UseSymbol0..9 Alterna el trading para el símbolo correspondiente. true para los símbolos 0–5, false para 6–9
Symbol0..9 Ticker para cada slot, resuelto vía SecurityProvider.LookupById. Ver predeterminados abajo
Volume0..9 Volumen de orden para cada slot (lotes en el EA original). 0.1–1.0

Configuración de símbolos predeterminada

Slot Habilitado Símbolo Volumen
0 EURUSD 0.1
1 GBPUSD 0.2
2 GBPJPY 0.3
3 EURCAD 0.4
4 USDCHF 0.5
5 USDJPY 0.6
6 USDCHF 0.7
7 GBPUSD 0.8
8 EURUSD 0.9
9 USDJPY 1.0

Notas de uso

  • Asegurarse de que la cuenta soporte cobertura si se planea replicar el comportamiento original de MetaTrader. En cuentas netting la estrategia automáticamente compensará posiciones opuestas al cambiar de dirección.
  • Proporcionar identificadores de instrumentos en los parámetros SymbolX exactamente como se conocen en el SecurityProvider de StockSharp (por ejemplo EURUSD@FXCM).
  • El flujo de velas solo se usa para impulsar la lógica de programación. Ajustar CandleType si la fuente de datos proporciona un intervalo de agregación diferente.
  • La protección de equity compara el equity en vivo contra el saldo capturado en OnStarted. Reiniciar la estrategia resetea el saldo de referencia.
  • La estrategia no incluye órdenes de stop protector o take-profit. Las salidas globales son controladas únicamente por los porcentajes de equity y la ventana de cierre.

Notas de conversión

  • El experto MT5 original usaba OnTick. En la versión StockSharp, las velas terminadas sustituyen a los eventos de tick para evaluar ventanas de tiempo de manera de alto nivel impulsada por eventos.
  • El filtrado por número mágico es innecesario porque la estrategia opera dentro del contenedor de estrategias de StockSharp; por lo tanto CloseAllManagedPositions solo itera a través de los símbolos configurados.
  • Las alertas sonoras y los comentarios en el gráfico fueron omitidos, pero la estrategia registra todas las acciones críticas vía LogInfo para una auditoría más fácil.
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>
/// Hedging scheduler strategy that opens positions during a configurable time window
/// and closes when equity targets are reached or a separate exit window arrives.
/// Simplified to single-security from the original multi-symbol version.
/// </summary>
public class MultiHedgingSchedulerStrategy : Strategy
{
	private readonly StrategyParam<Sides> _tradeDirection;
	private readonly StrategyParam<TimeSpan> _tradeStartTime;
	private readonly StrategyParam<TimeSpan> _tradeDuration;
	private readonly StrategyParam<bool> _enableTimeClose;
	private readonly StrategyParam<TimeSpan> _closeTime;
	private readonly StrategyParam<bool> _enableEquityClose;
	private readonly StrategyParam<decimal> _profitPercent;
	private readonly StrategyParam<decimal> _lossPercent;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _initialBalance;
	private bool _positionOpened;

	/// <summary>
	/// Trading direction used when opening positions.
	/// </summary>
	public Sides TradeDirection
	{
		get => _tradeDirection.Value;
		set => _tradeDirection.Value = value;
	}

	/// <summary>
	/// Time of day when the trading window starts.
	/// </summary>
	public TimeSpan TradeStartTime
	{
		get => _tradeStartTime.Value;
		set => _tradeStartTime.Value = value;
	}

	/// <summary>
	/// Duration of the trading and optional closing windows.
	/// </summary>
	public TimeSpan TradeDuration
	{
		get => _tradeDuration.Value;
		set => _tradeDuration.Value = value;
	}

	/// <summary>
	/// Enables the separate time based close window.
	/// </summary>
	public bool UseTimeClose
	{
		get => _enableTimeClose.Value;
		set => _enableTimeClose.Value = value;
	}

	/// <summary>
	/// Time of day when the closing window starts.
	/// </summary>
	public TimeSpan CloseTime
	{
		get => _closeTime.Value;
		set => _closeTime.Value = value;
	}

	/// <summary>
	/// Enables closing when equity reaches profit or loss thresholds.
	/// </summary>
	public bool CloseByEquityPercent
	{
		get => _enableEquityClose.Value;
		set => _enableEquityClose.Value = value;
	}

	/// <summary>
	/// Percentage profit target based on starting balance.
	/// </summary>
	public decimal PercentProfit
	{
		get => _profitPercent.Value;
		set => _profitPercent.Value = value;
	}

	/// <summary>
	/// Percentage loss threshold based on starting balance.
	/// </summary>
	public decimal PercentLoss
	{
		get => _lossPercent.Value;
		set => _lossPercent.Value = value;
	}

	/// <summary>
	/// Candle series driving the scheduling logic.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes the strategy.
	/// </summary>
	public MultiHedgingSchedulerStrategy()
	{
		_tradeDirection = Param(nameof(TradeDirection), Sides.Buy)
			.SetDisplay("Trade Direction", "Direction used for opening positions", "General");

		_tradeStartTime = Param(nameof(TradeStartTime), new TimeSpan(10, 0, 0))
			.SetDisplay("Trade Start", "Time of day to begin opening positions", "Scheduling");

		_tradeDuration = Param(nameof(TradeDuration), TimeSpan.FromMinutes(5))
			.SetDisplay("Window Length", "Duration of trading and closing windows", "Scheduling");

		_enableTimeClose = Param(nameof(UseTimeClose), true)
			.SetDisplay("Use Close Window", "Enable time based portfolio closing", "Scheduling");

		_closeTime = Param(nameof(CloseTime), new TimeSpan(17, 0, 0))
			.SetDisplay("Close Start", "Time of day to start the close window", "Scheduling");

		_enableEquityClose = Param(nameof(CloseByEquityPercent), true)
			.SetDisplay("Use Equity Targets", "Enable equity based exit", "Risk Management");

		_profitPercent = Param(nameof(PercentProfit), 1m)
			.SetDisplay("Profit %", "Equity percentage gain to close all positions", "Risk Management");

		_lossPercent = Param(nameof(PercentLoss), 55m)
			.SetDisplay("Loss %", "Equity percentage loss to close all positions", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle series driving the scheduler", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_initialBalance = 0m;
		_positionOpened = false;
	}

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

		_initialBalance = Portfolio?.CurrentValue ?? 0m;

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

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

		if (!IsFormed)
			return;

		var timeOfDay = candle.OpenTime.TimeOfDay;

		if (CloseByEquityPercent && TryHandleEquityTargets())
			return;

		if (UseTimeClose && IsWithinWindow(timeOfDay, CloseTime, TradeDuration))
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			else if (Position < 0)
				BuyMarket(Math.Abs(Position));

			_positionOpened = false;
			return;
		}

		var direction = TradeDirection;

		if (!IsWithinWindow(timeOfDay, TradeStartTime, TradeDuration))
			return;

		if (_positionOpened)
			return;

		var volume = Volume;
		if (volume <= 0m)
			volume = 1m;

		if (direction == Sides.Buy && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(volume);
			_positionOpened = true;
		}
		else if (direction == Sides.Sell && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Position);
			SellMarket(volume);
			_positionOpened = true;
		}
	}

	private bool TryHandleEquityTargets()
	{
		if (_initialBalance <= 0m)
			return false;

		var equity = Portfolio?.CurrentValue;
		if (equity == null)
			return false;

		var profitLevel = _initialBalance * (1m + PercentProfit / 100m);
		var lossLevel = _initialBalance * (1m - PercentLoss / 100m);

		if (equity.Value >= profitLevel || equity.Value <= lossLevel)
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			else if (Position < 0)
				BuyMarket(Math.Abs(Position));

			_positionOpened = false;
			return true;
		}

		return false;
	}

	private static bool IsWithinWindow(TimeSpan current, TimeSpan start, TimeSpan length)
	{
		if (length <= TimeSpan.Zero)
			return current == start;

		var end = start + length;

		if (end < TimeSpan.FromDays(1))
			return current >= start && current < end;

		var overflow = end - TimeSpan.FromDays(1);
		return current >= start || current < overflow;
	}
}