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
- 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.
- 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.
- 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.
- 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).
- 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
SIDE_BUY = 0
SIDE_SELL = 1
class multi_hedging_scheduler_strategy(Strategy):
def __init__(self):
super(multi_hedging_scheduler_strategy, self).__init__()
self._trade_direction = self.Param("TradeDirection", SIDE_BUY)
self._trade_start_hour = self.Param("TradeStartHour", 10)
self._trade_start_minute = self.Param("TradeStartMinute", 0)
self._trade_duration_minutes = self.Param("TradeDurationMinutes", 5)
self._enable_time_close = self.Param("UseTimeClose", True)
self._close_hour = self.Param("CloseHour", 17)
self._close_minute = self.Param("CloseMinute", 0)
self._enable_equity_close = self.Param("CloseByEquityPercent", True)
self._profit_percent = self.Param("PercentProfit", 1.0)
self._loss_percent = self.Param("PercentLoss", 55.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._initial_balance = 0.0
self._position_opened = False
@property
def TradeDirection(self):
return self._trade_direction.Value
@TradeDirection.setter
def TradeDirection(self, value):
self._trade_direction.Value = value
@property
def TradeStartHour(self):
return self._trade_start_hour.Value
@TradeStartHour.setter
def TradeStartHour(self, value):
self._trade_start_hour.Value = value
@property
def TradeStartMinute(self):
return self._trade_start_minute.Value
@TradeStartMinute.setter
def TradeStartMinute(self, value):
self._trade_start_minute.Value = value
@property
def TradeDurationMinutes(self):
return self._trade_duration_minutes.Value
@TradeDurationMinutes.setter
def TradeDurationMinutes(self, value):
self._trade_duration_minutes.Value = value
@property
def UseTimeClose(self):
return self._enable_time_close.Value
@UseTimeClose.setter
def UseTimeClose(self, value):
self._enable_time_close.Value = value
@property
def CloseHour(self):
return self._close_hour.Value
@CloseHour.setter
def CloseHour(self, value):
self._close_hour.Value = value
@property
def CloseMinute(self):
return self._close_minute.Value
@CloseMinute.setter
def CloseMinute(self, value):
self._close_minute.Value = value
@property
def CloseByEquityPercent(self):
return self._enable_equity_close.Value
@CloseByEquityPercent.setter
def CloseByEquityPercent(self, value):
self._enable_equity_close.Value = value
@property
def PercentProfit(self):
return self._profit_percent.Value
@PercentProfit.setter
def PercentProfit(self, value):
self._profit_percent.Value = value
@property
def PercentLoss(self):
return self._loss_percent.Value
@PercentLoss.setter
def PercentLoss(self, value):
self._loss_percent.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _is_within_window(self, current_minutes, start_hour, start_minute, duration_minutes):
start_total = start_hour * 60 + start_minute
end_total = start_total + duration_minutes
if end_total < 1440:
return current_minutes >= start_total and current_minutes < end_total
else:
overflow = end_total - 1440
return current_minutes >= start_total or current_minutes < overflow
def OnStarted2(self, time):
super(multi_hedging_scheduler_strategy, self).OnStarted2(time)
self._initial_balance = 0.0
self._position_opened = False
self.SubscribeCandles(self.CandleType).Bind(self.ProcessCandle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._initial_balance == 0.0 and self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._initial_balance = float(self.Portfolio.CurrentValue)
open_time = candle.OpenTime
hour = open_time.Hour
minute = open_time.Minute
current_minutes = hour * 60 + minute
duration = int(self.TradeDurationMinutes)
if self.CloseByEquityPercent and self._try_handle_equity_targets():
return
if self.UseTimeClose and self._is_within_window(current_minutes, int(self.CloseHour), int(self.CloseMinute), duration):
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._position_opened = False
return
if not self._is_within_window(current_minutes, int(self.TradeStartHour), int(self.TradeStartMinute), duration):
return
if self._position_opened:
return
direction = int(self.TradeDirection)
if direction == SIDE_BUY and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._position_opened = True
elif direction == SIDE_SELL and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._position_opened = True
def _try_handle_equity_targets(self):
if self._initial_balance <= 0.0:
return False
if self.Portfolio is None or self.Portfolio.CurrentValue is None:
return False
equity = float(self.Portfolio.CurrentValue)
profit_level = self._initial_balance * (1.0 + float(self.PercentProfit) / 100.0)
loss_level = self._initial_balance * (1.0 - float(self.PercentLoss) / 100.0)
if equity >= profit_level or equity <= loss_level:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._position_opened = False
return True
return False
def OnReseted(self):
super(multi_hedging_scheduler_strategy, self).OnReseted()
self._initial_balance = 0.0
self._position_opened = False
def CreateClone(self):
return multi_hedging_scheduler_strategy()