Auf GitHub ansehen

XFatl XSatl Cloud Gegentrend-Strategie

Diese StockSharp-Strategie recreiert den MT5-Experten Exp_XFatlXSatlCloud. Sie beobachtet die geglättete FATL/SATL-"Wolke" und handelt gegen die Richtung ihrer Kreuzung. Wenn die schnelle Linie (XFATL) nach dem Überschreiten der langsamen Linie (XSATL) zurückfällt, öffnet die Strategie eine Long-Position. Wenn die schnelle Linie nach dem Unterschreiten wieder steigt, öffnet sie eine Short-Position. Optionale Stop-Loss- und Take-Profit-Level werden in Instrumentenpreisschritten ausgedrückt.

Handelsstrategie

  • Die Standarddatenquelle ist ein 8-Stunden-Zeitrahmen. Andere Kerzentypen können mit dem CandleType-Parameter ausgewählt werden.
  • Zwei Glättungspipelines werden aus StockSharp-gleitenden Durchschnitten aufgebaut. Standardmäßig verwenden beide einen Jurik-gleitenden Durchschnitt mit konfigurierbarer Länge und Phase. Alternative Glättungsfamilien (SMA, EMA, SMMA, WMA) sind ebenfalls verfügbar.
  • Signale werden an der durch SignalBar definierten Bar ausgewertet (Versatz in Bars von der letzten geschlossenen Kerze). Die Strategie speichert ein rollendes Fenster aktueller Indikatorwerte, sodass die letzten und vorherigen Werte wie in der MT5-Version verglichen werden können.
  • Einstiegsregeln (konträr):
    • Long – die schnelle Linie war auf der vorherigen Bar über der langsamen Linie und hat jetzt auf oder unter sie gekreuzt.
    • Short – die schnelle Linie war auf der vorherigen Bar darunter und hat jetzt auf oder darüber gekreuzt.
  • Ausstiegsregeln:
    • Long-Positionen schließen, wenn die vorherige Bar eine bärische Wolke zeigte (schnell unter langsam) und AllowLongExit aktiviert ist.
    • Short-Positionen schließen, wenn die vorherige Bar eine bullische Wolke zeigte (schnell über langsam) und AllowShortExit aktiviert ist.
  • Eine neue Position wird erst geöffnet, nachdem die vorherige vollständig geschlossen wurde, was das Verhalten des ursprünglichen Expertenberaters widerspiegelt.

Risikomanagement

  • TradeVolume kontrolliert die für Marktorders verwendete Menge. Die Strategie skaliert nie hinein – jede neue Position verwendet dieselbe Größe.
  • TakeProfitTicks und StopLossTicks werden direkt in Preisschrittdistanzen umgerechnet und in das integrierte Schutzmodul von StockSharp eingespeist. Setzen Sie sie auf null, um die entsprechende Schutzorder zu deaktivieren.
  • Da der MT5-Experte auf broker-spezifischen Money-Management-Berechnungen basierte, ersetzt diese Version diese Logik durch explizite Volumen- und Schutzparameter.

Parameter

Parameter Beschreibung
CandleType Kerzentyp oder Zeitrahmen für Indikatorberechnungen.
FastMethod / SlowMethod Glättungsfamilie für XFATL und XSATL (Jurik standardmäßig).
FastLength / SlowLength Periodenlängen für die schnellen und langsamen Filter.
FastPhase / SlowPhase Phaseneingaben, die bei Unterstützung an den Jurik-gleitenden Durchschnitt weitergeleitet werden.
SignalBar Bar-Versatz bei der Kreuzungsbewertung (1 = vorherige Bar).
TradeVolume Ordergröße für Einstiege.
AllowLongEntry / AllowShortEntry Konträre Einstiege in jede Richtung aktivieren oder deaktivieren.
AllowLongExit / AllowShortExit Erlauben, dass der Indikator offene Positionen bei entgegengesetzten Signalen schließt.
TakeProfitTicks Abstand zum Take-Profit-Ziel in Preisschritten.
StopLossTicks Abstand zum Schutz-Stop in Preisschritten.

Implementierungshinweise

  • Die Strategie hält kurze Warteschlangen aktueller Indikatorausgaben und trimmt sie auf die von SignalBar benötigte Mindestlänge. Es werden keine zusätzlichen historischen Puffer erstellt.
  • Die Jurik-Phasenunterstützung wird über Reflection konfiguriert, damit die Strategie mit verschiedenen StockSharp-Versionen kompatibel bleibt. Wenn dem zugrunde liegenden Indikator eine Phase-Eigenschaft fehlt, wird der Wert einfach ignoriert.
  • Nur der Schlusskurs jeder Kerze wird verwendet, was der gebräuchlichsten Einstellung für den ursprünglichen Experten entspricht. Die Logik auf alternative Preistypen zu erweitern würde eine Erweiterung der Strategie erfordern.
  • High-Level-API-Komponenten (SubscribeCandles, Bind, StartProtection) werden durchgängig verwendet, sodass die Strategie sauber mit Designer und anderen StockSharp-Produkten integriert.
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>
/// Contrarian strategy based on the XFatl and XSatl cloud crossovers.
/// </summary>
public class XFatlXSatlCloudStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<SmoothMethods> _fastMethod;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _fastPhase;
	private readonly StrategyParam<SmoothMethods> _slowMethod;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _slowPhase;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<bool> _allowLongEntry;
	private readonly StrategyParam<bool> _allowShortEntry;
	private readonly StrategyParam<bool> _allowLongExit;
	private readonly StrategyParam<bool> _allowShortExit;
	private readonly StrategyParam<int> _takeProfitTicks;
	private readonly StrategyParam<int> _stopLossTicks;

	private readonly List<decimal> _fastHistory = new();
	private readonly List<decimal> _slowHistory = new();

	public XFatlXSatlCloudStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for indicator calculations", "General");

		_fastMethod = Param(nameof(FastMethod), SmoothMethods.Ema)
			.SetDisplay("Fast Method", "Smoothing algorithm for the fast line", "Indicators");

		_fastLength = Param(nameof(FastLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast Length", "Length of the fast filter", "Indicators");

		_fastPhase = Param(nameof(FastPhase), 15)
			.SetDisplay("Fast Phase", "Phase parameter for Jurik smoothing", "Indicators");

		_slowMethod = Param(nameof(SlowMethod), SmoothMethods.Ema)
			.SetDisplay("Slow Method", "Smoothing algorithm for the slow line", "Indicators");

		_slowLength = Param(nameof(SlowLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Slow Length", "Length of the slow filter", "Indicators");

		_slowPhase = Param(nameof(SlowPhase), 15)
			.SetDisplay("Slow Phase", "Phase parameter for Jurik smoothing", "Indicators");

		_signalBar = Param(nameof(SignalBar), 1)
			.SetNotNegative()
			.SetDisplay("Signal Bar", "Index of the bar used for signals", "Logic");

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order size in lots", "Risk");

		_allowLongEntry = Param(nameof(AllowLongEntry), true)
			.SetDisplay("Allow Long Entry", "Enable contrarian long trades", "Logic");

		_allowShortEntry = Param(nameof(AllowShortEntry), true)
			.SetDisplay("Allow Short Entry", "Enable contrarian short trades", "Logic");

		_allowLongExit = Param(nameof(AllowLongExit), true)
			.SetDisplay("Allow Long Exit", "Allow indicator to close long trades", "Logic");

		_allowShortExit = Param(nameof(AllowShortExit), true)
			.SetDisplay("Allow Short Exit", "Allow indicator to close short trades", "Logic");

		_takeProfitTicks = Param(nameof(TakeProfitTicks), 2000)
			.SetNotNegative()
			.SetDisplay("Take Profit Ticks", "Distance to take profit in price steps", "Risk");

		_stopLossTicks = Param(nameof(StopLossTicks), 1000)
			.SetNotNegative()
			.SetDisplay("Stop Loss Ticks", "Distance to stop loss in price steps", "Risk");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public SmoothMethods FastMethod
	{
		get => _fastMethod.Value;
		set => _fastMethod.Value = value;
	}

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int FastPhase
	{
		get => _fastPhase.Value;
		set => _fastPhase.Value = value;
	}

	public SmoothMethods SlowMethod
	{
		get => _slowMethod.Value;
		set => _slowMethod.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int SlowPhase
	{
		get => _slowPhase.Value;
		set => _slowPhase.Value = value;
	}

	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	public bool AllowLongEntry
	{
		get => _allowLongEntry.Value;
		set => _allowLongEntry.Value = value;
	}

	public bool AllowShortEntry
	{
		get => _allowShortEntry.Value;
		set => _allowShortEntry.Value = value;
	}

	public bool AllowLongExit
	{
		get => _allowLongExit.Value;
		set => _allowLongExit.Value = value;
	}

	public bool AllowShortExit
	{
		get => _allowShortExit.Value;
		set => _allowShortExit.Value = value;
	}

	public int TakeProfitTicks
	{
		get => _takeProfitTicks.Value;
		set => _takeProfitTicks.Value = value;
	}

	public int StopLossTicks
	{
		get => _stopLossTicks.Value;
		set => _stopLossTicks.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_fastHistory.Clear();
		_slowHistory.Clear();
	}

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

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

		var fastIndicator = CreateIndicator(FastMethod, FastLength, FastPhase);
		var slowIndicator = CreateIndicator(SlowMethod, SlowLength, SlowPhase);

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

		var step = Security?.PriceStep ?? 1m;
		Unit takeProfit = null;
		if (TakeProfitTicks > 0)
			takeProfit = new Unit(TakeProfitTicks * step, UnitTypes.Absolute);

		Unit stopLoss = null;
		if (StopLossTicks > 0)
			stopLoss = new Unit(StopLossTicks * step, UnitTypes.Absolute);

		if (takeProfit != null || stopLoss != null)
			StartProtection(takeProfit: takeProfit, stopLoss: stopLoss);
	}

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

		UpdateHistory(_fastHistory, fastValue);
		UpdateHistory(_slowHistory, slowValue);

		var required = SignalBar + 2;
		if (_fastHistory.Count < required || _slowHistory.Count < required)
			return;

		var fastCurrent = GetShiftedValue(_fastHistory, SignalBar);
		var fastPrevious = GetShiftedValue(_fastHistory, SignalBar + 1);
		var slowCurrent = GetShiftedValue(_slowHistory, SignalBar);
		var slowPrevious = GetShiftedValue(_slowHistory, SignalBar + 1);

		// The cloud is considered bullish when the fast line was above the slow line on the prior bar.
		var fastWasAbove = fastPrevious > slowPrevious;
		var fastWasBelow = fastPrevious < slowPrevious;

		var closeShort = AllowShortExit && fastWasAbove && Position < 0;
		if (closeShort)
		{
			BuyMarket();
		}

		var closeLong = AllowLongExit && fastWasBelow && Position > 0;
		if (closeLong)
		{
			SellMarket();
		}

		var enterLong = AllowLongEntry && fastWasAbove && fastCurrent <= slowCurrent;
		var enterShort = AllowShortEntry && fastWasBelow && fastCurrent >= slowCurrent;

		// Wait for the portfolio to flatten before issuing a new entry order.
		if (Position != 0)
			return;

		if (enterLong)
		{
			BuyMarket();
		}
		else if (enterShort)
		{
			SellMarket();
		}
	}

	private void UpdateHistory(List<decimal> history, decimal value)
	{
		history.Add(value);
		var maxSize = SignalBar + 2;
		while (history.Count > maxSize)
			history.RemoveAt(0);
	}

	private static decimal GetShiftedValue(List<decimal> history, int shift)
	{
		var index = history.Count - shift - 1;
		if (index >= 0 && index < history.Count)
			return history[index];

		return 0m;
	}

	private static IIndicator CreateIndicator(SmoothMethods method, int length, int phase)
	{
		return method switch
		{
			SmoothMethods.Sma => new SimpleMovingAverage { Length = length },
			SmoothMethods.Ema => new ExponentialMovingAverage { Length = length },
			SmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
			SmoothMethods.Wma => new WeightedMovingAverage { Length = length },
			_ => CreateJurikIndicator(length, phase),
		};
	}

	private static IIndicator CreateJurikIndicator(int length, int phase)
	{
		var jurik = new JurikMovingAverage { Length = length };

		// Configure the Jurik phase through reflection because the property is optional across versions.
		var phaseProperty = jurik.GetType().GetProperty("Phase");
		if (phaseProperty != null && phaseProperty.CanWrite)
		{
			var converted = Convert.ChangeType(phase, phaseProperty.PropertyType);
			phaseProperty.SetValue(jurik, converted);
		}

		return jurik;
	}

	public enum SmoothMethods
	{
		Sma = 1,
		Ema,
		Smma,
		Wma,
		Jurik
	}
}