Auf GitHub ansehen

MACD Beispiel für eine klassische Strategie

Die Strategie reproduziert den Expert Advisor „MetaTrader 4 „MACD Sample““ unter Verwendung des übergeordneten API von StockSharp. Es handelt mit einem einzigen Instrument in beide Richtungen und spiegelt die ursprüngliche Logik wider: Trades werden ausgeführt, wenn die MACD-Linie ihre Signallinie auf der richtigen Seite von Null kreuzt, während ein Trend EMA die Richtung bestätigt. Schutzaufträge werden mit optionalen Trailing Stops in den integrierten Risikomanager von StockSharp umgewandelt.

Handelslogik

  1. Warten Sie, bis mindestens 100 fertige Kerzen vorhanden sind, damit MACD und EMA genügend Verlauf enthalten.
  2. Berechnen Sie einen Standard MACD (12, 26, 9) zusammen mit seiner Signallinie und einem exponentiellen gleitenden Durchschnitt mit 26 Perioden, der als Richtungsfilter fungiert.
  3. Lange Eingabe – nur zulässig, wenn keine Position vorhanden ist. Der MACD muss unter Null liegen, aber die Signallinie überschreiten, der vorherige MACD-Wert lag unter seinem Signal, der absolute MACD-Wert überschreitet den konfigurierbaren MacdOpenLevel-Schwellenwert (in Preispunkten) und der Trend EMA ist steigend.
  4. Kurzer Einstieg – der symmetrische Aufbau: MACD über dem Nulldurchgang unter seinem Signal, der vorherige MACD lag über dem Signal, der aktuelle Wert überschreitet den Schwellenwert MacdOpenLevel und der Trend EMA ist fallend.
  5. Langer Ausstieg – wenn MACD das Signal auf der positiven Seite von Null wieder unterschreitet und der Wert über MacdCloseLevel liegt. Die Position kann auch früher durch den von StartProtection verwalteten Trailing Stop oder Take-Profit geschlossen werden.
  6. Kurzer Ausgang – wenn MACD das Signal auf der negativen Seite wieder kreuzt und der absolute MACD-Wert MacdCloseLevel überschreitet, oder durch die Schutzmodule.

Die Strategie hält nie mehr als eine Position gleichzeitig. Jeder Eintrag verwendet Marktaufträge, deren Größe durch die Eigenschaft Volume bestimmt wird. Die Schutzlogik basiert auf dem Risikocontroller von StockSharp, sodass Take-Profit-Abstände und Trailing-Stops mit der Tick-Größe des Instruments synchronisiert bleiben.

Parameter

Name Beschreibung Standard Notizen
FastEmaPeriod Schneller Zeitraum von EMA, der von MACD verwendet wird. 12 Optimierbarer Bereich 6…18.
SlowEmaPeriod Langsamer EMA Zeitraum, der von MACD verwendet wird. 26 Optimierbarer Bereich 20…32.
SignalPeriod Signalisieren Sie einen Zeitraum von EMA innerhalb von MACD. 9 Optimierbarer Bereich 5…13.
TrendMaPeriod EMA Länge für den Richtungsfilter. 26 Optimierbarer Bereich 20…40.
MacdOpenLevel Eintrittsschwelle ausgedrückt in MACD Punkten (Preisschritten). 3 Entspricht MACDOpenLevel im MT4-Code.
MacdCloseLevel Ausstiegsschwelle, ausgedrückt in MACD Punkten. 2 Entspricht MACDCloseLevel.
TakeProfitPoints Nehmen Sie Gewinne in Preispunkten mit (multipliziert mit der Tick-Größe des Instruments). 50 Auf 0 setzen, um Take-Profit zu deaktivieren.
TrailingStopPoints Trailing Stop in Preispunkten. 30 Auf 0 setzen, um den Trailing Stop zu deaktivieren.
CandleType Kerzenserie, die für Indikatoraktualisierungen verwendet wird. Zeitrahmen von 5 Minuten Unterstützt jeden Kerzentyp StockSharp.

Implementierungshinweise

  • Die Indikatoren MACD und EMA sind über BindEx/Bind an das Kerzenabonnement gebunden, sodass StockSharp gebrauchsfertige Werte ohne manuelles Zwischenspeichern einspeisen kann.
  • Positionen werden nur geöffnet, wenn die Plattform IsFormedAndOnlineAndAllowTrading() meldet, wodurch Trades verhindert werden, während historische Daten noch geladen werden oder die Verbindung offline ist.
  • Alle Schwellenwerte, die sich auf „Punkte“ beziehen, werden automatisch anhand der Preisstufe des Instruments skaliert und ahmen die Point-Konstante von MetaTrader nach.
  • StartProtection wandelt den festen Take-Profit und Trailing Stop von MetaTrader in börsenseitige Schutzaufträge um. Aktivieren oder deaktivieren Sie jedes Modul, indem Sie den entsprechenden Parameter ändern.
  • Eine umfassende Protokollierung (LogInfo) dokumentiert jede Handelsentscheidung und vereinfacht so den Vergleich mit dem ursprünglichen Expertenberater während der Migrationsvalidierung.

Nutzungstipps

  • Das Original EA zielt auf Forex-Majors in Intraday-Zeiträumen ab. Beginnen Sie mit ähnlichen Symbolen und passen Sie die Parameter an, wenn das Instrument eine andere Tick-Größe verwendet.
  • Stellen Sie beim Testen von Symbolen mit exotischen Tick-Werten sicher, dass Security.PriceStep konfiguriert ist. andernfalls wird der Standardwert 1.0 verwendet.
  • Kombinieren Sie es mit den Portfolioschutzfunktionen von StockSharp, wenn Sie eine Geldverwaltung auf Kontoebene über die Stopps pro Position hinaus benötigen.

Schlagworte

  • Trendfolge
  • Schwung
  • MACD Crossover
  • Intraday (Standard 5 Minuten)
  • Trailing Stop + Take Profit
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>
/// MACD strategy that replicates the original MetaTrader "MACD Sample" expert advisor behaviour.
/// </summary>
public class MacdSampleClassicStrategy : Strategy
{

	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _trendMaPeriod;
	private readonly StrategyParam<decimal> _macdOpenLevel;
	private readonly StrategyParam<decimal> _macdCloseLevel;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _minimumHistoryCandles;

	private decimal _pointSize;
	private decimal? _prevMacd;
	private decimal? _prevSignal;
	private decimal? _trendMaCurrent;
	private decimal? _trendMaPrevious;
	private int _finishedCandles;
	private DateTimeOffset? _lastProcessedTime;

	/// <summary>
	/// Fast EMA period for the MACD indicator.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period for the MACD indicator.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	/// <summary>
	/// Signal line period for the MACD indicator.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Period of the trend EMA used as direction filter.
	/// </summary>
	public int TrendMaPeriod
	{
		get => _trendMaPeriod.Value;
		set => _trendMaPeriod.Value = value;
	}

	/// <summary>
	/// Threshold for MACD entries expressed in points (price steps).
	/// </summary>
	public decimal MacdOpenLevel
	{
		get => _macdOpenLevel.Value;
		set => _macdOpenLevel.Value = value;
	}

	/// <summary>
	/// Threshold for MACD exits expressed in points (price steps).
	/// </summary>
	public decimal MacdCloseLevel
	{
		get => _macdCloseLevel.Value;
		set => _macdCloseLevel.Value = value;
	}

	/// <summary>
	/// Take profit distance measured in price points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Trailing stop distance measured in price points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	/// <summary>
	/// Number of finished candles required before the strategy begins trading.
	/// </summary>
	public int MinimumHistoryCandles
	{
		get => _minimumHistoryCandles.Value;
		set => _minimumHistoryCandles.Value = value;
	}

	/// <summary>
	/// Initialize default parameters for the MACD Sample strategy.
	/// </summary>
	public MacdSampleClassicStrategy()
	{
		Volume = 1;

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
		.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators")
		
		.SetOptimize(6, 18, 2);

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
		.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators")
		
		.SetOptimize(20, 32, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
		.SetDisplay("Signal EMA", "Signal EMA period for MACD", "Indicators")
		
		.SetOptimize(5, 13, 2);

		_trendMaPeriod = Param(nameof(TrendMaPeriod), 26)
		.SetDisplay("Trend EMA", "EMA period used for directional filter", "Indicators")
		
		.SetOptimize(20, 40, 2);

		_macdOpenLevel = Param(nameof(MacdOpenLevel), 0m)
		.SetDisplay("MACD Open", "Entry threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 5m, 1m);

		_macdCloseLevel = Param(nameof(MacdCloseLevel), 0m)
		.SetDisplay("MACD Close", "Exit threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 4m, 1m);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
		.SetDisplay("Take Profit", "Take profit distance in price points", "Risk")
		
		.SetOptimize(20m, 100m, 10m);

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 30m)
		.SetDisplay("Trailing Stop", "Trailing stop distance in price points", "Risk")
		
		.SetOptimize(10m, 60m, 10m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Candle type used for analysis", "General");
		_minimumHistoryCandles = Param(nameof(MinimumHistoryCandles), 30)
			.SetDisplay("Warm-up candles", "Number of finished candles required before trading starts", "General")
			.SetGreaterThanZero();
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_pointSize = 0m;
		_prevMacd = null;
		_prevSignal = null;
		_trendMaCurrent = null;
		_trendMaPrevious = null;
		_finishedCandles = 0;
		_lastProcessedTime = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pointSize = Security?.PriceStep ?? 1m;

		// Configure indicators exactly as in the original expert advisor.
		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastEmaPeriod },
				LongMa = { Length = SlowEmaPeriod },
			},
			SignalMa = { Length = SignalPeriod }
		};

		var trendMa = new ExponentialMovingAverage { Length = TrendMaPeriod };

		// Subscribe to candles and bind indicators for automatic updates.
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(macd, ProcessMacdValues);
		subscription.Bind(trendMa, ProcessTrendMaValue);
		subscription.Start();

		// Visualize price, indicators and trades when a chart is available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, macd);
			DrawIndicator(area, trendMa);
			DrawOwnTrades(area);
		}

		var takeProfitDistance = TakeProfitPoints * _pointSize;
		var trailingDistance = TrailingStopPoints * _pointSize;

		if (takeProfitDistance > 0m || trailingDistance > 0m)
		{
			StartProtection(
			takeProfitDistance > 0m ? new Unit(takeProfitDistance, UnitTypes.Absolute) : null,
			trailingDistance > 0m ? new Unit(trailingDistance, UnitTypes.Absolute) : null,
			isStopTrailing: trailingDistance > 0m);
		}
	}

	private void ProcessTrendMaValue(ICandleMessage candle, decimal maValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Keep current and previous EMA values for the directional filter.
		_trendMaPrevious = _trendMaCurrent;
		_trendMaCurrent = maValue;
	}

	private void ProcessMacdValues(ICandleMessage candle, IIndicatorValue macdValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Avoid duplicate processing for the same candle.
		if (_lastProcessedTime != candle.OpenTime)
		{
			_lastProcessedTime = candle.OpenTime;
			_finishedCandles++;
		}

		// indicators checked below

		if (_finishedCandles < MinimumHistoryCandles)
		return;

		var macdSignal = (MovingAverageConvergenceDivergenceSignalValue)macdValue;

		if (macdSignal.Macd is not decimal macdCurrent ||
		macdSignal.Signal is not decimal signalCurrent)
		{
			return;
		}

		if (_prevMacd is not decimal macdPrevious ||
		_prevSignal is not decimal signalPrevious ||
		_trendMaCurrent is not decimal trendMaCurrent ||
		_trendMaPrevious is not decimal trendMaPrevious)
		{
			_prevMacd = macdCurrent;
			_prevSignal = signalCurrent;
			return;
		}

		var macdOpenThreshold = MacdOpenLevel * _pointSize;
		var macdCloseThreshold = MacdCloseLevel * _pointSize;

		// Determine trend direction using EMA slope.
		var isTrendUp = trendMaCurrent > trendMaPrevious;
		var isTrendDown = trendMaCurrent < trendMaPrevious;

		var buySignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdOpenThreshold &&
		isTrendUp;

		var sellSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdOpenThreshold &&
		isTrendDown;

		var exitLongSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdCloseThreshold;

		var exitShortSignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdCloseThreshold;

		if (buySignal && Position == 0m)
		{
			// MACD crossed up in negative territory and EMA confirms uptrend.
			BuyMarket();
			LogInfo($"Open long: MACD {macdCurrent:F5} above signal {signalCurrent:F5}.");
		}
		else if (sellSignal && Position == 0m)
		{
			// MACD crossed down in positive territory and EMA confirms downtrend.
			SellMarket();
			LogInfo($"Open short: MACD {macdCurrent:F5} below signal {signalCurrent:F5}.");
		}
		else if (exitLongSignal && Position > 0m)
		{
			// MACD crossed back below the signal line in positive zone - close long.
			SellMarket();
			LogInfo($"Close long: MACD {macdCurrent:F5} dropped under signal {signalCurrent:F5}.");
		}
		else if (exitShortSignal && Position < 0m)
		{
			// MACD crossed back above the signal line in negative zone - close short.
			BuyMarket();
			LogInfo($"Close short: MACD {macdCurrent:F5} rose above signal {signalCurrent:F5}.");
		}

		_prevMacd = macdCurrent;
		_prevSignal = signalCurrent;
	}
}