Auf GitHub ansehen

MAMACD Strategy

Overview

This strategy is a direct conversion of the MetaTrader 5 expert advisor MAMACD (barabashkakvn's edition) from the MQL/19334 folder into StockSharp's high-level API. The approach combines trend detection on low prices through two linear weighted moving averages (LWMA) with a fast exponential moving average (EMA) trigger and confirmation from the MACD main line. Trading is performed once per finished candle and keeps the logic of the original EA, including the reset flags that require the fast EMA to leave the LWMA channel before a new entry is allowed.

Indicators

  • LWMA #1 (Low price, default 85) – slow baseline filter applied to candle lows.
  • LWMA #2 (Low price, default 75) – slightly faster filter on candle lows for channel confirmation.
  • EMA Trigger (Close price, default 5) – momentum trigger that has to cross above/below both LWMAs to arm a trade.
  • MACD main line (fast 15, slow 26) – confirmation filter; longs require positive or rising MACD, shorts require negative or declining MACD.

Entry Logic

  1. The strategy waits for completed candles only (CandleStates.Finished).
  2. When the trigger EMA drops below both LWMAs, a long-ready flag is set. A long position may be opened once the EMA comes back above both LWMAs and MACD is either above zero or greater than its previous value. Only one long position can be opened at a time.
  3. When the trigger EMA rises above both LWMAs, a short-ready flag is set. A short position may be opened after the EMA returns below both LWMAs and MACD is either below zero or smaller than its previous value. Only one short position is active at a time.
  4. Position sizing uses the strategy Volume property. When switching direction the algorithm closes the opposite exposure first.

Exit Logic

  • No discretionary exit logic is coded in the original EA. Protective orders are handled through StockSharp's StartProtection with optional stop-loss and take-profit distances measured in pips. Hitting either protection closes the position automatically.

Parameters

Name Description
FirstLowMaLength Period of the first LWMA applied to low prices (default 85).
SecondLowMaLength Period of the second LWMA applied to low prices (default 75).
TriggerEmaLength Period of the fast EMA trigger on closing prices (default 5).
MacdFastLength Fast EMA length of the MACD main line (default 15).
MacdSlowLength Slow EMA length of the MACD main line (default 26).
StopLossPips Stop-loss distance in pips; set to zero to disable (default 15).
TakeProfitPips Take-profit distance in pips; set to zero to disable (default 15).
CandleType Time frame of candles processed by the strategy (default 1 hour).

Implementation Notes

  • Pip size is derived from Security.PriceStep. For 3- and 5-digit symbols the code automatically multiplies the step by 10 to mimic the MT5 definition of a pip.
  • The MACD history buffer matches the EA: the very first valid MACD value is stored and used as reference for the following bar before signals are evaluated.
  • Flags _readyForLong and _readyForShort replicate the original startb/starts state machine, ensuring that price has to leave the LWMA channel before any new trade is taken.
  • Chart areas visualize the price series with moving averages and a separate MACD panel for easier verification of the conversion.

Conversion Mapping

MT5 element StockSharp equivalent
iMA on low/close WeightedMovingAverage (low feed) and ExponentialMovingAverage (close feed)
iMACD main line MovingAverageConvergenceDivergence main output
Position checks (buy, sell) Position sign and volume handling via BuyMarket / SellMarket
Magic number & slippage Not required in StockSharp high-level API
Stop-loss / Take-profit (pips) StartProtection with absolute price offsets computed from pip size

The resulting behaviour mirrors the MT5 version while leveraging StockSharp's strategy lifecycle, indicator binding, and risk management helpers.

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;
	}
}