PSAR Multi-Timeframe-Strategie
Überblick
Die Strategie repliziert den Expertenberater MetaTrader EA_PSar_002B. Es wertet Parabolic SAR-Werte in drei Zeitrahmen (M15, M30 und H1) aus und verwaltet gleichzeitig Positionen in einem einminütigen Stream. Der Handel erfolgt direktional: Es kann jeweils nur eine Nettoposition aktiv sein und neue Trades erscheinen nur, wenn das vorherige Engagement flach ist. Der ursprüngliche Experte wurde für EURUSD auf dem M1-Chart entwickelt und der Port behält den gleichen Kontext bei.
Handelslogik
- Parabolic SAR-Konvergenzfilter – die neuesten SAR-Werte von M15, M30 und H1 müssen innerhalb von 19 Mindestpreisschritten voneinander liegen. Dadurch bleiben die drei Kurven „eng“, bevor ein Ausbruch zugelassen wird.
- Langer Eintrag – eine der folgenden Sequenzen muss auftreten:
- Die Werte für M15, M30 und H1 SAR liegen unter ihren jeweiligen aktuellen Tiefstständen, der vorherige H1 SAR lag über dem vorherigen H1-Hoch und der neue H1 SAR fällt unter den aktuellen H1-Tiefststand.
- M15 und H1 SAR liegen unter ihren aktuellen Tiefstständen, während der vorherige M30 SAR über dem vorherigen M30-Hoch lag und der neue M30 SAR unter das aktuelle M30-Tief fällt.
- M30 und H1 SAR liegen unter ihren aktuellen Tiefstständen, während der vorherige M15 SAR über dem vorherigen M15-Hoch lag und der neue M15 SAR unter das aktuelle M15-Tief fällt.
- Short-Einstieg – Spiegelung der Bedingungen des Long-Setups mit umgekehrten Hochs/Tiefs.
- Take Profit / Stop Loss – Limits werden in Punkten ausgedrückt (minimale Preiserhöhungen). Standardmäßig beträgt das Ziel 999 Punkte und der Schutzstopp 399 Punkte, was den MQL-Werten nach Normalisierung der 4/5-stelligen Notierungen entspricht.
- Dynamischer Ausstieg – während eine Position offen ist, wird der M30 SAR überwacht.
- Long-Positionen schließen, wenn der vorherige SAR unter dem vorherigen M1-Tief lag, der aktuelle SAR jedoch über das aktuelle M1-Hoch springt.
- Shorts schließen, wenn der vorherige SAR über dem vorherigen M1-Hoch lag, der aktuelle SAR jedoch unter das aktuelle M1-Tief fällt.
- Wenn der aktuelle M30 SAR den Einstiegspreis überschreitet, wird der Stop auf dieses SAR-Niveau verschoben.
Geldmanagement
UseMoneyManagement reproduziert den Geldverwaltungswechsel von EA. Wenn deaktiviert, wird der Parameter FixedVolume verwendet. Wenn diese Option aktiviert ist, wird der angeforderte Prozentsatz des Portfoliokapitals in eine synthetische „Lot“-Größe umgewandelt, wobei die gleiche Formel wie in der MQL-Version verwendet wird (Prozent des freien Kapitals dividiert durch 100.000). Der Betrag wird an Security.VolumeStep angepasst und auf die Broker-Limits (VolumeMin/VolumeMax) begrenzt.
Parameter
BaseCandleType – Zeitrahmen für die Handelsverwaltung (standardmäßig M1).
FastSarCandleType, MediumSarCandleType, SlowSarCandleType – Zeitrahmen für die SAR-Filter (Standard: 15 m, 30 m, 60 m).
EnableParabolicFilter – spiegelt das Flag sar2 von MQL wider; Wenn Sie es ausschalten, wird der Handel vollständig gestoppt.
TakeProfitPoints, StopLossPoints – Offsets in Punkten (minimale Preiserhöhungen). Die Pip-Größe wird aus Security.PriceStep und Security.Decimals abgeleitet, um 3/5-stellige Forex-Kurse korrekt zu verarbeiten.
UseMoneyManagement, PercentMoneyManagement, FixedVolume – oben beschriebene Lautstärkeregler.
Konvertierungshinweise
- Es wird nur das übergeordnete StockSharp API verwendet. Alle Preisreihen werden über
SubscribeCandles().Bind(...) abonniert und Indikatordaten werden über Bindungen statt über manuelle Puffer empfangen.
- Schutzanordnungen werden durch explizite Marktaustritte umgesetzt, genau wie das ursprüngliche Skript, das
OrderClose aufrief.
- Der Broker-Ziffernkoeffizient von MQL wird durch die automatische Erkennung der Pip-Größe ersetzt (
PriceStep × 10 für 3/5-stellige Instrumente).
- Der EA verbot den Handel mit Nicht-EURUSD-Symbolen oder Nicht-M1-Charts durch das Drucken von Nachrichten. In StockSharp bleiben die Strategieprotokolle stumm, aber das Verhalten wird hier dokumentiert.
Anwendungstipps
- Hängen Sie die Strategie mit Ein-Minuten-Kerzen für das Basisabonnement an EURUSD an. Die Zeitrahmen des Indikators können immer noch geändert werden, wenn Experimente gewünscht sind.
- Stellen Sie sicher, dass die Sicherheitsmetadaten
PriceStep/Decimals offenlegen. Ohne sie fallen die Stopp- und Zielentfernungen auf die Einheitsgröße 1 zurück.
- Lassen Sie
EnableParabolicFilter aktiviert; es entspricht dem Hauptschalter des EA. Deaktivieren Sie es nur, wenn Sie absichtlich möchten, dass die Strategie untätig bleibt.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// PSAR Multi Timeframe: EMA crossover with RSI filter and ATR stops.
/// </summary>
public class PsarMultiTimeframeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public PsarMultiTimeframeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI 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 RsiLength { get => _rsiLength.Value; set => _rsiLength.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 rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, rsi, 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 rsiVal, 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 && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _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, RelativeStrengthIndex, AverageTrueRange
class psar_multi_timeframe_strategy(Strategy):
def __init__(self):
super(psar_multi_timeframe_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", 12) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI 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 RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(psar_multi_timeframe_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._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_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 and rv > 50:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(psar_multi_timeframe_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return psar_multi_timeframe_strategy()