Ver no GitHub

Estratégia MAMACD

Visão geral

Esta estratégia é uma conversão direta do consultor especialista de MetaTrader 5 MAMACD (edição de barabashkakvn) da pasta MQL/19334 para a API de alto nível do StockSharp. A abordagem combina detecção de tendência em preços mínimos através de duas médias móveis ponderadas linearmente (LWMA) com um gatilho de média móvel exponencial (EMA) rápida e confirmação da linha principal do MACD. As negociações são realizadas uma vez por candle finalizado e mantêm a lógica do EA original, incluindo os sinalizadores de reinicialização que exigem que a EMA rápida saia do canal LWMA antes que uma nova entrada seja permitida.

Indicadores

  • LWMA #1 (preço mínimo, padrão 85) – filtro de linha de base lento aplicado às mínimas dos candles.
  • LWMA #2 (preço mínimo, padrão 75) – filtro ligeiramente mais rápido nas mínimas dos candles para confirmação do canal.
  • Gatilho EMA (preço de fechamento, padrão 5) – gatilho de Momentum que deve cruzar acima/abaixo de ambas as LWMAs para armar uma negociação.
  • Linha principal MACD (rápida 15, lenta 26) – filtro de confirmação; compras exigem MACD positivo ou ascendente, vendas exigem MACD negativo ou descendente.

Lógica de entrada

  1. A estratégia aguarda apenas candles completados (CandleStates.Finished).
  2. Quando a EMA gatilho cai abaixo de ambas as LWMAs, um sinalizador de pronto para comprado é definido. Uma posição comprada pode ser aberta assim que a EMA retorna acima de ambas as LWMAs e o MACD está acima de zero ou maior que seu valor anterior. Apenas uma posição comprada pode ser aberta por vez.
  3. Quando a EMA gatilho sobe acima de ambas as LWMAs, um sinalizador de pronto para vendido é definido. Uma posição vendida pode ser aberta depois que a EMA retorna abaixo de ambas as LWMAs e o MACD está abaixo de zero ou menor que seu valor anterior. Apenas uma posição vendida está ativa por vez.
  4. O dimensionamento da posição usa a propriedade Volume da estratégia. Ao mudar de direção, o algoritmo fecha primeiro a exposição oposta.

Lógica de saída

  • Nenhuma lógica de saída discricionária é codificada no EA original. As ordens de proteção são tratadas através do StartProtection do StockSharp com distâncias opcionais de stop-loss e take-profit medidas em pips. Atingir qualquer proteção fecha a posição automaticamente.

Parâmetros

Nome Descrição
FirstLowMaLength Período do primeiro LWMA aplicado a preços mínimos (padrão 85).
SecondLowMaLength Período do segundo LWMA aplicado a preços mínimos (padrão 75).
TriggerEmaLength Período do gatilho EMA rápido em preços de fechamento (padrão 5).
MacdFastLength Comprimento da EMA rápida da linha principal MACD (padrão 15).
MacdSlowLength Comprimento da EMA lenta da linha principal MACD (padrão 26).
StopLossPips Distância de stop-loss em pips; definir como zero para desabilitar (padrão 15).
TakeProfitPips Distância de take-profit em pips; definir como zero para desabilitar (padrão 15).
CandleType Período dos candles processados pela estratégia (padrão 1 hora).

Notas de implementação

  • O tamanho do pip é derivado de Security.PriceStep. Para símbolos de 3 e 5 dígitos, o código multiplica automaticamente o passo por 10 para imitar a definição de pip do MT5.
  • O buffer de histórico do MACD corresponde ao EA: o primeiro valor MACD válido é armazenado e usado como referência para a barra seguinte antes de avaliar os sinais.
  • Os sinalizadores _readyForLong e _readyForShort replicam a máquina de estados startb/starts original, garantindo que o preço tenha que sair do canal LWMA antes que qualquer nova negociação seja realizada.
  • As áreas do gráfico visualizam a série de preços com médias móveis e um painel MACD separado para verificação mais fácil da conversão.

Mapeamento de conversão

Elemento MT5 Equivalente no StockSharp
iMA em mínimo/fechamento WeightedMovingAverage (feed de mínimos) e ExponentialMovingAverage (feed de fechamentos)
Linha principal iMACD Saída principal de MovingAverageConvergenceDivergence
Verificações de posição (buy, sell) Sinal de Position e tratamento de volume via BuyMarket / SellMarket
Número mágico e slippage Não necessário na API de alto nível do StockSharp
Stop-loss / Take-profit (pips) StartProtection com deslocamentos de preço absolutos calculados a partir do tamanho do pip

O comportamento resultante espelha a versão MT5 enquanto aproveita o ciclo de vida da estratégia, a vinculação de indicadores e os auxiliares de gestão de risco do 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;
	}
}