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
La pendiente actual de EMA (EMA[0] - EMA[1]) es positiva.
La pendiente previa de EMA (EMA[1] - EMA[2]) también es positiva, señalando aceleración.
El cierre de la vela anterior está por encima del valor EMA anterior.
La línea principal ADX es mayor que el umbral configurado.
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
La pendiente actual de EMA es negativa.
La pendiente previa de EMA también es negativa.
El cierre de la vela anterior está por debajo del valor EMA anterior.
La línea principal ADX es mayor que el umbral.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_crossover_adx_strategy(Strategy):
def __init__(self):
super(ma_crossover_adx_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(ma_crossover_adx_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(ma_crossover_adx_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return ma_crossover_adx_strategy()