Auf GitHub ansehen

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

  1. Langsame EMA steigt: aktueller Wert > vorheriger Wert > Wert vor zwei Kerzen.
  2. Schnelle LWMA steigt: aktueller Wert > vorheriger Wert > Wert vor zwei Kerzen.
  3. Vorheriger Kerzenschlusskurs liegt über dem vorherigen Wert der langsamen EMA.
  4. Aktueller Wert der langsamen EMA liegt über dem aktuellen Wert der schnellen LWMA.
  5. Aktuelle Position ist flach oder short.
  6. 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

  1. Langsame EMA fällt: aktueller Wert < vorheriger Wert < Wert vor zwei Kerzen.
  2. Schnelle LWMA fällt: aktueller Wert < vorheriger Wert < Wert vor zwei Kerzen.
  3. Vorheriger Kerzenschlusskurs liegt unter dem vorherigen Wert der langsamen EMA.
  4. Aktueller Wert der langsamen EMA liegt unter dem aktuellen Wert der schnellen LWMA.
  5. Aktuelle Position ist flach oder long.
  6. 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++;
	}
}