Auf GitHub ansehen

Lucky Shift Limit-Strategie

Die Lucky Shift Limit-Strategie ist eine direkte Umsetzung des MetaTrader 4 Expertenberaters Lucky_acnl6p6j89zn91fa.mq4. Es beobachtet die besten Geld-/Briefkurse in Echtzeit und reagiert auf plötzliche Sprünge, gemessen in MetaTrader „Punkten“ (Pips). Wenn der Briefkurs um die konfigurierte Verschiebungsdistanz nach oben beschleunigt, dämpft die Strategie die Bewegung durch Verkaufen, während ein starker Rückgang des Geldkurses einen konträren Kauf auslöst. Alle offenen Geschäfte werden ständig überwacht und geschlossen, sobald sie profitabel werden oder wenn der schwebende Verlust einen Sicherheitsschwellenwert überschreitet, der mit der ursprünglichen MQ4-Logik identisch ist.

Daten- und Ausführungsanforderungen

  • Marktdaten – abonniert nur Kurse der Stufe 1; Es sind keine Kerzen oder Markttiefe erforderlich.
  • Ausführungsstil – Ein- und Ausstiege basieren auf Marktaufträgen, um die unmittelbaren OrderSend-Aufrufe von MetaTrader nachzuahmen.
  • Kontomodus – funktioniert sowohl mit Hedging- als auch mit Netting-Konten. Bei Netting-Konten akkumuliert die Strategie das Engagement in einer einzigen Position und das Exit-Modul reduziert es.
  • Volumengröße – Die Standard-Ordergröße stammt von Strategy.Volume, aber der Helfer emuliert AccountFreeMargin/10000 von MetaTrader, wenn der Portfoliowert verfügbar ist.

Parameter

Name Standard Beschreibung
Shift points 3 Mindestanzahl von MetaTrader Punkten zwischen aufeinanderfolgenden Anfragen/Geboten, die eine neue Bestellung auslösen. Größere Werte filtern Rauschen heraus, kleinere Werte reagieren schneller.
Limit points 18 Maximal zulässige nachteilige Abweichung für einen offenen Handel. Wenn sich der Preis um so viele Punkte gegen die Position bewegt, wird der Handel zwangsweise geschlossen.

Beide Parameter werden in MetaTrader Punkten ausgedrückt und intern unter Verwendung der Tick-Größe des Instruments in absolute Preis-Offsets umgewandelt. Die Optimierungsgrenzen in der Benutzeroberfläche entsprechen den praktischen Bereichen der MQ4-Version.

Handelslogik

  1. Initialisierung
    • Konvertiert die punktbasierten Einstellungen mithilfe von Security.PriceStep in tatsächliche Preisentfernungen.
    • Setzt zwischengespeicherte Bid/Ask-Kurse zurück und startet ein Level-1-Abonnement mit Bind-Verarbeitung auf hoher Ebene.
  2. Eintrittsbedingungen
    • Wenn der Briefkurs im Vergleich zum vorherigen Briefkurs um mindestens Shift points steigt, sendet die Strategie einen Marktverkaufsauftrag (der die Spitze abschwächt) mit einer Protokollnotiz, die den Auslöser erläutert.
    • Wenn das Gebot im Vergleich zum vorherigen Gebot um mindestens den gleichen Abstand fällt, wird ein Marktkauf eröffnet.
    • Signale können mehrmals hintereinander ausgelöst werden, genau wie beim ursprünglichen Experten, bei dem die Anzahl der gleichzeitigen Positionen nicht eingeschränkt war.
  3. Exit-Management
    • Jeder Anführungsstrich ruft TryClosePosition() auf. Long-Positionen werden sofort geschlossen, wenn der Geldkurs über dem durchschnittlichen Einstiegswert liegt (realisierter Gewinn) oder wenn der Briefkurs um Limit points unter dem Einstiegswert liegt (Verlustobergrenze).
    • Short-Positionen spiegeln diese Logik wider und schließen bei profitablen Briefkursen oder wenn der Geldkurs den Einstiegskurs um das konfigurierte Limit übersteigt.
    • Alle Exits verwenden Marktaufträge, um OrderClose zu replizieren und sicherzustellen, dass die Position im selben Tick abgeflacht wird.
  4. Positionsgröße
    • Berechnet das Standardvolumen aus Portfolio-Eigenkapital (equity / 10,000, auf eine Dezimalstelle gerundet), sofern verfügbar, passend zum MQ4-Helfer GetLots().
    • Fällt auf die Strategieeigenschaft Volume zurück, wenn Aktiendaten fehlen.

Hinweise zur Implementierung

  • Verwendet nur StockSharp-APIs auf hoher Ebene: SubscribeLevel1().Bind(ProcessLevel1) macht manuelle Zitat-Listener überflüssig.
  • Es werden keine benutzerdefinierten Sammlungen gespeichert. Bisherige Geld-/Briefwerte werden gemäß den Richtlinien in einfachen Nullable-Variablen gespeichert.
  • Die Verlustobergrenze hängt von der Tick-Größe des Instruments ab, sodass exotische Symbole mit gebrochenen Pip-Schritten automatisch dem richtigen Preisdelta zugeordnet werden.
  • Parameteränderungen während der Laufzeit werden berücksichtigt – die Strategie berechnet die Schwellenwerte neu, wenn Daten der Ebene 1 eintreffen.
  • Protokollierungsanweisungen dokumentieren jeden Ein- und Ausstiegsgrund, was Backtesting und Live-Diagnosen vereinfacht.

Anwendungstipps

  • Ideal für hochliquide FX-Paare oder Indizes, bei denen es häufig zu Bid-Ask-Schocks kommt.
  • Erwägen Sie die Kombination der Strategie mit Schutzmaßnahmen auf Portfolioebene (StartProtection), wenn zusätzliche Stop-Loss- oder Drawdown-Limite erforderlich sind.
  • Erhöhen Sie Shift points bei verrauschten Feeds, um übermäßigen Handel zu reduzieren, oder verringern Sie ihn, um ultrakurzfristige Bewegungen zu erfassen.
  • Die Logik ist von Natur aus konträr; Wenn ein Ausbruchsverhalten gewünscht wird, stellen Sie einfach Shift points hoch genug ein oder kombinieren Sie es mit einem anderen Filterindikator.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Candle-based reversion strategy that reacts to sudden price jumps (high/low shifts)
/// and enforces a configurable loss cap. Adapted from a Level1 quote-reversion approach
/// to work with candle data for backtesting.
/// </summary>
public class LuckyShiftLimitStrategy : Strategy
{
	private readonly StrategyParam<int> _shiftPoints;
	private readonly StrategyParam<int> _limitPoints;

	private decimal? _previousHigh;
	private decimal? _previousLow;
	private decimal _shiftThreshold;
	private decimal _limitThreshold;
	private decimal _entryPrice;
	private bool _thresholdsReady;
	private int _holdBars;

	/// <summary>
	/// Minimum price shift (as percentage tenths) required to trigger an entry.
	/// </summary>
	public int ShiftPoints
	{
		get => _shiftPoints.Value;
		set => _shiftPoints.Value = value;
	}

	/// <summary>
	/// Maximum adverse excursion (as percentage) tolerated before force-closing losing trades.
	/// </summary>
	public int LimitPoints
	{
		get => _limitPoints.Value;
		set => _limitPoints.Value = value;
	}

	/// <summary>
	/// Initializes the strategy parameters taken from the original MQ4 expert.
	/// </summary>
	public LuckyShiftLimitStrategy()
	{
		_shiftPoints = Param(nameof(ShiftPoints), 3)
			.SetGreaterThanZero()
			.SetDisplay("Shift points", "Minimum price delta between consecutive candles", "Trading")

			.SetOptimize(1, 20, 1);

		_limitPoints = Param(nameof(LimitPoints), 18)
			.SetGreaterThanZero()
			.SetDisplay("Limit points", "Maximum allowed drawdown in percentage", "Risk management")

			.SetOptimize(5, 80, 5);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousHigh = null;
		_previousLow = null;
		_shiftThreshold = 0m;
		_limitThreshold = 0m;
		_entryPrice = 0m;
		_thresholdsReady = false;
		_holdBars = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var tf = TimeSpan.FromMinutes(5).TimeFrame();

		SubscribeCandles(tf)
			.Bind(ProcessCandle)
			.Start();
	}

	private void EnsureThresholds(decimal price)
	{
		if (_thresholdsReady)
			return;

		if (price <= 0m)
			return;

		// ShiftPoints=3 -> 0.9% shift threshold, LimitPoints=18 -> 1.8% limit threshold
		_shiftThreshold = price * ShiftPoints * 0.003m;
		_limitThreshold = price * LimitPoints * 0.01m;
		_thresholdsReady = true;
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var high = candle.HighPrice;
		var low = candle.LowPrice;
		var close = candle.ClosePrice;

		EnsureThresholds(close);

		if (!_thresholdsReady)
			return;

		// Count hold bars for position management.
		if (Position != 0)
			_holdBars++;

		// Entry logic: detect sudden shifts in high/low between consecutive candles.
		// Only enter when flat.
		if (Position == 0 && _previousHigh is decimal prevHigh && _previousLow is decimal prevLow)
		{
			// High jumped up sharply -> sell on expected reversion
			if (high - prevHigh >= _shiftThreshold)
			{
				SellMarket();
				_entryPrice = close;
				_holdBars = 0;
				LogInfo($"Sell triggered: high shift {high - prevHigh:0.##} >= {_shiftThreshold:0.##}. Price={close:0.#####}");
			}
			// Low dropped sharply -> buy on expected rebound
			else if (prevLow - low >= _shiftThreshold)
			{
				BuyMarket();
				_entryPrice = close;
				_holdBars = 0;
				LogInfo($"Buy triggered: low shift {prevLow - low:0.##} >= {_shiftThreshold:0.##}. Price={close:0.#####}");
			}
		}

		_previousHigh = high;
		_previousLow = low;

		TryClosePosition(close);
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (Position != 0m && _entryPrice == 0m)
			_entryPrice = trade.Trade.Price;

		if (Position == 0m)
			_entryPrice = 0m;
	}

	private void TryClosePosition(decimal currentPrice)
	{
		if (Position == 0)
			return;

		var avgPrice = _entryPrice;

		if (avgPrice <= 0m)
			return;

		// Minimum hold of 5 bars before checking exit.
		if (_holdBars < 5)
			return;

		// Use half of shift threshold as profit target.
		var profitTarget = _shiftThreshold * 0.5m;

		if (Position > 0)
		{
			// Close long on profit or loss cap.
			if (currentPrice - avgPrice >= profitTarget)
			{
				SellMarket();
				_holdBars = 0;
				LogInfo($"Closed long in profit. Price={currentPrice:0.#####}");
			}
			else if (_limitThreshold > 0m && avgPrice - currentPrice >= _limitThreshold)
			{
				SellMarket();
				_holdBars = 0;
				LogInfo($"Closed long on loss cap. Price={currentPrice:0.#####}");
			}
		}
		else if (Position < 0)
		{
			// Close short on profit or loss cap.
			if (avgPrice - currentPrice >= profitTarget)
			{
				BuyMarket();
				_holdBars = 0;
				LogInfo($"Closed short in profit. Price={currentPrice:0.#####}");
			}
			else if (_limitThreshold > 0m && currentPrice - avgPrice >= _limitThreshold)
			{
				BuyMarket();
				_holdBars = 0;
				LogInfo($"Closed short on loss cap. Price={currentPrice:0.#####}");
			}
		}
	}
}