El "ADX EA" original de MetaTrader combina rupturas del Índice Direccional Medio con cruces de +DI/−DI, confirmación de momentum
en marco temporal superior y un filtro MACD mensual. El port en C# replica ese flujo de trabajo multi-filtro sobre la API de alto
nivel de StockSharp. La estrategia se suscribe a tres flujos de velas:
Marco temporal principal (por defecto 5 minutos) — impulsa ADX, medias móviles linealmente ponderadas, comprobaciones de
estructura de precio y filtros de volumen.
Marco temporal de momentum (por defecto 15 minutos) — produce las desviaciones de momentum alrededor de la línea base 100
que condicionan las entradas.
Marco temporal de MACD (por defecto 30 días) — refleja el MACD mensual que controla las salidas de posición.
Lógica de Trading
Módulo de ruptura – Cuando está habilitado, las operaciones largas requieren:
ADX o +DI por encima de EntryLevel y la brecha entre +DI y −DI mayor que MinDirectionalDifference.
La LWMA rápida por encima de la LWMA lenta, estructura de vela alcista (Low[2] < High[1]) y momentum creciente
(Momentum[1] > Momentum[2]).
Al menos una de las últimas tres lecturas de momentum en el marco temporal superior que se desvíe de 100 en más de
MomentumBuyThreshold.
Volumen creciente en el marco temporal principal (Volume[1] > Volume[2] o Volume[1] > Volume[3]).
MACD en el marco temporal mensual alcista (MacdMain[1] > MacdSignal[1]).
ADX por encima de ExitLevel para confirmar la fortaleza general del trend.
Las rupturas cortas aplican la lógica simétrica con dominancia de −DI, estructura bajista (Low[1] < High[2]), momentum por
debajo de 100 en MomentumSellThreshold y una comparación MACD bajista.
Módulo de cruce – Cuando está activo, busca que +DI cruce por encima de −DI (largos) o −DI cruce por encima de +DI
(cortos). Los filtros opcionales reflejan el EA original:
RequireAdxSlope exige que ADX sea mayor que la lectura anterior.
ConfirmCrossOnBreakout añade las mismas verificaciones de umbral de ruptura en la barra de cruce.
MinAdxMainLine impone una fortaleza mínima de ADX durante el cruce.
La alineación de LWMA, la pendiente de momentum, la expansión de volumen y la polaridad de MACD aún deben estar de acuerdo
con la dirección prevista.
Piramidación – Cada nueva orden añade volumen según LotExponent. La estrategia trata TradeVolume como el tamaño de lote
base y lo incrementa por LotExponent^n, donde n es el número de escalones ya abiertos. MaxTrades limita la cantidad de
volumen neto que se puede acumular.
Gestión de Riesgos
Órdenes protectoras – TakeProfitSteps y StopLossSteps se pasan a StartProtection y se expresan en pasos de precio
del instrumento.
Trailing stop – TrailingStopSteps mantiene una barrera de trailing manual más allá del mejor precio de cierre.
Punto de equilibrio – Cuando UseBreakEven está habilitado, el stop se ajusta después de que el precio avanza
BreakEvenTrigger pasos y puede desplazar el stop BreakEvenOffset pasos.
Salida por MACD – Cuando EnableMacdExit es verdadero, la relación MACD mensual cierra los largos cuando MACD cae por
debajo de su señal (y viceversa para los cortos), coincidiendo con las rutinas Close_BUY/Close_SELL del EA.
Stop de capital – UseEquityStop rastrea la curva de beneficio flotante y liquida posiciones una vez que el drawdown
alcanza TotalEquityRisk por ciento.
Las funciones que dependían de objetivos en divisa de la cuenta ("Take Profit in Money", "Trailing Profit in Money", etc.) no
están portadas porque las estrategias de StockSharp típicamente gestionan la lógica de protección a través de distancias de stop
y el servicio de protección integrado. Todos los demás puntos de decisión del EA se preservan con equivalentes de indicadores.
Parámetros
Parámetro
Valor predeterminado
Descripción
TradeVolume
0.01
Tamaño de lote base para la primera entrada.
CandleType
Marco temporal de 5m
Serie de velas principal para la lógica ADX/LWMA.
MomentumCandleType
Marco temporal de 15m
Marco temporal superior para el filtro de desviación de momentum.
MacdCandleType
Marco temporal de 30 días
Marco temporal que alimenta el filtro de salida MACD.
FastMaPeriod
6
Longitud de la media móvil linealmente ponderada rápida.
SlowMaPeriod
85
Longitud de la media móvil linealmente ponderada lenta.
AdxPeriod
14
Período del Índice Direccional Medio.
MomentumPeriod
14
Período del indicador de momentum en el marco temporal superior.
MacdFastPeriod
12
Período de EMA rápido dentro del filtro de salida MACD.
MacdSlowPeriod
26
Período de EMA lento dentro del filtro de salida MACD.
MacdSignalPeriod
9
Período de SMA de señal dentro del filtro de salida MACD.
EnableBreakoutStrategy
true
Interruptor para la rama de ruptura ADX.
EnableCrossStrategy
true
Interruptor para la rama de cruce DI.
UseTrendFilter
true
Impone dominancia de +DI para largos y −DI para cortos durante las rupturas.
RequireAdxSlope
true
Requiere que ADX suba al evaluar cruces DI.
ConfirmCrossOnBreakout
true
Añade umbrales de ruptura al módulo de cruce.
EnableMacdExit
true
Habilita la rutina de salida basada en MACD.
EntryLevel
10
Nivel mínimo de ADX/+DI/−DI usado por las rupturas.
ExitLevel
10
Fortaleza mínima de ADX que permite nuevas entradas.
MinDirectionalDifference
10
Brecha requerida entre +DI y −DI.
MinAdxMainLine
10
Nivel mínimo de ADX durante los cruces DI.
MomentumBuyThreshold
0.3
Desviación requerida desde 100 para la confirmación de momentum alcista.
MomentumSellThreshold
0.3
Desviación requerida desde 100 para la confirmación de momentum bajista.
MaxTrades
10
Número máximo de escalones de piramidación.
LotExponent
1.44
Multiplicador de volumen para cada escalón adicional.
TakeProfitSteps
50
Distancia, en pasos de precio, para la orden de take-profit.
StopLossSteps
20
Distancia, en pasos de precio, para la orden de stop-loss.
TrailingStopSteps
40
Distancia del trailing stop manual en pasos de precio.
UseBreakEven
true
Activa la lógica de reubicación del punto de equilibrio.
BreakEvenTrigger
30
Pasos de movimiento favorable requeridos antes de armar el punto de equilibrio.
BreakEvenOffset
30
Pasos adicionales añadidos al precio de entrada al mover el stop.
UseEquityStop
true
Habilita la salida de emergencia basada en drawdown.
TotalEquityRisk
1
Porcentaje de drawdown permitido antes de cerrar todas las posiciones.
Consejos de Uso
Alinee MomentumCandleType y MacdCandleType con su marco temporal principal para imitar el mapeo de marcos temporales
original (p. ej., gráfico de 5 minutos → momentum de 15 minutos → MACD mensual).
Ajuste EntryLevel, MinDirectionalDifference y MinAdxMainLine juntos; bajar los tres afloja considerablemente el filtro
de ruptura.
LotExponent mayor que 1.0 recrea el escalado estilo martingala del EA. Configúrelo en 1.0 para mantener constantes los
tamaños de posición.
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;
/// <summary>
/// ADX EA strategy using EMA crossover with ADX trend strength filter.
/// Buys when fast EMA crosses above slow EMA with strong trend.
/// Sells on reverse crossover.
/// </summary>
public class AdxEaStrategy : 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;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public AdxEaStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
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;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// EMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 60;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 60;
}
_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 adx_ea_strategy(Strategy):
def __init__(self):
super(adx_ea_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 50) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 200) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit distance 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(adx_ea_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(adx_ea_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
# Check SL/TP
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 = 60
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 = 60
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 = 60
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 = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# EMA crossover
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 = 60
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 = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return adx_ea_strategy()