GitHub で見る

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

  1. 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.
  2. 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.
  3. 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.
  4. Time-based exit – if UseTimeClose is enabled, the strategy closes all tracked positions when the clock reaches the CloseTime window (lasting TradeDuration).
  5. 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;
	}
}