La estrategia de Elli transfiere el MetaTrader 4 asesor experto "Elli" al StockSharp nivel alto API. El robot original combinó la estructura Ichimoku Kinko Hyo en el período de tiempo H1 con un filtro de período de tiempo más bajo ADX y parámetros de riesgo estrictos. La conversión mantiene la misma lógica direccional, reemplaza la gestión manual de pedidos con StartProtection y expone cada perilla de ajuste como un StrategyParam<T> optimizable para que el comportamiento se pueda adaptar a diferentes mercados.
Lógica de trading
Ichimoku estructura de tendencia
La estrategia se suscribe al marco de tiempo definido por CandleType (H1 por defecto) y calcula los tramos Tenkan-sen, Kijun-sen y Senkou utilizando los períodos originales (19, 60, 120).
Una configuración alcista requiere Tenkan > Kijun > Senkou Span A > Senkou Span B con la vela cerca de Kijun. Las configuraciones bajistas reflejan esta condición.
La distancia absoluta entre Tenkan y Kijun debe exceder los TenkanKijunGapPips pips para evitar nubes planas o de gran alcance.
Confirmación de movimiento direccional
Una segunda suscripción de vela ejecuta el índice direccional promedio en el período de tiempo especificado por AdxCandleType (M1 de forma predeterminada).
Las señales largas solo se permiten cuando el valor +DI anterior está por debajo de ConvertLow y el +DI actual supera ConvertHigh. Los cortos requieren la misma relación para el componente −DI, replicando el filtro de aceleración presente en el código MT4.
Ejecución de entrada
Cuando todos los filtros se alinean, la estrategia emite una orden de mercado con volumen OrderVolume + |Position|. Esto cierra automáticamente cualquier exposición opuesta antes de unirse a la tendencia.
Sólo se mantiene una exposición direccional a la vez, siguiendo la guardia original OrdersTotal() < 1.
Gestión de riesgos
StartProtection adjunta órdenes de stop loss simétricas y toma de ganancias convertidas a partir de distancias de pips utilizando el tamaño de pip del instrumento.
Por lo demás, la posición se gestiona de forma pasiva, permitiendo que las órdenes de protección manejen las salidas como el asesor experto MT4.
Indicadores y Suscripciones de Datos
Velas primarias: CandleType (velas predeterminadas de 1 hora) para el procesamiento de Ichimoku.
ADX velas: AdxCandleType (velas predeterminadas de 1 minuto) para comprobaciones de aceleración DI.
Indicadores: Ichimoku (Tenkan, Kijun, Senkou Span B) y AverageDirectionalIndex (que proporcionan +DI/−DI).
Ambas suscripciones admiten la representación de gráficos a través de DrawCandles, DrawIndicator y DrawOwnTrades si hay un área de gráfico disponible.
Parámetros
Nombre
Predeterminado
Descripción
OrderVolume
1
Volumen de orden de mercado base.
TakeProfitPips
60
Distancia de toma de ganancias expresada en pips.
StopLossPips
30
Distancia de stop-loss expresada en pips.
TenkanPeriod
19
Período Tenkan-sen para el indicador Ichimoku.
KijunPeriod
60
Período Kijun-sen para el indicador Ichimoku.
SenkouSpanBPeriod
120
Período Senkou Span B para la nube Ichimoku.
TenkanKijunGapPips
20
Distancia mínima Tenkan/Kijun (en pips) requerida antes de operar.
ConvertHigh
13
Umbral DI que el valor actual debe superar para confirmar el impulso.
ConvertLow
6
Umbral DI por debajo del cual el valor anterior debe permanecer antes de realizar una nueva operación.
AdxPeriod
10
Período utilizado para el cálculo ADX.
CandleType
H1
Periodo de tiempo que impulsa el cálculo de Ichimoku.
AdxCandleType
M1
Plazo utilizado para ADX y seguimiento de DI.
Todos los parámetros se implementan con StrategyParam<T> ayudantes, lo que permite la optimización y ajustes de tiempo de ejecución dentro de StockSharp Designer.
Notas de implementación
La conversión de pips sigue la convención estándar de Forex (0,0001 para cotizaciones de 5 dígitos y 0,01 para instrumentos de 3 dígitos) para preservar los umbrales originales basados en pips.
Los valores ADX se almacenan en caché en _latestPlusDi, _previousPlusDi, _latestMinusDi y _previousMinusDi, lo que garantiza que la verificación de aceleración DI coincida con las llamadas MQL iADX con los turnos 0 y 1.
IsFormedAndOnlineAndAllowTrading() bloquea las señales hasta que la estrategia, los indicadores y las fuentes de datos estén listos, evitando operaciones prematuras durante el calentamiento.
Las entradas al mercado dependen de Volume + Math.Abs(Position), de modo que los cambios de dirección aplanan instantáneamente las operaciones existentes, emulando el comportamiento de posición única del script MT4.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Elli: EMA crossover with ATR momentum confirmation.
/// Fast EMA above slow EMA = bullish, below = bearish.
/// Entry when ATR expansion confirms trend strength.
/// </summary>
public class ElliStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _prevAtr;
private decimal _entryPrice;
public ElliStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastLength = Param(nameof(FastLength), 19)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowLength = Param(nameof(SlowLength), 60)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for momentum.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_prevAtr = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_prevAtr = 0;
_entryPrice = 0;
var fast = new ExponentialMovingAverage { Length = FastLength };
var slow = new ExponentialMovingAverage { Length = SlowLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
_prevAtr = atrVal;
return;
}
var close = candle.ClosePrice;
// Exit: stop or take based on ATR
if (Position > 0)
{
if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 3m)
{
SellMarket();
_entryPrice = 0;
}
else if (fastVal < slowVal)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 3m)
{
BuyMarket();
_entryPrice = 0;
}
else if (fastVal > slowVal)
{
BuyMarket();
_entryPrice = 0;
}
}
// Entry: EMA crossover with ATR expansion
if (Position == 0)
{
var atrRising = atrVal > _prevAtr;
if (_prevFast <= _prevSlow && fastVal > slowVal && atrRising)
{
_entryPrice = close;
BuyMarket();
}
else if (_prevFast >= _prevSlow && fastVal < slowVal && atrRising)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
_prevAtr = atrVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class elli_strategy(Strategy):
def __init__(self):
super(elli_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_length = self.Param("FastLength", 19) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_length = self.Param("SlowLength", 60) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for momentum", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_atr = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastLength(self):
return self._fast_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(elli_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_atr = 0.0
self._entry_price = 0.0
self._fast = ExponentialMovingAverage()
self._fast.Length = self.FastLength
self._slow = ExponentialMovingAverage()
self._slow.Length = self.SlowLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast, self._slow, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
self._prev_atr = av
return
close = float(candle.ClosePrice)
# Exit: stop or take based on ATR
if self.Position > 0:
if close <= self._entry_price - av * 2.0 or close >= self._entry_price + av * 3.0:
self.SellMarket()
self._entry_price = 0.0
elif fv < sv:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close >= self._entry_price + av * 2.0 or close <= self._entry_price - av * 3.0:
self.BuyMarket()
self._entry_price = 0.0
elif fv > sv:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = fv
self._prev_slow = sv
self._prev_atr = av
return
# Entry: EMA crossover with ATR expansion
if self.Position == 0:
atr_rising = av > self._prev_atr
if self._prev_fast <= self._prev_slow and fv > sv and atr_rising:
self._entry_price = close
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fv < sv and atr_rising:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
self._prev_atr = av
def OnReseted(self):
super(elli_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_atr = 0.0
self._entry_price = 0.0
def CreateClone(self):
return elli_strategy()