GitHub で見る

iVIDyA シンプル戦略

概要

この戦略は、MetaTrader エキスパート 「iVIDyA Simple」 の上位レベルの StockSharp 移植です。 Chande Momentum Oscillator(CMO)を介して市場の勢いに適応するVariable Index Dynamic Average(VIDYA)を追跡することにより、単一のシンボルを取引します。最も最近完成したローソク足がシフトされた VIDYA ラインを横切るたびに、この戦略はブレイクアウトの方向に市場ポジションを開き、オプションで保護的なストップロス注文と利益確定注文を付けます。

取引ロジック

  1. ローソク足データは、構成されたタイムフレーム (CandleType) から読み取られます。
  2. 期間 CmoPeriod の CMO はローソク足シリーズにバインドされています。その絶対値は、VIDYA の平滑化係数を動的にスケーリングします。元の MQL 実装と同様に、基本係数は 2 / (EmaPeriod + 1) に等しくなります。
  3. ローリング VIDYA 値は維持されます。完成したキャンドルごとにアルゴリズムは次のようになります。
    • ローソク足(終値、始値、中央値など)から適用される価格(AppliedPrice)を選択します。
    • 適応平滑化係数を使用して VIDYA を更新します。
    • MetaTrader の MA shift オプションをエミュレートする履歴値を保存します。
  4. ローソク足はシフトされた VIDYA 値と比較されます (MaShift バー戻ります)。
    • ローソク足が VIDYA より下で開き、VIDYA より上で閉じる場合、買いシグナルが生成されます。
    • ローソク足が VIDYA より上で開き、VIDYA より下で閉じる場合、売り シグナルが生成されます。
  5. 新しいポジションをオープンする前に、この戦略は、反転するために必要な全量を取引することにより、反対のエクスポージャーを平準化します。
  6. 各エントリの後、それぞれの距離が正の場合、SetStopLossSetTakeProfit が呼び出されます。

これは、新しい足のみで注文をトリガーし、CMO と EMA 期間から計算された VIDYA を使用し、ポイントで表現されるオプションのストップを付加した元のエキスパート アドバイザーを反映しています。

パラメーター

名前 デフォルト 説明
Volume 1 注文に使用される基本取引量。ポジションを反転すると、既存のエクスポージャが自動的にネッティングされます。
StopLossPoints 150 価格ステップのストップロス距離。無効にするには、0 に設定します。
TakeProfitPoints 460 価格ステップでの利食い距離。無効にするには、0 に設定します。
CmoPeriod 15 適応 VIDYA 重みを決定する Chande Momentum Oscillator の長さ。
EmaPeriod 12 VIDYA 式の基本平滑化係数を定義する EMA の長さ。
MaShift 1 MetaTrader インジケーターの ma_shift 入力と一致する、VIDYA ラインを前方にシフトするために使用される完了したローソク足の数。
AppliedPrice Close VIDYA 計算に渡される価格ソース (CloseOpenHighLowMedianTypicalWeighted)。
CandleType TimeSpan.FromMinutes(5) すべての計算とシグナルに使用されるローソク足のタイプと時間枠。

追加メモ

  • 保護命令は、組み込みの高レベルの API (SetStopLoss/SetTakeProfit) によって管理されますが、元の MQL コードは凍結レベルに対して手動チェックを実行していました。
  • この戦略は完了したローソク足のみをサブスクライブし、MetaTrader からの「新しいバー」実行制約を複製します。
  • VIDYA 履歴は自動的にトリミングされるため、MaShift が大きい場合でもメモリ フットプリントは小さく保たれます。
  • プロジェクトの要件に合わせて、コード内のコメントはすべて英語で書かれています。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "iVIDyA Simple" MetaTrader expert.
/// Computes a Variable Index Dynamic Average using CMO and EMA smoothing.
/// Trades when price crosses above/below the VIDYA line.
/// </summary>
public class IvidyaSimpleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cmoPeriod;
	private readonly StrategyParam<int> _emaPeriod;

	private ChandeMomentumOscillator _cmo;
	private decimal? _prevVidya;
	private decimal? _prevClose;

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

	public int CmoPeriod
	{
		get => _cmoPeriod.Value;
		set => _cmoPeriod.Value = value;
	}

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public IvidyaSimpleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle series", "General");

		_cmoPeriod = Param(nameof(CmoPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("CMO Period", "Chande Momentum Oscillator length", "Indicator");

		_emaPeriod = Param(nameof(EmaPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "Base EMA length used by VIDYA", "Indicator");
	}

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

		_cmo = new ChandeMomentumOscillator { Length = CmoPeriod };
		_prevVidya = null;
		_prevClose = null;

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		if (!_cmo.IsFormed)
			return;

		var close = candle.ClosePrice;

		// VIDYA = alpha * |CMO/100| * price + (1 - alpha * |CMO/100|) * prevVidya
		var alpha = 2m / (EmaPeriod + 1m);
		var momentumFactor = Math.Abs(cmoValue) / 100m;
		var sf = alpha * momentumFactor;

		var prevVidya = _prevVidya ?? close;
		var currentVidya = sf * close + (1m - sf) * prevVidya;

		if (_prevVidya is null || _prevClose is null)
		{
			_prevVidya = currentVidya;
			_prevClose = close;
			return;
		}

		// Price crosses above VIDYA -> buy
		var crossUp = _prevClose <= _prevVidya && close > currentVidya;
		// Price crosses below VIDYA -> sell
		var crossDown = _prevClose >= _prevVidya && close < currentVidya;
		var minDistance = close * 0.001m;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		if (crossUp && Math.Abs(close - currentVidya) >= minDistance)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown && Math.Abs(close - currentVidya) >= minDistance)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevVidya = currentVidya;
		_prevClose = close;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_cmo = null;
		_prevVidya = null;
		_prevClose = null;

		base.OnReseted();
	}
}