Ver en GitHub

Estrategia de Cambio de Rango XD

Descripción general

La estrategia de Cambio de Rango XD recrea el expert advisor de MetaTrader 5 Exp_XD-RangeSwitch usando la API de alto nivel de StockSharp. Se basa en el indicador de canal personalizado XD-RangeSwitch, que traza bandas superiores e inferiores alternantes junto con flechas cada vez que la banda dominante cambia. La estrategia puede ya sea desvanecer esas flechas (comportamiento contra-tendencia) o operar en la dirección del rompimiento dependiendo del parámetro TradeDirection. El dimensionamiento de órdenes sigue la configuración base Strategy.Volume, mientras que las fórmulas originales de gestión de dinero son reemplazadas por los helpers de gestión de posición de StockSharp.

Recreación del indicador XD-RangeSwitch

  • El indicador rastrea las últimas Peaks velas completadas para determinar los rangos más altos y más bajos.
  • Se imprime un canal alcista (banda inferior) cuando el cierre actual está por encima del máximo más alto de las Peaks barras previas. Su valor equivale al mínimo más bajo en la misma ventana más la barra actual.
  • Se imprime un canal bajista (banda superior) cuando el cierre actual está por debajo del mínimo más bajo de las Peaks barras previas. Su valor equivale al máximo más alto en la misma ventana más la barra actual.
  • Si no ocurre ningún rompimiento, los valores previos del canal se propagan hacia adelante.
  • Cada vez que un canal reaparece después de estar vacío, la estrategia registra una señal de flecha en el precio del canal. Esto refleja el comportamiento de los buffers 2 y 3 de MT5 utilizados por el expert original.
  • Solo se procesan velas completamente terminadas, asegurando valores consistentes en ejecuciones en vivo e históricas.

Lógica de trading

  1. La estrategia procesa velas del marco temporal seleccionado por CandleType y almacena los buffers de indicadores reconstruidos.
  2. Para cada nueva vela, inspecciona el valor del indicador que tiene SignalBar velas de antigüedad (el código MT5 usa el mismo desplazamiento al llamar a CopyBuffer).
  3. El mapeo de señales depende de la opción TradeDirection:
    • AgainstSignal replica el comportamiento predeterminado de MT5 — las flechas alcistas activan largos y también solicitan cerrar operaciones cortas; las flechas bajistas activan cortos y solicitan cerrar largos.
    • WithSignal invierte la interpretación, por lo que las flechas alcistas se tratan como puntos de salida para largos y puntos de entrada para cortos, operando efectivamente en la misma dirección que el rompimiento del canal.
  4. Los buffers de tendencia sin flechas aún se respetan como señales de salida, coincidiendo con los indicadores originales SELL_Close y BUY_Close.
  5. Los cierres siempre se ejecutan antes que las aperturas, permitiendo que la estrategia aplane una posición opuesta antes de entrar en la nueva dirección.
  6. Las órdenes se envían con helpers de ejecución de mercado (BuyMarket/SellMarket). Cuando ocurre un cambio mientras una posición opuesta está abierta, la cantidad solicitada se aumenta automáticamente para compensar completamente la exposición antes de establecer la nueva posición.

Gestión del riesgo

  • La lógica opcional de stop-loss y take-profit se proporciona a través de los parámetros UseStopLoss/StopLossPoints y UseTakeProfit/TakeProfitPoints.
  • Las distancias se miden en unidades de precio absolutas, reflejando las entradas de "puntos" en el script MT5.
  • Los stops y objetivos se evalúan en cada vela terminada usando los valores máximos/mínimos de la vela para emular la activación dentro de la barra.
  • Si tanto un stop como un objetivo están activos, el stop tiene prioridad — la posición se cierra una vez que se alcanza cualquiera de los niveles.

Parámetros

Nombre Predeterminado Descripción
CandleType Velas H4 Marco temporal utilizado para los cálculos de XD-RangeSwitch.
Peaks 4 Número de picos (longitud de lookback) analizados por el indicador.
SignalBar 1 Número de barras completadas hacia atrás al leer los buffers del indicador.
TradeDirection AgainstSignal Elegir entre interpretación contra-tendencia o seguimiento de tendencia de las señales.
AllowBuyEntry / AllowSellEntry true Habilitar o deshabilitar la apertura de nuevas posiciones en la dirección correspondiente.
AllowBuyExit / AllowSellExit true Permitir que la estrategia cierre posiciones existentes cuando el indicador lo solicita.
UseStopLoss / StopLossPoints true / 1000 Activar el manejo del stop-loss y definir su distancia en unidades de precio.
UseTakeProfit / TakeProfitPoints true / 2000 Activar el manejo del take-profit y definir su distancia en unidades de precio.

Notas

  • Los buffers de máximos/mínimos se mantienen internamente dentro de la estrategia en lugar de depender de colecciones de StockSharp, manteniéndose fiel a la implementación de MT5 y adhiriéndose a las pautas de conversión.
  • Las señales se evalúan solo en velas terminadas. Si SignalBar es mayor que cero, la orden se coloca en la siguiente vela después de la que produjo la señal, como en el expert de MT5.
  • Los valores del indicador se mantienen en un historial rodante corto que se extiende apenas más allá del mayor entre Peaks y SignalBar, asegurando un uso determinista de memoria incluso durante simulaciones largas.
  • La configuración predeterminada refleja los valores predeterminados de MT5: velas H4, Peaks = 4, SignalBar = 1, trading contra-tendencia y un sobre de riesgo de 1.000/2.000 puntos.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// XD-RangeSwitch strategy using Highest/Lowest channel breakouts.
/// Buys on breakout above channel high, sells on breakout below channel low.
/// Uses stop-loss and take-profit for risk management.
/// </summary>
public class XdRangeSwitchStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private Highest _highest;
	private Lowest _lowest;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Channel lookback period.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public XdRangeSwitchStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Lookback for highest/lowest channel", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_highest = null;
		_lowest = null;
		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_highest = new Highest { Length = ChannelPeriod };
		_lowest = new Lowest { Length = ChannelPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_highest, _lowest, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal highValue, decimal lowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_highest.IsFormed || !_lowest.IsFormed)
		{
			_prevHigh = highValue;
			_prevLow = lowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevHigh = highValue;
			_prevLow = lowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}
		}

		// Breakout above previous channel high
		if (close > _prevHigh && _prevHigh > 0 && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 30;
		}
		// Breakout below previous channel low
		else if (close < _prevLow && _prevLow > 0 && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 30;
		}

		_prevHigh = highValue;
		_prevLow = lowValue;
	}
}