Separate-Trade-Strategie
Überblick
Die Separate-Trade-Strategie ist eine Konvertierung des MetaTrader 5-Expertenberaters "Separate trade". Sie bewahrt die ursprüngliche Multi-Filter-Logik und adoptiert gleichzeitig die StockSharp-High-Level-API für robustes Orderverwaltung und Indikatorhandling. Die Strategie versucht, stille Marktwendepunkte zu erfassen, wenn Volatilität und Dispersion unterdrückt sind. Es wird jeweils nur eine Nettoposition gehalten, was die Absicht des ursprünglichen Codes widerspiegelt, der die Anzahl der gleichzeitigen Positionen begrenzte.
Indikatoren und Daten
- Zwei gleitende Durchschnitte mit konfigurierbarer Methode (SMA, EMA, SMMA oder LWMA) und gemeinsamer Preisquelle.
- Average True Range (ATR) mit separaten Zeiträumen und Schwellenwerten für Long- und Short-Filter.
- Standardabweichung unter Verwendung desselben angewendeten Preises wie die gleitenden Durchschnitte, wiederum mit richtungsspezifischen Zeiträumen und Obergrenzen.
- Kerzen werden über einen konfigurierbaren
DataType-Parameter geliefert, sodass die Strategie an jeden Zeitrahmen oder benutzerdefinierten Kerzenbauer angehängt werden kann.
Parameter
| Parameter |
Beschreibung |
Standard |
TradeVolume |
Ordergröße in Lots. |
1 |
SlowMaPeriod |
Zeitraum des langsameren gleitenden Durchschnitts. |
65 |
FastMaPeriod |
Zeitraum des schnelleren gleitenden Durchschnitts. |
14 |
MaMethod |
Glättungsmethode für beide gleitenden Durchschnitte (Simple, Exponential, Smoothed, LinearWeighted). |
Exponential |
PriceType |
Preiseingang für die gleitenden Durchschnitte und Standardabweichung (Close, Open, High, Low, Median, Typical, Weighted). |
Close |
StopLossBuyPips / StopLossSellPips |
Stop-Loss-Abstand für Long- und Short-Trades in Pips (0 deaktiviert den Stop). |
50 |
TakeProfitBuyPips / TakeProfitSellPips |
Take-Profit-Abstand für Long- und Short-Trades in Pips (0 deaktiviert den Take-Profit). |
50 |
TrailingStopPips |
Trailing-Stop-Abstand in Pips. |
5 |
TrailingStepPips |
Minimaler Gewinnvorschub in Pips, bevor der Trailing-Stop bewegt wird. Muss positiv sein, wenn Trailing aktiviert ist. |
5 |
MaxPositions |
Maximal erlaubte gleichzeitige Nettopositionen. Die StockSharp-Version arbeitet mit einer einzigen aggregierten Position, auch wenn der Wert größer als eins ist. |
1 |
DeltaBuyPips / DeltaSellPips |
Maximal erlaubter Abstand zwischen dem schnellen und langsamen gleitenden Durchschnitt (pro Richtung). Ein Wert von null deaktiviert den Abstandsfilter. |
2 |
AtrPeriodBuy / AtrPeriodSell |
ATR-Rückblickzeitraum für die Long- und Short-Filter. |
26 |
AtrLevelBuy / AtrLevelSell |
Oberer ATR-Schwellenwert, der vor dem Einstieg nicht überschritten werden darf. |
0.0016 |
StdDevPeriodBuy / StdDevPeriodSell |
Standardabweichungs-Rückblickzeitraum für die Long- und Short-Filter. |
54 |
StdDevLevelBuy / StdDevLevelSell |
Standardabweichungsobergrenze, die vor dem Einstieg nicht überschritten werden darf. |
0.0051 |
CandleType |
Vom Abonnement verwendeter Kerzendatentyp. |
TimeSpan.FromMinutes(15) |
Handelslogik
- Balkensynchronisation – die Strategie agiert nur auf fertigen Kerzen, die vom konfigurierten Abonnement empfangen werden. Dies repliziert den
OnTick-Neuer-Balken-Guard aus dem MetaTrader-Skript.
- Indikatorfilter – für Long-Einstiege muss der langsame MA unter dem schnellen MA liegen, ATR muss unter
AtrLevelBuy liegen, Standardabweichung muss unter StdDevLevelBuy liegen, und der MA-Abstand muss kleiner als DeltaBuyPips sein (wenn das Delta positiv ist). Short-Einstiege kehren die Bedingungen um und verwenden ihre eigenen ATR- und Abweichungsparameter.
- Positionssteuerung – Trades werden nur eingegangen, wenn keine offene Position besteht und die letzte Einstiegszeit für die jeweilige Seite älter als die aktuelle Kerze ist. Dies verhindert Re-Einstiege innerhalb desselben Balkens, was der
m_last_deal_IN_*-Prüfung im Quell-EA entspricht.
- Orderausführung – Marktorders werden mit dem konfigurierten Volumen platziert. Umkehrtrades flachen die aktuelle Position automatisch ab, bevor eine neue geöffnet wird, dank der
Volume + Math.Abs(Position)-Menge, die dem MQL-Verhalten des Schließens entgegengesetzter Exposure entspricht.
Risikomanagement
- Pip-Konvertierung – Pip-Abstände werden unter Verwendung des
PriceStep des Instruments konvertiert. Für Instrumente mit 3 oder 5 Dezimalstellen entspricht die Pip-Größe PriceStep * 10, was die ursprüngliche digits_adjust-Logik widerspiegelt.
- Stop-Loss / Take-Profit – die Strategie verfolgt Preislevels intern und steigt aus, wenn der Kerzenbereich den angegebenen Stop oder das Ziel berührt. Beide können deaktiviert werden, indem der Pip-Abstand auf null gesetzt wird.
- Trailing-Stop – sobald der Preis über
TrailingStopPips + TrailingStepPips hinausgeht, wird der Stop bewegt, um die Trailing-Distanz aufrechtzuerhalten. Die Trailing-Schrittanforderung stimmt mit der MetaTrader-Implementierung überein und vermeidet das Bewegen des Stops um einen unbedeutenden Betrag.
Implementierungshinweise
- Die Strategie verwendet eine einzelne aggregierte Position, da StockSharp standardmäßig mit Nettopositionen arbeitet. Obwohl der
MaxPositions-Parameter für die Kompatibilität beibehalten wird, verhindert eine Überschreitung von eins einfach neue Einstiege, bis die aktuelle Position geschlossen ist.
- Indikatorwerte werden unter Verwendung der StockSharp-Indikatorklassen und der
Bind-Infrastruktur berechnet, um manuellen Pufferzugriff gemäß den Projektrichtlinien zu vermeiden.
- Die Konvertierung hält alle Kommentare auf Englisch und ordnet jeden ursprünglichen Eingang einem dedizierten
StrategyParam zu, sodass Optimierung und Designer-Integration verfügbar bleiben.
- Wenn
TrailingStopPips positiv ist, muss TrailingStepPips auch positiv sein. Der Code stoppt die Strategie frühzeitig und schreibt eine Fehlermeldung, wenn diese Anforderung verletzt wird, was die Sicherheitsprüfung des MQL-Experten reproduziert.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Separate trade strategy using dual EMA crossover with ATR volatility filter.
/// Trades when fast EMA crosses slow EMA and ATR confirms adequate volatility.
/// </summary>
public class SeparateTradeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _atrPeriod;
private decimal? _prevFast;
private decimal? _prevSlow;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public SeparateTradeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 65)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevSlow = null;
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
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 fast, decimal slow, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast == null || _prevSlow == null)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast;
_prevSlow = slow;
// Require minimum ATR relative to price
if (atr < candle.ClosePrice * 0.0005m)
return;
// Golden cross
if (!prevAbove && currAbove)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
// Death cross
else if (prevAbove && !currAbove)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
}
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 ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class separate_trade_strategy(Strategy):
def __init__(self):
super(separate_trade_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 65) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
def OnReseted(self):
super(separate_trade_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(separate_trade_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, atr, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value, atr_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
# Require minimum ATR relative to price
av = float(atr_value)
close = float(candle.ClosePrice)
if av < close * 0.0005:
return
# Golden cross
if not prev_above and curr_above:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
# Death cross
elif prev_above and not curr_above:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return separate_trade_strategy()