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
La estrategia procesa velas del marco temporal seleccionado por CandleType y almacena los buffers de indicadores reconstruidos.
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).
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.
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.
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.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class xd_range_switch_strategy(Strategy):
def __init__(self):
super(xd_range_switch_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 200) \
.SetDisplay("Channel Period", "Lookback for highest/lowest channel", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk")
self._highest = None
self._lowest = None
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def channel_period(self):
return self._channel_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(xd_range_switch_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(xd_range_switch_strategy, self).OnStarted2(time)
self._highest = Highest()
self._highest.Length = self.channel_period
self._lowest = Lowest()
self._lowest.Length = self.channel_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._highest, self._lowest, self._process_candle)
subscription.Start()
def _process_candle(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
h_val = float(high_value)
l_val = float(low_value)
if not self._highest.IsFormed or not self._lowest.IsFormed:
self._prev_high = h_val
self._prev_low = l_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_high = h_val
self._prev_low = l_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 30
self._prev_high = h_val
self._prev_low = l_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 30
self._prev_high = h_val
self._prev_low = l_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 30
self._prev_high = h_val
self._prev_low = l_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 30
self._prev_high = h_val
self._prev_low = l_val
return
# Breakout above previous channel high
if close > self._prev_high and self._prev_high > 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 30
# Breakout below previous channel low
elif close < self._prev_low and self._prev_low > 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 30
self._prev_high = h_val
self._prev_low = l_val
def CreateClone(self):
return xd_range_switch_strategy()