Auf GitHub ansehen

Elli Ichimoku ADX-Strategie

Übersicht

Die Strategie ist ein C#-Port des MetaTrader-5-Experten "Elli" (barabashkakvn's edition). Sie kombiniert die Ichimoku-Kinko-Hyo-Struktur mit einem Average-Directional-Index-(+DI)-Ausbruchsfilter. Trades werden nur geöffnet, wenn ein starker direktionaler Impuls gleichzeitig durch die Ichimoku-Linienausrichtung und einen plötzlichen Anstieg des positiven Richtungsindex bestätigt wird.

Die StockSharp-Implementierung behält das ursprüngliche Verhalten der Arbeit mit zwei Kerzenströmen bei: Die Ichimoku-Analyse wird auf einem höheren Zeitrahmen (Standard 1 Stunde) durchgeführt, während ADX auf einer schnelleren Serie (Standard 1 Minute) ausgewertet wird. Orders werden mit einem festen Schutz-Stop und Ziel in Preisschritten eingegeben, identisch mit dem ursprünglichen Expertenberater.

Indikatoren und Daten

  • Ichimoku (Tenkan 19, Kijun 60, Senkou Span B 120 standardmäßig).
  • Average Directional Index (ADX), nur die +DI-Linie wird wie im Quellcode verwendet.
  • Optionale Chartbereiche zeigen die Kerzenserie, die Ichimoku-Wolke und die ADX-Linie.

Zwei unabhängige Kerzenabonnements werden erstellt:

  1. IchimokuCandleType (Standard 1 Stunde) – treibt Ichimoku-Berechnungen und generiert Handelsentscheidungen.
  2. AdxCandleType (Standard 1 Minute) – speist den ADX-Indikator und liefert aktuelle/vorherige +DI-Werte.

Parameter

Parameter Standard Beschreibung
TakeProfitPoints 60 Take-Profit-Distanz in Preisschritten. Auf 0 setzen zum Deaktivieren.
StopLossPoints 30 Stop-Loss-Distanz in Preisschritten. Auf 0 setzen zum Deaktivieren.
TenkanPeriod 19 Länge der Ichimoku-Tenkan-sen (Konversionslinie).
KijunPeriod 60 Länge der Ichimoku-Kijun-sen (Basislinie).
SenkouSpanBPeriod 120 Länge der Ichimoku-Senkou-Span-B-Linie.
AdxPeriod 10 Periode für den ADX-Indikator.
PlusDiHighThreshold 13 Schwellenwert, den der aktuelle +DI-Wert übersteigen muss.
PlusDiLowThreshold 6 Schwellenwert, unter dem der vorherige +DI-Wert bleiben muss.
BaselineDistanceThreshold 20 Minimaler Tenkan/Kijun-Spread (in Preisschritten) zur Bestätigung des Momentums.
IchimokuCandleType 1-Stunden-Kerzen Kerzenserie für die Ichimoku-Auswertung.
AdxCandleType 1-Minuten-Kerzen Kerzenserie für die ADX-Berechnung.

Handelslogik

  1. Eine fertige Ichimoku-Kerze abwarten.
  2. Sicherstellen, dass ADX mindestens zwei fertige Werte hat und die letzte Lesung einen +DI-Ausbruch erzeugte (vorheriges +DI < PlusDiLowThreshold und aktuelles +DI > PlusDiHighThreshold).
  3. Den Tenkan/Kijun-Spread in Preisschritte umrechnen und überprüfen, ob er BaselineDistanceThreshold übersteigt.
  4. Alle Orders werden blockiert, wenn bereits eine offene Position vorhanden ist.
  5. Kaufen wenn:
    • Tenkan > Kijun.
    • Kijun > Senkou Span A.
    • Senkou Span A > Senkou Span B (bullische Wolke).
    • Schlusskurs > Kijun.
  6. Verkaufen wenn die umgekehrte Ausrichtung beobachtet wird (Tenkan < Kijun < Senkou Span A < Senkou Span B und der Schluss unter Kijun liegt).
  7. Positionsausstiege verlassen sich auf den konfigurierten Schutz-Stop und das Ziel über StartProtection. Kein diskretionärer Ausstieg wird ausgelöst; dies spiegelt den ursprünglichen EA wider, der auf Stops/Ziele oder manuelle Eingriffe wartete.

Risikomanagement

StartProtection wird einmal beim Start aufgerufen. Wenn entweder Stop oder Ziel null ist, wird der entsprechende Schutz weggelassen. Orders werden mit Marktausführung gesendet (BuyMarket/SellMarket), was der MQL-Implementierung entspricht, die Marktorders mit angehängtem SL/TP verwendete.

Implementierungshinweise

  • Nur der positive Richtungsindex wird für Long- und Short-Signale verwendet, was die Logik des MQL5-Codes repliziert (der ursprüngliche Autor hat den -DI-Zweig auskommentiert).
  • Die Strategie verfolgt die Chikou-Spanne nicht explizit; stattdessen wird die Wolkenausrichtung durch Vergleich von Senkou Span A und B validiert.
  • Interne Felder speichern die letzten zwei +DI-Werte ohne GetValue aufzurufen, gemäß den High-Level-API-Richtlinien.
  • Wenn beide Kerzenparameter identisch sind, wird ein einzelnes Abonnement für Ichimoku und ADX wiederverwendet, um den Overhead zu reduzieren.

Verwendungstipps

  • AdxCandleType schneller als IchimokuCandleType halten, um die MT5-Version zu emulieren (z.B. M1 ADX vs. H1 Ichimoku).
  • BaselineDistanceThreshold bei hochvolatilen Instrumenten erhöhen, um eine größere Tenkan/Kijun-Trennung zu verlangen.
  • Da der Experte jeweils nur eine Position öffnet, die Strategie mit Risikokontrollen auf Portfolio-Ebene kombinieren, wenn mehrere Symbole gehandelt werden.
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();
		}
	}
}