Ver no GitHub

Estratégia XDidi Index Cloud Duplex

Visão geral

A estratégia XDidi Index Cloud Duplex replica a lógica de sinalização dupla comprado/vendido do especialista MQL5 original Exp_XDidi_Index_Cloud_Duplex. Duas configurações independentes do índice XDidi são avaliadas em períodos configuráveis. Cada configuração calcula uma razão entre médias móveis rápidas/médias e lentas/médias. Os cruzamentos entre essas razões acionam entradas de mercado enquanto divergências persistentes acionam saídas.

Lógica de trading

  1. Cálculo do indicador
    • Três médias móveis são calculadas para cada bloco (rápida, média, lenta) em uma fonte de preço selecionada.
    • As razões XDidi são derivadas como fast / medium e slow / medium. A inversão opcional corresponde à opção original Revers.
  2. Geração de sinais
    • Bloco comprado: quando a barra anterior tinha fast > slow e a barra de sinal fecha com fast <= slow, uma entrada comprada é solicitada. Se a barra anterior tinha fast < slow, uma saída comprada é solicitada.
    • Bloco vendido: quando a barra anterior tinha fast < slow e a barra de sinal fecha com fast >= slow, uma entrada vendida é solicitada. Se a barra anterior tinha fast > slow, uma saída vendida é solicitada.
    • Os offsets de barra de sinal reproduzem as entradas originais SignalBar.
  3. Gestão de ordens
    • As entradas são executadas com o volume da estratégia. Posições opostas são fechadas antes de reverter.
    • Níveis opcionais de stop-loss e take-profit são aplicados via StartProtection usando distâncias de passo de preço.

Parâmetros

Nome Descrição
LongCandleType, ShortCandleType Períodos de velas para cada bloco.
LongFastMethod / Medium / Slow & ShortFastMethod / Medium / Slow Métodos de suavização da média móvel para curvas rápida, média e lenta. Suavizadores legados não suportados revertem para a média exponencial.
LongFastLength, LongMediumLength, LongSlowLength Períodos para as médias móveis do bloco comprado.
ShortFastLength, ShortMediumLength, ShortSlowLength Períodos para as médias móveis do bloco vendido.
LongAppliedPrice, ShortAppliedPrice Fonte de preço usada para cada bloco (fechamento, abertura, típico, Demark, etc.).
EnableLongEntries, EnableShortEntries Alternar novas posições compradas/vendidas.
EnableLongExits, EnableShortExits Alternar saídas automáticas.
LongSignalBar, ShortSignalBar Deslocamento histórico (barras atrás) avaliado para cruzamentos.
LongReverse, ShortReverse Inverter razões (espelha o flag Revers no MQL).
StopLossPoints, TakeProfitPoints Distâncias de proteção expressas em passos de preço (definir como zero para desabilitar).
Volume (propriedade base da estratégia) Define o tamanho de operação padrão.

Notas de implementação

  • As médias móveis são tomadas da biblioteca de indicadores do StockSharp. Suavizadores avançados (JJMA, JurX, ParMA, VIDYA) usam suavização exponencial por padrão porque equivalentes diretos não estão disponíveis.
  • Os valores do indicador são processados apenas em velas finalizadas, correspondendo ao comportamento original de IsNewBar.
  • As filas de sinais mantêm apenas o número necessário de valores de razão históricos, evitando coleções pesadas.
  • Os stops de proteção são opcionais; se ambas as distâncias forem zero, a estratégia ainda chama StartProtection() para cumprir o ciclo de vida do framework.

Dicas de uso

  • Alinhe os tipos de velas com a assinatura de dados disponível no seu conector.
  • Otimize os comprimentos de médias móveis e preços aplicados para o instrumento negociado.
  • Quando usar períodos assimétricos (comprado/vendido), ambas as assinaturas são visualizadas em áreas de gráfico separadas para maior clareza.

Limitações em comparação com a versão MQL5

  • Os modos de gestão de dinheiro (MM, MarginMode) não estão replicados; o tamanho de operação segue a propriedade Volume do StockSharp.
  • Alguns algoritmos de suavização exóticos de SmoothAlgorithms.mqh são aproximados com médias móveis exponenciais.
  • As ordens de stop/limite são convertidas em níveis de proteção genéricos em vez de parâmetros de ordens individuais.
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>
/// Strategy that trades crossings between fast and slow XDidi index ratios calculated for long and short configurations.
/// </summary>
public class XDidiIndexCloudDuplexStrategy : Strategy
{
	private readonly StrategyParam<DataType> _longCandleType;
	private readonly StrategyParam<SmoothingMethods> _longFastMethod;
	private readonly StrategyParam<int> _longFastLength;
	private readonly StrategyParam<SmoothingMethods> _longMediumMethod;
	private readonly StrategyParam<int> _longMediumLength;
	private readonly StrategyParam<SmoothingMethods> _longSlowMethod;
	private readonly StrategyParam<int> _longSlowLength;
	private readonly StrategyParam<AppliedPrices> _longAppliedPrice;
	private readonly StrategyParam<bool> _longEnableEntry;
	private readonly StrategyParam<bool> _longEnableExit;
	private readonly StrategyParam<bool> _longReverse;
	private readonly StrategyParam<int> _longSignalBar;

	private readonly StrategyParam<DataType> _shortCandleType;
	private readonly StrategyParam<SmoothingMethods> _shortFastMethod;
	private readonly StrategyParam<int> _shortFastLength;
	private readonly StrategyParam<SmoothingMethods> _shortMediumMethod;
	private readonly StrategyParam<int> _shortMediumLength;
	private readonly StrategyParam<SmoothingMethods> _shortSlowMethod;
	private readonly StrategyParam<int> _shortSlowLength;
	private readonly StrategyParam<AppliedPrices> _shortAppliedPrice;
	private readonly StrategyParam<bool> _shortEnableEntry;
	private readonly StrategyParam<bool> _shortEnableExit;
	private readonly StrategyParam<bool> _shortReverse;
	private readonly StrategyParam<int> _shortSignalBar;

	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private DecimalLengthIndicator _longFastMa = null!;
	private DecimalLengthIndicator _longMediumMa = null!;
	private DecimalLengthIndicator _longSlowMa = null!;
	private DecimalLengthIndicator _shortFastMa = null!;
	private DecimalLengthIndicator _shortMediumMa = null!;
	private DecimalLengthIndicator _shortSlowMa = null!;

	private decimal?[] _longFastHistory = Array.Empty<decimal?>();
	private decimal?[] _longSlowHistory = Array.Empty<decimal?>();
	private decimal?[] _shortFastHistory = Array.Empty<decimal?>();
	private decimal?[] _shortSlowHistory = Array.Empty<decimal?>();

	/// <summary>
	/// Initializes a new instance of the <see cref="XDidiIndexCloudDuplexStrategy"/> class.
	/// </summary>
	public XDidiIndexCloudDuplexStrategy()
	{
		_longCandleType = Param(nameof(LongCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Long Candle Type", "Timeframe used for the long XDidi calculation", "General");

		_longFastMethod = Param(nameof(LongFastMethod), SmoothingMethods.Sma)
			.SetDisplay("Long Fast Method", "Smoothing method for the short moving average in the long block", "Indicators");

		_longFastLength = Param(nameof(LongFastLength), 3)
			.SetDisplay("Long Fast Length", "Length for the short moving average in the long block", "Indicators")
			.SetGreaterThanZero();

		_longMediumMethod = Param(nameof(LongMediumMethod), SmoothingMethods.Sma)
			.SetDisplay("Long Medium Method", "Smoothing method for the middle moving average in the long block", "Indicators");

		_longMediumLength = Param(nameof(LongMediumLength), 8)
			.SetDisplay("Long Medium Length", "Length for the middle moving average in the long block", "Indicators")
			.SetGreaterThanZero();

		_longSlowMethod = Param(nameof(LongSlowMethod), SmoothingMethods.Sma)
			.SetDisplay("Long Slow Method", "Smoothing method for the slow moving average in the long block", "Indicators");

		_longSlowLength = Param(nameof(LongSlowLength), 20)
			.SetDisplay("Long Slow Length", "Length for the slow moving average in the long block", "Indicators")
			.SetGreaterThanZero();

		_longAppliedPrice = Param(nameof(LongAppliedPrice), AppliedPrices.Close)
			.SetDisplay("Long Applied Price", "Price source used for the long XDidi calculation", "Indicators");

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

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

		_longReverse = Param(nameof(LongReverse), false)
			.SetDisplay("Reverse Long Ratios", "Invert long XDidi ratios (matches original indicator option)", "Indicators");

		_longSignalBar = Param(nameof(LongSignalBar), 0)
			.SetDisplay("Long Signal Bar", "Bar shift used for long signals", "Trading")
			.SetNotNegative();

		_shortCandleType = Param(nameof(ShortCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Short Candle Type", "Timeframe used for the short XDidi calculation", "General");

		_shortFastMethod = Param(nameof(ShortFastMethod), SmoothingMethods.Sma)
			.SetDisplay("Short Fast Method", "Smoothing method for the short moving average in the short block", "Indicators");

		_shortFastLength = Param(nameof(ShortFastLength), 3)
			.SetDisplay("Short Fast Length", "Length for the short moving average in the short block", "Indicators")
			.SetGreaterThanZero();

		_shortMediumMethod = Param(nameof(ShortMediumMethod), SmoothingMethods.Sma)
			.SetDisplay("Short Medium Method", "Smoothing method for the middle moving average in the short block", "Indicators");

		_shortMediumLength = Param(nameof(ShortMediumLength), 8)
			.SetDisplay("Short Medium Length", "Length for the middle moving average in the short block", "Indicators")
			.SetGreaterThanZero();

		_shortSlowMethod = Param(nameof(ShortSlowMethod), SmoothingMethods.Sma)
			.SetDisplay("Short Slow Method", "Smoothing method for the slow moving average in the short block", "Indicators");

		_shortSlowLength = Param(nameof(ShortSlowLength), 20)
			.SetDisplay("Short Slow Length", "Length for the slow moving average in the short block", "Indicators")
			.SetGreaterThanZero();

		_shortAppliedPrice = Param(nameof(ShortAppliedPrice), AppliedPrices.Close)
			.SetDisplay("Short Applied Price", "Price source used for the short XDidi calculation", "Indicators");

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

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

		_shortReverse = Param(nameof(ShortReverse), false)
			.SetDisplay("Reverse Short Ratios", "Invert short XDidi ratios (matches original indicator option)", "Indicators");

		_shortSignalBar = Param(nameof(ShortSignalBar), 0)
			.SetDisplay("Short Signal Bar", "Bar shift used for short signals", "Trading")
			.SetNotNegative();

		_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
			.SetDisplay("Stop Loss Points", "Protective stop in price steps applied to both directions", "Risk")
			.SetNotNegative();

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
			.SetDisplay("Take Profit Points", "Protective target in price steps applied to both directions", "Risk")
			.SetNotNegative();
	}

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

	/// <summary>
	/// Smoothing method for the fast moving average in the long block.
	/// </summary>
	public SmoothingMethods LongFastMethod
	{
		get => _longFastMethod.Value;
		set => _longFastMethod.Value = value;
	}

	/// <summary>
	/// Length for the fast moving average in the long block.
	/// </summary>
	public int LongFastLength
	{
		get => _longFastLength.Value;
		set => _longFastLength.Value = value;
	}

	/// <summary>
	/// Smoothing method for the medium moving average in the long block.
	/// </summary>
	public SmoothingMethods LongMediumMethod
	{
		get => _longMediumMethod.Value;
		set => _longMediumMethod.Value = value;
	}

	/// <summary>
	/// Length for the medium moving average in the long block.
	/// </summary>
	public int LongMediumLength
	{
		get => _longMediumLength.Value;
		set => _longMediumLength.Value = value;
	}

	/// <summary>
	/// Smoothing method for the slow moving average in the long block.
	/// </summary>
	public SmoothingMethods LongSlowMethod
	{
		get => _longSlowMethod.Value;
		set => _longSlowMethod.Value = value;
	}

	/// <summary>
	/// Length for the slow moving average in the long block.
	/// </summary>
	public int LongSlowLength
	{
		get => _longSlowLength.Value;
		set => _longSlowLength.Value = value;
	}

	/// <summary>
	/// Applied price for the long XDidi calculation.
	/// </summary>
	public AppliedPrices LongAppliedPrice
	{
		get => _longAppliedPrice.Value;
		set => _longAppliedPrice.Value = value;
	}

	/// <summary>
	/// Enable opening long positions.
	/// </summary>
	public bool EnableLongEntries
	{
		get => _longEnableEntry.Value;
		set => _longEnableEntry.Value = value;
	}

	/// <summary>
	/// Enable closing long positions.
	/// </summary>
	public bool EnableLongExits
	{
		get => _longEnableExit.Value;
		set => _longEnableExit.Value = value;
	}

	/// <summary>
	/// Invert ratios for the long XDidi block.
	/// </summary>
	public bool LongReverse
	{
		get => _longReverse.Value;
		set => _longReverse.Value = value;
	}

	/// <summary>
	/// Bar shift used for long signals.
	/// </summary>
	public int LongSignalBar
	{
		get => _longSignalBar.Value;
		set => _longSignalBar.Value = value;
	}

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

	/// <summary>
	/// Smoothing method for the fast moving average in the short block.
	/// </summary>
	public SmoothingMethods ShortFastMethod
	{
		get => _shortFastMethod.Value;
		set => _shortFastMethod.Value = value;
	}

	/// <summary>
	/// Length for the fast moving average in the short block.
	/// </summary>
	public int ShortFastLength
	{
		get => _shortFastLength.Value;
		set => _shortFastLength.Value = value;
	}

	/// <summary>
	/// Smoothing method for the medium moving average in the short block.
	/// </summary>
	public SmoothingMethods ShortMediumMethod
	{
		get => _shortMediumMethod.Value;
		set => _shortMediumMethod.Value = value;
	}

	/// <summary>
	/// Length for the medium moving average in the short block.
	/// </summary>
	public int ShortMediumLength
	{
		get => _shortMediumLength.Value;
		set => _shortMediumLength.Value = value;
	}

	/// <summary>
	/// Smoothing method for the slow moving average in the short block.
	/// </summary>
	public SmoothingMethods ShortSlowMethod
	{
		get => _shortSlowMethod.Value;
		set => _shortSlowMethod.Value = value;
	}

	/// <summary>
	/// Length for the slow moving average in the short block.
	/// </summary>
	public int ShortSlowLength
	{
		get => _shortSlowLength.Value;
		set => _shortSlowLength.Value = value;
	}

	/// <summary>
	/// Applied price for the short XDidi calculation.
	/// </summary>
	public AppliedPrices ShortAppliedPrice
	{
		get => _shortAppliedPrice.Value;
		set => _shortAppliedPrice.Value = value;
	}

	/// <summary>
	/// Enable opening short positions.
	/// </summary>
	public bool EnableShortEntries
	{
		get => _shortEnableEntry.Value;
		set => _shortEnableEntry.Value = value;
	}

	/// <summary>
	/// Enable closing short positions.
	/// </summary>
	public bool EnableShortExits
	{
		get => _shortEnableExit.Value;
		set => _shortEnableExit.Value = value;
	}

	/// <summary>
	/// Invert ratios for the short XDidi block.
	/// </summary>
	public bool ShortReverse
	{
		get => _shortReverse.Value;
		set => _shortReverse.Value = value;
	}

	/// <summary>
	/// Bar shift used for short signals.
	/// </summary>
	public int ShortSignalBar
	{
		get => _shortSignalBar.Value;
		set => _shortSignalBar.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (ShortCandleType == LongCandleType)
			return [(Security, LongCandleType)];

		return new[]
		{
			(Security, LongCandleType),
			(Security, ShortCandleType)
		};
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_longFastHistory = Array.Empty<decimal?>();
		_longSlowHistory = Array.Empty<decimal?>();
		_shortFastHistory = Array.Empty<decimal?>();
		_shortSlowHistory = Array.Empty<decimal?>();
	}

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

		_longFastMa = CreateMovingAverage(LongFastMethod, LongFastLength);
		_longMediumMa = CreateMovingAverage(LongMediumMethod, LongMediumLength);
		_longSlowMa = CreateMovingAverage(LongSlowMethod, LongSlowLength);

		_shortFastMa = CreateMovingAverage(ShortFastMethod, ShortFastLength);
		_shortMediumMa = CreateMovingAverage(ShortMediumMethod, ShortMediumLength);
		_shortSlowMa = CreateMovingAverage(ShortSlowMethod, ShortSlowLength);

		_longFastHistory = new decimal?[Math.Max(LongSignalBar + 2, 2)];
		_longSlowHistory = new decimal?[Math.Max(LongSignalBar + 2, 2)];
		_shortFastHistory = new decimal?[Math.Max(ShortSignalBar + 2, 2)];
		_shortSlowHistory = new decimal?[Math.Max(ShortSignalBar + 2, 2)];

		var longSubscription = SubscribeCandles(LongCandleType);

		ISubscriptionHandler<ICandleMessage> shortSubscriptionObj = null;

		if (ShortCandleType == LongCandleType)
		{
			longSubscription.Bind((ICandleMessage c) =>
			{
				ProcessLongCandle(c);
				ProcessShortCandle(c);
			}).Start();
		}
		else
		{
			longSubscription.Bind(ProcessLongCandle).Start();
			shortSubscriptionObj = SubscribeCandles(ShortCandleType);
			shortSubscriptionObj.Bind(ProcessShortCandle).Start();
		}

		var primaryArea = CreateChartArea();
		if (primaryArea != null)
		{
			DrawCandles(primaryArea, longSubscription);
			DrawIndicator(primaryArea, _longFastMa);
			DrawIndicator(primaryArea, _longMediumMa);
			DrawIndicator(primaryArea, _longSlowMa);

			if (ShortCandleType == LongCandleType)
			{
				DrawIndicator(primaryArea, _shortFastMa);
				DrawIndicator(primaryArea, _shortMediumMa);
				DrawIndicator(primaryArea, _shortSlowMa);
			}

			DrawOwnTrades(primaryArea);
		}

		if (shortSubscriptionObj != null)
		{
			var secondaryArea = CreateChartArea();
			if (secondaryArea != null)
			{
				DrawCandles(secondaryArea, shortSubscriptionObj);
				DrawIndicator(secondaryArea, _shortFastMa);
				DrawIndicator(secondaryArea, _shortMediumMa);
				DrawIndicator(secondaryArea, _shortSlowMa);
				DrawOwnTrades(secondaryArea);
			}
		}

		var priceStep = Security?.PriceStep ?? 0m;
		Unit stopLossUnit = null;
		Unit takeProfitUnit = null;

		if (priceStep > 0m)
		{
			if (StopLossPoints > 0m)
				stopLossUnit = new Unit(StopLossPoints * priceStep, UnitTypes.Absolute);

			if (TakeProfitPoints > 0m)
				takeProfitUnit = new Unit(TakeProfitPoints * priceStep, UnitTypes.Absolute);
		}

		StartProtection(stopLoss: stopLossUnit ?? new(), takeProfit: takeProfitUnit ?? new());
	}

	private void ProcessLongCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var price = GetAppliedPrice(LongAppliedPrice, candle);

		var fastValue = _longFastMa.Process(new DecimalIndicatorValue(_longFastMa, price, candle.OpenTime) { IsFinal = true });
		var mediumValue = _longMediumMa.Process(new DecimalIndicatorValue(_longMediumMa, price, candle.OpenTime) { IsFinal = true });
		var slowValue = _longSlowMa.Process(new DecimalIndicatorValue(_longSlowMa, price, candle.OpenTime) { IsFinal = true });

		if (!_longFastMa.IsFormed || !_longMediumMa.IsFormed || !_longSlowMa.IsFormed)
			return;

		var medium = mediumValue.GetValue<decimal>();
		if (medium == 0m)
			return;

		var fast = fastValue.GetValue<decimal>() / medium;
		var slow = slowValue.GetValue<decimal>() / medium;

		if (LongReverse)
		{
			fast = -fast;
			slow = -slow;
		}

		UpdateHistory(_longFastHistory, fast);
		UpdateHistory(_longSlowHistory, slow);

		if (!HasSignalData(_longFastHistory, _longSlowHistory, LongSignalBar))
			return;

		var currentFast = _longFastHistory[LongSignalBar]!.Value;
		var currentSlow = _longSlowHistory[LongSignalBar]!.Value;
		var previousFast = _longFastHistory[LongSignalBar + 1]!.Value;
		var previousSlow = _longSlowHistory[LongSignalBar + 1]!.Value;

		var openSignal = false;
		var closeSignal = false;

		if (previousFast > previousSlow && EnableLongEntries && currentFast <= currentSlow)
			openSignal = true;

		if (previousFast < previousSlow && EnableLongExits)
			closeSignal = true;

		ExecuteLongSignals(openSignal, closeSignal);
	}

	private void ProcessShortCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var price = GetAppliedPrice(ShortAppliedPrice, candle);

		var fastValue = _shortFastMa.Process(new DecimalIndicatorValue(_shortFastMa, price, candle.OpenTime) { IsFinal = true });
		var mediumValue = _shortMediumMa.Process(new DecimalIndicatorValue(_shortMediumMa, price, candle.OpenTime) { IsFinal = true });
		var slowValue = _shortSlowMa.Process(new DecimalIndicatorValue(_shortSlowMa, price, candle.OpenTime) { IsFinal = true });

		if (!_shortFastMa.IsFormed || !_shortMediumMa.IsFormed || !_shortSlowMa.IsFormed)
			return;

		var medium = mediumValue.GetValue<decimal>();
		if (medium == 0m)
			return;

		var fast = fastValue.GetValue<decimal>() / medium;
		var slow = slowValue.GetValue<decimal>() / medium;

		if (ShortReverse)
		{
			fast = -fast;
			slow = -slow;
		}

		UpdateHistory(_shortFastHistory, fast);
		UpdateHistory(_shortSlowHistory, slow);

		if (!HasSignalData(_shortFastHistory, _shortSlowHistory, ShortSignalBar))
			return;

		var currentFast = _shortFastHistory[ShortSignalBar]!.Value;
		var currentSlow = _shortSlowHistory[ShortSignalBar]!.Value;
		var previousFast = _shortFastHistory[ShortSignalBar + 1]!.Value;
		var previousSlow = _shortSlowHistory[ShortSignalBar + 1]!.Value;

		var openSignal = false;
		var closeSignal = false;

		if (previousFast < previousSlow && EnableShortEntries && currentFast >= currentSlow)
			openSignal = true;

		if (previousFast > previousSlow && EnableShortExits)
			closeSignal = true;

		ExecuteShortSignals(openSignal, closeSignal);
	}

	private void ExecuteLongSignals(bool openSignal, bool closeSignal)
	{

		if (closeSignal && Position > 0)
			SellMarket(Position);

		if (openSignal && Position <= 0)
		{
			var volume = Volume + (Position < 0 ? Math.Abs(Position) : 0m);
			if (volume > 0m)
				BuyMarket(volume);
		}
	}

	private void ExecuteShortSignals(bool openSignal, bool closeSignal)
	{

		if (closeSignal && Position < 0)
			BuyMarket(Math.Abs(Position));

		if (openSignal && Position >= 0)
		{
			var volume = Volume + (Position > 0 ? Position : 0m);
			if (volume > 0m)
				SellMarket(volume);
		}
	}

	private static void UpdateHistory(decimal?[] buffer, decimal value)
	{
		for (var i = buffer.Length - 1; i > 0; i--)
			buffer[i] = buffer[i - 1];

		buffer[0] = value;
	}

	private static bool HasSignalData(decimal?[] fastHistory, decimal?[] slowHistory, int signalBar)
	{
		var requiredIndex = signalBar + 1;

		if (requiredIndex >= fastHistory.Length || requiredIndex >= slowHistory.Length)
			return false;

		return fastHistory[signalBar].HasValue &&
		fastHistory[requiredIndex].HasValue &&
		slowHistory[signalBar].HasValue &&
		slowHistory[requiredIndex].HasValue;
	}

	private static decimal GetAppliedPrice(AppliedPrices priceType, ICandleMessage candle)
	{
		return priceType switch
		{
			AppliedPrices.Close => candle.ClosePrice,
			AppliedPrices.Open => candle.OpenPrice,
			AppliedPrices.High => candle.HighPrice,
			AppliedPrices.Low => candle.LowPrice,
			AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 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 baseSum = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
		var adjusted = candle.ClosePrice < candle.OpenPrice
			? (baseSum + candle.LowPrice) / 2m
			: candle.ClosePrice > candle.OpenPrice
				? (baseSum + candle.HighPrice) / 2m
				: (baseSum + candle.ClosePrice) / 2m;
		return ((adjusted - candle.LowPrice) + (adjusted - candle.HighPrice)) / 2m;
	}

	private static DecimalLengthIndicator CreateMovingAverage(SmoothingMethods method, int length)
	{
		return method switch
		{
			SmoothingMethods.Sma => new SimpleMovingAverage { Length = length },
			SmoothingMethods.Ema => new ExponentialMovingAverage { Length = length },
			SmoothingMethods.Smma => new SmoothedMovingAverage { Length = length },
			SmoothingMethods.Lwma => new WeightedMovingAverage { Length = length },
			SmoothingMethods.T3 => new TripleExponentialMovingAverage { Length = length },
			SmoothingMethods.Ama => new KaufmanAdaptiveMovingAverage { Length = length },
			_ => new ExponentialMovingAverage { Length = length }
		};
	}

	/// <summary>
	/// Available smoothing methods that approximate the original MQL implementation.
	/// </summary>
	public enum SmoothingMethods
	{
		Sma,
		Ema,
		Smma,
		Lwma,
		Jjma,
		JurX,
		ParMa,
		T3,
		Vidya,
		Ama
	}

	/// <summary>
	/// Price sources supported by the strategy.
	/// </summary>
	public enum AppliedPrices
	{
		Close = 1,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted,
		Simple,
		Quarter,
		TrendFollow0,
		TrendFollow1,
		Demark
	}
}