Ver en GitHub

Estrategia XDidi Index Cloud Duplex

Descripción general

La estrategia XDidi Index Cloud Duplex replica la lógica de señalización dual larga/corta del experto MQL5 original Exp_XDidi_Index_Cloud_Duplex. Se evalúan dos configuraciones independientes del índice XDidi en marcos temporales configurables. Cada configuración calcula una relación entre medias móviles rápidas/medias y lentas/medias. Los cruces entre estas relaciones desencadenan entradas de mercado mientras que las divergencias persistentes desencadenan salidas.

Lógica de trading

  1. Cálculo del indicador
    • Se calculan tres medias móviles para cada bloque (rápida, media, lenta) en una fuente de precio seleccionada.
    • Las relaciones XDidi se derivan como fast / medium y slow / medium. La inversión opcional coincide con la opción original Revers.
  2. Generación de señales
    • Bloque largo: cuando la barra anterior tenía fast > slow y la barra de señal cierra con fast <= slow, se solicita una entrada larga. Si la barra anterior tenía fast < slow, se solicita una salida larga.
    • Bloque corto: cuando la barra anterior tenía fast < slow y la barra de señal cierra con fast >= slow, se solicita una entrada corta. Si la barra anterior tenía fast > slow, se solicita una salida corta.
    • Los offsets de barra de señal reproducen las entradas originales SignalBar.
  3. Gestión de órdenes
    • Las entradas se ejecutan con el volumen de la estrategia. Las posiciones opuestas se cierran antes de revertir.
    • Los niveles opcionales de stop-loss y take-profit se aplican via StartProtection usando distancias de paso de precio.

Parámetros

Nombre Descripción
LongCandleType, ShortCandleType Marcos temporales de velas para cada bloque.
LongFastMethod / Medium / Slow & ShortFastMethod / Medium / Slow Métodos de suavizado de media móvil para curvas rápida, media y lenta. Los suavizadores heredados no soportados vuelven al promediado exponencial.
LongFastLength, LongMediumLength, LongSlowLength Períodos para las medias móviles del bloque largo.
ShortFastLength, ShortMediumLength, ShortSlowLength Períodos para las medias móviles del bloque corto.
LongAppliedPrice, ShortAppliedPrice Fuente de precio usada para cada bloque (cierre, apertura, típico, Demark, etc.).
EnableLongEntries, EnableShortEntries Activar/desactivar nuevas posiciones largas/cortas.
EnableLongExits, EnableShortExits Activar/desactivar salidas automáticas.
LongSignalBar, ShortSignalBar Desplazamiento histórico (barras atrás) evaluado para cruces.
LongReverse, ShortReverse Invertir relaciones (refleja el flag Revers en MQL).
StopLossPoints, TakeProfitPoints Distancias de protección expresadas en pasos de precio (establecer en cero para deshabilitar).
Volume (propiedad base de estrategia) Define el tamaño de operación predeterminado.

Notas de implementación

  • Las medias móviles se toman de la biblioteca de indicadores de StockSharp. Los suavizadores avanzados (JJMA, JurX, ParMA, VIDYA) usan suavizado exponencial por defecto porque no hay equivalentes directos disponibles.
  • Los valores del indicador se procesan solo en velas finalizadas, coincidiendo con el comportamiento original de IsNewBar.
  • Las colas de señales mantienen solo el número requerido de valores de relación históricos, evitando colecciones pesadas.
  • Los stops de protección son opcionales; si ambas distancias son cero, la estrategia aún llama a StartProtection() para cumplir con el ciclo de vida del framework.

Consejos de uso

  • Alinee los tipos de velas con la suscripción de datos disponible en su conector.
  • Optimice las longitudes de medias móviles y precios aplicados para adaptarse al instrumento negociado.
  • Cuando se usan marcos temporales asimétricos (largo/corto), ambas suscripciones se visualizan en áreas de gráfico separadas para mayor claridad.

Limitaciones en comparación con la versión MQL5

  • Los modos de gestión monetaria (MM, MarginMode) no están replicados; el tamaño de operación sigue la propiedad Volume de StockSharp.
  • Algunos algoritmos de suavizado exóticos de SmoothAlgorithms.mqh se aproximan con medias móviles exponenciales.
  • Las órdenes de stop/límite se convierten en niveles de protección genéricos en lugar de parámetros de órdenes individuales.
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
	}
}