GitHub で見る

ブレイクアウト・ボラティリティ戦略

ブレイクアウト・ボラティリティ戦略は、バー内ボラティリティの短い急増を探します。前のローソク足よりレンジが拡大しつつも、桁数正規化後で狭い帯域 (2 pip 相当) に収まるローソク足を待ちます。そのローソク足が陽線で終われば買い、陰線で終われば売ります。保護ストップ、任意の trailing stop、自動的な損失後反転シーケンスにより、リスクを管理し、不利な動きからの回復を試みます。

取引ロジック

  1. レンジ拡大フィルター
    • 現在のローソク足レンジ (High - Low) を計算し、前のローソク足と比較します。
    • 現在レンジがより大きく、かつ前回レンジを 2 正規化 pip 超えて上回らないことを要求します。
    • これにより、ボラティリティが増えながらもまだ抑制されているセットアップを作り、過剰なノイズなしの潜在的なブレイクアウトを示します。
  2. 方向バイアス
    • ローソク足が始値より上で引けた場合、ロングに入ります。
    • ローソク足が始値より下で引けた場合、ショートに入ります。
    • 同じローソク足でシグナルが繰り返されるのを避けるため、戦略は任意で 1 バーあたり複数のエントリーを禁止できます。
  3. ポジション管理
    • 初期 stop-loss と take-profit は、エントリー価格を基準とするポイント (pip 相当) で割り当てられます。
    • 任意の trailing stop は、価格が取引に有利な方向へ指定距離だけ動いた後、保護水準を引き締めます。trailing ステップにより、ごく小さな調整を防ぎます。
    • ポジションが損失で決済されると、戦略は即座に方向を反転できます。各反転では、追加リスクを補うために take-profit 距離が増えます。連続反転回数の上限により、無限のマーチンゲール動作を防ぎます。

パラメーター

名前 説明 デフォルト 最適化可能
TradeVolume 成行エントリーの基本注文数量。 0.1 はい
StopLossPoints ポイント単位の stop-loss 距離。 20 はい
TakeProfitPoints ポイント単位の take-profit 距離。 10 はい
TrailingStopPoints ポイント単位の trailing stop 距離。無効にするには 0 に設定します。 25 いいえ
TrailingStepPoints trailing stop を動かすときの最小増分ステップ。 5 いいえ
OnlyOnePositionPerBar 同じローソク足内で複数回エントリーすることを禁止します。 true いいえ
UseAutoDigits 3 桁または 5 桁小数のシンボルではポイントサイズを 10 倍し、pip 単位へ変換します。 true いいえ
ReverseAfterStop 損失後反転のワークフローを有効にします。 true いいえ
MaxReverseOrders 連続する反転取引の最大数。 2 いいえ
TakeProfitIncrease 各反転注文ごとに追加される take-profit ポイント。 100 いいえ
CandleType 計算に使うローソク足タイプと時間枠。 TimeSpan.FromMinutes(1) いいえ

リスク管理

  • stop-loss と take-profit のオフセットは、商品の価格ステップを使って再計算されます。桁数の自動検出は、5 桁クォートを pip サイズの距離へ変換します。
  • trailing ロジックは、市場が指定された trailing 距離だけ進んだ後にのみ有効になり、ストップを変更する前に最小ステップを強制します。
  • 反転取引は、利益の出たエグジット後、または設定された連続反転回数の上限に達した後にリセットされます。

実用上の注意

  • 小さなボラティリティ変化が momentum の急増を示しやすい、スプレッドの狭い通貨ペアで最もよく機能します。
  • ローソク足の時間枠を対象市場セッションに合わせることを検討してください。デフォルトの 1 分足時間枠は高頻度のブレイクアウトを捉えます。
  • 反転は損失決済の直後に実行されるため、連続取引に十分な証拠金があることを確認してください。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class BreakthroughVolatilityStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public BreakthroughVolatilityStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}