Konvertiert vom MetaTrader 4 Expert Advisor "CycleMarketOrder_V181". Die Strategie organisiert eine feste Anzahl von Slots innerhalb einer Preisskala und eröffnet Marktorders, wenn das aktuelle Bid/Ask durch einen einzelnen Slot handelt. Jeder Slot trägt sein eigenes Volumen, Break-Even-Schwellenwert und Trailing-Stop-Wert, sodass das Grid schrittweise in eine Position skalieren kann, während bereits erreichte Gewinne geschützt werden.
Handelslogik
Die Pip-Größe wird aus dem Instrument-Kursschritt und der Dezimalgenauigkeit abgeleitet (5/3-stellige Symbole entsprechen 10 Punkten pro Pip). Die Parameter MaxPrice, SpanPips und MaxCount werden dann verwendet, um den Preisbereich jedes Slots vorzuberechnen.
Level-1-Marktdaten werden konsumiert, um das Tick-basierte Verhalten des ursprünglichen Expert Advisors nachzubilden. Jedes Update aktualisiert die zwischengespeicherten besten Bid/Ask-Preise.
Wenn UseWeekendMode aktiviert ist, verweigert die Strategie den Handel außerhalb des konfigurierten Wochenend-Fensters (Samstag ab WeekendHour, den ganzen Sonntag und Montag vor WeekstartHour).
Bei Long-Zyklen (EntryDirection = 1) scannt der Algorithmus Slots vom niedrigsten zum höchsten Bezeichner. Wenn der aktuelle Ask-Preis zwischen startPrice und endPrice des Slots fällt, wird eine Markt-Kauforder mit OrderVolume gesendet. Short-Zyklen (EntryDirection = -1) spiegeln diese Logik und verwenden den Bid-Preis.
Slot-Zustände verfolgen ausstehende Ein-/Ausstiegsorders, gefülltes Volumen und den durchschnittlichen Einstiegspreis. Logging verwendet MagicNumberBase + index um die MT4-"Magic"-Bezeichner zu entsprechen.
Trailing-Verwaltung wird bei jedem Level-1-Update vor der Auswertung neuer Einträge ausgeführt. Sobald der Gewinn bei einem Long-Slot BreakEvenPips + TrailingStopPips überschreitet, wird der Stop auf Bid - TrailingStopPips gesetzt. Short-Slots verwenden Ask + TrailingStopPips und die gespiegelte Break-Even-Bedingung. Wenn der Marktpreis den gespeicherten Stop kreuzt, wird der Slot mit einer Marktorder geschlossen.
Da nur Marktorders verwendet werden, gibt es keine ausstehenden Orders zu stornieren. Teilfüllungen passen das verbleibende Slot-Volumen an, sodass die Strategie weiterhin traillen oder den Slot neu bewaffnen kann, sobald er flach wird.
Parameter
Parameter
Beschreibung
EntryDirection
Handelsrichtung: 1 kauft die Leiter, -1 verkauft sie, 0 deaktiviert neue Einträge, hält Trailing aktiv.
MaxPrice
Oberer Ankerkurs zur Berechnung der Slot-Bereiche.
MaxCount
Gesamtanzahl aktiver Slots im Grid.
SpanPips
Abstand in Pips zwischen aufeinanderfolgenden Slot-Grenzen.
OrderVolume
Volumen das gesendet wird wenn ein Slot auslöst.
BreakEvenPips
Gewinnabstand der überschritten werden muss bevor der Trailing Stop bewaffnet wird.
TrailingStopPips
Trailing-Abstand der nach Erreichen des Break-Even angewendet wird.
UseWeekendMode
Aktiviert das Wochenend-Handelssperrfenster.
WeekendHour
Stunde am Samstag (Terminalzeit) wenn der Handel ausgesetzt wird.
WeekstartHour
Stunde am Montag wenn der Handel wieder aufgenommen wird.
MagicNumberBase
Bezeichner-Offset in Log-Nachrichten um die Original-Magic-Numbers zu entsprechen.
Implementierungshinweise
Die Slot-Verwaltung verfolgt ausstehende Ein- und Ausstiegsorders, sodass wiederholte Füllungen kein doppeltes Volumen registrieren.
Die Strategie setzt ihren Trailing Stop zurück wann immer eine neue Füllung das Slot-Engagement erhöht und stellt sicher, dass der Stop den zuletzt gemittelten Einstiegspreis widerspiegelt.
Der Wochenendschutz überspringt einfach sowohl Trailing- als auch Einstiegslogik; bestehende Positionen bleiben während des Sperrzeitraums unberührt.
Level-1-Daten sind erforderlich weil die Logik rohe Bid/Ask-Preise statt Kerzenschlusskurse vergleicht und damit das Tick-für-Tick-Verhalten der MT4-Version eng reproduziert.
using System;
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;
public class CycleMarketOrderStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public CycleMarketOrderStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class cycle_market_order_strategy(Strategy):
def __init__(self):
super(cycle_market_order_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(cycle_market_order_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(cycle_market_order_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return cycle_market_order_strategy()