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 で維持され、終了したキャンドルのみが意思決定に参加するようになります。
- プロテクションレベルはロングポジションとショートポジションに分けて保存され、ポジションが決済されるか市場データが不十分な場合にリセットされます。
- ログ ステートメントにより、シグナル検出、ストップロス、テイクプロフィット イベントに関する洞察が得られ、バックテスト中のデバッグが簡素化されます。
使い方のヒント
- 各商品のデフォルトのティックしきい値を検証します。正確な同値がまれな不安定な市場の場合は、
DojiBodyThresholdSteps を増やします。
- スプレッドやノイズが大きい場合に、小さな偽のブレイクアウトをフィルタリングするために
BreakoutBufferSteps を最適化します。
- 複数のシンボルに同時に戦略を展開する場合は、戦略を外部リスク オーバーレイ (ポートフォリオ ストップ、取引セッション フィルター) と組み合わせます。
- シグナルは完了したローソク足に依存するため、希望する取引期間と互換性のあるローソク足のタイプを選択してください (例: スキャルピングの場合は 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class doji_arrows_breakout_strategy(Strategy):
def __init__(self):
super(doji_arrows_breakout_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for doji detection", "Indicators")
self._doji_threshold = self.Param("DojiThreshold", 0.3) \
.SetDisplay("Doji Threshold", "Max body/ATR ratio for doji", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._has_prev = False
@property
def atr_period(self):
return self._atr_period.Value
@property
def doji_threshold(self):
return self._doji_threshold.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(doji_arrows_breakout_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_was_doji = False
self._has_prev = False
def OnStarted2(self, time):
super(doji_arrows_breakout_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_was_doji = False
atr = AverageTrueRange()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self.process_candle).Start()
def process_candle(self, candle, atr):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr)
if atr_val <= 0:
return
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
is_doji = body / atr_val < self.doji_threshold
if self._has_prev and self._prev_was_doji:
if float(candle.ClosePrice) > self._prev_high and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif float(candle.ClosePrice) < self._prev_low and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._prev_was_doji = is_doji
self._has_prev = True
def CreateClone(self):
return doji_arrows_breakout_strategy()