Ma SAR ADX Bind Strategie
Überblick
Diese Strategie ist eine Konvertierung des ursprünglichen MaSarADX.mq5 MetaTrader 5-Expertenberaters auf die StockSharp High-Level-API. Das System kombiniert einen einfachen gleitenden Durchschnitt als Trendfilter mit Directional Movement Index (ADX)-Signalen und dem Parabolic SAR Trailing Stop. Handelsentscheidungen werden nur bei abgeschlossenen Kerzen ausgewertet, was das Verhalten des "ersten Tick einer neuen Bar" aus der MQL-Version repliziert. Wenn der Kerzenschlusskurs sowohl mit dem Trendtrend des gleitenden Durchschnitts als auch mit dem gerichteten Gleichgewicht des ADX übereinstimmt, wird eine Position geöffnet. Parabolic SAR leitet sowohl Traderichtung als auch Exits, indem er eine vollständige Liquidation erzwingt, wenn der Preis auf die entgegengesetzte Seite der SAR-Punkte kreuzt.
Indikatoren und Daten
- Einfacher gleitender Durchschnitt (SMA) – liefert den primären Trendrichtungsfilter. Standardlänge: 100 Kerzen.
- Average Directional Index (ADX) – liefert +DI und −DI zur Bestätigung der Richtungsstärke. Standardlänge: 14.
- Parabolic SAR – dient als Stop-and-Reverse-Overlay und definiert Ausstiegsbedingungen. Standardbeschleunigung: 0.02; maximale Beschleunigung: 0.10.
- Kerzen – jeder Zeitrahmen kann angefordert werden. Standardmäßig abonniert die Strategie 1-Stunden-Kerzen, aber der Parameter kann angepasst werden.
Die Implementierung abonniert StockSharp-Kerzenströme und bindet alle drei Indikatoren mit dem BindEx-Helper, sodass jeder Callback synchronisierte Werte für dieselbe Kerze empfängt.
Handelsstrategie
Long-Einstieg
- Kerzenschlusskurs liegt über dem gleitenden Durchschnitt.
- +DI ist größer oder gleich −DI, was bullischen Richtungsdruck anzeigt.
- Kerzenschlusskurs liegt über dem Parabolic SAR-Wert.
- Keine Long-Position ist aktuell offen (
Position <= 0).
Wenn alle Regeln übereinstimmen, wird eine Kauf-Marktorder für das konfigurierte Basisvolumen plus Größe zur Abdeckung einer Short-Position gesendet.
Short-Einstieg
- Kerzenschlusskurs liegt unter dem gleitenden Durchschnitt.
- +DI ist kleiner oder gleich −DI, was bärischen Richtungsdruck anzeigt.
- Kerzenschlusskurs liegt unter dem Parabolic SAR-Wert.
- Keine Short-Position ist aktuell offen (
Position >= 0).
Eine Verkauf-Marktorder wird platziert, wenn alle Short-Regeln übereinstimmen.
Ausstiege
- Long-Positionen werden sofort geschlossen, wenn der Preis unter den Parabolic SAR fällt.
- Short-Positionen werden gedeckt, wenn der Preis über den Parabolic SAR steigt.
Es werden keine separaten Stop-Loss- oder Take-Profit-Level hinzugefügt; der SAR-Kreuzungspunkt ist das einzige Ausstiegssignal, dem ursprünglichen Expertenberater folgend. Da Exits vor neuen Einstiegen ausgewertet werden, wird die Strategie nicht auf derselben Kerze von Short zu Long (oder umgekehrt) wechseln.
Parameter
| Name |
Beschreibung |
Standard |
Hinweise |
MaPeriod |
Länge des einfachen gleitenden Durchschnitts zur Definition des Trendfilters. |
100 |
Optimierbar, muss größer als null sein. |
AdxPeriod |
Periode der ADX-Berechnung, die +DI und −DI erzeugt. |
14 |
Optimierbar, muss größer als null sein. |
SarStep |
Beschleunigungsfaktor und Inkrement für den Parabolic SAR. |
0.02 |
Entspricht dem MQL step-Parameter. |
SarMax |
Maximaler Beschleunigungsfaktor für Parabolic SAR. |
0.10 |
Spiegelt die MQL maximum-Einstellung wider. |
Volume |
Basis-Ordergröße für neue Einstiege. |
1 |
Ersetzt die margenbasierte Lot-Größenberechnung der MetaTrader-Version. Die tatsächliche Ordergröße ist Volume + |Position|, sodass Umkehrungen das bestehende Engagement glätten. |
CandleType |
Der über StockSharp abonnierte Kerzendatentyp. |
1 Stunde |
Auf jeden Zeitrahmen einstellbar. |
Implementierungshinweise
- Die Indikatorverarbeitung verwendet StockSharps High-Level-
BindEx-Pipeline und stellt sicher, dass SMA, ADX und SAR im Gleichschritt ohne manuelles Puffern aktualisiert werden.
- Exits werden auch dann ausgeführt, wenn
AllowTrading vorübergehend deaktiviert ist, um Risikokontrollen jederzeit aktiv zu halten.
- Grafik-Hilfstools sind enthalten: das primäre Panel zeigt Preis, SMA und SAR, während ein sekundäres Panel den ADX-Indikator für Diagnosen anzeigt.
- Log-Anweisungen beschreiben jede Handelsentscheidung mit den zugrunde liegenden Indikatorwerten zur Vereinfachung von Vorwärtstests und Debugging.
Verwendungsrichtlinien
- Hängen Sie die Strategie an ein Wertpapier und Portfolio im Designer oder Backtester.
- Passen Sie den Kerzentyp an Ihren Handelshorizont an (z.B. M15-, H1- oder D1-Kerzen).
- Passen Sie den gleitenden Durchschnittszeitraum, den ADX-Zeitraum und SAR-Parameter an die Volatilität des Instruments an.
- Setzen Sie den
Volume-Parameter auf Ihre bevorzugte Positionsgröße. Wenn Sie das adaptive Money Management aus dem ursprünglichen Skript benötigen, integrieren Sie Ihre eigene portfoliobasierte Größenbestimmung vor dem Senden von Orders.
- Führen Sie die Strategie aus. Trades werden erst ausgelöst, nachdem alle Indikatoren ausreichend historische Werte für ihre Formierung erzeugt haben.
Unterschiede zum ursprünglichen Expertenberater
- Die margenbasierte Lot-Berechnung wurde durch einen festen
Volume-Parameter ersetzt, um die Strategie broker-neutral in StockSharp zu halten.
- Trade-Verwaltung, Indikatorwerte und die Auswertungsreihenfolge (Ausstieg vor Einstieg) folgen strikt der MetaTrader-Referenzlogik.
- Alle Kommentare im Quellcode sind auf Englisch, um den Projektrichtlinien zu entsprechen.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Conversion of the MaSarADX MetaTrader strategy to StockSharp high level API.
/// Combines a moving average, ADX directional movement and Parabolic SAR for entries and exits.
/// </summary>
public class MaSarAdxBindStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _sarStep;
private readonly StrategyParam<decimal> _sarMax;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousHigh;
private decimal? _previousLow;
private decimal? _previousClose;
private decimal _smoothedPlusDm;
private decimal _smoothedMinusDm;
private decimal _smoothedTrueRange;
private int _adxSamples;
/// <summary>
/// Moving average period used for the trend filter.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Average Directional Index calculation period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Acceleration step for the Parabolic SAR indicator.
/// </summary>
public decimal SarStep
{
get => _sarStep.Value;
set => _sarStep.Value = value;
}
/// <summary>
/// Maximum acceleration factor for the Parabolic SAR indicator.
/// </summary>
public decimal SarMax
{
get => _sarMax.Value;
set => _sarMax.Value = value;
}
/// <summary>
/// Type of candles processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MaSarAdxBindStrategy"/>.
/// </summary>
public MaSarAdxBindStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 120)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Length of the trend moving average", "Indicators")
.SetOptimize(20, 200, 10);
_adxPeriod = Param(nameof(AdxPeriod), 18)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Length of the Average Directional Index", "Indicators")
.SetOptimize(7, 28, 1);
_sarStep = Param(nameof(SarStep), 0.02m)
.SetRange(0.005m, 0.2m)
.SetDisplay("SAR Step", "Acceleration step for Parabolic SAR", "Indicators")
;
_sarMax = Param(nameof(SarMax), 0.1m)
.SetRange(0.05m, 1m)
.SetDisplay("SAR Maximum", "Maximum acceleration for Parabolic SAR", "Indicators")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to request", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHigh = null;
_previousLow = null;
_previousClose = null;
_smoothedPlusDm = 0m;
_smoothedMinusDm = 0m;
_smoothedTrueRange = 0m;
_adxSamples = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Instantiate indicators used in the original MetaTrader script.
var movingAverage = new SimpleMovingAverage
{
Length = MaPeriod
};
var parabolicSar = new ParabolicSar
{
Acceleration = SarStep,
AccelerationStep = SarStep,
AccelerationMax = SarMax
};
// Subscribe to candle data and bind indicator updates to a single handler.
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(movingAverage, parabolicSar, ProcessCandle)
.Start();
// Draw the trading context for visual debugging when charts are available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, movingAverage);
DrawIndicator(area, parabolicSar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal movingAverage, decimal sar)
{
// Work only with completed candles to mirror the original first-tick logic.
if (candle.State != CandleStates.Finished)
return;
var (plusDi, minusDi, isReady) = UpdateDirectionalMovement(candle);
if (!isReady)
return;
// Always allow risk exits even if trading is temporarily disabled.
if (Position > 0 && candle.ClosePrice < sar)
{
SellMarket();
return;
}
if (Position < 0 && candle.ClosePrice > sar)
{
BuyMarket();
return;
}
// Entry conditions replicated from the MetaTrader version.
var bullishSignal = candle.ClosePrice > movingAverage && plusDi >= minusDi && candle.ClosePrice > sar;
var bearishSignal = candle.ClosePrice < movingAverage && plusDi <= minusDi && candle.ClosePrice < sar;
if (bullishSignal && Position <= 0)
{
BuyMarket();
return;
}
if (bearishSignal && Position >= 0)
{
SellMarket();
}
}
private (decimal plusDi, decimal minusDi, bool isReady) UpdateDirectionalMovement(ICandleMessage candle)
{
if (_previousHigh is not decimal previousHigh ||
_previousLow is not decimal previousLow ||
_previousClose is not decimal previousClose)
{
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_previousClose = candle.ClosePrice;
return (0m, 0m, false);
}
var upMove = candle.HighPrice - previousHigh;
var downMove = previousLow - candle.LowPrice;
var plusDm = upMove > downMove && upMove > 0m ? upMove : 0m;
var minusDm = downMove > upMove && downMove > 0m ? downMove : 0m;
var trueRange = Math.Max(
candle.HighPrice - candle.LowPrice,
Math.Max(
Math.Abs(candle.HighPrice - previousClose),
Math.Abs(candle.LowPrice - previousClose)));
if (_adxSamples < AdxPeriod)
{
_smoothedPlusDm += plusDm;
_smoothedMinusDm += minusDm;
_smoothedTrueRange += trueRange;
_adxSamples++;
}
else
{
_smoothedPlusDm = _smoothedPlusDm - (_smoothedPlusDm / AdxPeriod) + plusDm;
_smoothedMinusDm = _smoothedMinusDm - (_smoothedMinusDm / AdxPeriod) + minusDm;
_smoothedTrueRange = _smoothedTrueRange - (_smoothedTrueRange / AdxPeriod) + trueRange;
}
_previousHigh = candle.HighPrice;
_previousLow = candle.LowPrice;
_previousClose = candle.ClosePrice;
if (_adxSamples < AdxPeriod || _smoothedTrueRange <= 0m)
return (0m, 0m, false);
return (
100m * _smoothedPlusDm / _smoothedTrueRange,
100m * _smoothedMinusDm / _smoothedTrueRange,
true);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class ma_sar_adx_bind_strategy(Strategy):
def __init__(self):
super(ma_sar_adx_bind_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 120)
self._adx_period = self.Param("AdxPeriod", 18)
self._sar_step = self.Param("SarStep", 0.02)
self._sar_max = self.Param("SarMax", 0.1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def SarStep(self):
return self._sar_step.Value
@SarStep.setter
def SarStep(self, value):
self._sar_step.Value = value
@property
def SarMax(self):
return self._sar_max.Value
@SarMax.setter
def SarMax(self, value):
self._sar_max.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(ma_sar_adx_bind_strategy, self).OnStarted2(time)
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
ma = SimpleMovingAverage()
ma.Length = self.MaPeriod
sar = ParabolicSar()
sar.Acceleration = float(self.SarStep)
sar.AccelerationStep = float(self.SarStep)
sar.AccelerationMax = float(self.SarMax)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ma, sar, self.ProcessCandle).Start()
def ProcessCandle(self, candle, moving_average, sar):
if candle.State != CandleStates.Finished:
return
ma_val = float(moving_average)
sar_val = float(sar)
plus_di, minus_di, is_ready = self._update_directional_movement(candle)
if not is_ready:
return
close = float(candle.ClosePrice)
# Exit conditions
if self.Position > 0 and close < sar_val:
self.SellMarket()
return
if self.Position < 0 and close > sar_val:
self.BuyMarket()
return
# Entry conditions
bullish_signal = close > ma_val and plus_di >= minus_di and close > sar_val
bearish_signal = close < ma_val and plus_di <= minus_di and close < sar_val
if bullish_signal and self.Position <= 0:
self.BuyMarket()
return
if bearish_signal and self.Position >= 0:
self.SellMarket()
def _update_directional_movement(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._previous_high is None or self._previous_low is None or self._previous_close is None:
self._previous_high = high
self._previous_low = low
self._previous_close = close
return (0.0, 0.0, False)
up_move = high - self._previous_high
down_move = self._previous_low - low
plus_dm = up_move if (up_move > down_move and up_move > 0.0) else 0.0
minus_dm = down_move if (down_move > up_move and down_move > 0.0) else 0.0
true_range = max(high - low, max(abs(high - self._previous_close), abs(low - self._previous_close)))
adx_period = int(self.AdxPeriod)
if self._adx_samples < adx_period:
self._smoothed_plus_dm += plus_dm
self._smoothed_minus_dm += minus_dm
self._smoothed_true_range += true_range
self._adx_samples += 1
else:
self._smoothed_plus_dm = self._smoothed_plus_dm - (self._smoothed_plus_dm / adx_period) + plus_dm
self._smoothed_minus_dm = self._smoothed_minus_dm - (self._smoothed_minus_dm / adx_period) + minus_dm
self._smoothed_true_range = self._smoothed_true_range - (self._smoothed_true_range / adx_period) + true_range
self._previous_high = high
self._previous_low = low
self._previous_close = close
if self._adx_samples < adx_period or self._smoothed_true_range <= 0.0:
return (0.0, 0.0, False)
return (
100.0 * self._smoothed_plus_dm / self._smoothed_true_range,
100.0 * self._smoothed_minus_dm / self._smoothed_true_range,
True)
def OnReseted(self):
super(ma_sar_adx_bind_strategy, self).OnReseted()
self._previous_high = None
self._previous_low = None
self._previous_close = None
self._smoothed_plus_dm = 0.0
self._smoothed_minus_dm = 0.0
self._smoothed_true_range = 0.0
self._adx_samples = 0
def CreateClone(self):
return ma_sar_adx_bind_strategy()