Auf GitHub ansehen

MACD AO Muster-Strategie

Übersicht

Diese Strategie ist eine originalgetreue StockSharp-Portierung des FORTRADER-Expertenberaters MACD.mq5. Sie implementiert das „AOP"-Muster, das den MACD-Oszillator auf tiefe Exkursionen von der Nulllinie beobachtet, gefolgt von einem Rückhaken in Richtung Neutralität. Wenn der Haken bestätigt wird, tritt die Strategie in Richtung der erwarteten Umkehr ein und wendet sofort feste Stop-Loss- und Take-Profit-Ziele in Pips an.

Strategie-Logik

Datenvorbereitung

  • Arbeitet auf der Kerzenserie, die durch den Parameter CandleType ausgewählt wurde (standardmäßig 5-Minuten-Kerzen).
  • Verwendet einen Standard-MACD-Indikator mit konfigurierbaren schnellen, langsamen und Signal-Perioden (Standardwerte 12/26/9).
  • Speichert die MACD-Hauptlinienwerte der drei zuletzt abgeschlossenen Kerzen, um den indexbasierten MQL-Zugriff zu reproduzieren (iMACD(...,1..3)).

Short-Setup (bärischer Haken)

  1. Aktivierung – sobald die MACD-Hauptlinie der zuletzt geschlossenen Kerze unter BearishExtremeLevel fällt (Standard −0.0015), beginnt die Strategie, auf eine Umkehr zu achten.
  2. Neutraler Rückzug – wenn MACD wieder über BearishNeutralLevel steigt (Standard −0.0005), wird die Hakenvalidierungsphase aktiv.
  3. Hakenbestätigung – die drei vorherigen MACD-Werte müssen ein lokales Maximum bilden (macd₁ < macd₂ > macd₃), während der aktuellste Wert unterhalb des Neutralniveaus bleibt und der ältere Wert darüber. Dies reproduziert das ursprüngliche Muster, das sicherstellt, dass der Momentum nachlässt.
  4. Einstieg – wenn keine Long-Position offen ist (Position <= 0), wird eine Marktverkaufsorder von OrderVolume gesendet. Schutzlevel werden sofort berechnet: Stop-Loss über dem Einstieg um StopLossPips und Take-Profit darunter um TakeProfitPips (in Preis umgewandelt durch GetPipSize).
  5. Jede positive MACD-Lesung bricht das Setup ab und setzt die interne bärische Zustandsmaschine zurück, bis ein neuer tiefer negativer Abschnitt erscheint.

Long-Setup (bullischer Haken)

  1. Aktivierung – sobald MACD über BullishExtremeLevel steigt (Standard +0.0015), wird der bullische Beobachtungsmodus aktiviert.
  2. Sofortige Stornierung – wenn MACD unter null fällt, wird das bullische Szenario aufgegeben und spiegelt die MQL-Logik wider.
  3. Neutraler Rückzug – ein Rückfall unter BullishNeutralLevel (Standard +0.0005) bereitet die Hakenbestätigung vor.
  4. Hakenbestätigung – die drei gespeicherten MACD-Werte müssen ein lokales Minimum bilden (macd₁ > macd₂ < macd₃) unter Beachtung der neutralen Schwellenwerte.
  5. Einstieg – wenn keine Short-Position vorhanden ist (Position >= 0), kauft die Strategie zum Marktpreis mit OrderVolume und setzt Stop-Loss und Take-Profit symmetrisch zu den Short-Regeln.

Risikomanagement

  • Stop-Loss und Take-Profit sind immer über _stopPrice und _takePrice aktiv. Sie werden bei jeder abgeschlossenen Kerze mit dem aufgezeichneten Hoch/Tief ausgewertet, um die brokerseitige Ausführung im Original-EA zu emulieren.
  • Pips werden mit Security.PriceStep in absolute Preise umgerechnet. Für 3- und 5-stellige FX-Symbole wird der Schritt mit 10 multipliziert, um die MQL-Anpassung für Bruchteils-Pips anzupassen.
  • Wenn die Strategie eine Position wegen der Schutzlevel verlässt, löscht sie diese sofort und wartet auf ein neues Setup bei den nächsten Kerzen.

Parameter

Parameter Beschreibung Standard
CandleType Von der Strategie verarbeitete Kerzendatenserie. 5-Minuten-Zeitrahmen
OrderVolume Mit jeder Marktorder eingereichte Menge. 0.1
TakeProfitPips Entfernung zum Gewinnziel in Pips. Für Optimierung markiert. 60
StopLossPips Entfernung zum Stop-Loss in Pips. Für Optimierung markiert. 70
MacdFastPeriod Schnelle EMA-Länge für MACD. 12
MacdSlowPeriod Langsame EMA-Länge für MACD. 26
MacdSignalPeriod Signal-EMA-Länge für MACD. 9
BearishExtremeLevel Negativer MACD-Schwellenwert, der Short-Chancen aktiviert. −0.0015
BearishNeutralLevel Negativer MACD-Schwellenwert zur Validierung des bärischen Hakens. −0.0005
BullishExtremeLevel Positiver MACD-Schwellenwert, der Long-Chancen aktiviert. +0.0015
BullishNeutralLevel Positiver MACD-Schwellenwert zur Validierung des bullischen Hakens. +0.0005

Zusätzliche Hinweise

  • Die Strategie reagiert nur einmal pro abgeschlossener Kerze und ahmt den ursprünglichen PrevBars-Schutz in MQL nach.
  • Die Stop-Loss/Take-Profit-Verwaltung ist rein preisbasiert; es gibt keine Trailing-Anpassungen oder Wiedereinstiege, bis der vollständige Zustandsmaschinenzyklus erneut durchläuft.
  • Für Hedging-Konten im Quell-EA konzipiert, aber dieser Port erzwingt eine einzige Nettoposition durch Überprüfung von Position vor dem Senden neuer Orders.
  • Keine Python-Version vorhanden, wie angefordert.
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-based reversal strategy that reproduces the FORTRADER AOP pattern.
/// </summary>
public class MacdAoPatternStrategy : Strategy
{
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<decimal> _bearishExtremeLevel;
	private readonly StrategyParam<decimal> _bearishNeutralLevel;
	private readonly StrategyParam<decimal> _bullishExtremeLevel;
	private readonly StrategyParam<decimal> _bullishNeutralLevel;
	private readonly StrategyParam<DataType> _candleType;

	private MACD _macd = null!;

	private decimal? _macdPrev1;
	private decimal? _macdPrev2;
	private decimal? _macdPrev3;

	private bool _bearishStageArmed;
	private bool _bearishTriggerReady;
	private bool _bearishSignalPending;

	private bool _bullishStageArmed;
	private bool _bullishTriggerReady;
	private bool _bullishSignalPending;

	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Distance to the take-profit level measured in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Distance to the stop-loss level measured in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Volume used for each market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

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

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

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

	/// <summary>
	/// MACD level that arms the bearish setup when the oscillator stretches deeply negative.
	/// </summary>
	public decimal BearishExtremeLevel
	{
		get => _bearishExtremeLevel.Value;
		set => _bearishExtremeLevel.Value = value;
	}

	/// <summary>
	/// MACD level that confirms the bearish hook back toward the zero line.
	/// </summary>
	public decimal BearishNeutralLevel
	{
		get => _bearishNeutralLevel.Value;
		set => _bearishNeutralLevel.Value = value;
	}

	/// <summary>
	/// MACD level that arms the bullish setup when the oscillator stretches deeply positive.
	/// </summary>
	public decimal BullishExtremeLevel
	{
		get => _bullishExtremeLevel.Value;
		set => _bullishExtremeLevel.Value = value;
	}

	/// <summary>
	/// MACD level that confirms the bullish hook back toward the zero line.
	/// </summary>
	public decimal BullishNeutralLevel
	{
		get => _bullishNeutralLevel.Value;
		set => _bullishNeutralLevel.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="MacdAoPatternStrategy"/>.
	/// </summary>
	public MacdAoPatternStrategy()
	{
		_takeProfitPips = Param(nameof(TakeProfitPips), 60)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk")
			;

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

		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Volume for every market order", "Orders");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "Fast EMA length", "Indicators");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow", "Slow EMA length", "Indicators");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal", "Signal EMA length", "Indicators");

		_bearishExtremeLevel = Param(nameof(BearishExtremeLevel), -100m)
			.SetDisplay("Bearish Extreme", "Negative MACD level that arms shorts", "Signals");

		_bearishNeutralLevel = Param(nameof(BearishNeutralLevel), -30m)
			.SetDisplay("Bearish Neutral", "Negative MACD level that confirms the hook", "Signals");

		_bullishExtremeLevel = Param(nameof(BullishExtremeLevel), 100m)
			.SetDisplay("Bullish Extreme", "Positive MACD level that arms longs", "Signals");

		_bullishNeutralLevel = Param(nameof(BullishNeutralLevel), 30m)
			.SetDisplay("Bullish Neutral", "Positive MACD level that confirms the hook", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Source series for the strategy", "General");
	}

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

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

		_macdPrev1 = null;
		_macdPrev2 = null;
		_macdPrev3 = null;

		_bearishStageArmed = false;
		_bearishTriggerReady = false;
		_bearishSignalPending = false;

		_bullishStageArmed = false;
		_bullishTriggerReady = false;
		_bullishSignalPending = false;

		_stopPrice = null;
		_takePrice = null;
	}

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

		Volume = OrderVolume;

		_macd = new MACD();
		_macd.ShortMa.Length = MacdFastPeriod;
		_macd.LongMa.Length = MacdSlowPeriod;

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

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

		// First handle protective exits using the finished candle range.
		HandlePositionExit(candle);

		if (!_macd.IsFormed)
		{
			UpdateMacdHistory(macdLine);
			return;
		}

		if (_macdPrev1 is null || _macdPrev2 is null || _macdPrev3 is null)
		{
			UpdateMacdHistory(macdLine);
			return;
		}

		var macd1 = _macdPrev1.Value;
		var macd2 = _macdPrev2.Value;
		var macd3 = _macdPrev3.Value;

		// --- Bearish sequence --------------------------------------------------

		if (macd1 < BearishExtremeLevel && !_bearishStageArmed)
		{
			// Arm the bearish setup after a deep negative MACD reading.
			_bearishStageArmed = true;
		}

		if (macd1 > BearishNeutralLevel && _bearishStageArmed)
		{
			// MACD returned toward zero, prepare for the hook confirmation.
			_bearishStageArmed = false;
			_bearishTriggerReady = true;
		}

		var bearishHook = _bearishTriggerReady &&
			macd1 < macd2 &&
			macd2 > macd3 &&
			macd1 < BearishNeutralLevel &&
			macd2 > BearishNeutralLevel;

		if (bearishHook)
		{
			// Confirm the bearish hook pattern.
			_bearishTriggerReady = false;
			_bearishSignalPending = true;
		}

		if (macd1 > 0)
		{
			// Positive MACD invalidates the bearish scenario.
			ResetBearishState();
		}

		if (_bearishSignalPending && Position <= 0)
		{
			// Execute the short entry with predefined stop-loss and take-profit.
			SellMarket();

			var pip = GetPipSize();
			var entryPrice = candle.ClosePrice;
			_stopPrice = entryPrice + StopLossPips * pip;
			_takePrice = entryPrice - TakeProfitPips * pip;

			ResetBearishState();
		}

		// --- Bullish sequence --------------------------------------------------

		if (macd1 > BullishExtremeLevel && !_bullishStageArmed)
		{
			// Arm the bullish setup after a strong positive MACD expansion.
			_bullishStageArmed = true;
		}

		if (macd1 < 0)
		{
			// Negative MACD cancels the bullish scenario immediately.
			ResetBullishState();
		}
		else if (macd1 < BullishNeutralLevel && _bullishStageArmed)
		{
			// MACD retraced toward zero, allow the hook confirmation.
			_bullishStageArmed = false;
			_bullishTriggerReady = true;
		}

		var bullishHook = _bullishTriggerReady &&
			macd1 > macd2 &&
			macd2 < macd3 &&
			macd1 > BullishNeutralLevel &&
			macd2 < BullishNeutralLevel;

		if (bullishHook)
		{
			// Confirm the bullish hook pattern.
			_bullishTriggerReady = false;
			_bullishSignalPending = true;
		}

		if (_bullishSignalPending && Position >= 0)
		{
			// Execute the long entry with the configured targets.
			BuyMarket();

			var pip = GetPipSize();
			var entryPrice = candle.ClosePrice;
			_stopPrice = entryPrice - StopLossPips * pip;
			_takePrice = entryPrice + TakeProfitPips * pip;

			ResetBullishState();
		}

		UpdateMacdHistory(macdLine);
	}

	private void HandlePositionExit(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var exitVolume = Math.Abs(Position);

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				// Long stop-loss hit inside the finished candle range.
				SellMarket();
				ResetProtectionLevels();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				// Long take-profit reached.
				SellMarket();
				ResetProtectionLevels();
			}
		}
		else if (Position < 0)
		{
			var exitVolume = Math.Abs(Position);

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				// Short stop-loss triggered within the candle.
				BuyMarket();
				ResetProtectionLevels();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				// Short take-profit reached.
				BuyMarket();
				ResetProtectionLevels();
			}
		}
	}

	private void UpdateMacdHistory(decimal macdValue)
	{
		_macdPrev3 = _macdPrev2;
		_macdPrev2 = _macdPrev1;
		_macdPrev1 = macdValue;
	}

	private void ResetBearishState()
	{
		_bearishStageArmed = false;
		_bearishTriggerReady = false;
		_bearishSignalPending = false;
	}

	private void ResetBullishState()
	{
		_bullishStageArmed = false;
		_bullishTriggerReady = false;
		_bullishSignalPending = false;
	}

	private void ResetProtectionLevels()
	{
		_stopPrice = null;
		_takePrice = null;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		var decimals = Security?.Decimals;

		if (decimals == 3 || decimals == 5)
			return step * 10m;

		return step;
	}
}