Auf GitHub ansehen

Dynamischer Stop-Loss

Überblick

Der ursprüngliche MetaTrader-Expertenberater „Dynamic Stop Loss“ eröffnet selbst keine neuen Trades. Stattdessen beobachtet es bestehende Marktpositionen und positioniert den schützenden Stop-Loss neu, sobald eine neue Kerze erscheint, sodass er in einem festen Abstand zum aktuellen Preis bleibt. Der StockSharp-Port behält das gleiche Verhalten bei: Jeder abgeschlossene Balken löst eine Neuberechnung des Schutzstopps für die Seite aus, die gerade geöffnet ist. Wenn keine Position vorhanden ist, bleibt die Strategie einfach im Leerlauf, bis eine neue Position erkannt wird.

Wie es funktioniert

  1. Die Strategie abonniert Kerzen, die durch den Parameter Candle Type definiert sind (Standardzeitrahmen 1 Minute).
  2. Wenn eine Kerze schließt, wird der Schlusskurs mit dem vom Benutzer ausgewählten Punktabstand multipliziert. Die Distanz wird von Punkten im MetaTrader-Stil über Security.PriceStep in ein absolutes Preisdelta umgewandelt (Fallback auf Security.Step, dann auf 1).
  3. Wenn eine Long-Position offen ist, storniert die Strategie alle bestehenden Stop-Orders und setzt einen neuen Verkaufs-Stopp bei Close - Distance.
  4. Wenn eine Short-Position offen ist, wird der Stop mithilfe einer Kauf-Stopp-Order auf Close + Distance verschoben.
  5. Wenn die Position geschlossen wird (manuell oder durch Stop-Filling), wird die Trailing-Order gelöscht, um veraltete Schutz-Orders zu vermeiden.

Dies führt zu derselben ständig neu verankerten Stop-Distanz wie bei der MQL-Version, was bedeutet, dass sich der Stop bei Schwankungen der Kerzen sowohl näher an den Markt als auch weiter davon entfernen kann.

Parameter

Name Standard Beschreibung
StopLossPoints 800 Abstand zwischen dem Marktpreis und dem Schutzstopp, gemessen in Instrumentenpunkten. Der Wert wird mit Security.PriceStep multipliziert (Fallback auf Security.Step, dann 1), bevor er auf den Schlusskurs angewendet wird. Auf 0 setzen, um die Stoppverwaltung zu deaktivieren.
CandleType TimeFrameCandle(00:01:00) Kerzentyp, der definiert, wann der Stop neu berechnet wird. Wählen Sie einen Zeitrahmen, der dem in MetaTrader verwendeten Diagramm entspricht.

Nutzungshinweise

  • Die Strategie geht davon aus, dass Trades durch externe Strategien, manuelle Vorgänge oder andere Komponenten eröffnet werden. Es verwaltet nur den Stop-Loss.
  • Stellen Sie sicher, dass die Sicherheitsmetadaten (PriceStep, Step, Volumen) gefüllt sind, damit die Point-to-Price-Konvertierung mit der Tick-Größe des Brokers übereinstimmt. Instrumente, die mit gebrochenen Pips notiert werden, müssen den richtigen Schritt anzeigen.
  • Da der Stop bei jedem Kerzenschluss neu berechnet wird, folgt er dem Preis, selbst wenn sich der Markt gegen die Position bewegt. Dies spiegelt die MetaTrader-Logik wider, bei der OrderModify immer die neuesten Bid/Ask minus/plus der konfigurierten Entfernung verwendet.
  • Die erstellten Stop-Orders ersetzen immer die vorherigen, um die Plattform mit dem neuesten Schutzniveau synchron zu halten.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Dynamic Stop Loss strategy: EMA trend with ATR-based dynamic stop management.
/// Enters on EMA trend direction, exits when price moves against by ATR distance.
/// </summary>
public class DynamicStopLossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _entryPrice;
	private decimal _stopPrice;
	private bool _prevAboveEma;
	private bool _hasPrevSignal;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public DynamicStopLossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for stop distance", "Indicators");
		_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
			.SetDisplay("ATR Multiplier", "ATR multiplier for stop distance", "Risk");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 12)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_stopPrice = 0m;
		_prevAboveEma = false;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_entryPrice = 0;
		_stopPrice = 0;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
	}

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

		var close = candle.ClosePrice;
		var stopDist = atr * AtrMultiplier;
		var aboveEma = close > ema;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (Position > 0)
		{
			var newStop = close - stopDist;
			if (newStop > _stopPrice) _stopPrice = newStop;
			if (close <= _stopPrice)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_candlesSinceTrade = 0;
				_prevAboveEma = aboveEma;
				_hasPrevSignal = true;
				return;
			}
		}
		else if (Position < 0)
		{
			var newStop = close + stopDist;
			if (newStop < _stopPrice || _stopPrice == 0) _stopPrice = newStop;
			if (close >= _stopPrice)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_candlesSinceTrade = 0;
				_prevAboveEma = aboveEma;
				_hasPrevSignal = true;
				return;
			}
		}

		if (_hasPrevSignal && aboveEma != _prevAboveEma && _candlesSinceTrade >= SignalCooldownCandles)
		{
			if (aboveEma && Position <= 0)
			{
				BuyMarket();
				_entryPrice = close;
				_stopPrice = close - stopDist;
				_candlesSinceTrade = 0;
			}
			else if (!aboveEma && Position >= 0)
			{
				SellMarket();
				_entryPrice = close;
				_stopPrice = close + stopDist;
				_candlesSinceTrade = 0;
			}
		}

		_prevAboveEma = aboveEma;
		_hasPrevSignal = true;
	}
}