Ver en GitHub

21-Hour Session Breakout Strategy

This strategy reproduces the MetaTrader "21hour" expert advisor inside StockSharp. It operates during two configurable trading windows and uses pending stop orders to capture breakouts at the top and bottom of the range. At the end of each window the strategy liquidates any open exposure and removes the working orders, ensuring that every trading day starts clean.

Core Idea

  • Trade direction is determined purely by price action around the specified session start times.
  • At the beginning of each session the strategy brackets the market with a buy stop above the current ask and a sell stop below the current bid.
  • When a stop order fills the opposite side is cancelled immediately and a fixed-distance take-profit order is placed.
  • At the configured session end time every position is closed and all orders are cancelled, even if the take-profit has not been reached yet.

Data Flow

  • Candles: 1-minute candles (configurable) are used only to provide time stamps and to fire the hourly schedule checks.
  • Order book: Level 1 quotes supply the current best bid/ask values that define the stop order activation prices.

Trading Rules

Entry Scheduling

  • At FirstSessionStartHour (default 08:00 server time) and at SecondSessionStartHour (default 22:00) the strategy:
    • Places a buy-stop at Ask + StepPoints * PriceStep.
    • Places a sell-stop at Bid - StepPoints * PriceStep.
  • Only one position is allowed. If a position is already open when the other session starts, all pending entry orders are removed before new ones are placed.

Order Management

  • When one of the stop orders is filled the opposite stop is cancelled immediately.
  • A take-profit limit order is registered at EntryPrice ± TakeProfitPoints * PriceStep depending on the trade direction.
  • Order sizes are fixed by the Volume parameter (defaults to 1 lot).

Exit Logic

  • Take-profit orders close winning trades automatically.
  • At FirstSessionStopHour (default 21:00) and SecondSessionStopHour (default 23:00) the strategy closes any open position at market and cancels all remaining pending orders.
  • If the position is flattened manually, the strategy also removes the outstanding take-profit order.

Parameters

Parameter Default Description
Volume 1 Order volume used for both stop entries and take-profit exits.
FirstSessionStartHour 8 Hour (0-23) when the first trading session begins.
FirstSessionStopHour 21 Hour when the first session ends and positions are closed.
SecondSessionStartHour 22 Hour when the evening session begins. Must be after the first session.
SecondSessionStopHour 23 Hour when the second session ends. Must be after the first session stop.
StepPoints 5 Distance from the best quote to the entry stop order, measured in price steps.
TakeProfitPoints 40 Distance between the entry price and the take-profit limit, measured in price steps.
CandleType 1 minute Candle type used to drive the intraday schedule checks.

All parameters are validated to avoid overlapping sessions or impossible hour combinations.

Tags & Characteristics

  • Style: Session breakout / time-based trend following.
  • Direction: Long and short.
  • Timeframe: Intraday, schedule-driven (1-minute candles for timing only).
  • Risk Controls: Fixed take-profit plus forced flat at session end (no stop-loss).
  • Market Types: Designed for FX, indices, or any instrument with continuous trading hours and reliable quotes.
  • Complexity: Low – no indicators, purely time and price based.

Implementation Notes

  • The strategy requires a valid Security.PriceStep; orders are skipped if price step or quotes are not available.
  • Take-profit volumes use the executed trade volume when available, falling back to the current position or configured volume.
  • The code keeps English inline comments for clarity and mirrors the original MQL logic while leveraging StockSharp high-level APIs (SubscribeCandles, SubscribeOrderBook, helper parameters, and order helpers).
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// 21-hour session breakout strategy. Places simulated stop entries via candle breakout logic.
/// </summary>
public class TwentyOneHourSessionBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _firstSessionStartHour;
	private readonly StrategyParam<int> _firstSessionStopHour;
	private readonly StrategyParam<decimal> _stepPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _sessionOpen;
	private decimal _entryPrice;
	private bool _inSession;

	public int FirstSessionStartHour
	{
		get => _firstSessionStartHour.Value;
		set => _firstSessionStartHour.Value = value;
	}

	public int FirstSessionStopHour
	{
		get => _firstSessionStopHour.Value;
		set => _firstSessionStopHour.Value = value;
	}

	public decimal StepPoints
	{
		get => _stepPoints.Value;
		set => _stepPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	public TwentyOneHourSessionBreakoutStrategy()
	{
		_firstSessionStartHour = Param(nameof(FirstSessionStartHour), 2)
			.SetDisplay("Session Start", "Hour of the trading window start", "Schedule");

		_firstSessionStopHour = Param(nameof(FirstSessionStopHour), 20)
			.SetDisplay("Session Stop", "Hour of the trading window stop", "Schedule");

		_stepPoints = Param(nameof(StepPoints), 40m)
			.SetGreaterThanZero()
			.SetDisplay("Step Points", "Distance from session open to breakout level", "Orders");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit Points", "Take-profit distance", "Orders");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles used to drive the trading schedule", "Data");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_sessionOpen = null;
		_entryPrice = 0m;
		_inSession = false;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

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

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

		var hour = candle.OpenTime.Hour;
		var priceStep = Security?.PriceStep ?? 1m;

		// Session start: record the open price
		if (hour >= FirstSessionStartHour && hour < FirstSessionStopHour)
		{
			if (!_inSession)
			{
				_sessionOpen = candle.OpenPrice;
				_inSession = true;
			}

			if (_sessionOpen == null)
				return;

			var stepOffset = StepPoints * priceStep;
			var buyLevel = _sessionOpen.Value + stepOffset;
			var sellLevel = _sessionOpen.Value - stepOffset;

			// Breakout entry
			if (Position == 0)
			{
				if (candle.HighPrice >= buyLevel)
				{
					BuyMarket();
					_entryPrice = buyLevel;
				}
				else if (candle.LowPrice <= sellLevel)
				{
					SellMarket();
					_entryPrice = sellLevel;
				}
			}

			// Take profit
			if (Position > 0)
			{
				var tp = _entryPrice + TakeProfitPoints * priceStep;
				if (candle.HighPrice >= tp)
				{
					SellMarket();
					_sessionOpen = candle.ClosePrice;
				}
			}
			else if (Position < 0)
			{
				var tp = _entryPrice - TakeProfitPoints * priceStep;
				if (candle.LowPrice <= tp)
				{
					BuyMarket();
					_sessionOpen = candle.ClosePrice;
				}
			}
		}
		else
		{
			// Session end: close position
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();

			_inSession = false;
			_sessionOpen = null;
		}
	}
}