Auf GitHub ansehen

XFatl XSatl Cloud Countertrend Strategy

This StockSharp strategy recreates the MT5 expert Exp_XFatlXSatlCloud. It watches the smoothed FATL/SATL "cloud" and trades against the direction of its crossover. When the fast line (XFATL) drops back below the slow line (XSATL) after being above it, the strategy opens a long position. When the fast line rises back above after being below, it opens a short position. Optional stop loss and take profit levels are expressed in instrument price steps.

Trading Logic

  • The default data source is an 8-hour time frame. Other candle types can be selected with the CandleType parameter.
  • Two smoothing pipelines are built from StockSharp moving averages. By default both use a Jurik moving average with configurable length and phase. Alternative smoothing families (SMA, EMA, SMMA, WMA) are also available.
  • Signals are evaluated on the bar defined by SignalBar (shift in bars from the latest closed candle). The strategy stores a rolling window of recent indicator values so the last and previous values can be compared just like the MT5 version.
  • Entry rules (contrarian):
    • Long – the fast line was above the slow line on the previous bar and has now crossed to or below it.
    • Short – the fast line was below on the previous bar and has now crossed to or above it.
  • Exit rules:
    • Long positions close when the previous bar showed a bearish cloud (fast below slow) and AllowLongExit is enabled.
    • Short positions close when the previous bar showed a bullish cloud (fast above slow) and AllowShortExit is enabled.
  • A new position is only opened once the previous position has fully closed, mirroring the behaviour of the original expert adviser.

Risk Management

  • TradeVolume controls the quantity used for market orders. The strategy never scales in—every new position uses the same size.
  • TakeProfitTicks and StopLossTicks convert directly into price-step distances and are wired into StockSharp's built-in protection module. Set them to zero to disable the corresponding protective order.
  • Because the MT5 expert relied on broker-specific money-management calculations, this version replaces that logic with explicit volume and protection parameters.

Parameters

Parameter Description
CandleType Candle type or time frame used for indicator calculations.
FastMethod / SlowMethod Smoothing family for XFATL and XSATL (Jurik by default).
FastLength / SlowLength Period lengths for the fast and slow filters.
FastPhase / SlowPhase Phase inputs forwarded to the Jurik moving average when supported.
SignalBar Bar shift used when evaluating crossovers (1 = previous bar).
TradeVolume Order size for entries.
AllowLongEntry / AllowShortEntry Enable or disable contrarian entries in each direction.
AllowLongExit / AllowShortExit Allow the indicator to close open positions on opposite signals.
TakeProfitTicks Distance to the take-profit target expressed in price steps.
StopLossTicks Distance to the protective stop in price steps.

Implementation Notes

  • The strategy keeps short queues of recent indicator outputs and trims them to the minimal length required by SignalBar. No additional historical buffers are created.
  • Jurik phase support is configured via reflection so the strategy stays compatible with different StockSharp versions. If the underlying indicator lacks a Phase property the value is simply ignored.
  • Only the close price of each candle is used, matching the most common setup for the original expert. Extending the logic to alternative price types would require augmenting the strategy.
  • High-level API components (SubscribeCandles, Bind, StartProtection) are used throughout, so the strategy integrates cleanly with Designer and other StockSharp products.
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
	}
}