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.
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;
}
}
}
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()