Ver no GitHub

Open Time Daily Window Strategy

Overview

The strategy reproduces the behaviour of the MetaTrader expert "OpenTime". It places market orders at a configurable time of day, optionally closes all exposure during a dedicated exit window, and applies simple money-management rules such as fixed stop-loss, take-profit, and trailing protection. The port uses the high-level StockSharp Strategy API, so the strategy can be combined with other components inside the framework.

How it works

  1. Every finished candle from the selected timeframe triggers a time-of-day check.
  2. When the current time falls inside the trading window, the strategy sends market orders for every enabled direction:
    • If only one side is enabled, the current net position is extended or reversed until the requested volume is reached.
    • When both sides are enabled, buy and sell orders are issued in the same window. Because StockSharp accounts exposure netted by side, opening the second direction automatically offsets the opposite exposure before establishing the new one.
  3. While the closing window is active, the strategy calls ClosePosition() once to flatten any outstanding exposure.
  4. Optional stop-loss, take-profit, and trailing stop distances are delegated to StartProtection, which manages the protective orders using market exits.

Parameters

  • Enable Close Window – mirrors the TimeClose flag. When enabled, Close Position Time and Window Length define when existing trades are closed.
  • Close Position Time – daily time at which the exit window begins (default 20:50).
  • Trading Time – daily time when new trades are allowed (default 18:50).
  • Window Length – duration of both the trading and closing windows (default 5 minutes, corresponding to the original Duration input).
  • Allow Sell Entries – corresponds to the MQL Sell switch; enables short entries (default true).
  • Allow Buy Entries – corresponds to the MQL Buy switch; enables long entries (default false).
  • Order Volume – target net volume for each new trade (default 0.1 lots). The strategy adds the absolute value of the current position when an opposite signal appears, so reversals occur in a single market order.
  • Stop-Loss Points – distance in points for the protective stop (default 0 disables the stop).
  • Take-Profit Points – distance in points for the profit target (default 0 disables the target).
  • Use Trailing Stop – enables the trailing stop logic from the original SimpleTrailing helper.
  • Trailing Stop Points – trailing distance expressed in points (default 300).
  • Trailing Step Points – additional progress required before advancing the trailing stop (default 3).
  • Candle Type – timeframe used for the time checks (default 1-minute candles).

Notes

  • The point size is derived from the security price step. For three- and five-decimal quotes the step is multiplied by 10, reproducing the pip handling used by the MQL script.
  • StartProtection attaches protective stops only when at least one of the distances is greater than zero. If trailing is active without a fixed stop-loss, the trailing distance is supplied as the initial protective value.
  • The strategy intentionally does not manage pending orders or repeated retries, because StockSharp already provides automatic error handling for market orders.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Open Time Daily Window: trades during a specific time window using
/// EMA direction for entry. Closes position at the end of window.
/// </summary>
public class OpenTimeDailyWindowStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _tradeHour;
	private readonly StrategyParam<int> _windowMinutes;
	private readonly StrategyParam<int> _closeHour;

	private decimal _prevEma;
	private decimal _entryPrice;

	public OpenTimeDailyWindowStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "EMA period for direction.", "Indicators");

		_tradeHour = Param(nameof(TradeHour), 10)
			.SetDisplay("Trade Hour", "Hour when trading window opens (UTC).", "Schedule");

		_windowMinutes = Param(nameof(WindowMinutes), 120)
			.SetDisplay("Window Minutes", "Duration of trading window.", "Schedule");

		_closeHour = Param(nameof(CloseHour), 20)
			.SetDisplay("Close Hour", "Hour to close positions (UTC).", "Schedule");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int TradeHour
	{
		get => _tradeHour.Value;
		set => _tradeHour.Value = value;
	}

	public int WindowMinutes
	{
		get => _windowMinutes.Value;
		set => _windowMinutes.Value = value;
	}

	public int CloseHour
	{
		get => _closeHour.Value;
		set => _closeHour.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevEma = 0;
		_entryPrice = 0;
	}

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

		var ema = new ExponentialMovingAverage { Length = EmaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawOwnTrades(area);
		}
	}

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

		var hour = candle.OpenTime.Hour;
		var minute = candle.OpenTime.Minute;
		var totalMinutes = hour * 60 + minute;
		var tradeStart = TradeHour * 60;
		var tradeEnd = tradeStart + WindowMinutes;
		var closeStart = CloseHour * 60;

		var close = candle.ClosePrice;

		// Close position at close hour
		if (Position != 0 && totalMinutes >= closeStart && totalMinutes < closeStart + 30)
		{
			if (Position > 0)
				SellMarket();
			else
				BuyMarket();
			_entryPrice = 0;
		}

		if (_prevEma == 0)
		{
			_prevEma = emaVal;
			return;
		}

		// Trade within window
		var inWindow = totalMinutes >= tradeStart && totalMinutes < tradeEnd;

		if (Position == 0 && inWindow)
		{
			var emaRising = emaVal > _prevEma;
			var emaFalling = emaVal < _prevEma;

			if (emaRising && close > emaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (emaFalling && close < emaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevEma = emaVal;
	}
}