Auf GitHub ansehen

Cycle Market Order-Strategie

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

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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.
  6. 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.
  7. 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;
	}
}