Ver no GitHub

Estratégia de Agendador de Hedge Múltiplo

Visão geral

A Estratégia de Agendador de Hedge Múltiplo é uma conversão direta do StockSharp do consultor especialista original MetaTrader 5 MultiHedg_1.mq5. A estratégia foi projetada para contas que permitem hedge e pode gerenciar até dez instrumentos diferentes simultaneamente. Abre posições na mesma direção durante uma janela de negociação configurável e fornece lógica de saída no nível do portfólio baseada em tempo ou limiares de porcentagem de equity.

Em vez de depender de indicadores, a estratégia usa um fluxo de velas de um minuto (configurável) puramente como fonte de temporização. Cada vela terminada aciona verificações para abrir operações, fechar tudo quando a janela de negociação expira e impor regras de risco baseadas em equity. A estratégia é, portanto, adequada para portfólios onde a execução é impulsionada por agenda e não por padrões de preço.

Lógica de negociação

  1. Seleção de instrumentos – Até dez símbolos podem ser habilitados. Para cada entrada habilitada, a estratégia resolve o ticker através do SecurityProvider, subscreve velas do tipo configurado e usa a mesma lógica em todos os instrumentos.
  2. Janela de negociação – quando o timestamp da vela entra na janela TradeStartTime (com duração TradeDuration), a estratégia abre uma posição de mercado na direção configurada (TradeDirection) para cada símbolo habilitado que ainda não tem uma posição aberta nessa direção. Se uma posição oposta existir, o volume é aumentado para inverter para o lado desejado.
  3. Proteção de equity – se CloseByEquityPercent estiver habilitado e o equity do portfólio desviar do saldo inicial em PercentProfit ou PercentLoss, cada posição aberta gerenciada pela estratégia é fechada.
  4. Saída baseada em tempo – se UseTimeClose estiver habilitado, a estratégia fecha todas as posições rastreadas quando o relógio alcança a janela CloseTime (com duração TradeDuration).
  5. Registro – ações como entradas, saídas baseadas em equity e saídas baseadas em tempo são registradas por chamadas LogInfo para rastreabilidade.

Parâmetros

Parâmetro Descrição Padrão
TradeDirection Direção de todas as ordens (Buy ou Sell). Buy
TradeStartTime Hora local quando a janela de entrada abre. 19:51
TradeDuration Duração das janelas de entrada e fechamento. 00:05:00
UseTimeClose Habilita a janela de fechamento baseada em tempo. true
CloseTime Hora local quando a janela de fechamento abre. 20:50
CloseByEquityPercent Habilita o fechamento de todas as posições nos limiares de equity. true
PercentProfit Porcentagem de ganho em equity que aciona um fechamento global. 1.0
PercentLoss Porcentagem de drawdown em equity que aciona um fechamento global. 55.0
CandleType Tipo de vela usado como condutor de agendamento. Período de 1 minuto
UseSymbol0..9 Alterna a negociação para o símbolo correspondente. true para os símbolos 0–5, false para 6–9
Symbol0..9 Ticker para cada slot, resolvido via SecurityProvider.LookupById. Ver padrões abaixo
Volume0..9 Volume de ordem para cada slot (lotes no EA original). 0.1–1.0

Configuração de símbolos padrão

Slot Habilitado Símbolo Volume
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

  • Certificar que a conta suporta hedge se planeja replicar o comportamento original do MetaTrader. Em contas de compensação, a estratégia compensará automaticamente posições opostas ao mudar de direção.
  • Fornecer identificadores de instrumentos nos parâmetros SymbolX exatamente como são conhecidos pelo SecurityProvider do StockSharp (por exemplo EURUSD@FXCM).
  • O fluxo de velas é usado apenas para impulsionar a lógica de agendamento. Ajustar CandleType se a fonte de dados fornecer um intervalo de agregação diferente.
  • A proteção de equity compara o equity ao vivo contra o saldo capturado em OnStarted. Reiniciar a estratégia redefine o saldo de referência.
  • A estratégia não inclui ordens de stop protetor ou take profit. As saídas globais são controladas exclusivamente pelas porcentagens de equity e a janela de fechamento.

Notas de conversão

  • O especialista MT5 original usava OnTick. Na versão StockSharp, as velas terminadas substituem os eventos de tick para avaliar janelas de tempo de forma orientada por eventos de alto nível.
  • A filtragem por número mágico é desnecessária porque a estratégia opera dentro do contêiner de estratégias do StockSharp; portanto CloseAllManagedPositions itera apenas pelos símbolos configurados.
  • Alertas sonoros e comentários no gráfico foram omitidos, mas a estratégia registra todas as ações críticas via LogInfo para auditoria mais 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;
	}
}