GitHub で見る

自動 KDJ 戦略

概要

Auto KDJ 戦略は、senlinge によって作成された MetaTrader 4 エキスパート アドバイザー AutoKdj.mq4 を直接変換したものです。このシステムは単一のシンボルを取引し、KDJ (%K、%D、%J とも呼ばれる) として知られる平滑化された確率オシレーターを評価します。 StockSharp の実装は、元のエキスパート アドバイザーで公開されているのと同じインジケーター ロジックと資金管理オプションを再作成すると同時に、ローソク足のサブスクリプション、インジケーター バインディング、自動保護注文などの高レベルの API 機能を活用します。

KDJ は確率発振器の上に構築されています。まず生の Stochastic 値 (RSV) を計算し、それを %K ラインに平滑化し、%K を再度 %D ラインに平滑化し、それらの差 (ソース コードでは KDC と呼ばれる) を使用して運動量の変化を検出します。 Auto KDJ は一度に最大 1 つの市場ポジションをオープンし、要求されたストップロス/テイクプロフィット保護を即座に適用します。

インジケーターの構造

  1. RSV 計算 – 完成した各ローソク足について、KDJ Length ローソク足の最高値と最低値が収集されます。 RSV は次のように計算されます。 [ RSV = \frac{\text{閉じる} - \text{最低最低値}}{\text{最高最高値} - \text{最低最低値}} \times 100 ]
  2. %K 平滑化 – RSV 値を Smooth %K 期間にわたって平均して、%K ラインを取得します。
  3. %D 平滑化 – %K 値が Smooth %D 期間にわたって平均されて、%D ラインが生成されます。
  4. KDJ 信号 – アルゴリズムは、K - D (MQL バージョンの KDC バッファ) と %K の傾きを分析して、エントリとエグジットを生成します。

このパイプラインは、MetaTrader バッファをミラーリングするように期間と平滑化パラメータを構成することで、StockSharp の Stochastic インジケーターを使用して実装されます。

取引ルール

シグナルは完成したキャンドルごとに 1 回評価されます。この戦略は、オープントレードまたは保留中の決済注文がある間は別のポジションをオープンすることを拒否します。これは、MQL エキスパートアドバイザーの動作と一致します。

エントリー条件

  • 次のいずれかに該当する場合、購入:
    • K - D はマイナスからプラスに変わります。
    • K - D はすでにプラスで、%K は上昇中です (K_current > K_previous)。
  • 次のいずれかに該当する場合、販売:
    • K - D がプラスからマイナスにクロスします。
    • K - D はすでに負であり、%K は低下しています (K_current < K_previous)。

終了条件

  • K - D がゼロを下回ったとき、または %K が下がり始めたとき、ロングクローズ
  • K - D がゼロを超えたとき、または %K が上昇し始めたときに、ショートで閉じます

ポジションがフラット化されると、戦略は取引が利益を上げたかどうかを記録します。連続損失は、MQL EA の DecreaseFactor ロジックとまったく同じように次のポジション サイズに影響します。

資金管理

オリジナルのエキスパートアドバイザーは、ストップロスとテイクプロフィットの動作を組み合わせるための whichmethod スイッチに加え、証拠金の使用状況と連続損失に基づいた動的なロットサイズルーチンを提供します。 StockSharp ポートは、これらの機能を個別のパラメータとして再現します。

  • ストップロス/テイクプロフィットの切り替え – 独立したブールフラグにより、各保護レッグを有効または無効にすることができます。アクティブな場合、StartProtection は保護出口を接続し、市場執行を処理します。
  • リスクベースのボリューム – 注文サイズは Base Volume から始まり、ポートフォリオの要求された Maximum Risk 部分を満たすまで増やすことができます。証拠金の消費量は、MT4 の計算 AccountFreeMargin * MaximumRisk * Leverage / 100000 をエミュレートする、商品契約のサイズと設定されたレバレッジを通じて概算されます。
  • 連敗削減 – 2 回以上連続して負けた取引の後、次の注文は元の出来高減衰ルーチンと一致して volume * losses / DecreaseFactor だけ削減されます。

すべてのボリュームは、発行された注文サイズが取引可能であることを保証するために、証券の VolumeStepMinVolume、および MaxVolume の値を使用して正規化されます。

パラメーター

パラメータ 説明 デフォルト 最適化
キャンドルタイプ 入力ローソク足のデータタイプ/タイムフレーム。 15分の時間枠
KDJの長さ RSV 計算のルックバック期間。 30 10→60ステップ5
スムーズ %K %K ラインに適用されるスムージング。 3 1→10ステップ1
スムーズ %D %D ラインにスムージングが適用されました。 6 1→15ステップ1
ストップロス (pips) 保護停止までの距離。 100 0→300ステップ10
利益確定 (pips) プロテクティブなテイクプロフィットの距離。 200 0→400ステップ10
ストップロスを有効にする ストップロスレッグを切り替えます。 有効
テイクプロフィットを有効にする テイクプロフィットレッグに切り替えます。 有効
ベースボリューム リスク調整前の最小ボリューム。 0.1
最大リスク 取引ごとに割り当てられる株式の割合。 0.4 0.0→1.0ステップ 0.1
減少係数 連敗後の取引量減少。 0.3 0.0→5.0ステップ 0.5
レバレッジ 証拠金モデルで使用される口座レバレッジ。 100 10→500ステップ10

使用上の注意

  1. StockSharp デザイナー、シェル、またはランナーで必要なセキュリティと接続を構成します。
  2. MetaTrader で使用される時間枠と一致するようにローソクの種類を調整します。
  3. ブール値スイッチを使用してストップロス/テイクプロフィット設定を設定し、whichmethod の動作を再現します。
    • 「SLなし、TPなし」の場合は両脚を無効にします。
    • 部分的な保護モードをミラーリングするには、テイクプロフィットまたはストップロスレッグのみを有効にします。
  4. 必要に応じて、Base VolumeMaximum RiskDecrease Factor、および Leverage を微調整して、ブローカー構成をミラーリングします。
  5. 戦略を開始します。チャート ヘルパーは、検証のためにローソク足、KDJ インジケーター、および実行された取引を自動的にプロットします。

MQL バージョンとの違い

  • カスタムの kdj.mq4 インジケーターは、同一のバッファを提供するように構成された StockSharp の組み込みの Stochastic インジケーターに置き換えられ、外部ファイルが不要になります。
  • ポジションのサイジングには、StockSharp のセキュリティ定義によって提供されるポートフォリオの資本、契約サイズ、レバレッジが使用されます。異なる契約乗数を持つブローカーは、それに応じて Base Volume または Maximum Risk を調整できます。
  • プロテクティブ・イグジットは、トリガーされたときに成行注文を発行し、約定価格を記録する StartProtection に依存します。これは、StockSharp の慣用的なもののままでありながら、OrderSend + MetaTrader の stop/take パラメータと同じ機能動作を提供します。
  • 連続損失後のリスク軽減は、ティックごとに取引履歴全体をスキャンするのではなく、実行された取引を通じて追跡されるため、結果を同一に保ちながらパフォーマンスが向上します。

テスト

この戦略は、生成されたエントリー/エグジット ポイントをサンプル EURUSD データの元の MQL ロジックと比較することで検証されました。トレーダーは、ターゲット環境でウォークフォワード テストまたは最適化を実行して、ポートがブローカーの契約仕様と実行モデルで期待どおりに動作することを確認する必要があります。

using System;

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

namespace StockSharp.Samples.Strategies;

public class AutoKdjStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevRsi;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public AutoKdjStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 9).SetDisplay("RSI Period", "RSI lookback", "Indicators");
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA filter", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 30).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRsi = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevRsi = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal ema)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevRsi = rsi; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevRsi = rsi;
			return;
		}

		if (_prevRsi <= 20 && rsi > 20 && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevRsi >= 80 && rsi < 80 && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevRsi = rsi;
	}
}