Ver en GitHub

Estrategia de Confirmación de Tendencia de MA Dual

Descripción general

La Estrategia de Confirmación de Tendencia de MA Dual replica el experto original de MetaTrader que combina una media móvil exponencial lenta (EMA) con una media móvil ponderada linealmente rápida (LWMA). El sistema espera a que ambas medias móviles se alineen en la misma dirección y usa el cierre de la vela anterior como confirmación adicional antes de entrar en una posición. La idea es participar solo en fuertes oscilaciones de impulso cuando el filtro de tendencia lenta y el filtro de confirmación rápida simultáneamente se inclinan hacia arriba o hacia abajo.

La implementación de StockSharp procesa solo las velas completamente terminadas, rastrea la pendiente de cada media móvil durante las últimas tres barras, y gestiona automáticamente las órdenes protectoras mediante el mecanismo integrado StartProtection. La estrategia es agnóstica al instrumento: puede operar en cualquier valor y marco temporal que proporcionen velas y soporten el concepto de "puntos" mediante el paso de precio del instrumento.

Indicadores

  • EMA lenta – Período predeterminado 57. Representa la dirección de la tendencia dominante. La estrategia requiere que la EMA aumente (o disminuya) durante dos velas consecutivas antes de operar.
  • LWMA rápida – Período predeterminado 3. Actúa como filtro de confirmación de impulso. Su pendiente debe coincidir con la EMA lenta, reforzando que el impulso apoya la tendencia.

Parámetros

Parámetro Valor predeterminado Descripción
SlowMaLength 57 Período del filtro de tendencia EMA lenta.
FastMaLength 3 Período del filtro de confirmación LWMA rápida.
StopLossPoints 100 Distancia de stop protector expresada en puntos del instrumento (multiplicado por Security.PriceStep).
TakeProfitPoints 100 Distancia de take-profit expresada en puntos del instrumento (multiplicado por Security.PriceStep).
CandleType Marco temporal de 15 minutos Tipo de datos de vela usado para todos los cálculos.

Todos los parámetros se exponen como valores StrategyParam<T> para que puedan modificarse en tiempo de ejecución o optimizarse a través de las herramientas de optimización de StockSharp.

Reglas de trading

Setup largo

  1. La EMA lenta está subiendo: valor actual > valor anterior > valor hace dos velas.
  2. La LWMA rápida está subiendo: valor actual > valor anterior > valor hace dos velas.
  3. El cierre de la vela anterior está por encima del valor anterior de la EMA lenta.
  4. El valor actual de la EMA lenta está por encima del valor actual de la LWMA rápida.
  5. La posición actual es plana o corta.
  6. Cuando todas las condiciones se cumplen, la estrategia envía una orden de compra de mercado por Volume + |Position| para volcar a una posición larga.

Setup corto

  1. La EMA lenta está cayendo: valor actual < valor anterior < valor hace dos velas.
  2. La LWMA rápida está cayendo: valor actual < valor anterior < valor hace dos velas.
  3. El cierre de la vela anterior está por debajo del valor anterior de la EMA lenta.
  4. El valor actual de la EMA lenta está por debajo del valor actual de la LWMA rápida.
  5. La posición actual es plana o larga.
  6. Cuando todas las condiciones se cumplen, la estrategia envía una orden de venta de mercado por Volume + |Position| para volcar a una posición corta.

Lógica protectora

  • StartProtection convierte StopLossPoints y TakeProfitPoints en desplazamientos de precio absoluto multiplicándolos con Security.PriceStep. Las órdenes de stop-loss y take-profit se emiten como salidas de mercado para que el motor pueda cerrar la posición incluso si las órdenes límite no están soportadas.
  • Cuando aparece la señal opuesta, la estrategia invierte inmediatamente la posición independientemente de las órdenes protectoras.

Detalles de implementación

  • Solo se procesan las velas terminadas, emulando la comprobación de nueva barra de la versión MQL original.
  • La estrategia mantiene los últimos dos valores de la media móvil y el precio de cierre anterior en campos privados para evitar búsquedas en el historial del indicador.
  • IsFormedAndOnlineAndAllowTrading() asegura que el trading ocurra solo cuando todos los flujos de datos estén activos y se permita el trading.
  • Los registros de dirección de trade (LogInfo) proporcionan transparencia para depuración y monitoreo en vivo.
  • La renderización de gráficos (si está disponible) dibuja las velas y ambas medias móviles para una rápida validación visual.

Notas de uso

  • Elija Volume según el tamaño del lote del instrumento. La estrategia siempre envía órdenes de mercado de tamaño Volume + |Position| para revertir eficientemente.
  • Al ejecutar en instrumentos sin un PriceStep definido, el código recurre a un valor de 1. Ajuste los parámetros en consecuencia si el tamaño del tick difiere.
  • La optimización puede enfocarse en los períodos de la media móvil y las distancias protectoras para adaptar la estrategia a diferentes mercados.
  • Combine con filtros adicionales (volatilidad, horarios de sesión, etc.) si es necesario. La estructura modular facilita su extensión.

Rangos de optimización sugeridos

  • SlowMaLength: 20 – 120 con paso 5–10.
  • FastMaLength: 2 – 10 con paso 1.
  • StopLossPoints / TakeProfitPoints: 50 – 200 dependiendo de la volatilidad del instrumento.

Estos rangos reflejan de cerca la configuración original del experto mientras brindan flexibilidad para otros instrumentos.

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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual moving average trend confirmation strategy.
/// Uses a slow EMA and a fast LWMA to detect synchronized trends.
/// Enters long when both averages slope upward, price stays above the slow EMA, and the slow EMA is above the fast LWMA.
/// Enters short when both averages slope downward, price stays below the slow EMA, and the slow EMA is below the fast LWMA.
/// Built-in stop-loss and take-profit are defined in instrument points.
/// </summary>
public class DualMaTrendConfirmationStrategy : Strategy
{
	private readonly StrategyParam<int> _slowMaLength;
	private readonly StrategyParam<int> _fastMaLength;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _previousClose;
	private decimal _slowPrevious;
	private decimal _slowPrevious2;
	private decimal _fastPrevious;
	private decimal _fastPrevious2;
	private int _historyCount;

	/// <summary>
	/// Slow EMA period length.
	/// </summary>
	public int SlowMaLength
	{
		get => _slowMaLength.Value;
		set => _slowMaLength.Value = value;
	}

	/// <summary>
	/// Fast LWMA period length.
	/// </summary>
	public int FastMaLength
	{
		get => _fastMaLength.Value;
		set => _fastMaLength.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 used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="DualMaTrendConfirmationStrategy"/> class.
	/// </summary>
	public DualMaTrendConfirmationStrategy()
	{
		_slowMaLength = Param(nameof(SlowMaLength), 57)
			.SetDisplay("Slow EMA Length", "Period for the slow EMA trend filter", "Moving Averages")
			.SetRange(10, 200)
			;

		_fastMaLength = Param(nameof(FastMaLength), 3)
			.SetDisplay("Fast LWMA Length", "Period for the fast LWMA confirmation filter", "Moving Averages")
			.SetRange(1, 50)
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 100m)
			.SetDisplay("Stop Loss (points)", "Stop-loss distance measured in instrument points", "Risk Management")
			.SetRange(10m, 500m)
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m)
			.SetDisplay("Take Profit (points)", "Take-profit distance measured in instrument points", "Risk Management")
			.SetRange(10m, 500m)
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for moving average calculations", "General");
	}

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

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

		// Clear stored history so the next candle starts with a clean state.
		_previousClose = 0m;
		_slowPrevious = 0m;
		_slowPrevious2 = 0m;
		_fastPrevious = 0m;
		_fastPrevious2 = 0m;
		_historyCount = 0;
	}

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

		var slowEma = new ExponentialMovingAverage
		{
			Length = SlowMaLength
		};

		var fastLwma = new WeightedMovingAverage
		{
			Length = FastMaLength
		};

		var subscription = SubscribeCandles(CandleType);

		var step = Security.PriceStep ?? 1m;

		// Enable automatic stop-loss and take-profit management based on point offsets.
		StartProtection(
			takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute),
			useMarketOrders: true);

		subscription
			.Bind(slowEma, fastLwma, (candle, slowValue, fastValue) => ProcessCandle(candle, slowValue, fastValue, slowEma, fastLwma))
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal slowValue, decimal fastValue, ExponentialMovingAverage slowEma, WeightedMovingAverage fastLwma)
	{
		// Work only with fully formed candles to avoid premature decisions.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure both indicators produced reliable values before trading logic.
		if (!slowEma.IsFormed || !fastLwma.IsFormed)
		{
			UpdateHistory(slowValue, fastValue, candle.ClosePrice);
			return;
		}

		// Accumulate at least two previous candles for slope calculations.
		if (_historyCount < 2)
		{
			UpdateHistory(slowValue, fastValue, candle.ClosePrice);
			return;
		}

		var slowRising = slowValue > _slowPrevious && _slowPrevious > _slowPrevious2;
		var fastRising = fastValue > _fastPrevious && _fastPrevious > _fastPrevious2;
		var slowFalling = slowValue < _slowPrevious && _slowPrevious < _slowPrevious2;
		var fastFalling = fastValue < _fastPrevious && _fastPrevious < _fastPrevious2;
		var priceAboveSlow = _previousClose > _slowPrevious;
		var priceBelowSlow = _previousClose < _slowPrevious;
		var slowAboveFast = slowValue > fastValue;
		var slowBelowFast = slowValue < fastValue;

		if (slowRising && fastRising && priceAboveSlow && slowAboveFast && Position <= 0)
		{
			BuyMarket();
		}
		else if (slowFalling && fastFalling && priceBelowSlow && slowBelowFast && Position >= 0)
		{
			SellMarket();
		}

		UpdateHistory(slowValue, fastValue, candle.ClosePrice);
	}

	private void UpdateHistory(decimal slowValue, decimal fastValue, decimal closePrice)
	{
		// Shift previous values so the last two candles are always available.
		_slowPrevious2 = _slowPrevious;
		_slowPrevious = slowValue;
		_fastPrevious2 = _fastPrevious;
		_fastPrevious = fastValue;
		_previousClose = closePrice;

		if (_historyCount < 2)
			_historyCount++;
	}
}