Strategy Tester Beispiel-Strategie
Dieses Beispiel veranschaulicht, wie Momentum und Trendstärke kombiniert werden können, um ein einfaches diskretionäres System zu bilden. Eine lineare Regressionsteigung misst das kurzfristige Momentum, während der Average Directional Index die Persistenz einer Bewegung bewertet. Zwei unabhängige Regeln lösen Einstiege aus: ein Momentum-Pivot begleitet von einem ADX-Rückgang, oder ein neues ADX-Hoch mit Momentum, das sich aus negativen Werten nach oben dreht.
Die Strategie ist bewusst einfach gehalten und konzentriert sich auf Long-Positionen. Sie dient als Vorlage zum Testen von Ideen wie ATR-basierten Risikoniveaus und optionalen Ausstiegskontrollen. Entwickler können die Ausstiegslogik erweitern oder Stop-Loss- Handling hinzufügen, um daraus ein vollständiges Trading-Modell zu machen.
Details
- Einstiegskriterien:
- Momentum-Pivot-Hoch und ADX im Rückgang.
- ADX-Pivot-Hoch mit Momentum, das von unterhalb null ansteigt.
- Long/Short: Standardmäßig nur Long.
- Ausstiegskriterien:
- Momentum-Pivot-Hoch (wenn Momentum-Ausstieg aktiviert ist).
- Platzhalter für benutzerdefinierten Strategieausstieg.
- Stops: Keine; ATR-Werte sind für externe Verwendung verfügbar.
- Standardwerte:
- Momentum-Länge = 20, DI-Länge = 14.
- ADX-Schlüsselniveau = 25, ATR-Länge = 14.
- Filter:
- Kategorie: Momentum
- Richtung: Long
- Indikatoren: Lineare Regression, ADX, ATR
- Stops: Nein
- Komplexität: Niedrig
- Zeitrahmen: Kurz/mittel
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Ja (Momentum-Pivots)
- Risikolevel: Mittel
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// ADX Tester Strategy.
/// Combines momentum (EMA slope) and ADX for entry signals.
/// Buys when ADX is above key level and DI+ > DI- with rising momentum.
/// Sells when ADX is above key level and DI- > DI+ with falling momentum.
/// </summary>
public class AdxTesterStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _adxLength;
private readonly StrategyParam<int> _adxKeyLevel;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _cooldownBars;
private AverageDirectionalIndex _adx;
private ExponentialMovingAverage _ema;
private decimal _prevEma;
private decimal _prevPrevEma;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AdxLength
{
get => _adxLength.Value;
set => _adxLength.Value = value;
}
public int AdxKeyLevel
{
get => _adxKeyLevel.Value;
set => _adxKeyLevel.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public AdxTesterStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_adxLength = Param(nameof(AdxLength), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Length", "ADX/DI period", "ADX");
_adxKeyLevel = Param(nameof(AdxKeyLevel), 20)
.SetDisplay("ADX Key Level", "Minimum ADX level for trending", "ADX");
_emaLength = Param(nameof(EmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Momentum EMA period", "Momentum");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adx = null;
_ema = null;
_prevEma = 0;
_prevPrevEma = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_adx = new AverageDirectionalIndex { Length = AdxLength };
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_adx, _ema, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_adx.IsFormed || !_ema.IsFormed)
return;
if (adxValue.IsEmpty || emaValue.IsEmpty)
return;
var emaVal = emaValue.ToDecimal();
var adxTyped = (AverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adxVal)
{
_prevPrevEma = _prevEma;
_prevEma = emaVal;
return;
}
// Get DI+/DI- from the Dx (DirectionalIndex) sub-indicator
var dxValue = adxTyped.Dx;
decimal? diPlus = dxValue?.Plus;
decimal? diMinus = dxValue?.Minus;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevPrevEma = _prevEma;
_prevEma = emaVal;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevPrevEma = _prevEma;
_prevEma = emaVal;
return;
}
if (_prevEma == 0 || _prevPrevEma == 0)
{
_prevPrevEma = _prevEma;
_prevEma = emaVal;
return;
}
// Momentum: EMA rising or falling
var momentumRising = emaVal > _prevEma && _prevEma > _prevPrevEma;
var momentumFalling = emaVal < _prevEma && _prevEma < _prevPrevEma;
// Strong trend
var strongTrend = adxVal > AdxKeyLevel;
// Buy: ADX above key level + momentum rising + (optionally DI+ > DI-)
var bullish = strongTrend && momentumRising;
if (diPlus.HasValue && diMinus.HasValue)
bullish = bullish && diPlus.Value > diMinus.Value;
// Sell: ADX above key level + momentum falling + (optionally DI- > DI+)
var bearish = strongTrend && momentumFalling;
if (diPlus.HasValue && diMinus.HasValue)
bearish = bearish && diMinus.Value > diPlus.Value;
if (bullish && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
else if (bearish && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: momentum turns negative
else if (Position > 0 && emaVal < _prevEma)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: momentum turns positive
else if (Position < 0 && emaVal > _prevEma)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevPrevEma = _prevEma;
_prevEma = emaVal;
}
}
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
from StockSharp.Algo.Indicators import AverageDirectionalIndex, ExponentialMovingAverage, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class adx_tester_strategy(Strategy):
"""ADX Tester Strategy."""
def __init__(self):
super(adx_tester_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._adx_length = self.Param("AdxLength", 14) \
.SetDisplay("ADX Length", "ADX/DI period", "ADX")
self._adx_key_level = self.Param("AdxKeyLevel", 20) \
.SetDisplay("ADX Key Level", "Minimum ADX level for trending", "ADX")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Momentum EMA period", "Momentum")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._adx = None
self._ema = None
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_tester_strategy, self).OnReseted()
self._adx = None
self._ema = None
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adx_tester_strategy, self).OnStarted2(time)
self._adx = AverageDirectionalIndex()
self._adx.Length = int(self._adx_length.Value)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._adx, self._ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, adx_value, ema_value):
if candle.State != CandleStates.Finished:
return
if not self._adx.IsFormed or not self._ema.IsFormed:
return
if adx_value.IsEmpty or ema_value.IsEmpty:
return
ema_val = float(IndicatorHelper.ToDecimal(ema_value))
if adx_value.MovingAverage is None:
self._prev_prev_ema = self._prev_ema
self._prev_ema = ema_val
return
adx_val = float(adx_value.MovingAverage)
dx_value = adx_value.Dx
di_plus = None
di_minus = None
if dx_value is not None:
di_plus = dx_value.Plus
di_minus = dx_value.Minus
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_prev_ema = self._prev_ema
self._prev_ema = ema_val
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_prev_ema = self._prev_ema
self._prev_ema = ema_val
return
if self._prev_ema == 0.0 or self._prev_prev_ema == 0.0:
self._prev_prev_ema = self._prev_ema
self._prev_ema = ema_val
return
cooldown = int(self._cooldown_bars.Value)
key_level = int(self._adx_key_level.Value)
momentum_rising = ema_val > self._prev_ema and self._prev_ema > self._prev_prev_ema
momentum_falling = ema_val < self._prev_ema and self._prev_ema < self._prev_prev_ema
strong_trend = adx_val > key_level
bullish = strong_trend and momentum_rising
if di_plus is not None and di_minus is not None:
bullish = bullish and float(di_plus) > float(di_minus)
bearish = strong_trend and momentum_falling
if di_plus is not None and di_minus is not None:
bearish = bearish and float(di_minus) > float(di_plus)
if bullish and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif bearish and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and ema_val < self._prev_ema:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and ema_val > self._prev_ema:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_prev_ema = self._prev_ema
self._prev_ema = ema_val
def CreateClone(self):
return adx_tester_strategy()