Die 21-Stunden-Strategie reproduziert das Verhalten des MQL4-Expertenberaters 21hour.mq4. Es arbeitet in einem täglichen Zeitfenster: Ausstehende Breakout-Orders werden zu einer konfigurierbaren Startzeit erstellt und alle Risiken werden zu einer konfigurierbaren Stoppzeit entfernt. Die StockSharp-Version behält die gleiche „zwei Stop-Orders um den Preis herum“-Idee bei und nutzt gleichzeitig das High-Level-API für die Auftragsverwaltung, Marktdatenabonnements und die schützende Take-Profit-Abwicklung.
Handelslogik
Zu Beginn jedes Handelstages, wenn die Serverzeit mit StartHour:00 übereinstimmt, liest die Strategie die neuesten Geld-/Briefkurse und platziert sowohl eine Kauf-Stopp- als auch eine Verkaufs-Stopp-Order.
Der Abstand vom aktuellen Briefkurs zum Kaufstopp-Trigger beträgt StepPoints * PriceStep.
Der Abstand vom aktuellen Geldkurs zum Verkaufsstopp-Auslöser liegt um den gleichen Betrag unter dem Markt.
TakeProfitPoints wird über die Preisstufe des Instruments in eine Preisdistanz umgewandelt und an StartProtection übergeben, sodass sowohl Long- als auch Short-Positionen direkt nach der Ausführung einen schützenden Take-Profit erhalten.
Pro Tag ist nur eine ausstehende Einrichtung zulässig. Wenn nur eine der beiden Stop-Orders aktiv bleibt (z. B. nachdem eine Seite ausgeführt wurde), storniert die Strategie die verbleibende Pending-Order, um die ursprüngliche EA-Logik widerzuspiegeln.
Wenn die Uhr StopHour:00 erreicht, schließt die Strategie alle offenen Positionen am Markt und storniert alle ausstehenden ausstehenden Aufträge. Dies gilt auch dann, wenn kein Ausbruch stattgefunden hat.
Der Standardkerzenstrom besteht aus einminütigen Daten. Es dient lediglich dazu, die stündlichen Überprüfungen fertiger Kerzen auszulösen, was den prevtime-Schutz aus der MQL-Version nachahmt.
Parameter
Parameter
Beschreibung
Standard
Volume
Auftragsvolumen in Lots für beide ausstehenden Aufträge.
0.1
StartHour
Stunde (0–23), in der das Paar ausstehender Aufträge erstellt wird.
10
StopHour
Stunde (0–23), in der die Strategie Positionen schließt und alle ausstehenden Aufträge entfernt.
22
StepPoints
Abstand in Instrumentenpunkten zwischen dem aktuellen Geld-/Briefkurs und jedem Stop-Eintrag. Durch Multiplikation mit PriceStep in den Preis umgerechnet.
15
TakeProfitPoints
Abstand in Punkten vom Einstiegspreis zum Take-Profit-Ziel, verwaltet von StartProtection. Auf 0 setzen, um das Ziel zu deaktivieren.
200
CandleType
Kerzendatentyp, der für die Zeiterfassung verwendet wird. Der Standardwert ist ein einminütiger Zeitrahmen (TimeSpan.FromMinutes(1).TimeFrame()).
1 minute
Implementierungshinweise
Verwendet SubscribeCandles, um fertige Kerzen zu erhalten und den Stundenplan nur einmal pro Minute auszuwerten.
Abonniert Level-1-Kurse über SubscribeLevel1(), um die neuesten Geld-/Briefwerte für eine genaue Stop-Platzierung beizubehalten.
Verlässt sich auf StartProtection mit einer Take-Profit-Einheit, um den Take-Profit für ausstehende Aufträge vom ursprünglichen EA zu emulieren, anstatt Aufträge manuell anzuhängen.
Verfolgt die aktiven Kauf- und Verkaufs-Stop-Orders und ruft CancelOrder auf, wenn nur noch eine Seite übrig ist, um sicherzustellen, dass das System nie mit einer ungepaarten ausstehenden Order läuft.
Ruft BuyMarket / SellMarket-Helfer auf, um Positionen zur Stoppstunde zu glätten, wobei ausschließlich die übergeordnete Strategie API verwendet wird.
Verhaltenshinweise
Die Strategie erwartet, dass die Brokerverbindung Preisschrittinformationen bereitstellt. Wenn PriceStep fehlt, bleiben die Preise ungerundet.
Ausstehende Bestellungen werden nur einmal pro Kalendertag generiert. Sie werden am nächsten Handelstag zur konfigurierten Startzeit neu erstellt, auch wenn der Ausbruch am Vortag nicht ausgelöst wurde.
Wenn TakeProfitPoints Null ist, platziert die Strategie immer noch ausstehende Aufträge, es wird jedoch kein schützender Take-Profit verwaltet.
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()