Ver en GitHub

Estrategia MAMACD

Descripción general

Esta estrategia es una conversión directa del asesor experto de MetaTrader 5 MAMACD (edición de barabashkakvn) de la carpeta MQL/19334 a la API de alto nivel de StockSharp. El enfoque combina la detección de tendencia en precios mínimos mediante dos medias móviles ponderadas linealmente (LWMA) con un disparador de media móvil exponencial (EMA) rápida y confirmación de la línea principal de MACD. Las operaciones se realizan una vez por vela completada y mantienen la lógica del EA original, incluidos los indicadores de reinicio que requieren que la EMA rápida salga del canal LWMA antes de permitir una nueva entrada.

Indicadores

  • LWMA #1 (precio mínimo, predeterminado 85) – filtro de línea base lento aplicado a los mínimos de las velas.
  • LWMA #2 (precio mínimo, predeterminado 75) – filtro ligeramente más rápido sobre los mínimos de las velas para confirmación del canal.
  • Disparador EMA (precio de cierre, predeterminado 5) – disparador de Momentum que debe cruzar por encima/por debajo de ambas LWMA para armar una operación.
  • Línea principal MACD (rápida 15, lenta 26) – filtro de confirmación; los largos requieren MACD positivo o ascendente, los cortos requieren MACD negativo o descendente.

Lógica de entrada

  1. La estrategia espera únicamente velas completadas (CandleStates.Finished).
  2. Cuando la EMA disparadora cae por debajo de ambas LWMA, se establece una indicador de listo para largo. Una posición larga puede abrirse una vez que la EMA regresa por encima de ambas LWMA y el MACD está por encima de cero o es mayor que su valor anterior. Solo se puede abrir una posición larga a la vez.
  3. Cuando la EMA disparadora sube por encima de ambas LWMA, se establece una indicador de listo para corto. Una posición corta puede abrirse después de que la EMA regresa por debajo de ambas LWMA y el MACD está por debajo de cero o es menor que su valor anterior. Solo hay una posición corta activa a la vez.
  4. El dimensionamiento de posición usa la propiedad Volume de la estrategia. Al cambiar de dirección, el algoritmo cierra primero la exposición contraria.

Lógica de salida

  • No se codifica lógica de salida discrecional en el EA original. Las órdenes de protección se gestionan a través del StartProtection de StockSharp con distancias opcionales de stop-loss y take-profit medidas en pips. Alcanzar cualquiera de las protecciones cierra la posición automáticamente.

Parámetros

Nombre Descripción
FirstLowMaLength Período de la primera LWMA aplicada a precios mínimos (predeterminado 85).
SecondLowMaLength Período de la segunda LWMA aplicada a precios mínimos (predeterminado 75).
TriggerEmaLength Período del disparador EMA rápido en precios de cierre (predeterminado 5).
MacdFastLength Longitud de la EMA rápida de la línea principal MACD (predeterminado 15).
MacdSlowLength Longitud de la EMA lenta de la línea principal MACD (predeterminado 26).
StopLossPips Distancia de stop-loss en pips; poner en cero para deshabilitar (predeterminado 15).
TakeProfitPips Distancia de take-profit en pips; poner en cero para deshabilitar (predeterminado 15).
CandleType Marco temporal de las velas procesadas por la estrategia (predeterminado 1 hora).

Notas de implementación

  • El tamaño del pip se deriva de Security.PriceStep. Para símbolos de 3 y 5 dígitos, el código multiplica automáticamente el paso por 10 para imitar la definición de pip de MT5.
  • El búfer de historial MACD coincide con el EA: el primer valor MACD válido se almacena y se usa como referencia para la barra siguiente antes de evaluar las señales.
  • Los indicadores _readyForLong y _readyForShort replican la máquina de estados startb/starts original, asegurando que el precio tenga que salir del canal LWMA antes de tomar una nueva operación.
  • Las áreas del gráfico visualizan la serie de precios con medias móviles y un panel MACD separado para facilitar la verificación de la conversión.

Mapeo de conversión

Elemento MT5 Equivalente en StockSharp
iMA en mínimo/cierre WeightedMovingAverage (alimentación de mínimos) y ExponentialMovingAverage (alimentación de cierres)
Línea principal iMACD Salida principal de MovingAverageConvergenceDivergence
Verificaciones de posición (buy, sell) Signo de Position y manejo de volumen vía BuyMarket / SellMarket
Número mágico y deslizamiento No requerido en la API de alto nivel de StockSharp
Stop-loss / Take-profit (pips) StartProtection con desplazamientos de precio absolutos calculados desde el tamaño del pip

El comportamiento resultante refleja la versión MT5 mientras aprovecha el ciclo de vida de estrategia, el enlace de indicadores y los asistentes de gestión de riesgo de 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;
	}
}