Ver en GitHub

Estrategia Elli Ichimoku ADX

Descripción general

La estrategia es un port en C# del experto MetaTrader 5 "Elli" (edición de barabashkakvn). Combina la estructura de Ichimoku Kinko Hyo con un filtro de ruptura del Average Directional Index (+DI). Las operaciones se abren solo cuando un fuerte impulso direccional es confirmado simultáneamente por la alineación de líneas de Ichimoku y un repentino aumento en el índice de dirección positiva.

La implementación de StockSharp mantiene el comportamiento original de trabajar con dos flujos de velas: el análisis de Ichimoku se realiza en un marco temporal superior (predeterminado 1 hora) mientras que ADX se evalúa en una serie más rápida (predeterminado 1 minuto). Las órdenes se ingresan con un stop protector fijo y objetivo medidos en pasos de precio, idénticos al asesor experto original.

Indicadores y datos

  • Ichimoku (Tenkan 19, Kijun 60, Senkou Span B 120 por defecto).
  • Average Directional Index (ADX), solo se usa la línea +DI como en el código fuente.
  • Las áreas de gráfico opcionales muestran la serie de velas, la nube de Ichimoku y la línea ADX.

Se crean dos suscripciones de velas independientes:

  1. IchimokuCandleType (predeterminado 1 hora) – impulsa los cálculos de Ichimoku y genera decisiones de trading.
  2. AdxCandleType (predeterminado 1 minuto) – alimenta el indicador ADX y suministra valores +DI actuales/anteriores.

Parámetros

Parámetro Predeterminado Descripción
TakeProfitPoints 60 Distancia de take profit en pasos de precio. Establecer en 0 para deshabilitar.
StopLossPoints 30 Distancia de stop loss en pasos de precio. Establecer en 0 para deshabilitar.
TenkanPeriod 19 Longitud de la línea Tenkan-sen (línea de conversión) de Ichimoku.
KijunPeriod 60 Longitud de la línea Kijun-sen (línea base) de Ichimoku.
SenkouSpanBPeriod 120 Longitud de la línea Senkou Span B de Ichimoku.
AdxPeriod 10 Período para el indicador ADX.
PlusDiHighThreshold 13 Umbral que el valor actual +DI debe superar.
PlusDiLowThreshold 6 Umbral que el valor anterior +DI debe permanecer por debajo.
BaselineDistanceThreshold 20 Separación mínima entre Tenkan/Kijun (en pasos de precio) requerida para confirmar momentum.
IchimokuCandleType velas de 1 hora Serie de velas usada para la evaluación de Ichimoku.
AdxCandleType velas de 1 minuto Serie de velas usada para el cálculo de ADX.

Lógica de trading

  1. Esperar una vela de Ichimoku terminada.
  2. Asegurarse de que ADX tenga al menos dos valores terminados y la última lectura produjo una ruptura +DI (+DI anterior < PlusDiLowThreshold y +DI actual > PlusDiHighThreshold).
  3. Convertir la separación Tenkan/Kijun en pasos de precio y verificar que supere BaselineDistanceThreshold.
  4. Todas las órdenes se bloquean si ya existe una posición abierta.
  5. Comprar cuando:
    • Tenkan > Kijun.
    • Kijun > Senkou Span A.
    • Senkou Span A > Senkou Span B (nube alcista).
    • Precio de cierre > Kijun.
  6. Vender cuando se observa la alineación inversa (Tenkan < Kijun < Senkou Span A < Senkou Span B y el cierre está por debajo de Kijun).
  7. Las salidas de posición dependen del stop protector y el objetivo configurados mediante StartProtection. No se activa ninguna salida discrecional; esto refleja el EA original que esperaba stops/objetivos o intervención manual.

Gestión de riesgos

StartProtection se llama una vez al inicio. Si el stop o el objetivo es cero, se omite la protección respectiva. Las órdenes se envían con ejecución de mercado (BuyMarket/SellMarket), coincidiendo con la implementación MQL que usaba órdenes de mercado con SL/TP adjuntos.

Notas de implementación

  • Solo se usa el índice de dirección positiva para señales largas y cortas, replicando la lógica del código MQL5 (el autor original comentó la rama -DI).
  • La estrategia no rastrea la línea Chikou explícitamente; en cambio, la alineación de la nube se valida comparando Senkou Span A y B.
  • Los campos internos almacenan los últimos dos valores +DI sin llamar a GetValue, de acuerdo con las pautas de la API de alto nivel.
  • Si ambos parámetros de vela son idénticos, se reutiliza una única suscripción para Ichimoku y ADX para reducir la sobrecarga.

Consejos de uso

  • Mantener AdxCandleType más rápido que IchimokuCandleType para emular la versión MT5 (p.ej., ADX M1 vs. Ichimoku H1).
  • Aumentar BaselineDistanceThreshold en instrumentos de alta volatilidad para exigir una mayor separación Tenkan/Kijun.
  • Dado que el experto abre solo una posición a la vez, combinar la estrategia con controles de riesgo a nivel de portafolio cuando se operan múltiples símbolos.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MQL5 strategy "Elli" combining Ichimoku and ADX filters.
/// Focuses on impulsive moves confirmed by +DI acceleration and Ichimoku line alignment.
/// </summary>
public class ElliIchimokuAdxStrategy : Strategy
{
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouSpanBPeriod;
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<decimal> _plusDiHighThreshold;
	private readonly StrategyParam<decimal> _plusDiLowThreshold;
	private readonly StrategyParam<decimal> _baselineDistanceThreshold;
	private readonly StrategyParam<DataType> _ichimokuCandleType;
	private readonly StrategyParam<DataType> _adxCandleType;

	private Ichimoku _ichimoku;

	private decimal? _previousPlusDi;
	private decimal? _currentPlusDi;
	private bool _isAdxReady;
	private decimal? _previousAdxHigh;
	private decimal? _previousAdxLow;
	private decimal? _previousAdxClose;
	private decimal _smoothedTrueRange;
	private decimal _smoothedPlusDm;
	private int _adxSamples;

	/// <summary>
	/// Take profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Tenkan-sen (conversion line) period.
	/// </summary>
	public int TenkanPeriod
	{
		get => _tenkanPeriod.Value;
		set => _tenkanPeriod.Value = value;
	}

	/// <summary>
	/// Kijun-sen (base line) period.
	/// </summary>
	public int KijunPeriod
	{
		get => _kijunPeriod.Value;
		set => _kijunPeriod.Value = value;
	}

	/// <summary>
	/// Senkou Span B period.
	/// </summary>
	public int SenkouSpanBPeriod
	{
		get => _senkouSpanBPeriod.Value;
		set => _senkouSpanBPeriod.Value = value;
	}

	/// <summary>
	/// ADX calculation period.
	/// </summary>
	public int AdxPeriod
	{
		get => _adxPeriod.Value;
		set => _adxPeriod.Value = value;
	}

	/// <summary>
	/// Upper threshold for +DI breakout confirmation.
	/// </summary>
	public decimal PlusDiHighThreshold
	{
		get => _plusDiHighThreshold.Value;
		set => _plusDiHighThreshold.Value = value;
	}

	/// <summary>
	/// Lower threshold that previous +DI must stay below before breakout.
	/// </summary>
	public decimal PlusDiLowThreshold
	{
		get => _plusDiLowThreshold.Value;
		set => _plusDiLowThreshold.Value = value;
	}

	/// <summary>
	/// Required Tenkan/Kijun separation measured in price steps.
	/// </summary>
	public decimal BaselineDistanceThreshold
	{
		get => _baselineDistanceThreshold.Value;
		set => _baselineDistanceThreshold.Value = value;
	}

	/// <summary>
	/// Candle type used for Ichimoku evaluation and trading decisions.
	/// </summary>
	public DataType IchimokuCandleType
	{
		get => _ichimokuCandleType.Value;
		set => _ichimokuCandleType.Value = value;
	}

	/// <summary>
	/// Candle type used for ADX calculation.
	/// </summary>
	public DataType AdxCandleType
	{
		get => _adxCandleType.Value;
		set => _adxCandleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="ElliIchimokuAdxStrategy"/>.
	/// </summary>
	public ElliIchimokuAdxStrategy()
	{
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 60m)
			.SetDisplay("Take Profit", "Take profit distance in price steps", "Risk Management")
			.SetNotNegative();

		_stopLossPoints = Param(nameof(StopLossPoints), 30m)
			.SetDisplay("Stop Loss", "Stop loss distance in price steps", "Risk Management")
			.SetNotNegative();

		_tenkanPeriod = Param(nameof(TenkanPeriod), 19)
			.SetDisplay("Tenkan Period", "Tenkan-sen (conversion line) length", "Ichimoku")
			.SetGreaterThanZero();

		_kijunPeriod = Param(nameof(KijunPeriod), 60)
			.SetDisplay("Kijun Period", "Kijun-sen (base line) length", "Ichimoku")
			.SetGreaterThanZero();

		_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 120)
			.SetDisplay("Senkou Span B Period", "Senkou Span B length", "Ichimoku")
			.SetGreaterThanZero();

		_adxPeriod = Param(nameof(AdxPeriod), 10)
			.SetDisplay("ADX Period", "Average Directional Index period", "ADX")
			.SetGreaterThanZero();

		_plusDiHighThreshold = Param(nameof(PlusDiHighThreshold), 10m)
			.SetDisplay("+DI High Threshold", "Level current +DI must exceed", "ADX")
			.SetGreaterThanZero();

		_plusDiLowThreshold = Param(nameof(PlusDiLowThreshold), 8m)
			.SetDisplay("+DI Low Threshold", "Level previous +DI must stay below", "ADX")
			.SetNotNegative();

		_baselineDistanceThreshold = Param(nameof(BaselineDistanceThreshold), 5m)
			.SetDisplay("Baseline Distance", "Minimum Tenkan/Kijun spread in steps", "Ichimoku")
			.SetNotNegative();

		_ichimokuCandleType = Param(nameof(IchimokuCandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Ichimoku Candle", "Candle series for Ichimoku", "General");

		_adxCandleType = Param(nameof(AdxCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("ADX Candle", "Candle series for ADX", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, IchimokuCandleType);

		if (AdxCandleType != IchimokuCandleType)
			yield return (Security, AdxCandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousPlusDi = null;
		_currentPlusDi = null;
		_isAdxReady = false;
		_previousAdxHigh = null;
		_previousAdxLow = null;
		_previousAdxClose = null;
		_smoothedTrueRange = 0m;
		_smoothedPlusDm = 0m;
		_adxSamples = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanPeriod },
			Kijun = { Length = KijunPeriod },
			SenkouB = { Length = SenkouSpanBPeriod }
		};

		var ichimokuSubscription = SubscribeCandles(IchimokuCandleType);
		ichimokuSubscription.BindEx(_ichimoku, ProcessIchimoku);

		if (AdxCandleType == IchimokuCandleType)
		{
			ichimokuSubscription.Bind(ProcessAdxCandle);
			ichimokuSubscription.Start();
		}
		else
		{
			ichimokuSubscription.Start();

			var adxSubscription = SubscribeCandles(AdxCandleType);
			adxSubscription.Bind(ProcessAdxCandle).Start();
		}

		if (TakeProfitPoints > 0m || StopLossPoints > 0m)
		{
			StartProtection(
				StopLossPoints > 0m ? new Unit(StopLossPoints, UnitTypes.Absolute) : null,
				TakeProfitPoints > 0m ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null);
		}

		var priceArea = CreateChartArea();
		if (priceArea != null)
		{
			DrawCandles(priceArea, ichimokuSubscription);
			DrawIndicator(priceArea, _ichimoku);
			DrawOwnTrades(priceArea);
		}

	}

	private void ProcessAdxCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_previousAdxHigh is not decimal previousHigh ||
			_previousAdxLow is not decimal previousLow ||
			_previousAdxClose is not decimal previousClose)
		{
			_previousAdxHigh = candle.HighPrice;
			_previousAdxLow = candle.LowPrice;
			_previousAdxClose = candle.ClosePrice;
			return;
		}

		var upMove = candle.HighPrice - previousHigh;
		var downMove = previousLow - candle.LowPrice;
		var plusDm = upMove > downMove && upMove > 0m ? upMove : 0m;
		var trueRange = Math.Max(
			candle.HighPrice - candle.LowPrice,
			Math.Max(
				Math.Abs(candle.HighPrice - previousClose),
				Math.Abs(candle.LowPrice - previousClose)));

		if (_adxSamples < AdxPeriod)
		{
			_smoothedPlusDm += plusDm;
			_smoothedTrueRange += trueRange;
			_adxSamples++;
		}
		else
		{
			_smoothedPlusDm = _smoothedPlusDm - (_smoothedPlusDm / AdxPeriod) + plusDm;
			_smoothedTrueRange = _smoothedTrueRange - (_smoothedTrueRange / AdxPeriod) + trueRange;
		}

		if (_adxSamples >= AdxPeriod && _smoothedTrueRange > 0m)
		{
			_previousPlusDi = _currentPlusDi;
			_currentPlusDi = 100m * _smoothedPlusDm / _smoothedTrueRange;
			_isAdxReady = _previousPlusDi.HasValue;
		}

		_previousAdxHigh = candle.HighPrice;
		_previousAdxLow = candle.LowPrice;
		_previousAdxClose = candle.ClosePrice;
	}

	private void ProcessIchimoku(ICandleMessage candle, IIndicatorValue ichimokuValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_currentPlusDi is not decimal currentPlus || _previousPlusDi is not decimal previousPlus)
			return;

		if (!_isAdxReady)
			return;

		var ich = (IchimokuValue)ichimokuValue;

		if (ich.Tenkan is not decimal tenkan ||
			ich.Kijun is not decimal kijun ||
			ich.SenkouA is not decimal senkouA ||
			ich.SenkouB is not decimal senkouB)
		{
			return;
		}

		var priceStep = Security?.PriceStep ?? 1m;
		if (priceStep <= 0m)
			priceStep = 1m;

		var baselineDistance = Math.Abs(tenkan - kijun) / priceStep;
		var hasPlusDiBreakout = currentPlus > PlusDiHighThreshold && previousPlus >= PlusDiLowThreshold && currentPlus >= previousPlus;

		if (!hasPlusDiBreakout)
			return;

		if (baselineDistance < BaselineDistanceThreshold)
			return;

		if (Position != 0)
			return;

		var priceAboveCloud = senkouA > senkouB && kijun > senkouA && tenkan > kijun && candle.ClosePrice > kijun;
		var priceBelowCloud = senkouA < senkouB && kijun < senkouA && tenkan < kijun && candle.ClosePrice < kijun;

		if (priceAboveCloud)
		{
			this.LogInfo($"Bullish signal: Tenkan {tenkan:F2} > Kijun {kijun:F2}, cloud rising, +DI from {previousPlus:F2} to {currentPlus:F2}.");
			BuyMarket();
		}
		else if (priceBelowCloud)
		{
			this.LogInfo($"Bearish signal: Tenkan {tenkan:F2} < Kijun {kijun:F2}, cloud falling, +DI from {previousPlus:F2} to {currentPlus:F2}.");
			SellMarket();
		}
	}
}