Auf GitHub ansehen

Stopreversal Trailing Strategie

Die Stopreversal Trailing Strategie reproduziert den MT5-Expert Exp_Stopreversal.mq5. Sie verwendet den benutzerdefinierten Stopreversal-Indikator, um eine dynamische Trailing-Stop-Linie um den ausgewählten Kerzenkurs zu bauen. Wenn der Preis diese Trailing-Linie nach oben durchbricht, behandelt die Strategie dies als bullische Umkehr, schließt optional Short-Positionen und eröffnet eine neue Long-Position. Ein Durchbruch nach unten erzeugt die symmetrische bärische Aktion. Signale können um eine konfigurierbare Anzahl geschlossener Balken verzögert werden, um das Verhalten des Original-Expert-Advisors zu replizieren.

Details

  • Einstiegslogik: reagiert auf Stopreversal-Indikator-Pfeile, die erzeugt werden, wenn der Preis den adaptiven Trailing Stop kreuzt.
  • Long/Short: beide Richtungen werden mit unabhängigen Schaltern für Long- oder Short-Einstiege unterstützt.
  • Ausstiegslogik: entgegengesetzte Stopreversal-Signale können bestehende Positionen schließen; schützende Stop-Loss- und Take-Profit-Niveaus sind ebenfalls verfügbar.
  • Stops: statischer Stop-Loss und Take-Profit in Kursschritten plus indikatorgesteuerte Umkehrungen.
  • Datenquelle: beliebiger Zeitrahmen; Standard verwendet 4-Stunden-Zeitrahmen-Kerzen und spiegelt den Multi-Timeframe-Aufruf des Original-Experts wider.
  • Signalverzögerung: der Parameter SignalBar verzögert die Orderausführung um die angegebene Anzahl abgeschlossener Balken (Standard 1 Balken).
  • Risikomanagement: optionale harte Stops in Kursschritten des Instruments; der Positionsschutzdienst wird beim Start aktiviert.
  • Indikatorparameter: der Trailing-Offset Npips steuert den Abstand zwischen Preis und Stop; PriceMode wählt den vom Trailing Stop verwendeten Kerzenkurs.
  • Standardwerte:
    • Volume = 1
    • StopLossSteps = 1000
    • TakeProfitSteps = 2000
    • BuyPositionOpen = true
    • SellPositionOpen = true
    • BuyPositionClose = true
    • SellPositionClose = true
    • Npips = 0.004
    • PriceMode = Close
    • SignalBar = 1

Parameter

Parameter Beschreibung
CandleType Kerzenabonnement für Stopreversal-Berechnungen und Handel. Standard ist ein 4-Stunden-Zeitrahmen.
Volume Basisordergröße beim Einstieg in eine neue Position.
StopLossSteps Abstand vom Einstieg zum Stop-Loss in Kursschritten; auf 0 setzen zum Deaktivieren.
TakeProfitSteps Abstand vom Einstieg zum Take-Profit in Kursschritten; auf 0 setzen zum Deaktivieren.
BuyPositionOpen Aktiviert das Öffnen von Long-Positionen bei einem bullischen Signal.
SellPositionOpen Aktiviert das Öffnen von Short-Positionen bei einem bärischen Signal.
BuyPositionClose Schließt bestehende Long-Positionen, wenn ein bärisches Signal empfangen wird.
SellPositionClose Schließt bestehende Short-Positionen, wenn ein bullisches Signal empfangen wird.
Npips Fraktionaler Multiplikator für den Trailing Stop zur Erweiterung oder Verengung des Umkehrabstands.
PriceMode Angewendete Preisvariante (Schluss, Eröffnung, Hoch, Tief, Median, typisch, gewichtet, einfacher Durchschnitt, Vierteldurchschnitt, Trendfolge oder Demark).
SignalBar Anzahl vollständig geschlossener Kerzen, die vor der Reaktion auf ein Signal gewartet werden, entsprechend dem MT5-Parameter.

Filter

  • Kategorie: Trendfolgende Umkehr
  • Richtung: Bidirektional
  • Indikatoren: Stopreversal (ATR-gestützter Trailing Stop)
  • Stops: Statischer Stop-Loss und Take-Profit, optional
  • Zeitrahmen: Konfigurierbar (Standard H4)
  • Saisonalität: Keine
  • Neuronale Netze: Nein
  • Divergenz: Nein
  • Komplexität: Mittel aufgrund der benutzerdefinierten Trailing-Logik
  • Risikolevel: Einstellbar durch Stop-Abstand und Trailing-Offset
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Stopreversal indicator based trailing stop strategy.
/// </summary>
public class StopreversalTrailingStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stopLossSteps;
	private readonly StrategyParam<int> _takeProfitSteps;
	private readonly StrategyParam<bool> _buyPositionOpen;
	private readonly StrategyParam<bool> _sellPositionOpen;
	private readonly StrategyParam<bool> _buyPositionClose;
	private readonly StrategyParam<bool> _sellPositionClose;
	private readonly StrategyParam<decimal> _npips;
	private readonly StrategyParam<AppliedPriceModes> _priceMode;
	private readonly StrategyParam<int> _signalBar;

	private readonly List<SignalInfo> _signals = new();

	private AverageTrueRange _atr = null!;
	private decimal? _previousStopLevel;
	private decimal? _previousPrice;

	private decimal? _longStop;
	private decimal? _longTake;
	private decimal? _shortStop;
	private decimal? _shortTake;

	/// <summary>
	/// Initializes a new instance of <see cref="StopreversalTrailingStrategy"/>.
	/// </summary>
	public StopreversalTrailingStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Stopreversal timeframe", "General");

		_atrPeriod = Param(nameof(AtrPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR lookback for trailing stop", "Indicator");

		_stopLossSteps = Param(nameof(StopLossSteps), 10)
		.SetNotNegative()
		.SetDisplay("Stop Loss Steps", "Stop loss distance in price steps", "Risk")
		;

		_takeProfitSteps = Param(nameof(TakeProfitSteps), 20)
		.SetNotNegative()
		.SetDisplay("Take Profit Steps", "Take profit distance in price steps", "Risk")
		;

		_buyPositionOpen = Param(nameof(BuyPositionOpen), true)
		.SetDisplay("Open Long", "Allow opening long positions", "Trading");

		_sellPositionOpen = Param(nameof(SellPositionOpen), true)
		.SetDisplay("Open Short", "Allow opening short positions", "Trading");

		_buyPositionClose = Param(nameof(BuyPositionClose), true)
		.SetDisplay("Close Long", "Close long positions on sell signals", "Trading");

		_sellPositionClose = Param(nameof(SellPositionClose), true)
		.SetDisplay("Close Short", "Close short positions on buy signals", "Trading");

		_npips = Param(nameof(Npips), 0.004m)
		.SetGreaterThanZero()
		.SetDisplay("Trailing Offset", "Fractional offset applied to the stop line", "Indicator")
		;

		_priceMode = Param(nameof(PriceMode), AppliedPriceModes.Close)
		.SetDisplay("Applied Price", "Price source used by the trailing stop", "Indicator");

		_signalBar = Param(nameof(SignalBar), 1)
		.SetNotNegative()
		.SetDisplay("Signal Bar", "Bar delay before acting on a signal", "Indicator")
		;
	}

	/// <summary>
	/// Candle subscription type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// ATR period used for the trailing stop calculation.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Stop loss distance measured in price steps.
	/// </summary>
	public int StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Take profit distance measured in price steps.
	/// </summary>
	public int TakeProfitSteps
	{
		get => _takeProfitSteps.Value;
		set => _takeProfitSteps.Value = value;
	}

	/// <summary>
	/// Enable long entries.
	/// </summary>
	public bool BuyPositionOpen
	{
		get => _buyPositionOpen.Value;
		set => _buyPositionOpen.Value = value;
	}

	/// <summary>
	/// Enable short entries.
	/// </summary>
	public bool SellPositionOpen
	{
		get => _sellPositionOpen.Value;
		set => _sellPositionOpen.Value = value;
	}

	/// <summary>
	/// Close long positions on short signals.
	/// </summary>
	public bool BuyPositionClose
	{
		get => _buyPositionClose.Value;
		set => _buyPositionClose.Value = value;
	}

	/// <summary>
	/// Close short positions on long signals.
	/// </summary>
	public bool SellPositionClose
	{
		get => _sellPositionClose.Value;
		set => _sellPositionClose.Value = value;
	}

	/// <summary>
	/// Fractional offset used by the trailing stop calculation.
	/// </summary>
	public decimal Npips
	{
		get => _npips.Value;
		set => _npips.Value = value;
	}

	/// <summary>
	/// Price source used when computing the trailing level.
	/// </summary>
	public AppliedPriceModes PriceMode
	{
		get => _priceMode.Value;
		set => _priceMode.Value = value;
	}

	/// <summary>
	/// Number of bars to delay before reacting to a signal.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_signals.Clear();
		_previousStopLevel = null;
		_previousPrice = null;
		_longStop = null;
		_longTake = null;
		_shortStop = null;
		_shortTake = null;
	}

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

		_atr = new AverageTrueRange
		{
			Length = AtrPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_atr, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _atr);
			DrawOwnTrades(area);
		}

		// no protection
	}

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

		UpdateStops(candle);

		var price = GetAppliedPrice(candle);
		var prevStop = _previousStopLevel ?? price * (1m - Npips);
		var prevPrice = _previousPrice ?? price;
		var hasPrev = _previousStopLevel.HasValue && _previousPrice.HasValue;

		var stop = CalculateStop(price, prevPrice, prevStop);

		var buySignal = hasPrev && price > stop && prevPrice < prevStop && prevStop != 0m;
		var sellSignal = hasPrev && price < stop && prevPrice > prevStop && prevStop != 0m;

		_previousPrice = price;
		_previousStopLevel = stop;

		_signals.Add(new SignalInfo
		{
			BuySignal = buySignal,
			SellSignal = sellSignal,
			ClosePrice = candle.ClosePrice,
			Time = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime
		});

		TrimSignals();

		if (_signals.Count <= SignalBar)
		return;

		var index = _signals.Count - 1 - SignalBar;
		if (index < 0)
		return;

		var signal = _signals[index];
		var allowTrading = _atr.IsFormed;

		ExecuteSignal(signal, allowTrading);
	}

	private void ExecuteSignal(SignalInfo signal, bool allowTrading)
	{
		if (SellPositionClose && signal.BuySignal && Position < 0)
		{
			BuyMarket();
			ResetShortStops();
		}

		if (BuyPositionClose && signal.SellSignal && Position > 0)
		{
			SellMarket();
			ResetLongStops();
		}

		if (!allowTrading || Position != 0)
		return;

		if (BuyPositionOpen && signal.BuySignal)
		{
			if (Volume > 0)
			{
				BuyMarket();
				ResetShortStops();
				SetLongStops(signal.ClosePrice);
			}
		}
		else if (SellPositionOpen && signal.SellSignal)
		{
			if (Volume > 0)
			{
				SellMarket();
				ResetLongStops();
				SetShortStops(signal.ClosePrice);
			}
		}
	}

	private void UpdateStops(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStop is decimal longStop && candle.LowPrice <= longStop)
			{
				SellMarket();
				ResetLongStops();
				return;
			}

			if (_longTake is decimal longTake && candle.HighPrice >= longTake)
			{
				SellMarket();
				ResetLongStops();
			}
		}
		else if (Position < 0)
		{
			if (_shortStop is decimal shortStop && candle.HighPrice >= shortStop)
			{
				BuyMarket();
				ResetShortStops();
				return;
			}

			if (_shortTake is decimal shortTake && candle.LowPrice <= shortTake)
			{
				BuyMarket();
				ResetShortStops();
			}
		}
	}

	private void TrimSignals()
	{
		var max = Math.Max(SignalBar + 5, 10);
		while (_signals.Count > max)
		{
			try { _signals.RemoveAt(0); } catch { break; }
		}
	}

	private decimal CalculateStop(decimal price, decimal prevPrice, decimal prevStop)
	{
		var offset = Npips;

		if (price == prevStop)
		return prevStop;

		if (prevPrice < prevStop && price < prevStop)
		return Math.Min(prevStop, price * (1m + offset));

		if (prevPrice > prevStop && price > prevStop)
		return Math.Max(prevStop, price * (1m - offset));

		return price > prevStop
		? price * (1m - offset)
		: price * (1m + offset);
	}

	private void SetLongStops(decimal basePrice)
	{
		var step = GetEffectiveStep();

		_longStop = StopLossSteps > 0 ? basePrice - step * StopLossSteps : null;
		_longTake = TakeProfitSteps > 0 ? basePrice + step * TakeProfitSteps : null;
	}

	private void SetShortStops(decimal basePrice)
	{
		var step = GetEffectiveStep();

		_shortStop = StopLossSteps > 0 ? basePrice + step * StopLossSteps : null;
		_shortTake = TakeProfitSteps > 0 ? basePrice - step * TakeProfitSteps : null;
	}

	private void ResetLongStops()
	{
		_longStop = null;
		_longTake = null;
	}

	private void ResetShortStops()
	{
		_shortStop = null;
		_shortTake = null;
	}

	private decimal GetEffectiveStep()
	{
		var step = Security?.PriceStep;
		if (step is decimal s && s > 0)
		return s;

		return 0.0001m;
	}

	private decimal GetAppliedPrice(ICandleMessage candle)
	{
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		return PriceMode switch
		{
			AppliedPriceModes.Close => close,
			AppliedPriceModes.Open => open,
			AppliedPriceModes.High => high,
			AppliedPriceModes.Low => low,
			AppliedPriceModes.Median => (high + low) / 2m,
			AppliedPriceModes.Typical => (close + high + low) / 3m,
			AppliedPriceModes.Weighted => (2m * close + high + low) / 4m,
			AppliedPriceModes.Simple => (open + close) / 2m,
			AppliedPriceModes.Quarter => (open + close + high + low) / 4m,
			AppliedPriceModes.TrendFollow0 => close > open ? high : close < open ? low : close,
			AppliedPriceModes.TrendFollow1 => close > open ? (high + close) / 2m : close < open ? (low + close) / 2m : close,
			AppliedPriceModes.Demark => CalculateDemarkPrice(open, high, low, close),
			_ => close
		};
	}

	private static decimal CalculateDemarkPrice(decimal open, decimal high, decimal low, decimal close)
	{
		var result = high + low + close;

		if (close < open)
		result = (result + low) / 2m;
		else if (close > open)
		result = (result + high) / 2m;
		else
		result = (result + close) / 2m;

		return ((result - low) + (result - high)) / 2m;
	}

	private sealed class SignalInfo
	{
		public bool BuySignal { get; init; }
		public bool SellSignal { get; init; }
		public decimal ClosePrice { get; init; }
		public DateTimeOffset Time { get; init; }
	}

	/// <summary>
	/// Available price calculation modes.
	/// </summary>
	public enum AppliedPriceModes
	{
		/// <summary>
		/// Closing price.
		/// </summary>
		Close,

		/// <summary>
		/// Opening price.
		/// </summary>
		Open,

		/// <summary>
		/// Highest price.
		/// </summary>
		High,

		/// <summary>
		/// Lowest price.
		/// </summary>
		Low,

		/// <summary>
		/// Median price (high + low) / 2.
		/// </summary>
		Median,

		/// <summary>
		/// Typical price (close + high + low) / 3.
		/// </summary>
		Typical,

		/// <summary>
		/// Weighted close price (2 * close + high + low) / 4.
		/// </summary>
		Weighted,

		/// <summary>
		/// Simple average of open and close.
		/// </summary>
		Simple,

		/// <summary>
		/// Average of open, close, high and low.
		/// </summary>
		Quarter,

		/// <summary>
		/// Trend follow price variant 0.
		/// </summary>
		TrendFollow0,

		/// <summary>
		/// Trend follow price variant 1.
		/// </summary>
		TrendFollow1,

		/// <summary>
		/// Demark price formula.
		/// </summary>
		Demark
	}
}