GitHub で見る

Ytg ADX レベル・クロス・ストラテジー

このストラテジーはYuriy Tokmanの_ADX.mq5エキスパートアドバイザーをStockSharpの高レベルAPIにポートしたものです。Average Directional Indexを監視し、+DIまたは-DIコンポーネントが設定可能な閾値を超えたときに反応します。注文は一度に1つのみ開かれ、元のMQLロジックを反映しており、価格ポイントで表された保護的なストップロスとテイクプロフィットのレベルが自動的に適用されます。

概要

  • 市場レジーム: ブレイクアウトにDIスパイクが伴うトレンドや強い方向性のある動きで機能します。
  • 方向: ロングまたはショートポジションを開きますが、両方同時には決してありません。
  • 時間軸: CandleTypeパラメーターで制御されます(デフォルトは1時間ローソク足)。
  • データ: AverageDirectionalIndexインジケーターからADX/DI値を計算するために完成したローソク足を使用します。

トレードロジック

  1. 選択したローソク足シリーズをサブスクライブし、設定されたAdxPeriodでADXインジケーターを構築します。
  2. 完成したローソク足ごとに+DIと-DIの値を収集し、Shiftパラメーターが必要とする量の履歴のみを保持します。MQLのデフォルトと同一のShift 1は、前の閉じたローソク足を評価します。
  3. ロングエントリー: シフトされた+DIの値がその前の値が同じ閾値を下回っていた間にLevelPlusを上回ると引き起こされます。ストラテジーは市場で買う前に現在ポジションが開いていないことを確認します。
  4. ショートエントリー: シフトされた-DIの値がその前の値がそのレベルを下回っていた間にLevelMinusを上回ると引き起こされます。アクティブなポジションがない場合のみ成行売りが送信されます。
  5. エグジットはStartProtectionを通じて開始される保護注文のみによって処理されます:価格ポイントで測定された固定のテイクプロフィットとストップロス、元のコードのTPSLに相当します。

この実装は意図的に、ポジションへの平均化、アクティブな取引中の再エントリー、または追加フィルターを避け、ソースEAの軽量な動作に対応しています。

パラメーター

パラメーター デフォルト 説明
CandleType 1時間の時間軸 ADX計算に使用するローソク足サブスクリプションの時間軸。
AdxPeriod 28 Average Directional IndexとそのDI計算の長さ。
LevelPlus 5 ロングポジションを開くために+DIシリーズが超える必要がある閾値。
LevelMinus 5 ショートポジションを開くために-DIシリーズが超える必要がある閾値。
Shift 1 DIクロスを評価する際に遡る閉じたローソク足の数(1 = 前のローソク足)。
TakeProfitPoints 500 テイクプロフィット注文の価格ポイント単位の距離。内部でインスツルメントのティックサイズで乗算されます。
StopLossPoints 500 保護的なストップロス注文の価格ポイント単位の距離。
TradeVolume 0.1 新しい成行注文のベースボリューム、MQLエキスパートのLots設定に対応。

リスク管理

  • StartProtectionはインスツルメントのPriceStepを使用して、ポイントベースのテイクプロフィットとストップロスの値を絶対価格距離に変換します。
  • トレーリングストップやブレークイーブンロジックは適用されません;エグジットは設定された保護注文を通じてのみ発生します。

注意事項とヒント

  • 非常に低いDI閾値は頻繁なホイップソウトレードを引き起こす可能性がありますが、より高いレベルはより強い方向性のバーストを待ちます。
  • Shiftパラメーターは以前のローソク足からの確認が必要な場合に増やすことができます。例えば、イントラバーノイズをフィルタリングするより高い時間軸で。
  • ストラテジーは一度に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>
/// Reimplementation of the YTG ADX threshold breakout expert using high level StockSharp API.
/// The strategy waits for the +DI or -DI line to break above configurable levels and opens
/// a position in the corresponding direction with protective stop-loss and take-profit.
/// </summary>
public class YtgAdxLevelCrossStrategy : Strategy
{
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<int> _levelPlus;
	private readonly StrategyParam<int> _levelMinus;
	private readonly StrategyParam<int> _shift;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private AverageDirectionalIndex _adx;

	private readonly List<decimal> _plusDiHistory = [];
	private readonly List<decimal> _minusDiHistory = [];

	public int AdxPeriod
	{
		get => _adxPeriod.Value;
		set => _adxPeriod.Value = value;
	}

	public int LevelPlus
	{
		get => _levelPlus.Value;
		set => _levelPlus.Value = value;
	}

	public int LevelMinus
	{
		get => _levelMinus.Value;
		set => _levelMinus.Value = value;
	}

	public int Shift
	{
		get => _shift.Value;
		set => _shift.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public YtgAdxLevelCrossStrategy()
	{
		_adxPeriod = Param(nameof(AdxPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ADX Period", "Period for the Average Directional Index", "Indicators")
			
			.SetOptimize(10, 40, 2);

		_levelPlus = Param(nameof(LevelPlus), 15)
			.SetNotNegative()
			.SetDisplay("+DI Level", "Threshold that the +DI line must break", "Signals")
			
			.SetOptimize(5, 40, 5);

		_levelMinus = Param(nameof(LevelMinus), 15)
			.SetNotNegative()
			.SetDisplay("-DI Level", "Threshold that the -DI line must break", "Signals")
			
			.SetOptimize(5, 40, 5);

		_shift = Param(nameof(Shift), 1)
			.SetNotNegative()
			.SetDisplay("Signal Shift", "Number of closed candles to look back", "Signals")
			
			.SetOptimize(0, 3, 1);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit (points)", "Distance to take profit in price points", "Risk");

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (points)", "Distance to stop loss in price points", "Risk");

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Base volume for market orders", "Orders");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();

		_plusDiHistory.Clear();
		_minusDiHistory.Clear();
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		Volume = TradeVolume;

		_adx = new AverageDirectionalIndex
		{
			Length = AdxPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var step = Security.PriceStep ?? 1m;
		Unit takeProfit = null;
		Unit stopLoss = null;

		if (TakeProfitPoints > 0)
			takeProfit = new Unit(TakeProfitPoints * step, UnitTypes.Absolute);

		if (StopLossPoints > 0)
			stopLoss = new Unit(StopLossPoints * step, UnitTypes.Absolute);

		if (takeProfit != null || stopLoss != null)
		{
			StartProtection(takeProfit: takeProfit, stopLoss: stopLoss);
		}
	}

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

		var adxValue = _adx.Process(candle);

		if (!_adx.IsFormed || !adxValue.IsFinal)
			return;

		if (adxValue is not AverageDirectionalIndexValue typed)
			return;

		if (typed.Dx.Plus is not decimal plusDi || typed.Dx.Minus is not decimal minusDi)
			return;

		UpdateHistory(_plusDiHistory, plusDi);
		UpdateHistory(_minusDiHistory, minusDi);

		var currentShift = Shift;
		var minCount = currentShift + 2;

		if (_plusDiHistory.Count < minCount || _minusDiHistory.Count < minCount)
			return;

		var currentIndex = _plusDiHistory.Count - 1 - currentShift;
		var previousIndex = currentIndex - 1;

		if (previousIndex < 0)
			return;

		var shiftedPlus = _plusDiHistory[currentIndex];
		var shiftedPlusPrev = _plusDiHistory[previousIndex];
		var shiftedMinus = _minusDiHistory[currentIndex];
		var shiftedMinusPrev = _minusDiHistory[previousIndex];

		var longSignal = shiftedPlus > LevelPlus && shiftedPlusPrev < LevelPlus;
		var shortSignal = shiftedMinus > LevelMinus && shiftedMinusPrev < LevelMinus;

		if (Position == 0)
		{
			if (longSignal)
			{
				// Enter a long position when +DI breaks above the configured level.
				BuyMarket();
			}
			else if (shortSignal)
			{
				// Enter a short position when -DI breaks above the configured level.
				SellMarket();
			}
		}
	}

	private void UpdateHistory(List<decimal> history, decimal value)
	{
		history.Add(value);

		var maxLength = Shift + 2;

		while (history.Count > maxLength)
		{
			// Keep only the amount of history required for the configured shift.
			history.RemoveAt(0);
		}
	}
}