View on GitHub

Macd Pattern Trader Trigger Strategy

Overview

Macd Pattern Trader Trigger Strategy ports the MetaTrader 4 expert advisor MacdPatternTraderv05cb to StockSharp's high-level strategy API. The system trades pure MACD histogram patterns, looking for a double-top structure below the zero line to open shorts and a mirror image double-bottom above the zero line to open longs. Trade management mirrors the original EA: every entry is submitted at market with a configurable fixed stop loss and take profit measured in instrument points.

Strategy logic

Indicator stream

  • A single candle subscription drives the logic (default: 15-minute candles). Each finished candle feeds a MovingAverageConvergenceDivergence indicator configured with the unusual MT4 parameters (fast = 13, slow = 5, signal = 1) used by the source EA.
  • Only the MACD main line is used. The strategy buffers the last three completed values in order to emulate iMACD(..., MODE_MAIN, shift=1..3) from MetaTrader.

Bullish setup (long entries)

  1. Arming condition – the MACD line must rise above Bullish Trigger (default 0.0015). This prepares the strategy to look for the pullback sequence. Any dip below zero clears the state immediately.
  2. Pullback window – once armed, the MACD has to fall back below Bullish Reset (default 0.0005). This marks the potential pullback area. The window remains active until a valid pattern is confirmed or MACD turns negative.
  3. Pattern confirmation – while the window is active, the last three buffered MACD readings must satisfy:
    • macd_curr > macd_last (momentum turns back up),
    • macd_last < macd_last3 (the previous bar set the swing low),
    • macd_curr > Bullish Reset and macd_last < Bullish Reset (price rebounds from the shallow pullback zone).
  4. Execution – when confirmed, the strategy buys at market. If there is an existing short position, the order size automatically includes the volume required to flatten before establishing the long exposure.

Bearish setup (short entries)

  1. Arming condition – the MACD line must drop below -Bearish Trigger (default -0.0015). Any move above zero clears all bearish state.
  2. Pullback window – once armed, the MACD has to rebound above -Bearish Reset (default -0.0005).
  3. Pattern confirmation – while the window is open, the buffered values must satisfy:
    • macd_curr < macd_last,
    • macd_last > macd_last3,
    • macd_curr < -Bearish Reset and macd_last > -Bearish Reset.
  4. Execution – a market sell order is submitted. If a long position exists, its volume is included in the order so the account ends up net short by the configured trade size.

Risk management

  • Fixed stop loss / take profit – distances are specified in points (price steps). The strategy multiplies them by the instrument's PriceStep and calls StartProtection to reproduce the original SL/TP behaviour. Setting a distance to 0 disables the respective level.
  • One signal per window – after placing an order, the arming and window flags are cleared to avoid repeated entries from the same MACD pattern.

Parameters

  • Trade Volume – market order volume. Opposite positions are closed automatically before opening the new trade.
  • Fast EMA / Slow EMA / Signal EMA – MACD lengths. Defaults replicate the original advisor but may be optimised.
  • Bullish Trigger / Reset – positive MACD thresholds (in indicator units) that arm the long setup and define its pullback zone.
  • Bearish Trigger / Reset – absolute MACD thresholds for the short setup. The trigger is applied with a negative sign during runtime.
  • Stop Loss / Take Profit – distances in points (price steps). A value of 0 disables the corresponding protection.
  • Candle Type – timeframe used for the MACD calculation and trading decisions.

Implementation notes

  • The StockSharp high-level API is used throughout: SubscribeCandles feeds the indicator and StartProtection mirrors the MT4 trade management.
  • The MACD history buffer ensures the decision logic operates on the previous three finished bars, matching MetaTrader's shift=1..3 calls.
  • There is no Python version of this strategy in the API package, only the C# implementation.
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>
/// Port of the MetaTrader advisor MacdPatternTrader.
/// Implements the MACD histogram double-top/double-bottom pattern with adaptive arming logic.
/// </summary>
public class MacdPatternTraderTriggerStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _bullishTrigger;
	private readonly StrategyParam<decimal> _bullishReset;
	private readonly StrategyParam<decimal> _bearishTrigger;
	private readonly StrategyParam<decimal> _bearishReset;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergenceSignal _macd = null!;

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

	private bool _bullishArmed;
	private bool _bullishWindow;
	private bool _bullishReady;

	private bool _bearishArmed;
	private bool _bearishWindow;
	private bool _bearishReady;

	private decimal _priceStep = 1m;

	/// <summary>
	/// Trade volume used for market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Fast EMA length of the MACD indicator.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA length of the MACD indicator.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

	/// <summary>
	/// Positive MACD value that arms the bullish pattern.
	/// </summary>
	public decimal BullishTrigger
	{
		get => _bullishTrigger.Value;
		set => _bullishTrigger.Value = value;
	}

	/// <summary>
	/// Threshold that marks the bullish pullback zone.
	/// </summary>
	public decimal BullishReset
	{
		get => _bullishReset.Value;
		set => _bullishReset.Value = value;
	}

	/// <summary>
	/// Negative MACD value that arms the bearish pattern.
	/// </summary>
	public decimal BearishTrigger
	{
		get => _bearishTrigger.Value;
		set => _bearishTrigger.Value = value;
	}

	/// <summary>
	/// Threshold that marks the bearish pullback zone.
	/// </summary>
	public decimal BearishReset
	{
		get => _bearishReset.Value;
		set => _bearishReset.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 that drives the MACD calculation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public MacdPatternTraderTriggerStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume for entries", "Trading")
			
			.SetOptimize(0.05m, 0.3m, 0.05m);

		_fastPeriod = Param(nameof(FastPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA length for MACD", "Indicators")
			
			.SetOptimize(8, 18, 1);

		_slowPeriod = Param(nameof(SlowPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA length for MACD", "Indicators")
			
			.SetOptimize(4, 15, 1);

		_signalPeriod = Param(nameof(SignalPeriod), 1)
			.SetGreaterThanZero()
			.SetDisplay("Signal EMA", "Signal EMA length for MACD", "Indicators")
			
			.SetOptimize(1, 5, 1);

		_bullishTrigger = Param(nameof(BullishTrigger), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Bullish Trigger", "MACD level that arms the bullish pattern", "Logic");

		_bullishReset = Param(nameof(BullishReset), 20m)
			.SetGreaterThanZero()
			.SetDisplay("Bullish Reset", "MACD pullback threshold for bullish setup", "Logic");

		_bearishTrigger = Param(nameof(BearishTrigger), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Bearish Trigger", "Absolute MACD level that arms the bearish pattern", "Logic");

		_bearishReset = Param(nameof(BearishReset), 20m)
			.SetGreaterThanZero()
			.SetDisplay("Bearish Reset", "MACD pullback threshold for bearish setup", "Logic");

		_stopLossPoints = Param(nameof(StopLossPoints), 100m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in points", "Risk")
			
			.SetOptimize(50m, 200m, 25m);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in points", "Risk")
			
			.SetOptimize(100m, 400m, 50m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for indicator calculations", "Data");
	}

	/// <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;
		_bullishArmed = false;
		_bullishWindow = false;
		_bullishReady = false;
		_bearishArmed = false;
		_bearishWindow = false;
		_bearishReady = false;
	}

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

		_priceStep = Security?.PriceStep ?? 1m;
		if (_priceStep <= 0m)
			_priceStep = 1m;

		var takeProfit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints * _priceStep, UnitTypes.Absolute) : null;
		var stopLoss = StopLossPoints > 0m ? new Unit(StopLossPoints * _priceStep, UnitTypes.Absolute) : null;

		StartProtection(takeProfit, stopLoss);

		_macd = new MovingAverageConvergenceDivergenceSignal();
		_macd.Macd.ShortMa.Length = FastPeriod;
		_macd.Macd.LongMa.Length = SlowPeriod;
		_macd.SignalMa.Length = SignalPeriod;

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _macd);
			DrawOwnTrades(area);
		}
	}

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

		if (!macdValue.IsFinal || macdValue is not MovingAverageConvergenceDivergenceSignalValue macdLineValue)
			return;

		if (macdLineValue.Macd is not decimal currentMacd)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			ShiftHistory(currentMacd);
			return;
		}

		if (_macdPrev1 is null || _macdPrev2 is null || _macdPrev3 is null)
		{
			ShiftHistory(currentMacd);
			return;
		}

		var macdCurr = _macdPrev1.Value;
		var macdLast = _macdPrev2.Value;
		var macdLast3 = _macdPrev3.Value;

		EvaluateBearishPattern(macdCurr, macdLast, macdLast3);
		EvaluateBullishPattern(macdCurr, macdLast, macdLast3);

		ShiftHistory(currentMacd);
	}

	private void EvaluateBullishPattern(decimal macdCurr, decimal macdLast, decimal macdLast3)
	{
		if (macdCurr < 0m)
		{
			_bullishArmed = false;
			_bullishWindow = false;
			_bullishReady = false;
		}
		else
		{
			if (!_bullishArmed && macdCurr > BullishTrigger)
				_bullishArmed = true;

			if (_bullishArmed && macdCurr < BullishReset)
			{
				_bullishArmed = false;
				_bullishWindow = true;
			}
		}

		if (_bullishWindow && macdCurr > macdLast && macdLast < macdLast3 && macdCurr > BullishReset && macdLast < BullishReset)
		{
			_bullishReady = true;
			_bullishWindow = false;
		}

		if (!_bullishReady)
			return;

		var volumeToBuy = TradeVolume + Math.Max(0m, -Position);
		if (volumeToBuy > 0m)
			BuyMarket(volumeToBuy);

		_bullishReady = false;
		_bullishArmed = false;
		_bullishWindow = false;
	}

	private void EvaluateBearishPattern(decimal macdCurr, decimal macdLast, decimal macdLast3)
	{
		if (macdCurr > 0m)
		{
			_bearishArmed = false;
			_bearishWindow = false;
			_bearishReady = false;
		}
		else
		{
			if (!_bearishArmed && macdCurr < -BearishTrigger)
				_bearishArmed = true;

			if (_bearishArmed && macdCurr > -BearishReset)
			{
				_bearishArmed = false;
				_bearishWindow = true;
			}
		}

		if (_bearishWindow && macdCurr < macdLast && macdLast > macdLast3 && macdCurr < -BearishReset && macdLast > -BearishReset)
		{
			_bearishReady = true;
			_bearishWindow = false;
		}

		if (!_bearishReady)
			return;

		var volumeToSell = TradeVolume + Math.Max(0m, Position);
		if (volumeToSell > 0m)
			SellMarket(volumeToSell);

		_bearishReady = false;
		_bearishArmed = false;
		_bearishWindow = false;
	}

	private void ShiftHistory(decimal current)
	{
		_macdPrev3 = _macdPrev2;
		_macdPrev2 = _macdPrev1;
		_macdPrev1 = current;
	}
}