Die XD Bereichswechsel-Strategie recreiert den MetaTrader 5 Expert Advisor Exp_XD-RangeSwitch mithilfe der StockSharp High-Level-API. Sie basiert auf dem benutzerdefinierten XD-RangeSwitch-Kanalindikator, der abwechselnd obere und untere Bänder zusammen mit Pfeilen zeichnet, sobald das dominante Band wechselt. Die Strategie kann entweder diese Pfeile ausblenden (Gegentrend-Verhalten) oder in Richtung des Ausbruchs handeln, abhängig vom TradeDirection-Parameter. Die Positionsgröße folgt der Basis-Strategy.Volume-Einstellung, während die ursprünglichen Money-Management-Formeln durch StockSharp-Positionsverwaltungshelfer ersetzt werden.
Nachbildung des XD-RangeSwitch-Indikators
Der Indikator verfolgt die letzten Peaks abgeschlossenen Kerzen, um die höchsten Hoch- und tiefsten Tief-Bereiche zu bestimmen.
Ein bullishes Kanal (unteres Band) wird gedruckt, wenn das aktuelle Schlusskurs über dem höchsten Hoch der vorherigen Peaks Kerzen liegt. Sein Wert entspricht dem minimalen Tief im selben Fenster plus der aktuellen Kerze.
Ein bearishes Kanal (oberes Band) wird gedruckt, wenn das aktuelle Schlusskurs unter dem tiefsten Tief der vorherigen Peaks Kerzen liegt. Sein Wert entspricht dem maximalen Hoch im selben Fenster plus der aktuellen Kerze.
Wenn kein Ausbruch auftritt, werden die vorherigen Kanalwerte vorwärtspropagiert.
Immer wenn ein Kanal nach einer Leerstelle wieder erscheint, zeichnet die Strategie ein Pfeilsignal am Kanalpreis auf. Dies spiegelt das Verhalten der MT5-Puffer 2 und 3 wider, die vom ursprünglichen Expert verwendet werden.
Es werden nur vollständig abgeschlossene Kerzen verarbeitet, was konsistente Werte bei Live- und historischen Läufen sicherstellt.
Handelslogik
Die Strategie verarbeitet Kerzen vom durch CandleType gewählten Zeitrahmen und speichert die rekonstruierten Indikatorbuffer.
Für jede neue Kerze untersucht sie den Indikatorwert, der SignalBar Kerzen alt ist (der MT5-Code verwendet denselben Versatz beim Aufruf von CopyBuffer).
Die Signalzuordnung hängt von der TradeDirection-Option ab:
AgainstSignal repliziert das Standard-MT5-Verhalten — bullishe Pfeile lösen Longs aus und fordern auch den Abschluss von Short-Trades, bearishe Pfeile lösen Shorts aus und fordern den Abschluss von Longs.
WithSignal dreht die Interpretation um, sodass bullishe Pfeile als Ausstiegspunkte für Longs und Einstiegspunkte für Shorts behandelt werden, was effektiv in derselben Richtung wie der Kanalausbruch handelt.
Trendbuffer ohne Pfeile werden weiterhin als Ausstiegssignale respektiert, was den ursprünglichen SELL_Close- und BUY_Close-Flags entspricht.
Schließungen werden immer vor Eröffnungen ausgeführt, sodass die Strategie eine entgegengesetzte Position ausgleichen kann, bevor sie in die neue Richtung einsteigt.
Aufträge werden mit Market-Execution-Helpern eingereicht (BuyMarket/SellMarket). Wenn ein Wechsel auftritt, während eine entgegengesetzte Position offen ist, wird die angeforderte Menge automatisch erhöht, um das Exposure vollständig auszugleichen, bevor die neue Position eingegangen wird.
Risikomanagement
Optionale Stop-Loss- und Take-Profit-Logik wird durch die UseStopLoss/StopLossPoints- und UseTakeProfit/TakeProfitPoints-Parameter bereitgestellt.
Die Abstände werden in absoluten Preiseinheiten gemessen und spiegeln die "Punkte"-Eingaben im MT5-Skript wider.
Stops und Ziele werden bei jeder abgeschlossenen Kerze unter Verwendung des Hoch/Tief der Kerze evaluiert, um die Intra-Bar-Auslösung zu emulieren.
Wenn sowohl ein Stop als auch ein Ziel aktiv sind, hat der Stop Priorität — die Position wird geschlossen, sobald eines der Levels erreicht wird.
Parameter
Name
Standard
Beschreibung
CandleType
H4-Kerzen
Zeitrahmen für die XD-RangeSwitch-Berechnungen.
Peaks
4
Anzahl der Peaks (Lookback-Länge), die vom Indikator analysiert werden.
SignalBar
1
Anzahl abgeschlossener Balken zurück beim Lesen von Indikatorbuffern.
TradeDirection
AgainstSignal
Wahl zwischen Gegentrend- oder Trendfolge-Interpretation der Signale.
AllowBuyEntry / AllowSellEntry
true
Neue Positionen in der entsprechenden Richtung aktivieren oder deaktivieren.
AllowBuyExit / AllowSellExit
true
Der Strategie erlauben, bestehende Positionen zu schließen, wenn der Indikator es anfordert.
UseStopLoss / StopLossPoints
true / 1000
Stop-Loss-Behandlung aktivieren und Distanz in Preiseinheiten festlegen.
UseTakeProfit / TakeProfitPoints
true / 2000
Take-Profit-Behandlung aktivieren und Distanz in Preiseinheiten festlegen.
Hinweise
Die Hoch/Tief-Buffer werden intern innerhalb der Strategie gepflegt, anstatt sich auf StockSharp-Kollektionen zu verlassen, was der MT5-Implementierung treu bleibt und gleichzeitig den Konvertierungsrichtlinien entspricht.
Signale werden nur bei abgeschlossenen Kerzen ausgewertet. Wenn SignalBar größer als null ist, wird die Order bei der nächsten Kerze nach der platziert, die das Signal erzeugt hat, wie im MT5-Expert.
Die Indikatorwerte werden in einer kurzen rollierenden Historie gespeichert, die nur etwas über das Maximum von Peaks und SignalBar hinausgeht, was eine deterministische Speichernutzung auch bei langen Simulationen sicherstellt.
Die Standardkonfiguration spiegelt die MT5-Standardwerte wider: H4-Kerzen, Peaks = 4, SignalBar = 1, Gegentrend-Handel und ein 1.000/2.000-Punkte-Risikorahmen.
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()