GitHub で見る

Elli Ichimoku ADX戦略

概要

この戦略はMetaTrader 5エキスパート「Elli」(barabashkakvn版)のC#ポートです。一目均衡表のキンコ・ハイオ構造とAverage Directional Index(+DI)ブレイクアウトフィルターを組み合わせています。取引は、一目均衡表のライン整列と正のディレクショナルインデックスの急上昇によって同時に強い方向性インパルスが確認された場合にのみ開かれます。

StockSharpの実装は2つのローソク足ストリームで動作する元の動作を維持しています:一目均衡表の分析はより高い時間軸(デフォルト1時間)で実行され、ADXはより速いシリーズ(デフォルト1分)で評価されます。注文は価格ステップで測定された固定の保護ストップと目標価格で入力され、元のエキスパートアドバイザーと同一です。

インジケーターとデータ

  • Ichimoku(転換線19、基準線60、先行スパンB 120をデフォルト)。
  • Average Directional Index (ADX)、ソースコードと同様に+DIラインのみが使用されます。
  • オプションのチャートエリアはローソク足シリーズ、一目均衡表の雲、ADXラインを表示します。

2つの独立したローソク足サブスクリプションが作成されます:

  1. IchimokuCandleType(デフォルト1時間)– 一目均衡表の計算を駆動し、取引判断を生成します。
  2. AdxCandleType(デフォルト1分)– ADXインジケーターを供給し、現在/前の+DI値を提供します。

パラメーター

パラメーター デフォルト 説明
TakeProfitPoints 60 価格ステップでのテイクプロフィット距離。無効にするには0に設定。
StopLossPoints 30 価格ステップでのストップロス距離。無効にするには0に設定。
TenkanPeriod 19 一目均衡表転換線(コンバージョンライン)の長さ。
KijunPeriod 60 一目均衡表基準線(ベースライン)の長さ。
SenkouSpanBPeriod 120 一目均衡表先行スパンBラインの長さ。
AdxPeriod 10 ADXインジケーターの期間。
PlusDiHighThreshold 13 現在の+DI値が超えなければならない閾値。
PlusDiLowThreshold 6 前の+DI値が下回っていなければならない閾値。
BaselineDistanceThreshold 20 モメンタムを確認するために必要な最小転換線/基準線スプレッド(価格ステップ単位)。
IchimokuCandleType 1時間ローソク足 一目均衡表評価に使用されるローソク足シリーズ。
AdxCandleType 1分ローソク足 ADX計算に使用されるローソク足シリーズ。

トレードロジック

  1. 1つの完成した一目均衡表ローソク足を待ちます。
  2. ADXが少なくとも2つの完成した値を持ち、最後の読み取りが+DIブレイクアウトを生成したことを確認します(前の+DI < PlusDiLowThresholdおよび現在の+DI > PlusDiHighThreshold)。
  3. 転換線/基準線スプレッドを価格ステップに変換し、BaselineDistanceThresholdを超えることを確認します。
  4. オープンポジションが既に存在する場合、すべての注文はブロックされます。
  5. 以下の場合に買い
    • 転換線 > 基準線。
    • 基準線 > 先行スパンA。
    • 先行スパンA > 先行スパンB(強気の雲)。
    • 終値 > 基準線。
  6. 逆のアライメントが観察される場合に売り(転換線 < 基準線 < 先行スパンA < 先行スパンBおよび終値が基準線より下)。
  7. ポジションのエグジットはStartProtectionで設定された保護ストップと目標に依存します。裁量的なエグジットはトリガーされません。これはストップ/目標または手動介入を待った元のEAを反映しています。

リスク管理

StartProtectionは開始時に一度呼び出されます。ストップまたは目標のいずれかがゼロの場合、それぞれの保護は省略されます。注文は成行執行(BuyMarket/SellMarket)で送信され、SL/TPが添付された成行注文を使用したMQL実装と一致します。

実装に関する注意事項

  • ロングとショートシグナルの両方に正のディレクショナルインデックスのみが使用され、MQL5コードのロジックを複製しています(元の著者は-DIブランチをコメントアウトしていました)。
  • 戦略はチコウスパンを明示的に追跡しません。代わりに、雲のアライメントは先行スパンAとBを比較することで検証されます。
  • 内部フィールドはGetValueを呼び出すことなく最後の2つの+DI値を保存し、高レベルAPIガイドラインに従います。
  • 両方のローソク足パラメーターが同一の場合、オーバーヘッドを削減するために一目均衡表とADXに対して単一のサブスクリプションが再利用されます。

使用上のヒント

  • MT5バージョンをエミュレートするためにAdxCandleTypeIchimokuCandleTypeより速く保ちます(例:M1 ADX対H1一目均衡表)。
  • 高ボラティリティのインストゥルメントではより広い転換線/基準線の分離を要求するためにBaselineDistanceThresholdを上げます。
  • エキスパートは一度に1つのポジションしか開かないため、複数のシンボルを取引する場合はポートフォリオレベルのリスク管理と組み合わせます。
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();
		}
	}
}