Doppel-MA-Trendbestätigungs-Strategie
Überblick
Die Doppel-MA-Trendbestätigungs-Strategie repliziert den ursprünglichen MetaTrader-Experten, der einen langsamen exponentiellen gleitenden Durchschnitt (EMA) mit einem schnellen linear gewichteten gleitenden Durchschnitt (LWMA) kombiniert. Das System wartet, bis beide gleitenden Durchschnitte in dieselbe Richtung ausgerichtet sind, und verwendet den Schlusskurs der vorherigen Kerze als zusätzliche Bestätigung vor dem Öffnen einer Position. Die Idee ist, nur an starken Momentum-Schwankungen teilzunehmen, wenn sowohl der langsame Trendfilter als auch der schnelle Bestätigungsfilter gleichzeitig nach oben oder unten geneigt sind.
Die StockSharp-Implementierung verarbeitet nur vollständig abgeschlossene Kerzen, verfolgt die Steigung jedes gleitenden Durchschnitts über die letzten drei Bars und verwaltet automatisch Schutzorders über den integrierten StartProtection-Mechanismus. Die Strategie ist instrument-agnostisch: Sie kann auf jedem Wertpapier und Zeitrahmen operieren, der Kerzen bereitstellt und das Konzept von "Punkten" über den Instrumentenpreisschritt unterstützt.
Indikatoren
- Langsame EMA – Standardperiode 57. Stellt die dominante Trendrichtung dar. Die Strategie erfordert, dass die EMA für zwei aufeinanderfolgende Kerzen steigt (oder fällt), bevor sie handelt.
- Schnelle LWMA – Standardperiode 3. Fungiert als Momentum-Bestätigungsfilter. Ihre Neigung muss mit der langsamen EMA übereinstimmen und verstärkt, dass das Momentum den Trend unterstützt.
Parameter
| Parameter |
Standard |
Beschreibung |
SlowMaLength |
57 |
Periode des langsamen EMA-Trendfilters. |
FastMaLength |
3 |
Periode des schnellen LWMA-Bestätigungsfilters. |
StopLossPoints |
100 |
Schutz-Stop-Abstand in Instrumentenpunkten (multipliziert mit Security.PriceStep). |
TakeProfitPoints |
100 |
Take-Profit-Abstand in Instrumentenpunkten (multipliziert mit Security.PriceStep). |
CandleType |
15-Minuten-Zeitrahmen |
Kerzendatentyp für alle Berechnungen. |
Alle Parameter werden als StrategyParam<T>-Werte exponiert, sodass sie zur Laufzeit geändert oder durch StockSharps Optimierungstools optimiert werden können.
Handelsregeln
Long-Setup
- Langsame EMA steigt: aktueller Wert > vorheriger Wert > Wert vor zwei Kerzen.
- Schnelle LWMA steigt: aktueller Wert > vorheriger Wert > Wert vor zwei Kerzen.
- Vorheriger Kerzenschlusskurs liegt über dem vorherigen Wert der langsamen EMA.
- Aktueller Wert der langsamen EMA liegt über dem aktuellen Wert der schnellen LWMA.
- Aktuelle Position ist flach oder short.
- Wenn alle Bedingungen erfüllt sind, sendet die Strategie eine Kauf-Marktorder für
Volume + |Position|, um in eine Long-Position zu wechseln.
Short-Setup
- Langsame EMA fällt: aktueller Wert < vorheriger Wert < Wert vor zwei Kerzen.
- Schnelle LWMA fällt: aktueller Wert < vorheriger Wert < Wert vor zwei Kerzen.
- Vorheriger Kerzenschlusskurs liegt unter dem vorherigen Wert der langsamen EMA.
- Aktueller Wert der langsamen EMA liegt unter dem aktuellen Wert der schnellen LWMA.
- Aktuelle Position ist flach oder long.
- Wenn alle Bedingungen erfüllt sind, sendet die Strategie eine Verkauf-Marktorder für
Volume + |Position|, um in eine Short-Position zu wechseln.
Schutzlogik
StartProtection konvertiert StopLossPoints und TakeProfitPoints in absolute Preisoffsets durch Multiplikation mit Security.PriceStep. Stop-Loss- und Take-Profit-Orders werden als Marktausstiege ausgegeben, damit die Engine die Position schließen kann, auch wenn Limit-Orders nicht unterstützt werden.
- Wenn das entgegengesetzte Signal erscheint, kehrt die Strategie sofort die Position um, unabhängig von den Schutzorders.
Implementierungsdetails
- Nur fertige Kerzen werden verarbeitet, was die Neuen-Bar-Prüfung der ursprünglichen MQL-Version emuliert.
- Die Strategie hält die letzten zwei gleitenden Durchschnittswerte und den vorherigen Schlusskurs in privaten Feldern, um Indikatorverlaufssuchen zu vermeiden.
IsFormedAndOnlineAndAllowTrading() stellt sicher, dass der Handel nur stattfindet, wenn alle Datenströme aktiv und der Handel erlaubt ist.
- Trade-Richtungslogs (
LogInfo) sorgen für Transparenz beim Debugging und Live-Monitoring.
- Chart-Rendering (falls verfügbar) zeichnet Kerzen und beide gleitenden Durchschnitte zur schnellen visuellen Validierung.
Verwendungshinweise
- Wählen Sie
Volume entsprechend der Instrument-Lotgröße. Die Strategie sendet immer Marktorders der Größe Volume + |Position|, um effizient umzukehren.
- Bei Instrumenten ohne definierten
PriceStep greift der Code auf einen Wert von 1 zurück. Passen Sie Parameter entsprechend an, wenn die Tick-Größe abweicht.
- Die Optimierung kann sich auf die gleitenden Durchschnittszeiträume und die Schutzabstände konzentrieren, um die Strategie an verschiedene Märkte anzupassen.
- Kombinieren Sie mit zusätzlichen Filtern (Volatilität, Session-Zeiten usw.) falls erforderlich. Die modulare Struktur ermöglicht einfache Erweiterung.
Empfohlene Optimierungsbereiche
SlowMaLength: 20 – 120 mit Schritt 5–10.
FastMaLength: 2 – 10 mit Schritt 1.
StopLossPoints / TakeProfitPoints: 50 – 200 je nach Instrumentenvolatilität.
Diese Bereiche spiegeln die ursprünglichen Experteneinstellungen genau wider und bieten Flexibilität für andere Instrumente.
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Dual moving average trend confirmation strategy.
/// Uses a slow EMA and a fast LWMA to detect synchronized trends.
/// Enters long when both averages slope upward, price stays above the slow EMA, and the slow EMA is above the fast LWMA.
/// Enters short when both averages slope downward, price stays below the slow EMA, and the slow EMA is below the fast LWMA.
/// Built-in stop-loss and take-profit are defined in instrument points.
/// </summary>
public class DualMaTrendConfirmationStrategy : Strategy
{
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _previousClose;
private decimal _slowPrevious;
private decimal _slowPrevious2;
private decimal _fastPrevious;
private decimal _fastPrevious2;
private int _historyCount;
/// <summary>
/// Slow EMA period length.
/// </summary>
public int SlowMaLength
{
get => _slowMaLength.Value;
set => _slowMaLength.Value = value;
}
/// <summary>
/// Fast LWMA period length.
/// </summary>
public int FastMaLength
{
get => _fastMaLength.Value;
set => _fastMaLength.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in instrument points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in instrument points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="DualMaTrendConfirmationStrategy"/> class.
/// </summary>
public DualMaTrendConfirmationStrategy()
{
_slowMaLength = Param(nameof(SlowMaLength), 57)
.SetDisplay("Slow EMA Length", "Period for the slow EMA trend filter", "Moving Averages")
.SetRange(10, 200)
;
_fastMaLength = Param(nameof(FastMaLength), 3)
.SetDisplay("Fast LWMA Length", "Period for the fast LWMA confirmation filter", "Moving Averages")
.SetRange(1, 50)
;
_stopLossPoints = Param(nameof(StopLossPoints), 100m)
.SetDisplay("Stop Loss (points)", "Stop-loss distance measured in instrument points", "Risk Management")
.SetRange(10m, 500m)
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m)
.SetDisplay("Take Profit (points)", "Take-profit distance measured in instrument points", "Risk Management")
.SetRange(10m, 500m)
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for moving average calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Clear stored history so the next candle starts with a clean state.
_previousClose = 0m;
_slowPrevious = 0m;
_slowPrevious2 = 0m;
_fastPrevious = 0m;
_fastPrevious2 = 0m;
_historyCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var slowEma = new ExponentialMovingAverage
{
Length = SlowMaLength
};
var fastLwma = new WeightedMovingAverage
{
Length = FastMaLength
};
var subscription = SubscribeCandles(CandleType);
var step = Security.PriceStep ?? 1m;
// Enable automatic stop-loss and take-profit management based on point offsets.
StartProtection(
takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute),
useMarketOrders: true);
subscription
.Bind(slowEma, fastLwma, (candle, slowValue, fastValue) => ProcessCandle(candle, slowValue, fastValue, slowEma, fastLwma))
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, slowEma);
DrawIndicator(area, fastLwma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal slowValue, decimal fastValue, ExponentialMovingAverage slowEma, WeightedMovingAverage fastLwma)
{
// Work only with fully formed candles to avoid premature decisions.
if (candle.State != CandleStates.Finished)
return;
// Ensure both indicators produced reliable values before trading logic.
if (!slowEma.IsFormed || !fastLwma.IsFormed)
{
UpdateHistory(slowValue, fastValue, candle.ClosePrice);
return;
}
// Accumulate at least two previous candles for slope calculations.
if (_historyCount < 2)
{
UpdateHistory(slowValue, fastValue, candle.ClosePrice);
return;
}
var slowRising = slowValue > _slowPrevious && _slowPrevious > _slowPrevious2;
var fastRising = fastValue > _fastPrevious && _fastPrevious > _fastPrevious2;
var slowFalling = slowValue < _slowPrevious && _slowPrevious < _slowPrevious2;
var fastFalling = fastValue < _fastPrevious && _fastPrevious < _fastPrevious2;
var priceAboveSlow = _previousClose > _slowPrevious;
var priceBelowSlow = _previousClose < _slowPrevious;
var slowAboveFast = slowValue > fastValue;
var slowBelowFast = slowValue < fastValue;
if (slowRising && fastRising && priceAboveSlow && slowAboveFast && Position <= 0)
{
BuyMarket();
}
else if (slowFalling && fastFalling && priceBelowSlow && slowBelowFast && Position >= 0)
{
SellMarket();
}
UpdateHistory(slowValue, fastValue, candle.ClosePrice);
}
private void UpdateHistory(decimal slowValue, decimal fastValue, decimal closePrice)
{
// Shift previous values so the last two candles are always available.
_slowPrevious2 = _slowPrevious;
_slowPrevious = slowValue;
_fastPrevious2 = _fastPrevious;
_fastPrevious = fastValue;
_previousClose = closePrice;
if (_historyCount < 2)
_historyCount++;
}
}
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 ExponentialMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class dual_ma_trend_confirmation_strategy(Strategy):
def __init__(self):
super(dual_ma_trend_confirmation_strategy, self).__init__()
self._slow_ma_length = self.Param("SlowMaLength", 57)
self._fast_ma_length = self.Param("FastMaLength", 3)
self._stop_loss_points = self.Param("StopLossPoints", 100.0)
self._take_profit_points = self.Param("TakeProfitPoints", 100.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._previous_close = 0.0
self._slow_previous = 0.0
self._slow_previous2 = 0.0
self._fast_previous = 0.0
self._fast_previous2 = 0.0
self._history_count = 0
@property
def SlowMaLength(self):
return self._slow_ma_length.Value
@SlowMaLength.setter
def SlowMaLength(self, value):
self._slow_ma_length.Value = value
@property
def FastMaLength(self):
return self._fast_ma_length.Value
@FastMaLength.setter
def FastMaLength(self, value):
self._fast_ma_length.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.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(dual_ma_trend_confirmation_strategy, self).OnStarted2(time)
self._previous_close = 0.0
self._slow_previous = 0.0
self._slow_previous2 = 0.0
self._fast_previous = 0.0
self._fast_previous2 = 0.0
self._history_count = 0
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowMaLength
self._fast_lwma = WeightedMovingAverage()
self._fast_lwma.Length = self.FastMaLength
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
self.StartProtection(
Unit(float(self.TakeProfitPoints) * step, UnitTypes.Absolute),
Unit(float(self.StopLossPoints) * step, UnitTypes.Absolute),
False, None, None, True)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._slow_ema, self._fast_lwma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, slow_value, fast_value):
if candle.State != CandleStates.Finished:
return
sv = float(slow_value)
fv = float(fast_value)
close = float(candle.ClosePrice)
if not self._slow_ema.IsFormed or not self._fast_lwma.IsFormed:
self._update_history(sv, fv, close)
return
if self._history_count < 2:
self._update_history(sv, fv, close)
return
slow_rising = sv > self._slow_previous and self._slow_previous > self._slow_previous2
fast_rising = fv > self._fast_previous and self._fast_previous > self._fast_previous2
slow_falling = sv < self._slow_previous and self._slow_previous < self._slow_previous2
fast_falling = fv < self._fast_previous and self._fast_previous < self._fast_previous2
price_above_slow = self._previous_close > self._slow_previous
price_below_slow = self._previous_close < self._slow_previous
slow_above_fast = sv > fv
slow_below_fast = sv < fv
if slow_rising and fast_rising and price_above_slow and slow_above_fast and self.Position <= 0:
self.BuyMarket()
elif slow_falling and fast_falling and price_below_slow and slow_below_fast and self.Position >= 0:
self.SellMarket()
self._update_history(sv, fv, close)
def _update_history(self, slow_value, fast_value, close):
self._slow_previous2 = self._slow_previous
self._slow_previous = slow_value
self._fast_previous2 = self._fast_previous
self._fast_previous = fast_value
self._previous_close = close
if self._history_count < 2:
self._history_count += 1
def OnReseted(self):
super(dual_ma_trend_confirmation_strategy, self).OnReseted()
self._previous_close = 0.0
self._slow_previous = 0.0
self._slow_previous2 = 0.0
self._fast_previous = 0.0
self._fast_previous2 = 0.0
self._history_count = 0
def CreateClone(self):
return dual_ma_trend_confirmation_strategy()