Открыть на GitHub

Стратегия «21-Hour Session Breakout»

Стратегия воспроизводит эксперта MetaTrader "21hour" на платформе StockSharp. Торговля ведётся в два настраиваемых временных окна: утром и вечером. В начале каждого окна выставляется пара отложенных стоп-заявок для пробоя, а в конце интервала позиции принудительно закрываются и все заявки снимаются.

Основная идея

  • Направление сделок определяется исключительно поведением цены во время стартов сессий.
  • В момент запуска окна выставляется buy stop выше текущего ask и sell stop ниже текущего bid на фиксированном расстоянии.
  • После срабатывания одной из стоп-заявок противоположная мгновенно отменяется и регистрируется тейк-профит.
  • В конце каждого окна позиция закрывается по рынку (если ещё открыта), остаточные отложенные ордера удаляются.

Поток данных

  • Свечи: минутные свечи (тип можно изменить) используются только для получения отметки времени и проверки расписания.
  • Стакан: котировки первого уровня обеспечивают актуальные значения bid/ask, от которых рассчитываются уровни стоп-заявок.

Правила торговли

Открытие позиций

  • В час FirstSessionStartHour (по умолчанию 08:00 серверного времени) и SecondSessionStartHour (по умолчанию 22:00) стратегия:
    • Размещает buy stop на цене Ask + StepPoints * PriceStep.
    • Размещает sell stop на цене Bid - StepPoints * PriceStep.
  • В рынке разрешена только одна позиция. При наличии открытой позиции перед стартом следующей сессии все отложенные ордера удаляются и формируются заново.

Управление ордерами

  • После исполнения стоп-ордера противоположная сторона отменяется.
  • Регистрируется тейк-профит на расстоянии TakeProfitPoints * PriceStep от цены входа (в сторону прибыли).
  • Объём каждой заявки задаётся параметром Volume (по умолчанию 1 лот).

Завершение позиций

  • Тейк-профит закрывает прибыльные сделки автоматически.
  • В моменты FirstSessionStopHour (21:00 по умолчанию) и SecondSessionStopHour (23:00) стратегия закрывает позицию по рынку и удаляет все активные заявки.
  • При внешнем закрытии позиции тейк-профит также снимается.

Параметры

Параметр Значение по умолчанию Описание
Volume 1 Объём ордеров для входа и тейк-профита.
FirstSessionStartHour 8 Час начала первой торговой сессии (0-23).
FirstSessionStopHour 21 Час завершения первой сессии.
SecondSessionStartHour 22 Час начала вечерней сессии. Должен быть позже первой.
SecondSessionStopHour 23 Час окончания второй сессии. Должен быть позже окончания первой.
StepPoints 5 Расстояние от лучшей котировки до стоп-заявки в шагах цены.
TakeProfitPoints 40 Расстояние от цены входа до тейк-профита в шагах цены.
CandleType 1 минута Тип свечей, используемых для тайминга.

Все параметры проходят проверку на корректность, чтобы исключить пересечение сессий и невозможные комбинации часов.

Теги и характеристики

  • Стиль: пробой временных диапазонов / тайминг.
  • Направление: длинные и короткие позиции.
  • Таймфрейм: внутридневной, управление по расписанию (минутные свечи только для времени).
  • Риск-менеджмент: фиксированный тейк-профит + принудительное закрытие в конце сессии (стоп-лосс не предусмотрен).
  • Инструменты: валютные пары, индексы и другие ликвидные инструменты с непрерывной торговлей.
  • Сложность: низкая, индикаторы не используются.

Особенности реализации

  • Требуется корректно заданный Security.PriceStep; при отсутствии шага цены или котировок ордера не выставляются.
  • Объём тейк-профита рассчитывается по фактическому объёму исполнения, при его отсутствии берётся текущая позиция или заданный объём.
  • Логика полностью повторяет MQL-версию, но реализована через высокоуровневые API StockSharp (SubscribeCandles, SubscribeOrderBook, параметры стратегии и хелперы заявок).
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;
		}
	}
}