Die MO Bidir Hedge Strategy ist eine StockSharp-Portierung des MetaTrader 4-Expertenberaters mo_bidir_v0_1. Der ursprüngliche Roboter wurde für den Fünf-Minuten-Chart entwickelt und hielt immer ein abgesichertes Marktrisiko aufrecht: Jeder neue Balken eröffnete sowohl eine Long- als auch eine Short-Position mit vordefinierten Stop-Loss- und Take-Profit-Abständen. Die StockSharp-Version reproduziert dieses Verhalten mit fertigen Kerzen, High-Level-Order-Helfern und expliziten Risikoparametern, die in Instrumentenpunkten gemessen werden.
Handelslogik
Abonnieren Sie den konfigurierten Kerzentyp (standardmäßig fünfminütiger Zeitrahmen) und verarbeiten Sie nur fertige Kerzen.
Sobald eine Kerze schließt, überprüfen Sie die inneren Heckenschenkel. Bleibt ein Teil offen, wartet die Strategie auf die Auslösung von Schutzaufträgen und eröffnet keine weiteren Positionen.
Wenn keine Beine aktiv sind, übermitteln Sie einen Marktkauf- und einen Marktverkaufsauftrag gleicher Größe. Jeder ausgeführte Auftrag wird zu einem unabhängigen Sicherungszweig, der von der Strategie verfolgt wird.
Nachdem jeder Eintrag gefüllt wurde, werden die Stop-Loss- und Take-Profit-Schwellenwerte berechnet, indem die konfigurierten Punktabstände mit der Preisstufe des Instruments (oder dem minimalen Preisinkrement, wenn die Stufe nicht verfügbar ist) multipliziert werden.
Bei jeder weiteren fertigen Kerze überprüft die Strategie die Kerzenhochs und -tiefs:
Lange Beine werden über einen Marktverkauf geschlossen, wenn das Tief das Stop-Level durchbricht; Wenn es nicht gestoppt wird, schließt ein Hoch, das das Ziel erreicht, das Segment mit Gewinn.
Kurze Beine schließen über einen Marktkauf, wenn das Hoch den Stopp berührt; andernfalls realisiert ein Tief, das das Ziel erreicht, den Gewinn.
Wenn beide Schwellenwerte innerhalb derselben Kerze liegen, wird der Stop-Loss priorisiert, da seine Berührung die Position in der MetaTrader-Implementierung zuerst geschlossen hätte.
Sobald alle Beine ihre Schutzniveaus erreicht haben, bereitet die Strategie beim nächsten Kerzenschluss sofort das nächste abgesicherte Paar vor.
Dieser Workflow behält die Parität mit der MT4-Logik bei und stützt sich dabei ausschließlich auf High-Level-StockSharp-APIs (BuyMarket/SellMarket) und eine kerzenbasierte Verarbeitung, die in den Konvertierungsrichtlinien vorgeschrieben ist.
Parameter
Parameter
Beschreibung
TradeVolume
Auftragsgröße gilt für beide Seiten der Absicherung. Muss positiv sein.
StopLossPoints
Abstand vom Einstiegspreis bis zum Schutzstopp, gemessen in Instrumentenpunkten. Verwenden Sie 0, um den Stopp zu deaktivieren.
TakeProfitPoints
Zielabstand vom Einstiegspreis in Instrumentenpunkten. Verwenden Sie 0, um das Gewinnziel zu deaktivieren.
CandleType
Zeitrahmen, der zum Erkennen neuer Balken verwendet wird. Standardmäßig ist ein Zeitrahmen von fünf Minuten eingestellt.
Alle punktbasierten Entfernungen werden in absolute Preise umgerechnet, indem der konfigurierte Wert mit dem Instrument PriceStep multipliziert wird. Wenn der Schritt nicht definiert ist, wird der minimale Preisanstieg verwendet; Wenn keiner der beiden Werte verfügbar ist, bleiben die Schutzstufen inaktiv.
Risikomanagement
Beide Seiten der Absicherung nutzen das gleiche Fixvolumen und setzen auf symmetrische Schutzaufträge.
Stop-Loss- und Take-Profit-Abstände spiegeln die MetaTrader-Standardwerte wider (80 bzw. 750 Punkte), wobei das Verhältnis „8 Pips vs. 75 Pips“ bei einem 5-stelligen Forex-Symbol erhalten bleibt.
Jeder Zweig wird mit einer Marktorder geschlossen, wodurch die Marge sofort freigegeben wird und der verbleibende Zweig unbeaufsichtigt weitergeführt werden kann, bis sein eigenes Schutzniveau erreicht ist.
Implementierungshinweise
Die Strategie verarbeitet ausschließlich fertige Kerzen, um den projektweiten Konvertierungsregeln zu entsprechen. Intrabar-Stopp- oder Zielberührungen werden aus den Kerzenextremen abgeleitet, sodass Backtests ohne Tick-Daten davon ausgehen, dass der Stop vor dem Ziel ausgelöst wurde, wenn beide Preise innerhalb desselben Balkens erschienen.
Das interne Hedge-Ledger verfolgt die Füllungen unabhängig von der Nettoportfolioposition. Dies spiegelt das Verhalten von MetaTrader wider, bei dem Long- und Short-Positionen gleichzeitig existieren.
Es werden keine automatisierte Trailing-Logik, Sitzungsfilter oder zusätzliche Indikatoren eingeführt – die StockSharp-Version bleibt bewusst so minimalistisch wie der ursprüngliche Expert Advisor.
Nutzungstipps
Passen Sie TradeVolume an die Größe der Broker-Verträge an und stellen Sie sicher, dass das Instrument die gleichzeitige Kauf-/Verkaufsabsicherung unterstützt, wenn die Umgebung dies erfordert.
Wenn Sie Pip-basierte Werte benötigen (z. B. 8 Pips), multiplizieren Sie diese mit der Anzahl der Punkte, die einen Pip für das aktuelle Symbol darstellen, bevor Sie den Parameter zuweisen.
Kombinieren Sie die Strategie mit StockSharp Risikomodulen oder StartProtection, wenn zusätzliche Schutzmaßnahmen auf Portfolioebene erforderlich sind.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Mo Bidir: EMA crossover with ATR stops.
/// </summary>
public class MoBidirStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public MoBidirStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 25)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class mo_bidir_strategy(Strategy):
def __init__(self):
super(mo_bidir_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 25) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(mo_bidir_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(mo_bidir_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return mo_bidir_strategy()