Die Parabolic SAR EA-Strategie ist die StockSharp High-Level-Konvertierung des MetaTrader-Expertenberaters Parabolic SAR EA.mq5 aus MQL/23039. Das ursprüngliche MQL-Skript reagiert auf Parabolic-SAR-Umkehrungen auf einem konfigurierbaren Zeitrahmen, öffnet Marktpositionen mit festen Stop-Loss- und Take-Profit-Abständen, die in MetaTrader-"Pips" ausgedrückt werden (einschließlich Bruchteil-Pip-Unterstützung). Der C#-Port abonniert Kerzen, bindet den eingebauten ParabolicSar-Indikator und reproduziert denselben Bar-für-Bar-Entscheidungsprozess unter Einhaltung der StockSharp-Best-Practices.
Trading-Logik
Datenvorbereitung
Die Strategie abonniert den vom Benutzer ausgewählten Kerzentyp (standardmäßig 30-Minuten-Kerzen) und bindet einen Parabolic-SAR-Indikator mit anpassbarem Beschleunigungsschritt und Maximalwerten.
Der SAR-Wert wird für jede Kerze berechnet und über den High-Level-Bind-Callback an die Strategie geliefert.
Signalgenerierung
Kaufsignal: wenn der Parabolic-SAR-Wert der abgeschlossenen Kerze streng unterhalb des Kerzen-Tiefs liegt.
Verkaufssignal: wenn der Parabolic-SAR-Wert der abgeschlossenen Kerze streng oberhalb des Kerzen-Hochs liegt.
Signale werden nur auf abgeschlossenen Kerzen ausgewertet (CandleStates.Finished), um die MQL-Neubalken-Verarbeitung zu entsprechen.
Positionsmanagement
Die entgegengesetzte Exposition wird vor einem neuen Einstieg geflacht, indem die angeforderte Market-Order-Größe um den absoluten aktuellen Positionswert erhöht wird, was die MetaTrader-Sequenz ClosePosition plus OpenPosition repliziert.
Jeder Einstieg berechnet die Schutz-Stop-Loss- und Take-Profit-Levels unter Verwendung derselben Pip-zu-Preis-Konvertierungsregeln wie MetaTrader neu (3/5-stellige Instrumente erhalten einen ×10-Multiplikator für den PriceStep).
Schutzausgänge
Bei jeder abgeschlossenen Kerze prüft die Strategie, ob das Hoch/Tief den gespeicherten Stop-Loss- oder Take-Profit-Level verletzt. Wenn ausgelöst, wird die Position mit einer Market-Order geschlossen und die entsprechenden Ziele werden gelöscht.
Die Schutzlogik feuert vor neuen Signalen auf demselben Balken und spiegelt damit das ursprüngliche Expertenberater-Verhalten wider, bei dem Stop-Orders broker-seitig liegen.
Indikator- und Datenhinweise
Verwendet den eingebauten ParabolicSar-Indikator von StockSharp mit den Parametern SarStep und SarMaximum.
Das Kerzen-Abonnement wird über SubscribeCandles abgewickelt, ohne den Indikator zu Strategy.Indicators hinzuzufügen, wie von den Projektrichtlinien gefordert.
Das Handeln ist nur erlaubt, wenn IsFormedAndOnlineAndAllowTrading() true meldet, um sicherzustellen, dass Live-Daten vorhanden sind und der Connector die Ordereinreichung erlaubt.
Parameter
Name
Standard
Beschreibung
TradeVolume
1
Market-Order-Größe in Lots. Eine Aktualisierung des Werts aktualisiert auch Strategy.Volume.
StopLossPips
50
Stop-Loss-Abstand in MetaTrader-Pips. Ein Pip entspricht PriceStep × 10 bei Instrumenten mit 3 oder 5 Dezimalstellen, sonst nur PriceStep. Auf 0 setzen zum Deaktivieren.
TakeProfitPips
50
Take-Profit-Abstand in MetaTrader-Pips unter Verwendung derselben Konvertierungsregeln wie der Stop-Loss. Auf 0 setzen zum Deaktivieren.
SarStep
0.02
Beschleunigungsschritt des Parabolic-SAR-Indikators.
SarMaximum
0.2
Maximaler Beschleunigungsfaktor für Parabolic SAR.
CandleType
30m timeframe
Kerzentyp für Berechnungen. Unterstützt jeden DataType aus TimeFrame.
Risikomanagement und Verhalten
Stop-Loss und Take-Profit werden nach jeder Ausführung neu berechnet und intern gespeichert; keine ausstehenden Orders werden an der Börse registriert.
Wenn beide Schutzlevel innerhalb einer einzigen Kerze ausgelöst werden, feuert die Stop-Loss-Prüfung zuerst und repliziert damit die konservative Behandlung der Quell-MQL-Logik.
Wenn der Connector keinen gültigen PriceStep meldet, fällt die Konvertierung auf 0.0001 zurück, um Schutzlevel mit Nullabstand zu vermeiden.
Es wird kein Averaging oder Pyramidisieren durchgeführt; die Strategie operiert mit einer einzigen Nettoposition und dreht die Richtung um, wenn der Parabolic SAR den Preis kreuzt.
Konvertierungshinweise
MetaTrader InpBarCurrent entspricht 1, was bedeutet, dass der EA die vorherige abgeschlossene Kerze auswertet. Der StockSharp-Port erzielt dasselbe Ergebnis, indem im Bind-Callback nur Finished-Kerzen verarbeitet werden.
Der ursprüngliche Expertenberater verwendete CheckVolumeValue zur Validierung von Lots und Broker-Beschränkungen. StockSharp delegiert diese Prüfungen an den Connector, während der TradeVolume-Parameter weiterhin eine positive Volumenanforderung durchsetzt.
Die Python-Implementierung ist absichtlich weggelassen, entsprechend den Aufgabenanforderungen.
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>
/// Parabolic SAR EA strategy using EMA crossover as proxy.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ParabolicSarEaStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public ParabolicSarEaStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_ea_strategy(Strategy):
def __init__(self):
super(parabolic_sar_ea_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_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(parabolic_sar_ea_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(parabolic_sar_ea_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_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
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 = 100
self._prev_fast = fast_val
self._prev_slow = slow_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 = 100
self._prev_fast = fast_val
self._prev_slow = slow_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 = 100
self._prev_fast = fast_val
self._prev_slow = slow_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 = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return parabolic_sar_ea_strategy()