Diese StockSharp-Strategie ist eine originalgetreue C#-Konvertierung des MetaTrader 4 Expert Advisors Strategy_of_Regularities_of_Exchange_Rates.mq4. Das System wurde als täglicher Breakout-Straddle konzipiert: Es umschließt den Markt mit Stop-Orders, wenn eine bestimmte Stunde erreicht ist, und hält diese Orders bis zur nächtlichen Handelsschlusszeit aktiv. Jede gefüllte Position wird sowohl durch einen Stop-Loss auf Brokerseite als auch durch einen Intraday-Take-Profit-Watchdog überwacht, sodass Geschäfte nicht über die definierte Handelssitzung hinaus andauern.
Im Gegensatz zu indikatorgesteuerten Systemen konzentriert sich die Logik ausschließlich auf Zeit und Entfernung. Wenn der Zeitplan besagt, dass der Markt bereit sein sollte, misst die Strategie einen festen Offset in Brokerpunkten (Pips) vom aktuellen Geld- und Briefkurs und platziert ein Paar symmetrischer Stop-Orders. Der Code passt die Punktberechnung automatisch an Symbole mit 3- oder 5-stelligen Anführungszeichen an und entspricht damit dem Verhalten der ursprünglichen MQL-Version.
Handelslogik
Öffnungsstunde – sobald eine fertige Kerze OpeningHour meldet, storniert die Strategie alle verbleibenden ausstehenden Aufträge und setzt einen Kaufstopp über dem aktuellen Brief und einen Verkaufsstopp unter dem aktuellen Gebot. Der Abstand beträgt EntryOffsetPoints * point, wobei der Wert point vom Instrument PriceStep abgeleitet und für gebrochene Anführungszeichen angepasst wird.
Schutzanordnungen – unmittelbar nach dem Start aktiviert die Strategie StartProtection mit dem konfigurierten StopLossPoints. Jeder ausgeführte Trade erhält daher einen Makler-Stop-Loss, der mit dem ursprünglichen EA identisch ist.
Take-Profit-Überwachung – bei jeder abgeschlossenen Kerze prüft der Algorithmus, ob der aktuelle Gewinn TakeProfitPoints * point übersteigt. Wenn ja, wird die Position zum Marktwert geschlossen. Dies spiegelt die ursprüngliche OrderClose-Schleife wider, die beendet wurde, als der Gewinn den Schwellenwert erreichte.
Schlussstunde – wenn die Uhr ClosingHour erreicht, schließt die Strategie alle offenen Positionen zwangsweise und storniert die Stop-Orders, um sicherzustellen, dass das Buch für die nächste Sitzung unverändert bleibt.
Täglicher Reset – ein neuer Stapel ausstehender Aufträge wird nur einmal pro Handelstag gesendet, wodurch Duplikate vermieden werden und gleichzeitig die ursprüngliche Absicht einer einzelnen Einrichtung pro Sitzung respektiert wird.
Parameter
Parameter
Standard
Beschreibung
OpeningHour
9
Stunde (0–23), in der das Paar Stop-Orders platziert wird.
ClosingHour
2
Stunde (0–23), in der ausstehende Aufträge entfernt und alle offenen Geschäfte abgeflacht werden.
EntryOffsetPoints
20
Abstand in Brokerpunkten vom aktuellen Geld-/Briefkurs zu den Stop-Orders.
TakeProfitPoints
20
Gewinnziel in Brokerpunkten, das einen Marktausstieg auslöst. Auf 0 setzen, um den manuellen Take-Profit zu deaktivieren.
StopLossPoints
500
Entfernung in Brokerpunkten für den über StartProtection angeschlossenen Schutzstopp.
OrderVolume
0.1
Volumen jeder Stop-Order.
CandleType
30 minute time frame
Kerzenserien zur Auswertung des Zeitplans. Bei jedem Zeitrahmen ≤ 1 Stunde bleibt das Verhalten im Einklang mit dem MQL-Skript.
Konvertierungshinweise
Der ursprüngliche Fachberater arbeitete an Tick-Ereignissen und verwies direkt auf Hour(). In StockSharp hört die Strategie auf fertige Kerzen und verwendet deren Öffnungszeit, wodurch die Einmal-pro-Stunde-Logik erhalten bleibt und gleichzeitig die Repository-Richtlinien für Kerzenzustände eingehalten werden.
Ausstehende Aufträge werden mit Security.ShrinkPrice normalisiert, sodass die generierten Preise immer mit der Tick-Größe des Instruments übereinstimmen.
Das Stop-Management wird an StartProtection delegiert und stellt den von der Plattform generierten Stop-Loss wieder her, den MetaTrader während OrderSend angehängt hat.
Der Code verfolgt das letzte Handelsdatum, um zu vermeiden, dass die gleiche Handelsspanne mehrmals am selben Tag erneut übermittelt wird, was im ursprünglichen EA in Zeitrahmen von weniger als einer Stunde passieren konnte.
Ausführliche Inline-Kommentare verdeutlichen jeden Schritt des Arbeitsablaufs für zukünftige Wartungsarbeiten oder Experimente.
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>
/// Time-based breakout strategy converted from the "Strategy of Regularities of Exchange Rates" MQL expert advisor.
/// At a scheduled hour captures reference price, then enters on breakout above/below offset levels.
/// Exits at a closing hour or on take-profit/stop-loss hit.
/// </summary>
public class RegularitiesOfExchangeRatesStrategy : Strategy
{
private readonly StrategyParam<int> _openingHour;
private readonly StrategyParam<int> _closingHour;
private readonly StrategyParam<decimal> _entryOffsetPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _dummySma;
private decimal _pointSize;
private DateTime? _lastEntryDate;
private decimal _referencePrice;
private decimal _entryPrice;
private bool _waitingForBreakout;
public int OpeningHour
{
get => _openingHour.Value;
set => _openingHour.Value = value;
}
public int ClosingHour
{
get => _closingHour.Value;
set => _closingHour.Value = value;
}
public decimal EntryOffsetPoints
{
get => _entryOffsetPoints.Value;
set => _entryOffsetPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public RegularitiesOfExchangeRatesStrategy()
{
_openingHour = Param(nameof(OpeningHour), 9)
.SetDisplay("Opening Hour", "Hour (0-23) when breakout levels are set", "Schedule")
.SetRange(0, 23);
_closingHour = Param(nameof(ClosingHour), 2)
.SetDisplay("Closing Hour", "Hour (0-23) when the strategy exits", "Schedule")
.SetRange(0, 23);
_entryOffsetPoints = Param(nameof(EntryOffsetPoints), 20m)
.SetDisplay("Entry Offset (points)", "Distance from reference price for breakout", "Orders")
.SetGreaterThanZero();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
.SetDisplay("Take Profit (points)", "Profit target distance in points", "Risk")
.SetNotNegative();
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetDisplay("Stop Loss (points)", "Stop-loss distance in points", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to evaluate trading hours", "General");
}
protected override void OnReseted()
{
base.OnReseted();
_pointSize = 0m;
_lastEntryDate = null;
_referencePrice = 0m;
_entryPrice = 0m;
_waitingForBreakout = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointSize = Security?.PriceStep ?? 0.01m;
if (_pointSize <= 0m)
_pointSize = 0.01m;
_dummySma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_dummySma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var hour = candle.OpenTime.Hour;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
// At closing hour: flatten position and cancel breakout watch
if (hour == ClosingHour)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_waitingForBreakout = false;
_entryPrice = 0m;
}
// Manage take-profit and stop-loss for existing position
if (Position != 0 && _entryPrice > 0m)
{
var tp = TakeProfitPoints * _pointSize;
var sl = StopLossPoints * _pointSize;
if (Position > 0)
{
if ((tp > 0m && close - _entryPrice >= tp) || (sl > 0m && _entryPrice - close >= sl))
{
SellMarket(Position);
_entryPrice = 0m;
_waitingForBreakout = false;
}
}
else if (Position < 0)
{
if ((tp > 0m && _entryPrice - close >= tp) || (sl > 0m && close - _entryPrice >= sl))
{
BuyMarket(-Position);
_entryPrice = 0m;
_waitingForBreakout = false;
}
}
}
// At opening hour: set reference price for breakout
if (hour == OpeningHour && Position == 0)
{
var date = candle.OpenTime.Date;
if (!_lastEntryDate.HasValue || _lastEntryDate.Value != date)
{
_referencePrice = close;
_waitingForBreakout = true;
_lastEntryDate = date;
}
}
// Check for breakout entry
if (_waitingForBreakout && Position == 0 && _referencePrice > 0m)
{
var offset = EntryOffsetPoints * _pointSize;
var buyLevel = _referencePrice + offset;
var sellLevel = _referencePrice - offset;
if (high >= buyLevel)
{
BuyMarket();
_entryPrice = close;
_waitingForBreakout = false;
}
else if (low <= sellLevel)
{
SellMarket();
_entryPrice = close;
_waitingForBreakout = false;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class regularities_of_exchange_rates_strategy(Strategy):
def __init__(self):
super(regularities_of_exchange_rates_strategy, self).__init__()
self._opening_hour = self.Param("OpeningHour", 9) \
.SetDisplay("Opening Hour", "Hour (0-23) when breakout levels are set", "Schedule")
self._closing_hour = self.Param("ClosingHour", 2) \
.SetDisplay("Closing Hour", "Hour (0-23) when the strategy exits", "Schedule")
self._entry_offset_points = self.Param("EntryOffsetPoints", 20.0) \
.SetDisplay("Entry Offset (points)", "Distance from reference price for breakout", "Orders")
self._take_profit_points = self.Param("TakeProfitPoints", 20.0) \
.SetDisplay("Take Profit (points)", "Profit target distance in points", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 500.0) \
.SetDisplay("Stop Loss (points)", "Stop-loss distance in points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe used to evaluate trading hours", "General")
self._point_size = 0.01
self._last_entry_date = None
self._reference_price = 0.0
self._entry_price = 0.0
self._waiting_for_breakout = False
@property
def OpeningHour(self):
return self._opening_hour.Value
@property
def ClosingHour(self):
return self._closing_hour.Value
@property
def EntryOffsetPoints(self):
return self._entry_offset_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(regularities_of_exchange_rates_strategy, self).OnStarted2(time)
self._point_size = 0.01
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps > 0:
self._point_size = ps
self._dummy_sma = SimpleMovingAverage()
self._dummy_sma.Length = 2
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._dummy_sma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
hour = candle.OpenTime.Hour
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# At closing hour: flatten position and cancel breakout watch
if hour == self.ClosingHour:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._waiting_for_breakout = False
self._entry_price = 0.0
# Manage take-profit and stop-loss for existing position
if self.Position != 0 and self._entry_price > 0:
tp = float(self.TakeProfitPoints) * self._point_size
sl = float(self.StopLossPoints) * self._point_size
if self.Position > 0:
if (tp > 0 and close - self._entry_price >= tp) or (sl > 0 and self._entry_price - close >= sl):
self.SellMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._waiting_for_breakout = False
elif self.Position < 0:
if (tp > 0 and self._entry_price - close >= tp) or (sl > 0 and close - self._entry_price >= sl):
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._waiting_for_breakout = False
# At opening hour: set reference price for breakout
if hour == self.OpeningHour and self.Position == 0:
candle_date = candle.OpenTime.Date
if self._last_entry_date is None or self._last_entry_date != candle_date:
self._reference_price = close
self._waiting_for_breakout = True
self._last_entry_date = candle_date
# Check for breakout entry
if self._waiting_for_breakout and self.Position == 0 and self._reference_price > 0:
offset = float(self.EntryOffsetPoints) * self._point_size
buy_level = self._reference_price + offset
sell_level = self._reference_price - offset
if high >= buy_level:
self.BuyMarket()
self._entry_price = close
self._waiting_for_breakout = False
elif low <= sell_level:
self.SellMarket()
self._entry_price = close
self._waiting_for_breakout = False
def OnReseted(self):
super(regularities_of_exchange_rates_strategy, self).OnReseted()
self._point_size = 0.01
self._last_entry_date = None
self._reference_price = 0.0
self._entry_price = 0.0
self._waiting_for_breakout = False
def CreateClone(self):
return regularities_of_exchange_rates_strategy()