Auf GitHub ansehen

MAMACD-Strategie

Übersicht

Diese Strategie ist eine direkte Konvertierung des MetaTrader 5 Expert Advisors MAMACD (Edition von barabashkakvn) aus dem Ordner MQL/19334 in die High-Level-API von StockSharp. Der Ansatz kombiniert Trenderkennung auf Tiefpreisen durch zwei linear gewichtete gleitende Durchschnitte (LWMA) mit einem schnellen exponentiellen gleitenden Durchschnitt (EMA) als Auslöser und Bestätigung durch die MACD-Hauptlinie. Trades werden einmal pro abgeschlossener Kerze ausgeführt und behalten die Logik des Original-EAs bei, einschließlich der Reset-Flags, die erfordern, dass die schnelle EMA den LWMA-Kanal verlässt, bevor ein neuer Einstieg erlaubt wird.

Indikatoren

  • LWMA #1 (Tiefstkurs, Standard 85) – langsamer Basislinienfilter, angewendet auf Kerzentiefs.
  • LWMA #2 (Tiefstkurs, Standard 75) – etwas schnellerer Filter auf Kerzentiefs zur Kanalbestätigung.
  • EMA-Auslöser (Schlusskurs, Standard 5) – Momentum-Auslöser, der über/unter beide LWMAs kreuzen muss, um einen Trade zu aktivieren.
  • MACD-Hauptlinie (schnell 15, langsam 26) – Bestätigungsfilter; Long-Positionen erfordern positiven oder steigenden MACD, Short-Positionen erfordern negativen oder fallenden MACD.

Einstiegslogik

  1. Die Strategie wartet nur auf abgeschlossene Kerzen (CandleStates.Finished).
  2. Wenn der Auslöser-EMA unter beide LWMAs fällt, wird ein Long-Bereitschafts-Flag gesetzt. Eine Long-Position kann eröffnet werden, sobald der EMA über beide LWMAs zurückkehrt und der MACD entweder über null liegt oder größer als sein vorheriger Wert ist. Es kann jeweils nur eine Long-Position eröffnet werden.
  3. Wenn der Auslöser-EMA über beide LWMAs steigt, wird ein Short-Bereitschafts-Flag gesetzt. Eine Short-Position kann eröffnet werden, nachdem der EMA unter beide LWMAs zurückkehrt und der MACD entweder unter null liegt oder kleiner als sein vorheriger Wert ist. Es ist jeweils nur eine Short-Position aktiv.
  4. Die Positionsgröße verwendet die Volume-Eigenschaft der Strategie. Beim Richtungswechsel schließt der Algorithmus zuerst die entgegengesetzte Exposition.

Ausstiegslogik

  • Im Original-EA ist keine diskretionäre Ausstiegslogik kodiert. Schutzaufträge werden über StockSharp's StartProtection mit optionalen Stop-Loss- und Take-Profit-Abständen in Pips verwaltet. Das Erreichen eines der Schutzniveaus schließt die Position automatisch.

Parameter

Name Beschreibung
FirstLowMaLength Periode des ersten LWMA, angewendet auf Tiefpreise (Standard 85).
SecondLowMaLength Periode des zweiten LWMA, angewendet auf Tiefpreise (Standard 75).
TriggerEmaLength Periode des schnellen EMA-Auslösers auf Schlusspreise (Standard 5).
MacdFastLength Länge des schnellen EMA der MACD-Hauptlinie (Standard 15).
MacdSlowLength Länge des langsamen EMA der MACD-Hauptlinie (Standard 26).
StopLossPips Stop-Loss-Abstand in Pips; auf null setzen zum Deaktivieren (Standard 15).
TakeProfitPips Take-Profit-Abstand in Pips; auf null setzen zum Deaktivieren (Standard 15).
CandleType Zeitrahmen der von der Strategie verarbeiteten Kerzen (Standard 1 Stunde).

Implementierungshinweise

  • Die Pip-Größe wird aus Security.PriceStep abgeleitet. Bei 3- und 5-stelligen Symbolen multipliziert der Code den Schritt automatisch mit 10, um die MT5-Definition eines Pips nachzuahmen.
  • Der MACD-Verlaufspuffer entspricht dem EA: Der erste gültige MACD-Wert wird gespeichert und als Referenz für den folgenden Balken verwendet, bevor Signale ausgewertet werden.
  • Die Flags _readyForLong und _readyForShort replizieren die ursprüngliche startb/starts-Zustandsmaschine und stellen sicher, dass der Kurs den LWMA-Kanal verlassen muss, bevor ein neuer Trade eingegangen wird.
  • Chartbereiche visualisieren die Preisserie mit gleitenden Durchschnitten und ein separates MACD-Panel zur einfacheren Überprüfung der Konvertierung.

Konvertierungszuordnung

MT5-Element StockSharp-Äquivalent
iMA auf Tief/Schluss WeightedMovingAverage (Tief-Feed) und ExponentialMovingAverage (Schluss-Feed)
iMACD-Hauptlinie MovingAverageConvergenceDivergence-Hauptausgabe
Positionsprüfungen (buy, sell) Position-Vorzeichen und Volumenbehandlung über BuyMarket / SellMarket
Magic Number & Slippage Nicht erforderlich in der StockSharp High-Level-API
Stop-Loss / Take-Profit (Pips) StartProtection mit absoluten Preisoffsets, berechnet aus der Pip-Größe

Das resultierende Verhalten spiegelt die MT5-Version wider und nutzt gleichzeitig den Strategie-Lebenszyklus, die Indikatorbindung und die Risikomanagement-Hilfsmittel von StockSharp.

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>
/// MAMACD trend-following strategy converted from the original MetaTrader 5 expert advisor.
/// Combines two low-price LWMA filters, a fast EMA trigger, and a MACD confirmation filter.
/// </summary>
public class MamAcdStrategy : Strategy
{
	private readonly StrategyParam<int> _firstLowMaLength;
	private readonly StrategyParam<int> _secondLowMaLength;
	private readonly StrategyParam<int> _triggerEmaLength;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<DataType> _candleType;

	private WeightedMovingAverage _firstLowMa = null!;
	private WeightedMovingAverage _secondLowMa = null!;
	private ExponentialMovingAverage _triggerEma = null!;
	private MovingAverageConvergenceDivergence _macd = null!;

	private decimal? _previousMacd;
	private bool _readyForLong;
	private bool _readyForShort;
	private decimal _pipSize;

	/// <summary>
	/// Period of the first LWMA calculated on low prices.
	/// </summary>
	public int FirstLowMaLength
	{
		get => _firstLowMaLength.Value;
		set => _firstLowMaLength.Value = value;
	}

	/// <summary>
	/// Period of the second LWMA calculated on low prices.
	/// </summary>
	public int SecondLowMaLength
	{
		get => _secondLowMaLength.Value;
		set => _secondLowMaLength.Value = value;
	}

	/// <summary>
	/// Period of the fast EMA calculated on close prices.
	/// </summary>
	public int TriggerEmaLength
	{
		get => _triggerEmaLength.Value;
		set => _triggerEmaLength.Value = value;
	}

	/// <summary>
	/// Fast EMA period of the MACD filter.
	/// </summary>
	public int MacdFastLength
	{
		get => _macdFastLength.Value;
		set => _macdFastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA period of the MACD filter.
	/// </summary>
	public int MacdSlowLength
	{
		get => _macdSlowLength.Value;
		set => _macdSlowLength.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in pips. Set to zero to disable protective stop.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in pips. Set to zero to disable take-profit.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="MamAcdStrategy"/> with default parameters.
	/// </summary>
	public MamAcdStrategy()
	{
		_firstLowMaLength = Param(nameof(FirstLowMaLength), 85)
		.SetGreaterThanZero()
		.SetDisplay("LWMA #1", "Length of the first LWMA on lows", "Indicators")
		;

		_secondLowMaLength = Param(nameof(SecondLowMaLength), 75)
		.SetGreaterThanZero()
		.SetDisplay("LWMA #2", "Length of the second LWMA on lows", "Indicators")
		;

		_triggerEmaLength = Param(nameof(TriggerEmaLength), 5)
		.SetGreaterThanZero()
		.SetDisplay("Trigger EMA", "Length of the EMA on closes", "Indicators")
		;

		_macdFastLength = Param(nameof(MacdFastLength), 15)
		.SetGreaterThanZero()
		.SetDisplay("MACD Fast", "Fast EMA length of MACD", "Indicators")
		;

		_macdSlowLength = Param(nameof(MacdSlowLength), 26)
		.SetGreaterThanZero()
		.SetDisplay("MACD Slow", "Slow EMA length of MACD", "Indicators")
		;

		_stopLossPips = Param(nameof(StopLossPips), 500)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk management");

		_takeProfitPips = Param(nameof(TakeProfitPips), 500)
		.SetNotNegative()
		.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles used for calculations", "General");
	}

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

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

		_previousMacd = null;
		_readyForLong = false;
		_readyForShort = false;
		_pipSize = 0m;
	}

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

		_firstLowMa = new WeightedMovingAverage { Length = FirstLowMaLength };
		_secondLowMa = new WeightedMovingAverage { Length = SecondLowMaLength };
		_triggerEma = new EMA { Length = TriggerEmaLength };
		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = MacdFastLength },
			LongMa = { Length = MacdSlowLength }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		_pipSize = CalculatePipSize();

		var takeProfit = TakeProfitPips > 0 ? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute) : new Unit();
		var stopLoss = StopLossPips > 0 ? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute) : new Unit();
		StartProtection(takeProfit, stopLoss);

		var priceArea = CreateChartArea();
		if (priceArea != null)
		{
			DrawCandles(priceArea, subscription);
			DrawIndicator(priceArea, _firstLowMa);
			DrawIndicator(priceArea, _secondLowMa);
			DrawIndicator(priceArea, _triggerEma);
			DrawOwnTrades(priceArea);

			var macdArea = CreateChartArea();
			if (macdArea != null)
			{
				macdArea.Title = "MACD";
				DrawIndicator(macdArea, _macd);
			}
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Feed indicator chain: LWMAs work on low prices, EMA and MACD on closes.
		var firstLowValue = _firstLowMa.Process(new DecimalIndicatorValue(_firstLowMa, candle.LowPrice, candle.OpenTime) { IsFinal = true });
		var secondLowValue = _secondLowMa.Process(new DecimalIndicatorValue(_secondLowMa, candle.LowPrice, candle.OpenTime) { IsFinal = true });
		var triggerValue = _triggerEma.Process(new DecimalIndicatorValue(_triggerEma, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
		var macdValue = _macd.Process(new DecimalIndicatorValue(_macd, candle.ClosePrice, candle.OpenTime) { IsFinal = true });

		// Wait for all indicators to collect enough history.
		if (!_firstLowMa.IsFormed || !_secondLowMa.IsFormed || !_triggerEma.IsFormed || !_macd.IsFormed)
		{
			if (_macd.IsFormed)
			_previousMacd = macdValue.ToDecimal();
			return;
		}

		// indicators already checked above

		var ma1 = firstLowValue.ToDecimal();
		var ma2 = secondLowValue.ToDecimal();
		var ma3 = triggerValue.ToDecimal();
		var macd = macdValue.ToDecimal();

		// Store the first complete MACD observation before evaluating signals.
		if (_previousMacd is null)
		{
			_previousMacd = macd;
			return;
		}

		// Skip calculations when MACD lacks momentum confirmation just like the original EA.
		if (macd == 0m || _previousMacd.Value == 0m)
		{
			_previousMacd = macd;
			return;
		}

		// Track reset flags: EMA must dip below both LWMAs to prepare for a new long, and rise above them for shorts.
		if (ma3 < ma1 && ma3 < ma2)
		_readyForLong = true;

		if (ma3 > ma1 && ma3 > ma2)
		_readyForShort = true;

		var macdImproving = macd > _previousMacd.Value;
		var longSignal = ma3 > ma1 && ma3 > ma2 && _readyForLong && (macd > 0m || macdImproving);
		var shortSignal = ma3 < ma1 && ma3 < ma2 && _readyForShort && (macd < 0m || !macdImproving);

		if (longSignal && Position <= 0)
		{
			var volume = Volume + (Position < 0 ? -Position : 0m);
			if (volume > 0)
			{
				BuyMarket();
				_readyForLong = false;
			}
		}
		else if (shortSignal && Position >= 0)
		{
			var volume = Volume + (Position > 0 ? Position : 0m);
			if (volume > 0)
			{
				SellMarket();
				_readyForShort = false;
			}
		}

		_previousMacd = macd;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 1m;

		if (step <= 0m)
		return 1m;

		var decimals = CountDecimalPlaces(step);

		return decimals == 3 || decimals == 5 ? step * 10m : step;
	}

	private static int CountDecimalPlaces(decimal value)
	{
		value = Math.Abs(value);

		var count = 0;
		while (value != Math.Truncate(value) && count < 10)
		{
			value *= 10m;
			count++;
		}

		return count;
	}
}