Estrategia Eliot Wave (adaptada desde MQL4 "Eliot Wave I")
Descripción general
La estrategia Eliot Wave es una adaptación a la API de StockSharp del asesor experto original de MetaTrader 4 "Eliot Wave I". El sistema combina un cruce rápido/lento de media móvil ponderada lineal (LWMA) con confirmación de momentum multitemporal y un filtro MACD muy lento. El objetivo es identificar movimientos impulsivos en la dirección de la tendencia predominante, manteniendo el riesgo limitado mediante reglas de protección incorporadas.
Indicadores principales
LWMA rápida (predeterminado 6) - rastrea la dirección de corto plazo usando el precio típico (High + Low + Close) / 3.
LWMA lenta (predeterminado 85) - mide la tendencia más amplia en el mismo marco temporal.
Momentum (período predeterminado 14) - se evalúa en un marco temporal superior y se convierte en una desviación respecto al nivel neutral 100. Una lectura por encima del umbral configurado indica un impulso suficientemente fuerte.
MACD (12, 26, 9) - se calcula en un marco temporal muy lento (mensual por defecto) y se usa como filtro de largo plazo. La estrategia solo compra cuando la línea principal MACD está por encima de la línea de señal y vende cuando está por debajo.
Parámetros
Nombre
Descripción
Predeterminado
Base Candle
Marco temporal principal para el procesamiento LWMA.
Velas de 15 minutos
Momentum Candle
Marco temporal superior usado para la confirmación de momentum.
Velas de 1 hora
MACD Candle
Marco temporal muy lento para el filtro de tendencia MACD.
Velas de 30 días
Fast LWMA
Longitud de la media móvil ponderada lineal rápida.
6
Slow LWMA
Longitud de la media móvil ponderada lineal lenta.
85
Momentum Period
Período retrospectivo del indicador de momentum en el marco temporal de confirmación.
14
Momentum Buy Threshold
Desviación mínima por encima de 100 necesaria para validar una configuración larga.
0.3
Momentum Sell Threshold
Desviación mínima por encima de 100 necesaria para validar una configuración corta.
0.3
Stop Loss (pts)
Distancia del stop de protección expresada en puntos del instrumento.
20
Take Profit (pts)
Distancia del objetivo expresada en puntos del instrumento.
50
Trade Volume
Tamaño de orden para cada entrada.
1 lote
Max Position
Exposición neta absoluta permitida; evita que la estrategia supere el límite Max_Trades del EA MQL.
10 lotes
Todos los parámetros están implementados como StrategyParam<T> para que puedan optimizarse directamente en Designer o Runner.
Reglas de trading
Filtro de tendencia y estructura
La LWMA rápida debe permanecer por encima de la LWMA lenta para considerar operaciones largas.
La LWMA rápida debe permanecer por debajo de la LWMA lenta para considerar cortos.
Las dos últimas velas completadas deben solaparse (Low[2] < High[1] para compras, Low[1] < High[2] para ventas), replicando el requisito de consolidación del EA.
Confirmación de momentum
El momentum del marco temporal superior se transforma en valores abs(momentum - 100).
Si cualquiera de los tres últimos valores supera el umbral configurado, el impulso se considera válido.
Filtro de tendencia macro
Las compras requieren que la línea principal MACD esté por encima de la línea de señal en el marco temporal lento.
Las ventas requieren que la línea principal MACD esté por debajo de la línea de señal.
Ejecución de órdenes
Cuando todas las condiciones coinciden, la estrategia envía una orden de mercado dimensionada para invertir la posición actual y añadir el volumen de operación configurado.
Se admiten giros de posición para que el comportamiento coincida con la lógica de promediado del EA original.
Gestión de riesgos
StartProtection aplica automáticamente distancias de stop-loss y take-profit en puntos del instrumento.
La lógica adicional de salida cierra posiciones largas cuando la LWMA rápida cae por debajo de la LWMA lenta o cuando el filtro MACD se vuelve bajista (y viceversa para cortos). Esto refleja los bloques de salida MQL.
El parámetro Max Position evita que la estrategia acumule exposición más allá del límite configurado, respetando la restricción Max_Trades del EA.
Diferencias con el EA original
Las comprobaciones gráficas de líneas de tendencia y las notificaciones manuales de operación se eliminaron porque son específicas de MetaTrader y no tienen equivalente en StockSharp.
Las variantes de break-even y trailing-stop complejo del script MQL se sustituyen por el mecanismo más simple StartProtection. Los usuarios pueden ampliar la estrategia si necesitan esos comportamientos.
La protección de equity basada en dinero no está implementada; el riesgo se controla mediante stops fijos y el límite de posición.
Notas de uso
Conecte la estrategia a un instrumento líquido y asegúrese de que los tres flujos de velas estén disponibles.
Ajuste Trade Volume, las distancias de stop/objetivo y los umbrales según la volatilidad del mercado operado.
Optimice los umbrales por separado para impulsos alcistas y bajistas si el instrumento muestra comportamiento asimétrico.
Considere habilitar las visualizaciones incorporadas del gráfico (velas, LWMAs, marcadores de operaciones) para depurar con mayor facilidad.
Esta adaptación se centra en reproducir la lógica de señales del EA original usando la API de alto nivel de StockSharp, manteniendo una implementación idiomática y fácil de mantener.
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 EliotWaveStrategy : 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 EliotWaveStrategy()
{
_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 eliot_wave_strategy(Strategy):
def __init__(self):
super(eliot_wave_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(eliot_wave_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(eliot_wave_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 eliot_wave_strategy()