Стратегия воспроизводит эксперта 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;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class twenty_one_hour_session_breakout_strategy(Strategy):
def __init__(self):
super(twenty_one_hour_session_breakout_strategy, self).__init__()
self._first_session_start_hour = self.Param("FirstSessionStartHour", 2)
self._first_session_stop_hour = self.Param("FirstSessionStopHour", 20)
self._step_points = self.Param("StepPoints", 40.0)
self._take_profit_points = self.Param("TakeProfitPoints", 200.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._session_open = None
self._entry_price = 0.0
self._in_session = False
@property
def FirstSessionStartHour(self):
return self._first_session_start_hour.Value
@FirstSessionStartHour.setter
def FirstSessionStartHour(self, value):
self._first_session_start_hour.Value = value
@property
def FirstSessionStopHour(self):
return self._first_session_stop_hour.Value
@FirstSessionStopHour.setter
def FirstSessionStopHour(self, value):
self._first_session_stop_hour.Value = value
@property
def StepPoints(self):
return self._step_points.Value
@StepPoints.setter
def StepPoints(self, value):
self._step_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(twenty_one_hour_session_breakout_strategy, self).OnStarted2(time)
self._session_open = None
self._entry_price = 0.0
self._in_session = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if price_step <= 0.0:
price_step = 1.0
start_hour = int(self.FirstSessionStartHour)
stop_hour = int(self.FirstSessionStopHour)
if hour >= start_hour and hour < stop_hour:
if not self._in_session:
self._session_open = float(candle.OpenPrice)
self._in_session = True
if self._session_open is None:
return
step_offset = float(self.StepPoints) * price_step
buy_level = self._session_open + step_offset
sell_level = self._session_open - step_offset
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position == 0:
if high >= buy_level:
self.BuyMarket()
self._entry_price = buy_level
elif low <= sell_level:
self.SellMarket()
self._entry_price = sell_level
if self.Position > 0:
tp = self._entry_price + float(self.TakeProfitPoints) * price_step
if high >= tp:
self.SellMarket()
self._session_open = close
elif self.Position < 0:
tp = self._entry_price - float(self.TakeProfitPoints) * price_step
if low <= tp:
self.BuyMarket()
self._session_open = close
else:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._in_session = False
self._session_open = None
def OnReseted(self):
super(twenty_one_hour_session_breakout_strategy, self).OnReseted()
self._session_open = None
self._entry_price = 0.0
self._in_session = False
def CreateClone(self):
return twenty_one_hour_session_breakout_strategy()