Multi Hedging Scheduler Strategy
Overview
The Multi Hedging Scheduler Strategy is a direct StockSharp conversion of the original MetaTrader 5 expert advisor MultiHedg_1.mq5. The strategy is designed for accounts that allow hedging and can manage up to ten different instruments simultaneously. It opens positions of the same direction during a configurable trading window and provides portfolio-level exit logic based on time or equity percentage thresholds.
Instead of relying on indicators, the strategy uses a one-minute candle stream (configurable) purely as a timing source. Each finished candle triggers checks to open trades, close everything when the trading window expires, and enforce equity-based risk rules. The strategy is therefore suitable for portfolios where execution is driven by schedule rather than price patterns.
Trading Logic
- Instrument selection – up to ten symbols can be enabled. For every enabled entry the strategy resolves the ticker through the
SecurityProvider, subscribes to candles of the configured type, and uses the same logic across all instruments.
- Trading window – when the candle timestamp enters the
TradeStartTime window (lasting TradeDuration), the strategy opens a market position in the configured direction (TradeDirection) for every enabled symbol that does not already have an open position in that direction. If an opposite position exists, the volume is increased to flip into the desired side.
- Equity protection – if
CloseByEquityPercent is enabled and the portfolio equity deviates from the starting balance by PercentProfit or PercentLoss, every open position managed by the strategy is closed.
- Time-based exit – if
UseTimeClose is enabled, the strategy closes all tracked positions when the clock reaches the CloseTime window (lasting TradeDuration).
- Logging – actions such as entries, equity-based exits, and time-based exits are logged through
LogInfo calls for traceability.
Parameters
| Parameter |
Description |
Default |
TradeDirection |
Direction of all orders (Buy or Sell). |
Buy |
TradeStartTime |
Local time when the entry window opens. |
19:51 |
TradeDuration |
Length of both entry and closing windows. |
00:05:00 |
UseTimeClose |
Enables the time-based close window. |
true |
CloseTime |
Local time when the closing window opens. |
20:50 |
CloseByEquityPercent |
Enables closing all positions on equity thresholds. |
true |
PercentProfit |
Percentage gain on equity that triggers a global close. |
1.0 |
PercentLoss |
Percentage drawdown on equity that triggers a global close. |
55.0 |
CandleType |
Candle type used as a scheduling driver. |
1-minute time frame |
UseSymbol0..9 |
Toggles trading for the corresponding symbol. |
true for symbols 0–5, false for 6–9 |
Symbol0..9 |
Ticker for each slot, resolved via SecurityProvider.LookupById. |
See defaults below |
Volume0..9 |
Order volume for each slot (lots in original EA). |
0.1–1.0 |
Default symbol configuration
| Slot |
Enabled |
Symbol |
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 |
Usage Notes
- Make sure the account supports hedging if you plan to replicate the original MetaTrader behaviour. On netting accounts the strategy will automatically offset opposite positions when switching directions.
- Provide instrument identifiers in the
SymbolX parameters exactly as they are known to the StockSharp SecurityProvider (for example EURUSD@FXCM).
- The candle stream is only used to drive the scheduling logic. Adjust
CandleType if your data source provides a different aggregation interval.
- Equity protection compares the live equity against the balance captured at
OnStarted. Restarting the strategy resets the reference balance.
- The strategy does not include protective stop or take-profit orders. Global exits are controlled solely by the equity percentages and the closing window.
Conversion Notes
- The original MT5 expert used
OnTick. In the StockSharp version, finished candles substitute tick events to evaluate time windows in a high-level, event-driven manner.
- Magic number filtering is unnecessary because the strategy operates inside StockSharp’s strategy container; therefore
CloseAllManagedPositions iterates only through the configured symbols.
- Sound alerts and on-chart comments were omitted, but the strategy logs all critical actions via
LogInfo for easier auditing.
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()