Diese Strategie recreiert den klassischen MetaTrader Expert "Pending orders by time" für StockSharp. Sie läuft nach einem diskreten Zeitplan: Jeden Tag platziert sie symmetrische Stop-Orders rund um den Markt, wenn eine neue Sessionsstunde beginnt, und löscht alle Orders plus offenen Positionen zu einer angegebenen Schlussstunde. Die Implementierung behält die ursprünglichen pip-basierten Eingaben bei, konvertiert sie in native Kurseinheiten und verwendet die High-Level-API zur Risikoverwaltung.
Funktionsweise
Zeitbasierter Trigger – Wenn eine Kerze empfangen wird, die zur konfigurierten Eröffnungsstunde endet, sendet die Strategie einen Buy Stop über dem Ask und einen Sell Stop unter dem Bid. Beide Orders werden um den Parameter Distance (pips) verschoben, der in Kurseinheiten umgerechnet wird.
Schutzbefehle – StartProtection fügt automatisch Stop-Loss- und Take-Profit-Schutz mit den in den Parametern definierten Pip-Abständen hinzu. ManageRisk fungiert auch als Absicherung und schließt jede Restposition, wenn eine abgeschlossene Kerze zeigt, dass die Schwellenwerte überschritten wurden.
Sitzungsschluss – Wenn die Schlussstunde ankommt, storniert die Strategie alle verbleibenden Pending Orders und schließt offene Trades unabhängig von Gewinn oder Verlust zwangsweise. Dies reproduziert das Verhalten des ursprünglichen Experts, am Ende der Sitzung zurückzusetzen.
Stellengerechte Pip-Größe – Der Pip-Multiplikator emuliert die MetaTrader-Implementierung, indem der Preisschritt für Symbole mit drei oder fünf Dezimalstellen (z.B. JPY oder 5-stellige FX-Paare) mit zehn multipliziert wird. Dies hält Legacy-Eingaben konsistent über Broker hinweg.
Der Standard-Kerzentyp sind 30-Minuten-Balken, um unter der ursprünglichen Beschränkung von Perioden kürzer als H1 zu bleiben. Jeder andere Zeitrahmen kann verwendet werden, solange die resultierenden Stunden-Zeitstempel den gewünschten Sessionsstunden entsprechen.
Parameter
Name
Beschreibung
Standard
Opening Hour
Stunde (0-23), zu der die Strategie das Paar Stop-Orders platziert.
9
Closing Hour
Stunde (0-23), zu der alle Orders storniert und Positionen geschlossen werden.
2
Distance (pips)
Versatz in Pips zwischen dem aktuellen Kurs und den ausstehenden Stop-Einstiegen.
20
Stop Loss (pips)
Pip-Abstand für den Schutz-Stop, sobald eine Position offen ist.
20
Take Profit (pips)
Pip-Abstand für das Gewinnziel, sobald eine Position offen ist.
500
Order Volume
Menge für jede ausstehende Stop-Order.
0.1
Candle Type
Zeitrahmen, der den Stundenplan steuert.
30-Minuten-Zeitrahmen
Alle Parameter können optimiert werden. Pip-basierte Eingaben werden intern mit dem Preisschritt des Instruments konvertiert, sodass sie zwischen FX-Symbolen mit unterschiedlicher Dezimalpräzision portabel bleiben.
Täglicher Arbeitsablauf
Bei jedem Kerzenschluss prüft die Strategie, ob der Stop-Loss- oder Take-Profit-Abstand erreicht wurde. Wenn ja, schließt sie die aktive Position zum Marktpreis.
Wenn die Schlussstunde erreicht wird, storniert sie alle ungefüllten Pending Orders und schließt die Position, um sicherzustellen, dass das Buch vor der nächsten Sitzung flach ist.
Wenn die Eröffnungsstunde erreicht wird (und die Strategie flach ist), storniert sie vorsichtshalber alte Orders und sendet einen neuen Sell Stop unter dem Bid und einen Buy Stop über dem Ask. Die Orders sind um den Spread gespiegelt, damit jeder Ausbruch erfasst werden kann.
Während der gesamten Sitzung hält der von StartProtection erstellte Plattformschutz einen Stop-Loss und Take-Profit angehängt, der sofort reagiert, wenn die Intrabar-Kursaction die Schwellenwerte erreicht.
Verwendungshinweise
Verwenden Sie Instrumente, deren Tick-Größe einen einzelnen "Punkt" darstellt, damit die Pip-Anpassung den ursprünglichen Expert widerspiegelt. Exotische Tick-Größen können manuelle Abstimmung der Abstandsparameter erfordern.
Die Logik nimmt einen Handelszyklus pro Tag an. Wenn Sie Intraday-Daten mit mehreren Öffnungs-/Schließungs-Übereinstimmungen verwenden, passen Sie die Stunden entsprechend an.
Da alle Aktionen beim Kerzenschluss stattfinden, wählen Sie eine Kerzengröße, die Ihrer gewünschten Bewertungshäufigkeit entspricht. Zum Beispiel bieten Stundenkerzen die gleiche Kadenz wie die MetaTrader-Version.
Die Strategie platziert neue Pending Orders nur wenn die Position flach ist, um Überexponierung zu vermeiden, wenn ein Ausbruchstrade während der nächsten Eröffnungsstunde noch aktiv ist.
Unterschiede zur MQL-Version
Schutzausstiege werden über StartProtection plus explizite Prüfungen verwaltet und nutzen die High-Level-API von StockSharp anstatt direkter Stop-Loss-Zuweisung am Pending-Order-Ticket.
Bid/Ask-Preise werden aus Security.BestBid und Security.BestAsk gelesen. Wenn diese Kurse nicht verfügbar sind, wird der Kerzenschluss als Fallback-Referenz verwendet.
Marktorders werden verwendet, um Positionen zur Schlussstunde der Einfachheit halber zu liquidieren und brokerspezifische Verhaltensweisen zu vermeiden.
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()