This strategy recreates the classic “Pending orders by time” MetaTrader expert for StockSharp. It runs on a discrete schedule: every day it places symmetric stop orders around the market when a new session hour begins, and it clears all orders plus open positions at a specified closing hour. The implementation keeps the original pip-based inputs, converts them to native price units, and uses the high-level API to manage risk.
How it works
Time-based trigger – When a candle that ends at the configured opening hour is received, the strategy submits a buy stop above the ask and a sell stop below the bid. Both orders are offset by the Distance (pips) parameter converted to price units.
Protective orders – StartProtection automatically attaches stop-loss and take-profit protection using the pip distances defined in the parameters. ManageRisk doubles as a safeguard, closing any residual position if a completed candle shows the thresholds have been crossed.
Session shutdown – When the closing hour arrives, the strategy cancels any remaining pending orders and forcefully exits open trades regardless of profit or loss. This reproduces the original expert’s behaviour of resetting at the end of the session.
Digit-aware pip size – The pip multiplier emulates the MetaTrader implementation by multiplying the price step by ten for symbols quoted with three or five decimal places (e.g., JPY or 5-digit FX pairs). This keeps legacy inputs consistent across brokers.
The default candle type is 30-minute bars to stay under the original restriction of periods shorter than H1. Any other time frame can be used, as long as the resulting hourly timestamps match the desired session hours.
Parameters
Name
Description
Default
Opening Hour
Hour (0-23) when the strategy will place the pair of stop orders.
9
Closing Hour
Hour (0-23) when all orders are cancelled and positions are closed.
2
Distance (pips)
Offset, in pips, between current price and the pending stop entries.
20
Stop Loss (pips)
Pip distance for the protective stop once a position is open.
20
Take Profit (pips)
Pip distance for the profit target once a position is open.
500
Order Volume
Quantity used when placing each pending stop order.
0.1
Candle Type
Time frame that drives the hourly schedule.
30-minute TimeFrame
All parameters can be optimised. Pip-based inputs are converted internally using the instrument’s price step so they remain portable between FX symbols with different decimal precision.
Daily workflow
At every candle close the strategy checks whether the stop-loss or take-profit distance has been hit. If so, it closes the active position at market.
When the closing hour is reached it cancels any unfilled pending orders and exits the position, ensuring the book is flat before the next session.
When the opening hour is reached (and the strategy is flat) it cancels old orders just in case and submits a fresh sell stop below the bid and a buy stop above the ask. The orders are mirrored around the spread so either breakout can be captured.
Throughout the session the platform-level protection created by StartProtection keeps a stop-loss and take-profit attached, acting immediately if intrabar price action hits the thresholds.
Usage notes
Use instruments whose tick size represents a single “point” so that the pip adjustment mirrors the original expert. Exotic tick sizes may require manual tuning of the distance parameters.
The logic assumes one trading cycle per day. If you use intraday data with multiple opening/closing matches, adjust the hours accordingly.
Because all actions happen on candle completion, select a candle size that matches how often you want to evaluate the schedule. For example, hourly candles provide the same cadence as the MetaTrader version.
The strategy only places new pending orders when the position is flat, avoiding overexposure if a breakout trade is still active during the next opening hour.
Differences from the MQL version
Protective exits are handled via StartProtection plus explicit checks, leveraging StockSharp’s high-level API instead of direct stop-loss assignment on the pending order ticket.
Bid/ask prices are read from Security.BestBid and Security.BestAsk. If those quotes are unavailable, the candle close is used as a fallback reference.
Market orders are used to liquidate positions at the closing hour for simplicity and to avoid broker-specific behaviours.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Places simulated symmetric stop entries at scheduled hours and manages them with daily resets.
/// </summary>
public class PendingOrdersByTimeStrategy : Strategy
{
private readonly StrategyParam<int> _openingHour;
private readonly StrategyParam<int> _closingHour;
private readonly StrategyParam<decimal> _distancePips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<DataType> _candleType;
private decimal _pipSize;
private decimal? _pendingBuyPrice;
private decimal? _pendingSellPrice;
private decimal? _entryPrice;
public int OpeningHour
{
get => _openingHour.Value;
set => _openingHour.Value = value;
}
public int ClosingHour
{
get => _closingHour.Value;
set => _closingHour.Value = value;
}
public decimal DistancePips
{
get => _distancePips.Value;
set => _distancePips.Value = value;
}
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public PendingOrdersByTimeStrategy()
{
_openingHour = Param(nameof(OpeningHour), 2)
.SetDisplay("Opening Hour", "Hour to activate pending orders", "Schedule")
.SetRange(0, 23);
_closingHour = Param(nameof(ClosingHour), 22)
.SetDisplay("Closing Hour", "Hour to cancel orders and flat positions", "Schedule")
.SetRange(0, 23);
_distancePips = Param(nameof(DistancePips), 500m)
.SetDisplay("Distance (pips)", "Offset for entry stop orders", "Orders")
.SetGreaterThanZero();
_stopLossPips = Param(nameof(StopLossPips), 500m)
.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Risk")
.SetGreaterThanZero();
_takeProfitPips = Param(nameof(TakeProfitPips), 2000m)
.SetDisplay("Take Profit (pips)", "Profit target distance", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Working timeframe for the schedule", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_pendingBuyPrice = null;
_pendingSellPrice = null;
_entryPrice = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0.01m;
if (step <= 0m)
return 0.01m;
return step;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.OpenTime.Hour;
// Check pending stop entries
CheckPendingEntries(candle);
// Manage existing position
ManageRisk(candle);
if (hour == ClosingHour)
{
// Closing hour: cancel pending and exit any open trades.
_pendingBuyPrice = null;
_pendingSellPrice = null;
ExitPosition();
}
if (hour == OpeningHour && hour != ClosingHour && Position == 0m && !_pendingBuyPrice.HasValue)
{
// Opening hour: set up new pending entries.
SetupPendingEntries(candle.ClosePrice);
}
}
private void CheckPendingEntries(ICandleMessage candle)
{
if (_pendingBuyPrice is decimal buyPrice && candle.HighPrice >= buyPrice && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = buyPrice;
_pendingBuyPrice = null;
_pendingSellPrice = null;
return;
}
if (_pendingSellPrice is decimal sellPrice && candle.LowPrice <= sellPrice && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = sellPrice;
_pendingBuyPrice = null;
_pendingSellPrice = null;
}
}
private void ManageRisk(ICandleMessage candle)
{
if (_pipSize <= 0m || _entryPrice is not decimal entry)
return;
var takeProfitDistance = TakeProfitPips * _pipSize;
var stopLossDistance = StopLossPips * _pipSize;
if (Position > 0m)
{
if (takeProfitDistance > 0m && candle.HighPrice - entry >= takeProfitDistance)
{
SellMarket();
_entryPrice = null;
return;
}
if (stopLossDistance > 0m && entry - candle.LowPrice >= stopLossDistance)
{
SellMarket();
_entryPrice = null;
}
}
else if (Position < 0m)
{
if (takeProfitDistance > 0m && entry - candle.LowPrice >= takeProfitDistance)
{
BuyMarket();
_entryPrice = null;
return;
}
if (stopLossDistance > 0m && candle.HighPrice - entry >= stopLossDistance)
{
BuyMarket();
_entryPrice = null;
}
}
}
private void ExitPosition()
{
if (Position > 0m)
SellMarket();
else if (Position < 0m)
BuyMarket();
_entryPrice = null;
}
private void SetupPendingEntries(decimal referencePrice)
{
if (_pipSize <= 0m)
return;
var distance = DistancePips * _pipSize;
if (distance <= 0m)
return;
_pendingBuyPrice = referencePrice + distance;
_pendingSellPrice = referencePrice - distance;
}
}
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 pending_orders_by_time_strategy(Strategy):
"""Places symmetric stop entries at scheduled hours with daily resets and SL/TP management."""
def __init__(self):
super(pending_orders_by_time_strategy, self).__init__()
self._opening_hour = self.Param("OpeningHour", 2) \
.SetDisplay("Opening Hour", "Hour to activate pending orders", "Schedule")
self._closing_hour = self.Param("ClosingHour", 22) \
.SetDisplay("Closing Hour", "Hour to cancel orders and flat positions", "Schedule")
self._distance_pips = self.Param("DistancePips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Distance (pips)", "Offset for entry stop orders", "Orders")
self._stop_loss_pips = self.Param("StopLossPips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 2000.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pips)", "Profit target distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Working timeframe for the schedule", "General")
self._pip_size = 0.0
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = None
@property
def OpeningHour(self):
return int(self._opening_hour.Value)
@property
def ClosingHour(self):
return int(self._closing_hour.Value)
@property
def DistancePips(self):
return float(self._distance_pips.Value)
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _calculate_pip_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.01
if step <= 0:
return 0.01
return step
def OnStarted2(self, time):
super(pending_orders_by_time_strategy, self).OnStarted2(time)
self._pip_size = self._calculate_pip_size()
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
# Check pending stop entries
self._check_pending_entries(candle)
# Manage existing position
self._manage_risk(candle)
if hour == self.ClosingHour:
# Closing hour: cancel pending and exit any open trades
self._pending_buy_price = None
self._pending_sell_price = None
self._exit_position()
if hour == self.OpeningHour and hour != self.ClosingHour and self.Position == 0 and self._pending_buy_price is None:
# Opening hour: set up new pending entries
self._setup_pending_entries(float(candle.ClosePrice))
def _check_pending_entries(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self._pending_buy_price is not None and h >= self._pending_buy_price and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = self._pending_buy_price
self._pending_buy_price = None
self._pending_sell_price = None
return
if self._pending_sell_price is not None and lo <= self._pending_sell_price and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = self._pending_sell_price
self._pending_buy_price = None
self._pending_sell_price = None
def _manage_risk(self, candle):
if self._pip_size <= 0 or self._entry_price is None:
return
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
entry = self._entry_price
tp_dist = self.TakeProfitPips * self._pip_size
sl_dist = self.StopLossPips * self._pip_size
if self.Position > 0:
if tp_dist > 0 and h - entry >= tp_dist:
self.SellMarket()
self._entry_price = None
return
if sl_dist > 0 and entry - lo >= sl_dist:
self.SellMarket()
self._entry_price = None
elif self.Position < 0:
if tp_dist > 0 and entry - lo >= tp_dist:
self.BuyMarket()
self._entry_price = None
return
if sl_dist > 0 and h - entry >= sl_dist:
self.BuyMarket()
self._entry_price = None
def _exit_position(self):
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_price = None
def _setup_pending_entries(self, reference_price):
if self._pip_size <= 0:
return
distance = self.DistancePips * self._pip_size
if distance <= 0:
return
self._pending_buy_price = reference_price + distance
self._pending_sell_price = reference_price - distance
def OnReseted(self):
super(pending_orders_by_time_strategy, self).OnReseted()
self._pip_size = 0.0
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = None
def CreateClone(self):
return pending_orders_by_time_strategy()