Ver no GitHub

Estratégia XFatlXSatlCloud Duplex

Visão geral

XFatlXSatlCloud Duplex é uma estratégia bidirecional convertida do expert advisor original do MQL5. Ela opera cruzamentos do indicador XFatlXSatlCloud, que combina um filtro digital FATL rápido com um filtro SATL mais lento e, em seguida, suaviza ambos com médias móveis configuráveis. Configurações separadas podem ser aplicadas aos lados comprado e vendido, incluindo diferentes períodos, métodos de suavização e fontes de preço aplicadas.

Lógica de trading

A estratégia avalia apenas velas terminadas. Duas assinaturas independentes são executadas em paralelo: uma impulsiona a lógica comprada e a outra a lógica vendida. Cada assinatura alimenta o indicador XFatlXSatlCloud implementado em C# e produz o seguinte comportamento:

  • Entrada comprada – acionada quando a linha rápida cruza acima da linha lenta na barra definida por LongSignalBar. Se uma posição vendida estiver aberta, ela é fechada primeiro (somente se ShortAllowClose estiver habilitado). Uma ordem de compra a mercado com LongVolume contratos é então enviada e o preço de entrada é registrado para verificações de risco.
  • Saída comprada – executada quando a linha rápida cai abaixo da linha lenta na barra deslocada. Verificações opcionais de stop-loss e take-profit baseadas em preço (LongStopLoss, LongTakeProfit) podem fechar a posição mais cedo se o range da vela violar os deslocamentos definidos.
  • Entrada vendida – acionada quando a linha rápida cruza abaixo da linha lenta na barra definida por ShortSignalBar. A exposição comprada existente é nivelada primeiro se LongAllowClose estiver habilitado. Uma ordem de venda a mercado com ShortVolume contratos é enviada em seguida.
  • Saída vendida – executada quando a linha rápida sobe acima da linha lenta na barra deslocada. Controles de risco opcionais (ShortStopLoss, ShortTakeProfit) monitoram extremos intrabarra.

Todos os valores de indicadores são calculados apenas em velas terminadas, garantindo que cada decisão se baseie em dados finais e espelhe o comportamento MQL original.

Gestão de risco

A estratégia rastreia o último preço de entrada separadamente para posições compradas e vendidas. Se um offset de stop-loss ou take-profit for especificado e a vela atual violar o limiar correspondente, a posição é fechada imediatamente (sujeito à bandeira AllowClose relevante). Os offsets são medidos em unidades de preço absolutas do instrumento negociado.

Parâmetros

Grupo Nome Descrição
Trading LongVolume Tamanho da ordem para entradas compradas (maior que zero).
Trading ShortVolume Tamanho da ordem para entradas vendidas (maior que zero).
Trading LongAllowOpen Habilitar ou desabilitar a abertura de novas posições compradas.
Trading LongAllowClose Habilitar ou desabilitar saídas compradas (necessário para stops e saídas cruzadas).
Trading ShortAllowOpen Habilitar ou desabilitar a abertura de novas posições vendidas.
Trading ShortAllowClose Habilitar ou desabilitar saídas vendidas.
Signals LongSignalBar Número de barras concluídas a olhar para trás ao verificar o cruzamento para comprados.
Signals ShortSignalBar Número de barras concluídas a olhar para trás ao verificar o cruzamento para vendidos.
Data LongCandleType Tipo de vela (período) usado para a assinatura do indicador comprado.
Data ShortCandleType Tipo de vela usado para a assinatura do indicador vendido.
Indicators LongMethod1 Método de suavização aplicado à saída FATL no lado comprado. Valores suportados: SMA, EMA, SMMA, LWMA, Jurik, ZeroLag, Kaufman.
Indicators LongLength1 Comprimento do suavizador rápido comprado.
Indicators LongPhase1 Parâmetro de fase encaminhado ao suavizador rápido (mantido por compatibilidade, apenas Jurik o usa conceitualmente).
Indicators LongMethod2 Método de suavização aplicado à saída SATL no lado comprado (mesmo conjunto suportado acima).
Indicators LongLength2 Comprimento do suavizador lento comprado.
Indicators LongPhase2 Parâmetro de fase para o suavizador lento comprado.
Indicators LongAppliedPrice Preço aplicado usado para construir o indicador comprado (fechamento, abertura, mediana, típico, ponderado, simples, quarto, trend-follow ou Demark).
Indicators ShortMethod1 Método de suavização para a linha rápida vendida.
Indicators ShortLength1 Comprimento do suavizador rápido vendido.
Indicators ShortPhase1 Parâmetro de fase para o suavizador rápido vendido.
Indicators ShortMethod2 Método de suavização para a linha lenta vendida.
Indicators ShortLength2 Comprimento do suavizador lento vendido.
Indicators ShortPhase2 Parâmetro de fase para o suavizador lento vendido.
Indicators ShortAppliedPrice Preço aplicado usado para construir o indicador vendido.
Risk LongStopLoss Distância de preço absoluta para o stop-loss comprado (0 desabilita a verificação).
Risk LongTakeProfit Distância de preço absoluta para o take-profit comprado (0 desabilita a verificação).
Risk ShortStopLoss Distância de preço absoluta para o stop-loss vendido (0 desabilita a verificação).
Risk ShortTakeProfit Distância de preço absoluta para o take-profit vendido (0 desabilita a verificação).

Notas de implementação

  • O indicador XFatlXSatlCloud é implementado como um indicador de alto nível do StockSharp. Os componentes rápido e lento são produzidos aplicando os coeficientes de resposta ao impulso finito FATL/SATL originais seguidos de indicadores de suavização selecionados pelo usuário.
  • Apenas médias móveis do StockSharp comumente disponíveis são expostas (Sma, Ema, Smma, Lwma, Jurik, ZeroLag, Kaufman). Outras famílias de suavização MQL (como Parabolic ou T3) não estão incluídas.
  • LongSignalBar e ShortSignalBar imitam o parâmetro original SignalBar. Um valor de 1 significa "usar a barra concluída anterior" ao detectar o cruzamento.
  • Os offsets de stop-loss e take-profit esperam distâncias de preço absolutas. São aplicados usando o máximo/mínimo da vela relativo ao preço de entrada registrado e não dependem de valores de ponto específicos do broker.
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>
/// Duplex strategy based on XFatlXSatlCloud indicator crossovers.
/// </summary>
public class XFatlXSatlCloudDuplexStrategy : Strategy
{
	/// <summary>
	/// Supported smoothing methods for XFatlXSatlCloud indicator.
	/// </summary>
	public enum XmaMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Sma,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Ema,

		/// <summary>
		/// Smoothed moving average (RMA).
		/// </summary>
		Smma,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		Lwma,

		/// <summary>
		/// Jurik moving average.
		/// </summary>
		Jurik,

		/// <summary>
		/// Zero lag exponential moving average.
		/// </summary>
		ZeroLag,

		/// <summary>
		/// Kaufman adaptive moving average.
		/// </summary>
		Kaufman,
	}

	public enum AppliedPrices
	{
		Close,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted,
		Simple,
		Quarter,
		TrendFollow0,
		TrendFollow1,
		Demark,
	}

	private readonly StrategyParam<decimal> _longVolume;
	private readonly StrategyParam<decimal> _shortVolume;
	private readonly StrategyParam<bool> _longAllowOpen;
	private readonly StrategyParam<bool> _longAllowClose;
	private readonly StrategyParam<bool> _shortAllowOpen;
	private readonly StrategyParam<bool> _shortAllowClose;
	private readonly StrategyParam<int> _longSignalBar;
	private readonly StrategyParam<int> _shortSignalBar;
	private readonly StrategyParam<DataType> _longCandleType;
	private readonly StrategyParam<DataType> _shortCandleType;
	private readonly StrategyParam<XmaMethods> _longMethod1;
	private readonly StrategyParam<int> _longLength1;
	private readonly StrategyParam<int> _longPhase1;
	private readonly StrategyParam<XmaMethods> _longMethod2;
	private readonly StrategyParam<int> _longLength2;
	private readonly StrategyParam<int> _longPhase2;
	private readonly StrategyParam<AppliedPrices> _longPriceType;
	private readonly StrategyParam<XmaMethods> _shortMethod1;
	private readonly StrategyParam<int> _shortLength1;
	private readonly StrategyParam<int> _shortPhase1;
	private readonly StrategyParam<XmaMethods> _shortMethod2;
	private readonly StrategyParam<int> _shortLength2;
	private readonly StrategyParam<int> _shortPhase2;
	private readonly StrategyParam<AppliedPrices> _shortPriceType;
	private readonly StrategyParam<decimal> _longStopLoss;
	private readonly StrategyParam<decimal> _longTakeProfit;
	private readonly StrategyParam<decimal> _shortStopLoss;
	private readonly StrategyParam<decimal> _shortTakeProfit;

	private XFatlXSatlCloudIndicator _longIndicator = null!;
	private XFatlXSatlCloudIndicator _shortIndicator = null!;
	private readonly List<(decimal fast, decimal slow)> _longHistory = new();
	private readonly List<(decimal fast, decimal slow)> _shortHistory = new();
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;

	/// <summary>
	/// Initializes a new instance of the <see cref="XFatlXSatlCloudDuplexStrategy"/>.
	/// </summary>
	public XFatlXSatlCloudDuplexStrategy()
	{
		_longVolume = Param(nameof(LongVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Long Volume", "Order volume for long entries", "Trading");

		_shortVolume = Param(nameof(ShortVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Short Volume", "Order volume for short entries", "Trading");

		_longAllowOpen = Param(nameof(LongAllowOpen), true)
			.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");

		_longAllowClose = Param(nameof(LongAllowClose), true)
			.SetDisplay("Enable Long Exits", "Allow closing long positions", "Trading");

		_shortAllowOpen = Param(nameof(ShortAllowOpen), true)
			.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");

		_shortAllowClose = Param(nameof(ShortAllowClose), true)
			.SetDisplay("Enable Short Exits", "Allow closing short positions", "Trading");

		_longSignalBar = Param(nameof(LongSignalBar), 1)
			.SetNotNegative()
			.SetDisplay("Long Signal Shift", "Bars to look back for long signals", "Signals");

		_shortSignalBar = Param(nameof(ShortSignalBar), 1)
			.SetNotNegative()
			.SetDisplay("Short Signal Shift", "Bars to look back for short signals", "Signals");

		_longCandleType = Param(nameof(LongCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Long Candle Type", "Timeframe for long indicator", "Data");

		_shortCandleType = Param(nameof(ShortCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Short Candle Type", "Timeframe for short indicator", "Data");

		_longMethod1 = Param(nameof(LongMethod1), XmaMethods.Jurik)
			.SetDisplay("Long Fast Method", "Smoothing method for the fast long line", "Indicators");

		_longLength1 = Param(nameof(LongLength1), 3)
			.SetGreaterThanZero()
			.SetDisplay("Long Fast Length", "Length for the fast long smoother", "Indicators");

		_longPhase1 = Param(nameof(LongPhase1), 15)
			.SetDisplay("Long Fast Phase", "Phase parameter for the fast long smoother", "Indicators");

		_longMethod2 = Param(nameof(LongMethod2), XmaMethods.Jurik)
			.SetDisplay("Long Slow Method", "Smoothing method for the slow long line", "Indicators");

		_longLength2 = Param(nameof(LongLength2), 5)
			.SetGreaterThanZero()
			.SetDisplay("Long Slow Length", "Length for the slow long smoother", "Indicators");

		_longPhase2 = Param(nameof(LongPhase2), 15)
			.SetDisplay("Long Slow Phase", "Phase parameter for the slow long smoother", "Indicators");

		_longPriceType = Param(nameof(LongAppliedPrice), AppliedPrices.Close)
			.SetDisplay("Long Applied Price", "Price type used for the long indicator", "Indicators");

		_shortMethod1 = Param(nameof(ShortMethod1), XmaMethods.Jurik)
			.SetDisplay("Short Fast Method", "Smoothing method for the fast short line", "Indicators");

		_shortLength1 = Param(nameof(ShortLength1), 3)
			.SetGreaterThanZero()
			.SetDisplay("Short Fast Length", "Length for the fast short smoother", "Indicators");

		_shortPhase1 = Param(nameof(ShortPhase1), 15)
			.SetDisplay("Short Fast Phase", "Phase parameter for the fast short smoother", "Indicators");

		_shortMethod2 = Param(nameof(ShortMethod2), XmaMethods.Jurik)
			.SetDisplay("Short Slow Method", "Smoothing method for the slow short line", "Indicators");

		_shortLength2 = Param(nameof(ShortLength2), 5)
			.SetGreaterThanZero()
			.SetDisplay("Short Slow Length", "Length for the slow short smoother", "Indicators");

		_shortPhase2 = Param(nameof(ShortPhase2), 15)
			.SetDisplay("Short Slow Phase", "Phase parameter for the slow short smoother", "Indicators");

		_shortPriceType = Param(nameof(ShortAppliedPrice), AppliedPrices.Close)
			.SetDisplay("Short Applied Price", "Price type used for the short indicator", "Indicators");

		_longStopLoss = Param(nameof(LongStopLoss), 0m)
			.SetNotNegative()
			.SetDisplay("Long Stop Loss", "Price distance for long stop loss (0 disables)", "Risk");

		_longTakeProfit = Param(nameof(LongTakeProfit), 0m)
			.SetNotNegative()
			.SetDisplay("Long Take Profit", "Price distance for long take profit (0 disables)", "Risk");

		_shortStopLoss = Param(nameof(ShortStopLoss), 0m)
			.SetNotNegative()
			.SetDisplay("Short Stop Loss", "Price distance for short stop loss (0 disables)", "Risk");

		_shortTakeProfit = Param(nameof(ShortTakeProfit), 0m)
			.SetNotNegative()
			.SetDisplay("Short Take Profit", "Price distance for short take profit (0 disables)", "Risk");
	}

	/// <summary>
	/// Gets or sets volume for long trades.
	/// </summary>
	public decimal LongVolume
	{
		get => _longVolume.Value;
		set => _longVolume.Value = value;
	}

	/// <summary>
	/// Gets or sets volume for short trades.
	/// </summary>
	public decimal ShortVolume
	{
		get => _shortVolume.Value;
		set => _shortVolume.Value = value;
	}

	/// <summary>
	/// Allow opening long trades.
	/// </summary>
	public bool LongAllowOpen
	{
		get => _longAllowOpen.Value;
		set => _longAllowOpen.Value = value;
	}

	/// <summary>
	/// Allow closing long trades.
	/// </summary>
	public bool LongAllowClose
	{
		get => _longAllowClose.Value;
		set => _longAllowClose.Value = value;
	}

	/// <summary>
	/// Allow opening short trades.
	/// </summary>
	public bool ShortAllowOpen
	{
		get => _shortAllowOpen.Value;
		set => _shortAllowOpen.Value = value;
	}

	/// <summary>
	/// Allow closing short trades.
	/// </summary>
	public bool ShortAllowClose
	{
		get => _shortAllowClose.Value;
		set => _shortAllowClose.Value = value;
	}

	/// <summary>
	/// Number of bars to shift long signals.
	/// </summary>
	public int LongSignalBar
	{
		get => _longSignalBar.Value;
		set => _longSignalBar.Value = value;
	}

	/// <summary>
	/// Number of bars to shift short signals.
	/// </summary>
	public int ShortSignalBar
	{
		get => _shortSignalBar.Value;
		set => _shortSignalBar.Value = value;
	}

	/// <summary>
	/// Candle type for long indicator.
	/// </summary>
	public DataType LongCandleType
	{
		get => _longCandleType.Value;
		set => _longCandleType.Value = value;
	}

	/// <summary>
	/// Candle type for short indicator.
	/// </summary>
	public DataType ShortCandleType
	{
		get => _shortCandleType.Value;
		set => _shortCandleType.Value = value;
	}

	/// <summary>
	/// Smoothing method for fast long line.
	/// </summary>
	public XmaMethods LongMethod1
	{
		get => _longMethod1.Value;
		set => _longMethod1.Value = value;
	}

	/// <summary>
	/// Length for fast long smoother.
	/// </summary>
	public int LongLength1
	{
		get => _longLength1.Value;
		set => _longLength1.Value = value;
	}

	/// <summary>
	/// Phase for fast long smoother.
	/// </summary>
	public int LongPhase1
	{
		get => _longPhase1.Value;
		set => _longPhase1.Value = value;
	}

	/// <summary>
	/// Smoothing method for slow long line.
	/// </summary>
	public XmaMethods LongMethod2
	{
		get => _longMethod2.Value;
		set => _longMethod2.Value = value;
	}

	/// <summary>
	/// Length for slow long smoother.
	/// </summary>
	public int LongLength2
	{
		get => _longLength2.Value;
		set => _longLength2.Value = value;
	}

	/// <summary>
	/// Phase for slow long smoother.
	/// </summary>
	public int LongPhase2
	{
		get => _longPhase2.Value;
		set => _longPhase2.Value = value;
	}

	/// <summary>
	/// Applied price for long calculations.
	/// </summary>
	public AppliedPrices LongAppliedPrice
	{
		get => _longPriceType.Value;
		set => _longPriceType.Value = value;
	}

	/// <summary>
	/// Smoothing method for fast short line.
	/// </summary>
	public XmaMethods ShortMethod1
	{
		get => _shortMethod1.Value;
		set => _shortMethod1.Value = value;
	}

	/// <summary>
	/// Length for fast short smoother.
	/// </summary>
	public int ShortLength1
	{
		get => _shortLength1.Value;
		set => _shortLength1.Value = value;
	}

	/// <summary>
	/// Phase for fast short smoother.
	/// </summary>
	public int ShortPhase1
	{
		get => _shortPhase1.Value;
		set => _shortPhase1.Value = value;
	}

	/// <summary>
	/// Smoothing method for slow short line.
	/// </summary>
	public XmaMethods ShortMethod2
	{
		get => _shortMethod2.Value;
		set => _shortMethod2.Value = value;
	}

	/// <summary>
	/// Length for slow short smoother.
	/// </summary>
	public int ShortLength2
	{
		get => _shortLength2.Value;
		set => _shortLength2.Value = value;
	}

	/// <summary>
	/// Phase for slow short smoother.
	/// </summary>
	public int ShortPhase2
	{
		get => _shortPhase2.Value;
		set => _shortPhase2.Value = value;
	}

	/// <summary>
	/// Applied price for short calculations.
	/// </summary>
	public AppliedPrices ShortAppliedPrice
	{
		get => _shortPriceType.Value;
		set => _shortPriceType.Value = value;
	}

	/// <summary>
	/// Stop loss distance for long positions.
	/// </summary>
	public decimal LongStopLoss
	{
		get => _longStopLoss.Value;
		set => _longStopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance for long positions.
	/// </summary>
	public decimal LongTakeProfit
	{
		get => _longTakeProfit.Value;
		set => _longTakeProfit.Value = value;
	}

	/// <summary>
	/// Stop loss distance for short positions.
	/// </summary>
	public decimal ShortStopLoss
	{
		get => _shortStopLoss.Value;
		set => _shortStopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance for short positions.
	/// </summary>
	public decimal ShortTakeProfit
	{
		get => _shortTakeProfit.Value;
		set => _shortTakeProfit.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		var result = new List<(Security, DataType)> { (Security, LongCandleType) };

		if (ShortCandleType != LongCandleType)
			result.Add((Security, ShortCandleType));

		return result;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_longHistory.Clear();
		_shortHistory.Clear();
		_longEntryPrice = null;
		_shortEntryPrice = null;
	}

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

		// Create indicator instances with current parameter values for both directions.
		_longIndicator = new XFatlXSatlCloudIndicator(LongMethod1, LongLength1, LongPhase1, LongMethod2, LongLength2, LongPhase2, LongAppliedPrice);
		_shortIndicator = new XFatlXSatlCloudIndicator(ShortMethod1, ShortLength1, ShortPhase1, ShortMethod2, ShortLength2, ShortPhase2, ShortAppliedPrice);

		// Subscribe to candles that drive the long side of the strategy.
		var longSubscription = SubscribeCandles(LongCandleType);
		longSubscription.BindEx(_longIndicator, ProcessLong).Start();

		// Subscribe separately for the short side (timeframe can differ from the long one).
		var shortSubscription = SubscribeCandles(ShortCandleType);
		shortSubscription.BindEx(_shortIndicator, ProcessShort).Start();
	}

	private void ProcessLong(ICandleMessage candle, IIndicatorValue indicatorValue)
	{
		if (candle.State != CandleStates.Finished || !indicatorValue.IsFinal)
			return;

		var value = (XFatlXSatlValue)indicatorValue;
		// Store the latest indicator readings so we can evaluate the configured shift.
		_longHistory.Insert(0, (value.Fast, value.Slow));
		var maxSize = Math.Max(LongSignalBar + 2, 2);
		if (_longHistory.Count > maxSize)
			_longHistory.RemoveAt(_longHistory.Count - 1);

		// Risk management can close the position immediately before analyzing crossovers.
		if (HandleLongRisk(candle))
			return;

		if (_longHistory.Count <= LongSignalBar + 1)
			return;

		var current = _longHistory[LongSignalBar];
		var previous = _longHistory[LongSignalBar + 1];
		var crossUp = current.fast > current.slow && previous.fast <= previous.slow;
		var crossDown = current.fast < current.slow && previous.fast >= previous.slow;

		// Close an existing long when the fast line drops below the slow line.
		if (LongAllowClose && crossDown && Position > 0m)
		{
			SellMarket(Position);
			_longEntryPrice = null;
		}

		if (!LongAllowOpen || !crossUp)
			return;

		// Flatten shorts before reversing into a long position.
		if (Position < 0m)
		{
			if (!ShortAllowClose)
				return;

			BuyMarket(-Position);
			_shortEntryPrice = null;
		}

		// Open the long trade only if no opposite exposure remains.
		if (Position <= 0m)
		{
			BuyMarket(LongVolume);
			_longEntryPrice = candle.ClosePrice;
		}
	}

	private bool HandleLongRisk(ICandleMessage candle)
	{
		if (!LongAllowClose || Position <= 0m || _longEntryPrice is not decimal entry)
			return false;

		// Hard stop: candle low moved below entry minus configured distance.
		if (LongStopLoss > 0m && candle.LowPrice <= entry - LongStopLoss)
		{
			SellMarket(Position);
			_longEntryPrice = null;
			return true;
		}

		// Hard target: candle high exceeded entry plus configured distance.
		if (LongTakeProfit > 0m && candle.HighPrice >= entry + LongTakeProfit)
		{
			SellMarket(Position);
			_longEntryPrice = null;
			return true;
		}

		return false;
	}

	private void ProcessShort(ICandleMessage candle, IIndicatorValue indicatorValue)
	{
		if (candle.State != CandleStates.Finished || !indicatorValue.IsFinal)
			return;

		var value = (XFatlXSatlValue)indicatorValue;
		// Maintain the rolling history for the short configuration.
		_shortHistory.Insert(0, (value.Fast, value.Slow));
		var maxSize = Math.Max(ShortSignalBar + 2, 2);
		if (_shortHistory.Count > maxSize)
			_shortHistory.RemoveAt(_shortHistory.Count - 1);

		// Stop or target may close the short before trend analysis.
		if (HandleShortRisk(candle))
			return;

		if (_shortHistory.Count <= ShortSignalBar + 1)
			return;

		var current = _shortHistory[ShortSignalBar];
		var previous = _shortHistory[ShortSignalBar + 1];
		var crossDown = current.fast < current.slow && previous.fast >= previous.slow;
		var crossUp = current.fast > current.slow && previous.fast <= previous.slow;

		// Cover a short when the fast line rises above the slow line again.
		if (ShortAllowClose && crossUp && Position < 0m)
		{
			BuyMarket(-Position);
			_shortEntryPrice = null;
		}

		if (!ShortAllowOpen || !crossDown)
			return;

		// Close existing longs before flipping into a short position.
		if (Position > 0m)
		{
			if (!LongAllowClose)
				return;

			SellMarket(Position);
			_longEntryPrice = null;
		}

		// Enter the new short once the direction is clear.
		if (Position >= 0m)
		{
			SellMarket(ShortVolume);
			_shortEntryPrice = candle.ClosePrice;
		}
	}

	private bool HandleShortRisk(ICandleMessage candle)
	{
		if (!ShortAllowClose || Position >= 0m || _shortEntryPrice is not decimal entry)
			return false;

		// Stop loss for the short side is triggered by a move above the entry price.
		if (ShortStopLoss > 0m && candle.HighPrice >= entry + ShortStopLoss)
		{
			BuyMarket(-Position);
			_shortEntryPrice = null;
			return true;
		}

		// Take profit for shorts fires when the low pierces the target distance.
		if (ShortTakeProfit > 0m && candle.LowPrice <= entry - ShortTakeProfit)
		{
			BuyMarket(-Position);
			_shortEntryPrice = null;
			return true;
		}

		return false;
	}

	private sealed class XFatlXSatlCloudIndicator : BaseIndicator
	{
		private static readonly decimal[] FatlCoefficients =
		{
	0.4360409450m,
	0.3658689069m,
	0.2460452079m,
	0.1104506886m,
	-0.0054034585m,
	-0.0760367731m,
	-0.0933058722m,
	-0.0670110374m,
	-0.0190795053m,
	0.0259609206m,
	0.0502044896m,
	0.0477818607m,
	0.0249252327m,
	-0.0047706151m,
	-0.0272432537m,
	-0.0338917071m,
	-0.0244141482m,
	-0.0055774838m,
	0.0128149838m,
	0.0226522218m,
	0.0208778257m,
	0.0100299086m,
	-0.0036771622m,
	-0.0136744850m,
	-0.0160483392m,
	-0.0108597376m,
	-0.0016060704m,
	0.0069480557m,
	0.0110573605m,
	0.0095711419m,
	0.0040444064m,
	-0.0023824623m,
	-0.0067093714m,
	-0.0072003400m,
	-0.0047717710m,
	0.0005541115m,
	0.0007860160m,
	0.0130129076m,
	0.0040364019m,
	};

		private static readonly decimal[] SatlCoefficients =
		{
	0.0982862174m,
	0.0975682269m,
	0.0961401078m,
	0.0940230544m,
	0.0912437090m,
	0.0878391006m,
	0.0838544303m,
	0.0793406350m,
	0.0743569346m,
	0.0689666682m,
	0.0632381578m,
	0.0572428925m,
	0.0510534242m,
	0.0447468229m,
	0.0383959950m,
	0.0320735368m,
	0.0258537721m,
	0.0198005183m,
	0.0139807863m,
	0.0084512448m,
	0.0032639979m,
	-0.0015350359m,
	-0.0059060082m,
	-0.0098190256m,
	-0.0132507215m,
	-0.0161875265m,
	-0.0186164872m,
	-0.0205446727m,
	-0.0219739146m,
	-0.0229204861m,
	-0.0234080863m,
	-0.0234566315m,
	-0.0231017777m,
	-0.0223796900m,
	-0.0213300463m,
	-0.0199924534m,
	-0.0184126992m,
	-0.0166377699m,
	-0.0147139428m,
	-0.0126796776m,
	-0.0105938331m,
	-0.0084736770m,
	-0.0063841850m,
	-0.0043466731m,
	-0.0023956944m,
	-0.0005535180m,
	0.0011421469m,
	0.0026845693m,
	0.0040471369m,
	0.0052380201m,
	0.0062194591m,
	0.0070340085m,
	0.0076266453m,
	0.0080376628m,
	0.0083037666m,
	0.0083694798m,
	0.0082901022m,
	0.0080741359m,
	0.0077543820m,
	0.0073260526m,
	0.0068163569m,
	0.0062325477m,
	0.0056078229m,
	0.0049516078m,
	0.0161380976m,
	};

		private readonly IIndicator _fastSmoother;
		private readonly IIndicator _slowSmoother;
		private readonly AppliedPrices _appliedPrice;
		private readonly decimal[] _priceBuffer = new decimal[SatlCoefficients.Length];
		private int _bufferIndex;
		private int _bufferCount;

		public XFatlXSatlCloudIndicator(XmaMethods fastMethod, int fastLength, int fastPhase, XmaMethods slowMethod, int slowLength, int slowPhase, AppliedPrices appliedPrice)
		{
			_fastSmoother = CreateSmoother(fastMethod, fastLength, fastPhase);
			_slowSmoother = CreateSmoother(slowMethod, slowLength, slowPhase);
			_appliedPrice = appliedPrice;
		}

		protected override IIndicatorValue OnProcess(IIndicatorValue input)
		{
			var candle = input.GetValue<ICandleMessage>();
			var price = SelectPrice(candle, _appliedPrice);
			// Feed the latest price into the circular buffer used by the FIR filters.
			_priceBuffer[_bufferIndex] = price;
			_bufferIndex = (_bufferIndex + 1) % _priceBuffer.Length;
			if (_bufferCount < _priceBuffer.Length)
				_bufferCount++;

			var fastRaw = ComputeFilter(FatlCoefficients);
			var slowRaw = ComputeFilter(SatlCoefficients);

			// Smooth both raw filters with the configured moving averages.
			var fastValue = _fastSmoother.Process(new DecimalIndicatorValue(_fastSmoother, fastRaw, input.Time) { IsFinal = input.IsFinal });
			var slowValue = _slowSmoother.Process(new DecimalIndicatorValue(_slowSmoother, slowRaw, input.Time) { IsFinal = input.IsFinal });
			var fast = fastValue.ToDecimal();
			var slow = slowValue.ToDecimal();

			IsFormed = _bufferCount >= SatlCoefficients.Length && fastValue.IsFinal && slowValue.IsFinal;
			return new XFatlXSatlValue(this, input.Time, fast, slow, fastRaw, slowRaw) { IsFinal = input.IsFinal };
		}

		public override void Reset()
		{
			base.Reset();
			Array.Clear(_priceBuffer, 0, _priceBuffer.Length);
			_bufferIndex = 0;
			_bufferCount = 0;
			_fastSmoother.Reset();
			_slowSmoother.Reset();
		}

		private decimal ComputeFilter(IReadOnlyList<decimal> coefficients)
		{
			if (_bufferCount < coefficients.Count)
				return 0m;

			decimal sum = 0m;
			for (var i = 0; i < coefficients.Count; i++)
			{
				// Traverse the ring buffer backwards to align with the newest price first.
				var index = _bufferIndex - 1 - i;
				if (index < 0)
					index += _priceBuffer.Length;

				sum += coefficients[i] * _priceBuffer[index];
			}

			return sum;
		}

		private static IIndicator CreateSmoother(XmaMethods method, int length, int phase)
		{
			length = Math.Max(1, length);

			// Map the MQL smoothing options to the closest available StockSharp moving averages.
			return method switch
			{
				XmaMethods.Sma => new SimpleMovingAverage { Length = length },
				XmaMethods.Ema => new ExponentialMovingAverage { Length = length },
				XmaMethods.Smma => new SmoothedMovingAverage { Length = length },
				XmaMethods.Lwma => new WeightedMovingAverage { Length = length },
				XmaMethods.Jurik => new JurikMovingAverage { Length = length },
				XmaMethods.ZeroLag => new ZeroLagExponentialMovingAverage { Length = length },
				XmaMethods.Kaufman => new KaufmanAdaptiveMovingAverage { Length = length },
				_ => throw new ArgumentOutOfRangeException(nameof(method), method, "Unsupported smoothing method."),
			};
		}

		private static decimal SelectPrice(ICandleMessage candle, AppliedPrices price)
		{
			return price switch
			{
				AppliedPrices.Open => candle.OpenPrice,
				AppliedPrices.High => candle.HighPrice,
				AppliedPrices.Low => candle.LowPrice,
				AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
				AppliedPrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
				AppliedPrices.Weighted => (candle.ClosePrice * 2m + candle.HighPrice + candle.LowPrice) / 4m,
				AppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
				AppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
				AppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
				AppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
				AppliedPrices.Demark => CalculateDemarkPrice(candle),
				_ => candle.ClosePrice,
			};
		}

		private static decimal CalculateDemarkPrice(ICandleMessage candle)
		{
			var sum = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
			if (candle.ClosePrice < candle.OpenPrice)
				sum = (sum + candle.LowPrice) / 2m;
			else if (candle.ClosePrice > candle.OpenPrice)
				sum = (sum + candle.HighPrice) / 2m;
			else
				sum = (sum + candle.ClosePrice) / 2m;

			return ((sum - candle.LowPrice) + (sum - candle.HighPrice)) / 2m;
		}
	}

	private sealed class XFatlXSatlValue : DecimalIndicatorValue
	{
		public XFatlXSatlValue(IIndicator indicator, DateTime time, decimal fast, decimal slow, decimal fastRaw, decimal slowRaw)
			: base(indicator, fast, time)
		{
			Fast = fast;
			Slow = slow;
			FastRaw = fastRaw;
			SlowRaw = slowRaw;
		}

		public decimal Fast { get; }
		public decimal Slow { get; }
		public decimal FastRaw { get; }
		public decimal SlowRaw { get; }
	}
}