Ver en GitHub

Estrategia Contratendencia XFatl XSatl Cloud

Esta estrategia de StockSharp recrea el experto MT5 Exp_XFatlXSatlCloud. Observa la "nube" FATL/SATL suavizada y opera en contra de la dirección de su cruce. Cuando la línea rápida (XFATL) cae de nuevo por debajo de la línea lenta (XSATL) después de haber estado por encima, la estrategia abre una posición larga. Cuando la línea rápida sube de nuevo por encima después de haber estado por debajo, abre una posición corta. Los niveles opcionales de stop loss y take profit se expresan en pasos de precio del instrumento.

Lógica de trading

  • La fuente de datos predeterminada es un marco temporal de 8 horas. Otros tipos de velas pueden seleccionarse con el parámetro CandleType.
  • Se construyen dos pipelines de suavizado a partir de medias móviles de StockSharp. Por defecto ambas usan una media móvil Jurik con longitud y fase configurables. También están disponibles familias de suavizado alternativas (SMA, EMA, SMMA, WMA).
  • Las señales se evalúan en la barra definida por SignalBar (desplazamiento en barras desde la última vela cerrada). La estrategia almacena una ventana rodante de valores recientes del indicador para que los últimos y anteriores valores puedan compararse igual que la versión MT5.
  • Reglas de entrada (contrario):
    • Largo – la línea rápida estaba por encima de la línea lenta en la barra anterior y ahora ha cruzado hacia ella o por debajo.
    • Corto – la línea rápida estaba por debajo en la barra anterior y ahora ha cruzado hacia ella o por encima.
  • Reglas de salida:
    • Las posiciones largas se cierran cuando la barra anterior mostró una nube bajista (rápida por debajo de lenta) y AllowLongExit está habilitado.
    • Las posiciones cortas se cierran cuando la barra anterior mostró una nube alcista (rápida por encima de lenta) y AllowShortExit está habilitado.
  • Una nueva posición solo se abre una vez que la posición anterior se ha cerrado completamente, reflejando el comportamiento del asesor experto original.

Gestión de riesgos

  • TradeVolume controla la cantidad usada para las órdenes de mercado. La estrategia nunca escala — cada nueva posición usa el mismo tamaño.
  • TakeProfitTicks y StopLossTicks se convierten directamente en distancias de paso de precio y se conectan al módulo de protección integrado de StockSharp. Configúrelos en cero para deshabilitar la orden protectora correspondiente.
  • Dado que el experto MT5 dependía de cálculos de gestión de dinero específicos del broker, esta versión reemplaza esa lógica con parámetros explícitos de volumen y protección.

Parámetros

Parámetro Descripción
CandleType Tipo de vela o marco temporal usado para los cálculos del indicador.
FastMethod / SlowMethod Familia de suavizado para XFATL y XSATL (Jurik por defecto).
FastLength / SlowLength Longitudes de período para los filtros rápido y lento.
FastPhase / SlowPhase Entradas de fase reenviadas a la media móvil Jurik cuando se admite.
SignalBar Desplazamiento de barra usado al evaluar cruces (1 = barra anterior).
TradeVolume Tamaño de orden para entradas.
AllowLongEntry / AllowShortEntry Habilitar o deshabilitar entradas contrarias en cada dirección.
AllowLongExit / AllowShortExit Permitir que el indicador cierre posiciones abiertas en señales opuestas.
TakeProfitTicks Distancia al objetivo de take-profit expresada en pasos de precio.
StopLossTicks Distancia al stop protector en pasos de precio.

Notas de implementación

  • La estrategia mantiene colas cortas de salidas recientes del indicador y las recorta a la longitud mínima requerida por SignalBar. No se crean buffers históricos adicionales.
  • El soporte de fase de Jurik se configura vía reflexión para que la estrategia siga siendo compatible con diferentes versiones de StockSharp. Si el indicador subyacente carece de una propiedad Phase, el valor simplemente se ignora.
  • Solo se usa el precio de cierre de cada vela, coincidiendo con la configuración más común para el experto original. Extender la lógica a tipos de precio alternativos requeriría aumentar la estrategia.
  • Se usan componentes de la API de alto nivel (SubscribeCandles, Bind, StartProtection) a lo largo, por lo que la estrategia se integra limpiamente con Designer y otros productos de StockSharp.
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
	}
}