Ver en GitHub

MACD Ejemplo de estrategia clásica

La estrategia reproduce el asesor experto MetaTrader 4 "MACD Muestra" utilizando el API de alto nivel de StockSharp. Opera en ambas direcciones en un solo instrumento y refleja la lógica original: realiza operaciones cuando la línea MACD cruza su línea de señal en el lado correcto de cero mientras una tendencia EMA confirma la dirección. Las órdenes de protección se convierten al administrador de riesgos integrado de StockSharp con paradas finales opcionales.

Lógica de trading

  1. Espere al menos 100 velas terminadas para que MACD y EMA contengan suficiente historial.
  2. Calcula un MACD estándar (12, 26, 9) junto con su línea de señal y una media móvil exponencial de 26 períodos que actúa como filtro direccional.
  3. Entrada larga: permitida solo cuando no existe ninguna posición. El MACD debe estar por debajo de cero pero cruzando por encima de la línea de señal, el valor anterior de MACD estaba por debajo de su señal, el valor absoluto de MACD excede el umbral configurable MacdOpenLevel (en puntos de precio) y la tendencia EMA está aumentando.
  4. Entrada corta – la configuración simétrica: MACD por encima del cruce por cero por debajo de su señal, el MACD anterior estaba por encima de la señal, el valor actual excede el umbral MacdOpenLevel y la tendencia EMA está cayendo.
  5. Salida larga: cuando MACD vuelve a cruzar por debajo de la señal en el lado positivo de cero y el valor está por encima de MacdCloseLevel. La posición también puede cerrarse antes mediante el trailing stop o take-profit gestionado por StartProtection.
  6. Salida corta – cuando MACD vuelve a cruzar la señal en el lado negativo y el valor absoluto de MACD excede MacdCloseLevel, o por los módulos de protección.

La estrategia nunca ocupa más de una posición a la vez. Cada entrada utiliza órdenes de mercado dimensionadas por la propiedad Volume. La lógica de protección se basa en el controlador de riesgos de StockSharp, por lo que las distancias de obtención de beneficios y los trailingstops permanecen sincronizados con el tamaño del tick del instrumento.

Parámetros

Nombre Descripción Predeterminado Notas
FastEmaPeriod Período EMA rápida utilizado por MACD. 12 Rango optimizable 6…18.
SlowEmaPeriod Período lento de EMA utilizado por MACD. 26 Rango optimizable 20…32.
SignalPeriod Periodo de señal EMA dentro de MACD. 9 Rango optimizable 5…13.
TrendMaPeriod EMA longitud para el filtro direccional. 26 Rango optimizable 20…40.
MacdOpenLevel Umbral de entrada expresado en MACD puntos (escalones de precio). 3 Equivalente a MACDOpenLevel en código MT4.
MacdCloseLevel Umbral de salida expresado en MACD puntos. 2 Equivalente a MACDCloseLevel.
TakeProfitPoints Obtenga ganancias en puntos de precio (multiplicados por el tamaño del tick del instrumento). 50 Establezca en 0 para deshabilitar la toma de ganancias.
TrailingStopPoints Trailing stop en puntos de precio. 30 Establezca en 0 para desactivar el trailing stop.
CandleType Serie de velas utilizadas para actualizaciones de indicadores. marco de tiempo de 5 minutos Admite cualquier tipo de vela StockSharp.

Notas de implementación

  • Los indicadores MACD y EMA están vinculados a la suscripción de vela a través de BindEx/Bind, lo que permite a StockSharp alimentar valores listos para usar sin almacenamiento en caché manual.
  • Las posiciones se abren solo cuando la plataforma informa IsFormedAndOnlineAndAllowTrading(), lo que impide realizar transacciones mientras los datos históricos aún se están cargando o la conexión está fuera de línea.
  • Todos los umbrales que se refieren a "puntos" se escalan automáticamente según el paso del precio del instrumento, imitando la constante Point de MetaTrader.
  • StartProtection convierte la toma de ganancias fija y el trailing stop de MetaTrader en órdenes de protección del lado del mercado. Habilite o deshabilite cada módulo cambiando el parámetro correspondiente.
  • El registro extenso (LogInfo) documenta cada decisión comercial, lo que simplifica la comparación con el asesor experto original durante la validación de la migración.

Consejos de uso

  • El EA original apunta a las principales divisas en marcos de tiempo intradiarios. Comience con símbolos similares y ajuste los parámetros si el instrumento utiliza un tamaño de marca diferente.
  • Al probar símbolos con valores de tick exóticos, verifique que Security.PriceStep esté configurado; de lo contrario, se utilizará el valor predeterminado 1.0.
  • Combínelo con las funciones de protección de cartera de StockSharp si necesita administración de dinero a nivel de cuenta más allá de las paradas por posición.

Etiquetas

  • Seguimiento de tendencias
  • Impulso
  • MACD cruce
  • Intradiario (5 minutos predeterminado)
  • Trailing stop + toma de ganancias
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 strategy that replicates the original MetaTrader "MACD Sample" expert advisor behaviour.
/// </summary>
public class MacdSampleClassicStrategy : Strategy
{

	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _trendMaPeriod;
	private readonly StrategyParam<decimal> _macdOpenLevel;
	private readonly StrategyParam<decimal> _macdCloseLevel;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _minimumHistoryCandles;

	private decimal _pointSize;
	private decimal? _prevMacd;
	private decimal? _prevSignal;
	private decimal? _trendMaCurrent;
	private decimal? _trendMaPrevious;
	private int _finishedCandles;
	private DateTimeOffset? _lastProcessedTime;

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

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

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

	/// <summary>
	/// Period of the trend EMA used as direction filter.
	/// </summary>
	public int TrendMaPeriod
	{
		get => _trendMaPeriod.Value;
		set => _trendMaPeriod.Value = value;
	}

	/// <summary>
	/// Threshold for MACD entries expressed in points (price steps).
	/// </summary>
	public decimal MacdOpenLevel
	{
		get => _macdOpenLevel.Value;
		set => _macdOpenLevel.Value = value;
	}

	/// <summary>
	/// Threshold for MACD exits expressed in points (price steps).
	/// </summary>
	public decimal MacdCloseLevel
	{
		get => _macdCloseLevel.Value;
		set => _macdCloseLevel.Value = value;
	}

	/// <summary>
	/// Take profit distance measured in price points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Trailing stop distance measured in price points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	/// <summary>
	/// Number of finished candles required before the strategy begins trading.
	/// </summary>
	public int MinimumHistoryCandles
	{
		get => _minimumHistoryCandles.Value;
		set => _minimumHistoryCandles.Value = value;
	}

	/// <summary>
	/// Initialize default parameters for the MACD Sample strategy.
	/// </summary>
	public MacdSampleClassicStrategy()
	{
		Volume = 1;

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
		.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators")
		
		.SetOptimize(6, 18, 2);

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
		.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators")
		
		.SetOptimize(20, 32, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
		.SetDisplay("Signal EMA", "Signal EMA period for MACD", "Indicators")
		
		.SetOptimize(5, 13, 2);

		_trendMaPeriod = Param(nameof(TrendMaPeriod), 26)
		.SetDisplay("Trend EMA", "EMA period used for directional filter", "Indicators")
		
		.SetOptimize(20, 40, 2);

		_macdOpenLevel = Param(nameof(MacdOpenLevel), 0m)
		.SetDisplay("MACD Open", "Entry threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 5m, 1m);

		_macdCloseLevel = Param(nameof(MacdCloseLevel), 0m)
		.SetDisplay("MACD Close", "Exit threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 4m, 1m);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
		.SetDisplay("Take Profit", "Take profit distance in price points", "Risk")
		
		.SetOptimize(20m, 100m, 10m);

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 30m)
		.SetDisplay("Trailing Stop", "Trailing stop distance in price points", "Risk")
		
		.SetOptimize(10m, 60m, 10m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Candle type used for analysis", "General");
		_minimumHistoryCandles = Param(nameof(MinimumHistoryCandles), 30)
			.SetDisplay("Warm-up candles", "Number of finished candles required before trading starts", "General")
			.SetGreaterThanZero();
	}

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

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

		_pointSize = 0m;
		_prevMacd = null;
		_prevSignal = null;
		_trendMaCurrent = null;
		_trendMaPrevious = null;
		_finishedCandles = 0;
		_lastProcessedTime = null;
	}

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

		_pointSize = Security?.PriceStep ?? 1m;

		// Configure indicators exactly as in the original expert advisor.
		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastEmaPeriod },
				LongMa = { Length = SlowEmaPeriod },
			},
			SignalMa = { Length = SignalPeriod }
		};

		var trendMa = new ExponentialMovingAverage { Length = TrendMaPeriod };

		// Subscribe to candles and bind indicators for automatic updates.
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(macd, ProcessMacdValues);
		subscription.Bind(trendMa, ProcessTrendMaValue);
		subscription.Start();

		// Visualize price, indicators and trades when a chart is available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, macd);
			DrawIndicator(area, trendMa);
			DrawOwnTrades(area);
		}

		var takeProfitDistance = TakeProfitPoints * _pointSize;
		var trailingDistance = TrailingStopPoints * _pointSize;

		if (takeProfitDistance > 0m || trailingDistance > 0m)
		{
			StartProtection(
			takeProfitDistance > 0m ? new Unit(takeProfitDistance, UnitTypes.Absolute) : null,
			trailingDistance > 0m ? new Unit(trailingDistance, UnitTypes.Absolute) : null,
			isStopTrailing: trailingDistance > 0m);
		}
	}

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

		// Keep current and previous EMA values for the directional filter.
		_trendMaPrevious = _trendMaCurrent;
		_trendMaCurrent = maValue;
	}

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

		// Avoid duplicate processing for the same candle.
		if (_lastProcessedTime != candle.OpenTime)
		{
			_lastProcessedTime = candle.OpenTime;
			_finishedCandles++;
		}

		// indicators checked below

		if (_finishedCandles < MinimumHistoryCandles)
		return;

		var macdSignal = (MovingAverageConvergenceDivergenceSignalValue)macdValue;

		if (macdSignal.Macd is not decimal macdCurrent ||
		macdSignal.Signal is not decimal signalCurrent)
		{
			return;
		}

		if (_prevMacd is not decimal macdPrevious ||
		_prevSignal is not decimal signalPrevious ||
		_trendMaCurrent is not decimal trendMaCurrent ||
		_trendMaPrevious is not decimal trendMaPrevious)
		{
			_prevMacd = macdCurrent;
			_prevSignal = signalCurrent;
			return;
		}

		var macdOpenThreshold = MacdOpenLevel * _pointSize;
		var macdCloseThreshold = MacdCloseLevel * _pointSize;

		// Determine trend direction using EMA slope.
		var isTrendUp = trendMaCurrent > trendMaPrevious;
		var isTrendDown = trendMaCurrent < trendMaPrevious;

		var buySignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdOpenThreshold &&
		isTrendUp;

		var sellSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdOpenThreshold &&
		isTrendDown;

		var exitLongSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdCloseThreshold;

		var exitShortSignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdCloseThreshold;

		if (buySignal && Position == 0m)
		{
			// MACD crossed up in negative territory and EMA confirms uptrend.
			BuyMarket();
			LogInfo($"Open long: MACD {macdCurrent:F5} above signal {signalCurrent:F5}.");
		}
		else if (sellSignal && Position == 0m)
		{
			// MACD crossed down in positive territory and EMA confirms downtrend.
			SellMarket();
			LogInfo($"Open short: MACD {macdCurrent:F5} below signal {signalCurrent:F5}.");
		}
		else if (exitLongSignal && Position > 0m)
		{
			// MACD crossed back below the signal line in positive zone - close long.
			SellMarket();
			LogInfo($"Close long: MACD {macdCurrent:F5} dropped under signal {signalCurrent:F5}.");
		}
		else if (exitShortSignal && Position < 0m)
		{
			// MACD crossed back above the signal line in negative zone - close short.
			BuyMarket();
			LogInfo($"Close short: MACD {macdCurrent:F5} rose above signal {signalCurrent:F5}.");
		}

		_prevMacd = macdCurrent;
		_prevSignal = signalCurrent;
	}
}