Ver en GitHub

Estrategia MA Crossover ADX

Visión general

La estrategia MA Crossover ADX es un port directo del asesor experto de MetaTrader MA_Crossover_ADX. Combina la pendiente de una media móvil exponencial (EMA) con confirmación del Average Directional Index (ADX) para participar solo en entornos tendenciales. La implementación StockSharp procesa velas completadas de un marco configurable y sincroniza actualizaciones de EMA y ADX antes de emitir señales. Las distancias protectoras de stop loss y take profit se adjuntan automáticamente a cada nueva posición mediante los parámetros de riesgo basados en puntos.

Indicadores y datos

  • Media móvil exponencial (EMA): actúa como filtro principal de tendencia. La estrategia sigue los tres últimos valores de EMA para calcular dos pendientes consecutivas, imitando las comprobaciones StateEMA(0) y StateEMA(1) del EA original.
  • Average Directional Index (ADX): proporciona la línea principal de fuerza de tendencia y los indicadores direccionales positivo/negativo (DI+/DI-). El diferencial entre DI+ y DI- replica la condición StateADX(0) del EA, mientras que la línea principal exige una fuerza mínima.
  • Serie de precios de cierre: el cierre de la vela anterior se compara con la EMA anterior para asegurar que el mercado se separó de la media antes de tomar la entrada.

Todos los indicadores operan sobre la misma suscripción de velas, lo que garantiza que los valores de EMA y ADX estén finalizados para la misma barra exacta antes de tomar decisiones.

Lógica de negociación

Entrada larga

  1. La pendiente actual de EMA (EMA[0] - EMA[1]) es positiva.
  2. La pendiente previa de EMA (EMA[1] - EMA[2]) también es positiva, señalando aceleración.
  3. El cierre de la vela anterior está por encima del valor EMA anterior.
  4. La línea principal ADX es mayor que el umbral configurado.
  5. DI+ supera a DI-, indicando dominio direccional alcista.

Cuando todas las reglas se alinean y no hay posición abierta, la estrategia envía una orden de compra a mercado con el volumen configurado. Si existe una posición corta, se cierra tan pronto como aparecen las condiciones alcistas.

Entrada corta

  1. La pendiente actual de EMA es negativa.
  2. La pendiente previa de EMA también es negativa.
  3. El cierre de la vela anterior está por debajo del valor EMA anterior.
  4. La línea principal ADX es mayor que el umbral.
  5. DI- supera a DI+, resaltando momentum bajista.

Se coloca una orden de venta a mercado cuando las cinco condiciones se cumplen y la estrategia está plana. Las posiciones largas abiertas se cierran de inmediato si aparecen los filtros bajistas.

Reglas de salida

  • Posiciones largas: salir cuando se materializan las condiciones de entrada corta, asegurando que el sistema abandone los largos cuando el momentum del mercado gira a la baja.
  • Posiciones cortas: salir cuando se materializan las condiciones de entrada larga.
  • Órdenes protectoras: StartProtection adjunta órdenes de stop loss y take profit calculadas desde el PriceStep del instrumento multiplicado por las distancias configuradas en puntos. Estas órdenes siguen la posición activa según el motor nativo de órdenes protectoras de StockSharp.

Parámetros

Nombre Predeterminado Descripción
AdxPeriod 33 Número de barras usadas para calcular ADX.
AdxThreshold 22 Valor mínimo de la línea principal ADX requerido para validar una tendencia.
EmaPeriod 39 Longitud de la EMA usada para detectar pendiente.
StopLossPoints 400 Distancia de stop loss medida en puntos del instrumento (multiplicada por PriceStep).
TakeProfitPoints 900 Distancia de take profit medida en puntos del instrumento.
TradeVolume 0.1 Volumen enviado con cada nueva orden de mercado.
CandleType Marco de 1 hora Tipo de vela que alimenta todos los cálculos de indicadores.

Notas de uso

  • Asegúrese de que el instrumento proporcione un PriceStep válido. Si no hay paso disponible, la estrategia usa 1 punto por defecto para poder calcular las órdenes protectoras.
  • Los parámetros son aptos para optimización mediante SetCanOptimize(true), lo que permite backtesting u optimización con distintas combinaciones de EMA/ADX.
  • Todos los comentarios de la implementación C# están escritos intencionalmente en inglés, según las directrices del proyecto.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class MaCrossoverAdxStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public MaCrossoverAdxStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}