GitHub で見る

童子矢の戦略

概要

Doji Arrows 戦略は、MetaTrader エキスパート アドバイザー Doji_arrows_expert1.mq4 の StockSharp 移植です。取引のアイデアは、ニュートラルなドージローソク足を検出し、次のバーに続くブレイクアウトをすぐに取引することです。市場が非常に小さな実体ローソク足 (始値 ≈ 終値) を示し、その後のローソク足が同値の高値または安値を超えて閉じると、戦略はその動きを方向性のブレイクアウトとして解釈し、その方向にエントリーします。

取引ロジック

  • シグナル検出ウィンドウ – この戦略は、以前に完了した 2 つのキャンドルを継続的にバッファリングします。最も古いローソクはドージである必要があり、より新しいローソクはブレイクアウトを確認します。
  • ドージの定義 – 始値と終値の絶対差が DojiBodyThresholdSteps * PriceStep 以下の場合、ローソクはドージとして認定されます。デフォルトのしきい値の 1 ステップでは、バーは最大 1 ティックだけずれることがあります。
  • ブレイクアウトの確認
    • 長いセットアップ: ドージに続くローソク足がドージ高値とオプションの BreakoutBufferSteps フィルターを上回って閉じます。
    • 短いセットアップ: ドージに続くローソク足は、ドージ安値から同じバッファを引いた値より下で閉じます。
  • シングルショットシグナル – この戦略は、前のバーがすでにロングシグナルをトリガーしたかショートシグナルをトリガーしたかを記憶し、新たなブレイクアウトにのみ反応します。この動作は、ブレイクアウト シーケンスごとに 1 つの矢印を生成する元のエキスパートを反映しています。
  • 注文執行
    • 既存の反対ポジションに対してブレイクアウトが現れた場合、戦略はまずそれをクローズし、次にボリューム Volume + |Position| で新しい方向にエントリーして、反転して新しい取引を開始します。
    • 中立状態では、ブレイクアウト方向に成行注文を開きます。

リスク管理

  • 初期ストップロス – 各エントリーの後、ストラテジーは約定価格から内部保護レベル InitialStopSteps * PriceStep を設定します。
  • 固定テイクプロフィット – 価格がエントリーから TakeProfitSteps * PriceStep に達したときにポジションの一部またはすべてを決済します。
  • トレーリングストップ – 取引が TrailingStopSteps * PriceStep を超えて有利に動くと、ストップレベルはローソク足ごとにトレーリングされ、動きを実行しながら利益を確定させます。
  • すべての保護計算はネイティブの価格ステップで実行されるため、ロジック機器に依存しません。

パラメーター

名前 説明 デフォルト
CandleType 分析するローソクの種類/時間枠。 5分の時間枠
DojiBodyThresholdSteps 最大の童子本体を価格ステップで表します。 1
BreakoutBufferSteps ブレイクアウトを受け入れる前に、ドージの極値の上/下に追加のフィルターを適用します。 0
InitialStopSteps ステップ単位でのエントリーからの最初のストップロス距離。 20
TakeProfitSteps エントリーからのテイクプロフィット距離を段階的に設定します。 25
TrailingStopSteps トレードが利益になった後は、トレーリングストップ距離が維持されます。 10

すべてのパラメータは StrategyParam<T> を通じて公開され、UI に表示され、最適化の準備が整います。

実装メモ

  • このクラスは、フレームワークのベスト プラクティスとの同期を保つために、高レベルのキャンドル サブスクリプション API (SubscribeCandles().Bind(...)) に基づいて構築されています。
  • 呼び出し間の状態は _previousCandle_twoCandlesAgo で維持され、終了したキャンドルのみが意思決定に参加するようになります。
  • プロテクションレベルはロングポジションとショートポジションに分けて保存され、ポジションが決済されるか市場データが不十分な場合にリセットされます。
  • ログ ステートメントにより、シグナル検出、ストップロス、テイクプロフィット イベントに関する洞察が得られ、バックテスト中のデバッグが簡素化されます。

使い方のヒント

  1. 各商品のデフォルトのティックしきい値を検証します。正確な同値がまれな不安定な市場の場合は、DojiBodyThresholdSteps を増やします。
  2. スプレッドやノイズが大きい場合に、小さな偽のブレイクアウトをフィルタリングするために BreakoutBufferSteps を最適化します。
  3. 複数のシンボルに同時に戦略を展開する場合は、戦略を外部リスク オーバーレイ (ポートフォリオ ストップ、取引セッション フィルター) と組み合わせます。
  4. シグナルは完了したローソク足に依存するため、希望する取引期間と互換性のあるローソク足のタイプを選択してください (例: スキャルピングの場合は 1 分、スイング エントリーの場合は 15 分)。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Doji Arrows breakout strategy.
/// Detects doji candles (small body) and trades breakout of the doji range on the next candle.
/// Uses ATR to define what constitutes a small body.
/// </summary>
public class DojiArrowsBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _dojiThreshold;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _prevWasDoji;
	private bool _hasPrev;

	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal DojiThreshold { get => _dojiThreshold.Value; set => _dojiThreshold.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public DojiArrowsBreakoutStrategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "ATR period for doji detection", "Indicators");

		_dojiThreshold = Param(nameof(DojiThreshold), 0.3m)
			.SetDisplay("Doji Threshold", "Max body/ATR ratio for doji", "Indicators");

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

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevHigh = 0m; _prevLow = 0m; _prevWasDoji = false; _hasPrev = false; }

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

		_hasPrev = false;
		_prevWasDoji = false;

		var atr = new AverageTrueRange { Length = AtrPeriod };

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

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

		if (atr <= 0)
			return;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var isDoji = body / atr < DojiThreshold;

		if (_hasPrev && _prevWasDoji)
		{
			// Breakout above doji high
			if (candle.ClosePrice > _prevHigh && Position <= 0)
			{
				if (Position < 0)
					BuyMarket();
				BuyMarket();
			}
			// Breakout below doji low
			else if (candle.ClosePrice < _prevLow && Position >= 0)
			{
				if (Position > 0)
					SellMarket();
				SellMarket();
			}
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevWasDoji = isDoji;
		_hasPrev = true;
	}
}