GitHub で見る

Vortex インジケーターシステム戦略

概要

  • 出典:MetaTrader 5 エキスパートアドバイザー "Vortex Indicator System"(MQL ID 19137)から変換。
  • コンセプト:Vortex インジケーターを使用して強気または弱気のクロスオーバーを検出し、クロスオーバーローソク足の高値/安値にブレイクアウトトリガーをセットします。
  • 執行スタイル:ブレイクアウト追跡型;取引はトリガーレベルを超えることでクロスオーバーを価格が確認した後にのみ開始されます。
  • 市場体制:Vortex インジケーターとローソク足データをサポートする任意のインストゥルメントと時間軸で動作します;ブローカー固有の機能は不要です。
  • 注文タイプBuyMarketSellMarket によるマーケット注文。戦略は新しいトリガーをキューに入れる前に反対方向のポジションを自動的に閉じます。

トレーディングロジック

  1. 設定されたローソク足タイプをサブスクライブし、指定された長さで Vortex インジケーターを計算します。
  2. 前のローソク足で VI+VI- を下回った後に上回ったとき、強気のクロスオーバーを検出します:
    • ClosePosition() を使用して既存のショートポジションを閉じます。
    • クロスオーバーローソク足の高値をロングトリガー価格として保存します。
    • 保留中のショートトリガーをキャンセルします。
  3. 前のローソク足で VI-VI+ を下回った後に上回ったとき、弱気のクロスオーバーを検出します:
    • 既存のロングポジションを閉じます。
    • クロスオーバーローソク足の安値をショートトリガー価格として保存します。
    • 保留中のロングトリガーをキャンセルします。
  4. トリガーがアクティブな間、後続のローソク足を監視します:
    • 高値が保存されたロングトリガーを突破し、現在のポジションがフラットまたはショートの場合、ショートエクスポージャーを逆転させるサイズのマーケット買い注文を送ります。
    • 安値が保存されたショートトリガーを突破し、現在のポジションがフラットまたはロングの場合、ロングエクスポージャーを逆転させるサイズのマーケット売り注文を送ります。
  5. 各執行された取引はその対応するトリガーをクリアします。反対方向のトリガーは相互に排他的です。

パラメーター

パラメーター デフォルト 説明
Length 14 Vortex インジケーターの期間。元の MQL 入力 VI_Length に対応します。
CandleType 60分時間軸 インジケーター計算とトリガー評価に使用するローソク足タイプ。接続されたデータソースがサポートする任意の時間軸に調整できます。
Volume ベース Strategy プロパティから取得 マーケット注文に使用する取引ボリューム。1 コントラクト/ロット以外の値が必要な場合は、戦略を開始する前に設定してください。

パラメーターが動作に与える影響

  • Length を増やすと Vortex ラインが滑らかになり、クロスオーバーの数は減りますが信頼性が向上します。
  • Length を減らすとシステムがより反応的になり、より多くのトリガーと潜在的な取引が生成されます。
  • CandleType は元の MQL セットアップのデータ粒度(通常はチャートの時間軸)と一致させる必要があります。より短いローソク足はより速いシグナルを提供し、より長いローソク足はより広いトレンドに焦点を当てます。

リスク管理の注意事項

  • 元のエキスパートアドバイザーはストップロスまたはテイクプロフィットレベルを定義しません。この変換はその動作を維持します;リスク管理は外部で処理するか、戦略を拡張することで対応する必要があります。
  • ポジションの反転は即座に行われます:反対方向のシグナルが発生すると、戦略は ClosePosition() を発行し、新しい方向に入る前にトリガーを超えるブレイクアウトを待ちます。
  • 一度にアクティブにできるトリガー(ロングまたはショート)は1つのみです。価格がトリガーを突破するか、反対方向のクロスオーバーが発生するとトリガーはクリアされます。

使用手順

  1. 戦略を StockSharp プロジェクトに追加し、StockSharp.Algo.Indicators パッケージが利用可能であることを確認します。
  2. ホストアプリケーションで希望する銘柄とコネクターを設定します。
  3. CandleType パラメーターを取引したい時間軸に設定します。選択したインストゥルメントの利用可能なローソク足サブスクリプションに対応している必要があります。
  4. 必要に応じて、戦略を開始する前またはオプティマイゼーションを通じて LengthVolume を調整します。
  5. 戦略を開始します。インジケーターが形成されリアルタイムデータが利用可能になると注文が生成されます。

実装のポイント

  • クリーンなイベント駆動型処理のためにインジケーターバインディング(Bind)を使用したハイレベルの SubscribeCandles API を使用します。
  • MQL 実装と全く同じようにクロスオーバーを検出するために、前の Vortex 値を保存します(2つの連続したローソク足での VI+VI- の比較)。
  • エントリートリガーはオリジナルの「アームとブレイク」メカニズムを模倣するために null 許容の decimal フィールドとして実装されています。
  • C# ファイルの英語インラインコメントが各決定ステップを説明し、コードの維持に役立ちます。

可能な拡張

  • より厳密なリスク管理が必要な場合は、ストップロスとテイクプロフィットルール(例:ATR ベースの決済)を追加します。
  • トリガーが実行されないときの長いフラット期間を避けるために、クールダウン期間または最大保有時間を導入します。
  • 価格幅がブレイクアウトの試みを正当化するのに十分に広い場合にのみ取引するために、ボラティリティフィルターと組み合わせます。
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>
/// Breakout strategy based on the Vortex indicator crossover system.
/// Replicates the logic of the original MQL expert by arming entry triggers
/// on the candle where VI+ and VI- lines cross and executing when price breaks the trigger.
/// </summary>
public class VortexIndicatorSystemStrategy : Strategy
{
	private readonly StrategyParam<int> _length;
	private readonly StrategyParam<DataType> _candleType;

	private VortexIndicator _vortex = null!;
	private decimal _previousPlus;
	private decimal _previousMinus;
	private bool _hasPrevious;
	private decimal? _pendingBuyTrigger;
	private decimal? _pendingSellTrigger;

	/// <summary>
	/// Length of the Vortex indicator.
	/// </summary>
	public int Length
	{
		get => _length.Value;
		set => _length.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes parameters for the strategy.
	/// </summary>
	public VortexIndicatorSystemStrategy()
	{
		_length = Param(nameof(Length), 14)
			.SetDisplay("Vortex Length", "Period for the Vortex indicator", "General")
			.SetGreaterThanZero()
			
			.SetOptimize(7, 28, 7);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for analysis", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousPlus = 0m;
		_previousMinus = 0m;
		_hasPrevious = false;
		_pendingBuyTrigger = null;
		_pendingSellTrigger = null;
	}

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

		_vortex = new VortexIndicator
		{
			Length = Length
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(_vortex, ProcessCandle)
			.Start();
	}

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

		if (!_vortex.IsFormed)
			return;

		if (vortexValue is not VortexIndicatorValue typed)
			return;

		var viPlusN = typed.PlusVi;
		var viMinusN = typed.MinusVi;
		if (viPlusN is not decimal viPlus || viMinusN is not decimal viMinus)
			return;

		if (_pendingBuyTrigger is decimal buyTrigger && candle.HighPrice > buyTrigger)
		{
			if (Position <= 0)
			{
				// Reverse existing short if present and open a new long position when price breaks the trigger.
				BuyMarket();
			}

			_pendingBuyTrigger = null;
		}
		else if (_pendingSellTrigger is decimal sellTrigger && candle.LowPrice < sellTrigger)
		{
			if (Position >= 0)
			{
				// Reverse existing long if present and open a new short position when price breaks the trigger.
				SellMarket();
			}

			_pendingSellTrigger = null;
		}

		if (!_hasPrevious)
		{
			_previousPlus = viPlus;
			_previousMinus = viMinus;
			_hasPrevious = true;
			return;
		}

		var crossedUp = _previousPlus <= _previousMinus && viPlus > viMinus;
		var crossedDown = _previousPlus >= _previousMinus && viPlus < viMinus;

		if (crossedUp)
		{
			if (Position < 0)
			{
				// Flatten existing short positions when a bullish crossover appears.
				BuyMarket();
			}

			_pendingBuyTrigger = candle.HighPrice;
			_pendingSellTrigger = null;
		}
		else if (crossedDown)
		{
			if (Position > 0)
			{
				// Flatten existing long positions when a bearish crossover appears.
				SellMarket();
			}

			_pendingSellTrigger = candle.LowPrice;
			_pendingBuyTrigger = null;
		}

		_previousPlus = viPlus;
		_previousMinus = viMinus;
	}
}