Die ADX Expert Strategie ist eine direkte Konvertierung des ursprünglichen MetaTrader 4 Expert Advisors "ADX Expert" (MQL-Skript 20315). Der Expert sucht nach Kreuzungen zwischen den positiven und negativen Directional Index (DI)-Linien, während der Average Directional Index (ADX) unter einem angegebenen Schwellenwert bleibt, was darauf hinweist, dass der Markt seitwärts tendiert. Es kann jeweils nur eine Position offen sein, genau wie im Quell-Expert.
Handelslogik
Die Strategie abonniert die ausgewählte Kerzenreihe (standardmäßig 15-Minuten-Kerzen) und berechnet den Average Directional Index mit dem konfigurierten Zeitraum.
Eine Kauforder wird platziert, wenn:
Die +DI-Linie über die -DI-Linie kreuzt.
Der ADX-Wert unter dem definierten Schwellenwert (Standard 20) bleibt und einen schwachen Trend signalisiert.
Der aktuelle Spread unter dem MaxSpreadPoints-Filter liegt.
Derzeit keine Position offen ist.
Eine Verkaufsorder wird platziert, wenn:
Die +DI-Linie unter die -DI-Linie kreuzt.
Der ADX-Wert noch unter dem zulässigen Schwellenwert liegt.
Die Spread-Anforderung und die Flat-Position-Bedingung erfüllt sind.
Schutz-Stop-Loss- und Take-Profit-Niveaus werden über StartProtection zugewiesen und spiegeln den festen Stop und das Ziel aus der MQL-Version wider. Sie werden in Preispunkten (Preisschritten) ausgedrückt und können durch Setzen der Werte auf null deaktiviert werden.
Die Strategie basiert auf einem Einzelpositions-Workflow: Neue Signale werden ignoriert, bis die aktuelle Position durch ihre Schutzorders geschlossen wird.
Parameter
Parameter
Beschreibung
Standard
TradeVolume
Ordergröße für jede Marktorder.
0.1
AdxPeriod
Periode für die ADX-Berechnung.
14
AdxThreshold
Maximaler ADX-Wert, der noch einen Trade erlaubt.
20
MaxSpreadPoints
Maximal erlaubter Spread in Preispunkten. Auf 0 setzen, um den Filter zu deaktivieren.
20
StopLossPoints
Stop-Loss-Abstand in Preispunkten.
200
TakeProfitPoints
Take-Profit-Abstand in Preispunkten.
400
CandleType
Kerzentyp für Indikatorberechnungen (standardmäßig 15-Minuten-Kerzen).
15-Minuten-Zeitrahmen
Zusätzliche Hinweise
Der Spread-Filter erfordert Orderbuch-Updates zum Lesen der besten Geld- und Briefkurse. Stellen Sie sicher, dass Ihr Datenanbieter diese Informationen liefert.
Alle Kommentare und Protokolle sind aus Gründen der Klarheit auf Englisch verfasst und entsprechen den Repository-Richtlinien.
Die Strategie ist für Bildungszwecke gedacht. Testen Sie sie gründlich in einer simulierten Umgebung, bevor Sie sie im Live-Handel einsetzen.
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>
/// ADX crossover strategy translated from the original MQL expert.
/// Opens a single position when DI lines cross while ADX remains weak.
/// </summary>
public class AdxExpertStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _maxSpreadPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private AverageDirectionalIndex _adx = null!;
private decimal _previousPlusDi;
private decimal _previousMinusDi;
private bool _hasPreviousDi;
/// <summary>
/// Trading volume for every market order.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// ADX calculation period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Maximum ADX level that still allows new trades.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Maximum allowed bid-ask spread measured in price points.
/// </summary>
public decimal MaxSpreadPoints
{
get => _maxSpreadPoints.Value;
set => _maxSpreadPoints.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="AdxExpertStrategy"/>.
/// </summary>
public AdxExpertStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Trade volume", "Order volume used for entries", "Risk management")
.SetOptimize(0.1m, 1m, 0.1m);
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX period", "Smoothing length for the ADX indicator", "Indicators")
.SetOptimize(7, 28, 7);
_adxThreshold = Param(nameof(AdxThreshold), 20m)
.SetGreaterThanZero()
.SetDisplay("ADX threshold", "Upper ADX limit that allows trades", "Signals")
.SetOptimize(15m, 35m, 5m);
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 20m)
.SetNotNegative()
.SetDisplay("Max spread (points)", "Maximum allowed bid-ask spread in points", "Risk management")
.SetOptimize(5m, 40m, 5m);
_stopLossPoints = Param(nameof(StopLossPoints), 200m)
.SetNotNegative()
.SetDisplay("Stop loss (points)", "Protective stop distance in price points", "Risk management")
.SetOptimize(100m, 400m, 50m);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400m)
.SetNotNegative()
.SetDisplay("Take profit (points)", "Target distance in price points", "Risk management")
.SetOptimize(200m, 600m, 100m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle type", "Type of candles used for ADX", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousPlusDi = 0m;
_previousMinusDi = 0m;
_hasPreviousDi = false;
_entryPrice = 0m;
}
private decimal _entryPrice;
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
IIndicatorValue adxResult;
try
{
adxResult = _adx.Process(candle);
}
catch (IndexOutOfRangeException)
{
return;
}
if (adxResult.IsEmpty || !_adx.IsFormed)
return;
if (adxResult is not AverageDirectionalIndexValue adxData)
return;
var plusDi = adxData.Dx.Plus ?? 0m;
var minusDi = adxData.Dx.Minus ?? 0m;
if (adxData.MovingAverage is not decimal currentAdx)
{
_previousPlusDi = plusDi;
_previousMinusDi = minusDi;
_hasPreviousDi = true;
return;
}
if (!_hasPreviousDi)
{
_previousPlusDi = plusDi;
_previousMinusDi = minusDi;
_hasPreviousDi = true;
return;
}
// Manage open position SL/TP
if (Position != 0)
{
var step = Security?.PriceStep ?? 1m;
if (Position > 0)
{
if (StopLossPoints > 0m && candle.LowPrice <= _entryPrice - StopLossPoints * step)
{
SellMarket(Position);
goto updateDi;
}
if (TakeProfitPoints > 0m && candle.HighPrice >= _entryPrice + TakeProfitPoints * step)
{
SellMarket(Position);
goto updateDi;
}
}
else
{
var vol = Math.Abs(Position);
if (StopLossPoints > 0m && candle.HighPrice >= _entryPrice + StopLossPoints * step)
{
BuyMarket(vol);
goto updateDi;
}
if (TakeProfitPoints > 0m && candle.LowPrice <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket(vol);
goto updateDi;
}
}
}
var bullishCross = _previousPlusDi <= _previousMinusDi && plusDi > minusDi;
var bearishCross = _previousPlusDi >= _previousMinusDi && plusDi < minusDi;
if (currentAdx < AdxThreshold && Position == 0)
{
if (bullishCross)
{
BuyMarket(TradeVolume);
_entryPrice = candle.ClosePrice;
}
else if (bearishCross)
{
SellMarket(TradeVolume);
_entryPrice = candle.ClosePrice;
}
}
updateDi:
_previousPlusDi = plusDi;
_previousMinusDi = minusDi;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import AverageDirectionalIndex, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan
class adx_expert_strategy(Strategy):
def __init__(self):
super(adx_expert_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 0.1)
self._adx_period = self.Param("AdxPeriod", 14)
self._adx_threshold = self.Param("AdxThreshold", 20.0)
self._stop_loss_points = self.Param("StopLossPoints", 200.0)
self._take_profit_points = self.Param("TakeProfitPoints", 400.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._adx = None
self._prev_plus_di = 0.0
self._prev_minus_di = 0.0
self._has_prev_di = False
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(adx_expert_strategy, self).OnStarted2(time)
self._adx = AverageDirectionalIndex()
self._adx.Length = self._adx_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
try:
civ = CandleIndicatorValue(self._adx, candle)
civ.IsFinal = True
adx_result = self._adx.Process(civ)
except Exception:
return
if adx_result.IsEmpty or not self._adx.IsFormed:
return
plus_di = float(adx_result.Dx.Plus) if adx_result.Dx.Plus is not None else 0.0
minus_di = float(adx_result.Dx.Minus) if adx_result.Dx.Minus is not None else 0.0
current_adx_val = adx_result.MovingAverage
if current_adx_val is None:
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
self._has_prev_di = True
return
current_adx = float(current_adx_val)
if not self._has_prev_di:
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
self._has_prev_di = True
return
# Manage SL/TP
if self.Position != 0:
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:
if self._stop_loss_points.Value > 0 and float(candle.LowPrice) <= self._entry_price - self._stop_loss_points.Value * step:
self.SellMarket(self.Position)
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
if self._take_profit_points.Value > 0 and float(candle.HighPrice) >= self._entry_price + self._take_profit_points.Value * step:
self.SellMarket(self.Position)
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
else:
vol = abs(self.Position)
if self._stop_loss_points.Value > 0 and float(candle.HighPrice) >= self._entry_price + self._stop_loss_points.Value * step:
self.BuyMarket(vol)
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
if self._take_profit_points.Value > 0 and float(candle.LowPrice) <= self._entry_price - self._take_profit_points.Value * step:
self.BuyMarket(vol)
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
bullish_cross = self._prev_plus_di <= self._prev_minus_di and plus_di > minus_di
bearish_cross = self._prev_plus_di >= self._prev_minus_di and plus_di < minus_di
if current_adx < self._adx_threshold.Value and self.Position == 0:
if bullish_cross:
self.BuyMarket(self._trade_volume.Value)
self._entry_price = float(candle.ClosePrice)
elif bearish_cross:
self.SellMarket(self._trade_volume.Value)
self._entry_price = float(candle.ClosePrice)
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
def OnReseted(self):
super(adx_expert_strategy, self).OnReseted()
self._prev_plus_di = 0.0
self._prev_minus_di = 0.0
self._has_prev_di = False
self._entry_price = 0.0
def CreateClone(self):
return adx_expert_strategy()