The 21hour strategy reproduces the behaviour of the MQL4 expert advisor 21hour.mq4. It operates around a daily time window: pending breakout orders are created at a configurable start hour and all exposure is removed at a configurable stop hour. The StockSharp version keeps the same "two stop orders around the price" idea while leveraging the high-level API for order management, market data subscriptions and protective take-profit handling.
Trading Logic
At the start of every trading day, when the server time matches StartHour:00, the strategy reads the latest bid/ask quotes and places both a buy stop and a sell stop order.
The distance from the current ask price to the buy stop trigger is StepPoints * PriceStep.
The distance from the current bid price to the sell stop trigger is the same amount below the market.
TakeProfitPoints is converted to price distance through the instrument price step and passed to StartProtection, so both long and short positions receive a protective take-profit right after execution.
Only one pending setup is allowed per day. If only one of the two stop orders remains active (for example, after one side was filled), the strategy cancels the surviving pending order to mirror the original EA logic.
When the clock reaches StopHour:00, the strategy closes any open position at market and cancels all outstanding pending orders. This applies even if no breakout occurred.
The default candle stream is one-minute data. It is used purely to trigger the hourly checks on finished candles, which mimics the prevtime guard from the MQL version.
Parameters
Parameter
Description
Default
Volume
Order volume in lots for both pending orders.
0.1
StartHour
Hour (0–23) when the pair of pending orders is created.
10
StopHour
Hour (0–23) when the strategy closes positions and removes all pending orders.
22
StepPoints
Distance in instrument points between the current bid/ask price and each stop entry. Converted to price by multiplying with PriceStep.
15
TakeProfitPoints
Distance in points from the entry price to the take-profit target managed by StartProtection. Set to 0 to disable the target.
200
CandleType
Candle data type used for time tracking. Default is one-minute time frame (TimeSpan.FromMinutes(1).TimeFrame()).
1 minute
Implementation Notes
Uses SubscribeCandles to obtain finished candles and evaluate the hourly schedule only once per minute.
Subscribes to level-1 quotes via SubscribeLevel1() to keep the latest bid/ask values for accurate stop placement.
Relies on StartProtection with a take-profit unit to emulate the pending-order take-profit from the original EA instead of manually attaching orders.
Keeps track of the active buy and sell stop orders and calls CancelOrder if only one side remains, ensuring the system never runs with an unpaired pending order.
Invokes BuyMarket / SellMarket helpers for flatting positions at the stop hour, strictly using the high-level Strategy API.
Behaviour Notes
The strategy expects the broker connection to provide price step information. If PriceStep is absent, prices are left unrounded.
Pending orders are generated only once per calendar day. They will be re-created on the next trading day at the configured start hour even if the previous day's breakout did not trigger.
When TakeProfitPoints is zero the strategy still places pending orders but no protective take-profit is managed.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Time-based breakout strategy. At start hour, detects breakout direction from previous candle range.
/// At stop hour, closes all positions.
/// </summary>
public class TwentyOneHourStrategy : Strategy
{
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _stopHour;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevHigh;
private decimal _prevLow;
private bool _hasPrev;
private bool _tradedToday;
private int _lastTradeDay;
public TwentyOneHourStrategy()
{
_startHour = Param(nameof(StartHour), 10)
.SetDisplay("Start Hour", "Hour to look for breakout entries.", "Schedule");
_stopHour = Param(nameof(StopHour), 22)
.SetDisplay("Stop Hour", "Hour to close positions.", "Schedule");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candles used for time tracking.", "General");
}
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
public int StopHour
{
get => _stopHour.Value;
set => _stopHour.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0;
_prevLow = 0;
_hasPrev = false;
_tradedToday = false;
_lastTradeDay = -1;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_hasPrev = false;
_tradedToday = false;
_lastTradeDay = -1;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.OpenTime.Hour;
var day = candle.OpenTime.DayOfYear;
// Reset daily flag
if (day != _lastTradeDay)
{
_tradedToday = false;
_lastTradeDay = day;
}
// Close at stop hour
if (hour >= StopHour && Position != 0)
{
if (Position > 0)
SellMarket();
else
BuyMarket();
}
// Entry at start hour window
if (hour >= StartHour && hour < StopHour && !_tradedToday && _hasPrev && Position == 0)
{
if (candle.ClosePrice > _prevHigh)
{
BuyMarket();
_tradedToday = true;
}
else if (candle.ClosePrice < _prevLow)
{
SellMarket();
_tradedToday = true;
}
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_hasPrev = true;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class twenty_one_hour_strategy(Strategy):
"""Time-based breakout: enter on breakout from previous candle range, close at stop hour."""
def __init__(self):
super(twenty_one_hour_strategy, self).__init__()
self._start_hour = self.Param("StartHour", 10).SetDisplay("Start Hour", "Hour to look for breakout entries", "Schedule")
self._stop_hour = self.Param("StopHour", 22).SetDisplay("Stop Hour", "Hour to close positions", "Schedule")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(twenty_one_hour_strategy, self).OnReseted()
self._prev_high = 0
self._prev_low = 0
self._has_prev = False
self._traded_today = False
self._last_trade_day = -1
def OnStarted2(self, time):
super(twenty_one_hour_strategy, self).OnStarted2(time)
self._prev_high = 0
self._prev_low = 0
self._has_prev = False
self._traded_today = False
self._last_trade_day = -1
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
day = candle.OpenTime.DayOfYear
# Reset daily flag
if day != self._last_trade_day:
self._traded_today = False
self._last_trade_day = day
# Close at stop hour
start_h = self._start_hour.Value
stop_h = self._stop_hour.Value
if hour >= stop_h and self.Position != 0:
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
# Entry at start hour window
if hour >= start_h and hour < stop_h and not self._traded_today and self._has_prev and self.Position == 0:
close = float(candle.ClosePrice)
if close > self._prev_high:
self.BuyMarket()
self._traded_today = True
elif close < self._prev_low:
self.SellMarket()
self._traded_today = True
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._has_prev = True
def CreateClone(self):
return twenty_one_hour_strategy()