GitHub で見る

スモール・インサイド・バー戦略

概要

スモール・インサイド・バー戦略は、2本の連続したローソク足の間のモメンタムシフトに続くコンパクトなインサイドバーパターンを探します。オリジナルのMetaTrader 5のエキスパートがStockSharpの高レベルAPIに変換され、現在は完成したローソク足のみで動作します。このアプローチは、ボラティリティが圧縮されたフェーズによってトリガーされるブレイクアウトスタイルのエントリーを好むトレーダー向けに設計されています。

パターン定義

戦略は最新の2本の完成したローソク足を評価します:

  1. インサイドバー条件 – 最後に完成したローソク足は前のローソク足の範囲内に完全に収まっていなければなりません。
  2. レンジ比フィルター – マザーバー(2本前のバー)のレンジはインサイドバーのレンジの設定可能な倍数以上でなければなりません。デフォルトの比率は2:1です。
  3. 方向フィルター
    • ロングセットアップは、弱気のマザーバーとともにマザーバーの下半分に形成される強気のインサイドバーを必要とします。
    • ショートセットアップは、強気のマザーバーとともにマザーバーの上半分に形成される弱気のインサイドバーを必要とします。
  4. オプションの反転は同じ幾何学的要件を維持しながらロングとショートの解釈を入れ替えます。

ポジション管理

OpenMode パラメーターはオリジナルEAの動作を反映します:

  • AnySignal – すべてのシグナルで新しい成行注文を送信します。反対のポジションが存在する場合、StockSharpはネッティングアカウントを使用するため部分的に相殺されます。
  • SwingWithRefill – エントリー前に反対のエクスポージャーをフラットにし、同じ方向への複数の追加を許可します。
  • SingleSwing – 最大1つの方向取引を維持します。整合したポジションがオープンの間は新しいシグナルは無視されます。

ロングとショートの両エントリーは独立して有効化できます。リバーサル取引は単純にどのセットアップがロングまたはショートの注文を生成するかを反転させます。

パラメーター

名前 デフォルト 説明
CandleType 1時間の時間軸 パターン検出に使用されるローソク足サブスクリプション。
RangeRatioThreshold 2.0 最小マザー対インサイドレンジ比。
EnableLong true 強気取引を許可する。
EnableShort true 弱気取引を許可する。
ReverseSignals false ロングとショートのパターン方向を入れ替える。
OpenMode SwingWithRefill 新しいシグナルで既存のエクスポージャーをどう処理するかを制御する。

取引ロジック

  1. 設定されたローソク足シリーズをサブスクライブして完成したバーを待ちます。
  2. パターンを評価するために最後の2本の完成したローソク足を維持します。
  3. パターンとレンジフィルターが整合したら、方向シグナルを決定し、オプションでリバーサルを適用します。
  4. 取引が許可されていること(IsFormedAndOnlineAndAllowTrading)と関連する方向が有効になっていることを確認します。
  5. 選択された OpenMode に基づいて注文サイズを計算し、戦略のベースボリュームを使用して成行注文を送信します。
  6. 最新のローソク足が次の評価サイクルの一部になるよう内部のローソク足履歴を更新します。

実装メモ

  • 戦略は組み込みのリスクマネージャーを有効化するために StartProtection() を使用します(事前定義されたストップやテイクプロフィット値なし)。必要に応じて追加のフィルターを外部から追加できます。
  • インジケーターの状態はコレクションに保存されません。パターンに必要な最新の2本のローソク足のみが保持されます。
  • アルゴリズムは高レベルAPIのベストプラクティスに沿って、バー内計算を避けて完成したローソク足のみに依存します。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Implements the "Small Inside Bar" pattern strategy converted from MetaTrader 5.
/// The strategy searches for an inside bar with a small range compared to the mother bar
/// and opens positions following the direction of the pattern conditions.
/// </summary>
public class SmallInsideBarStrategy : Strategy
{
	/// <summary>
	/// Defines how the strategy manages simultaneous entries.
	/// </summary>
	public enum SmallInsideBarOpenModes
	{
		/// <summary>
		/// Open a new position on every signal without forcing opposite positions to close.
		/// </summary>
		AnySignal,

		/// <summary>
		/// Close opposite positions first and allow adding to the current swing direction.
		/// </summary>
		SwingWithRefill,

		/// <summary>
		/// Maintain a single position in the market and ignore additional entries while it is active.
		/// </summary>
		SingleSwing
	}

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _rangeRatioThreshold;
	private readonly StrategyParam<bool> _enableLong;
	private readonly StrategyParam<bool> _enableShort;
	private readonly StrategyParam<bool> _reverseSignals;
	private readonly StrategyParam<SmallInsideBarOpenModes> _openMode;

	private ICandleMessage _previousCandle;
	private ICandleMessage _twoBackCandle;

	/// <summary>
	/// Type of candles used for pattern detection.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Minimum ratio between the mother bar range and the inside bar range.
	/// </summary>
	public decimal RangeRatioThreshold
	{
		get => _rangeRatioThreshold.Value;
		set => _rangeRatioThreshold.Value = value;
	}

	/// <summary>
	/// Allow long trades.
	/// </summary>
	public bool EnableLong
	{
		get => _enableLong.Value;
		set => _enableLong.Value = value;
	}

	/// <summary>
	/// Allow short trades.
	/// </summary>
	public bool EnableShort
	{
		get => _enableShort.Value;
		set => _enableShort.Value = value;
	}

	/// <summary>
	/// Reverse long and short signals.
	/// </summary>
	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	/// <summary>
	/// Mode for handling position entries.
	/// </summary>
	public SmallInsideBarOpenModes OpenMode
	{
		get => _openMode.Value;
		set => _openMode.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public SmallInsideBarStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for pattern detection", "General");

		_rangeRatioThreshold = Param(nameof(RangeRatioThreshold), 2.25m)
			.SetGreaterThanZero()
			.SetDisplay("Range Ratio", "Minimum mother-to-inside bar range ratio", "Pattern")
			
			.SetOptimize(1.5m, 3m, 0.25m);

		_enableLong = Param(nameof(EnableLong), true)
			.SetDisplay("Enable Long", "Allow bullish trades", "Trading");

		_enableShort = Param(nameof(EnableShort), true)
			.SetDisplay("Enable Short", "Allow bearish trades", "Trading");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse Signals", "Invert long and short signals", "Trading");

		_openMode = Param(nameof(OpenMode), SmallInsideBarOpenModes.SwingWithRefill)
			.SetDisplay("Open Mode", "Position management mode", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousCandle = null;
		_twoBackCandle = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

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

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

		if (_previousCandle == null)
		{
			_previousCandle = candle;
			return;
		}

		if (_twoBackCandle == null)
		{
			_twoBackCandle = _previousCandle;
			_previousCandle = candle;
			return;
		}

		var insideHigh = _previousCandle.HighPrice;
		var insideLow = _previousCandle.LowPrice;
		var motherHigh = _twoBackCandle.HighPrice;
		var motherLow = _twoBackCandle.LowPrice;

		if (insideHigh <= insideLow || motherHigh <= motherLow)
		{
			ShiftHistory(candle);
			return;
		}

		if (!(insideHigh < motherHigh && insideLow > motherLow))
		{
			ShiftHistory(candle);
			return;
		}

		var insideRange = insideHigh - insideLow;
		var motherRange = motherHigh - motherLow;
		var ratio = insideRange == 0 ? decimal.MaxValue : motherRange / insideRange;

		if (ratio <= RangeRatioThreshold)
		{
			ShiftHistory(candle);
			return;
		}

		var midpoint = (motherHigh + motherLow) / 2m;

		var bullishInside = _previousCandle.ClosePrice > _previousCandle.OpenPrice && insideHigh < midpoint && _twoBackCandle.ClosePrice < _twoBackCandle.OpenPrice;
		var bearishInside = _previousCandle.ClosePrice < _previousCandle.OpenPrice && insideLow < midpoint && _twoBackCandle.ClosePrice > _twoBackCandle.OpenPrice;

		if (ReverseSignals)
		{
			(bullishInside, bearishInside) = (bearishInside, bullishInside);
		}

		var shouldOpenLong = bullishInside && EnableLong;
		var shouldOpenShort = bearishInside && EnableShort;

		if (shouldOpenLong)
		{
			var volume = CalculateOrderVolume(true);

			if (volume > 0)
				BuyMarket(volume);
		}

		if (shouldOpenShort)
		{
			var volume = CalculateOrderVolume(false);

			if (volume > 0)
				SellMarket(volume);
		}

		ShiftHistory(candle);
	}

	private decimal CalculateOrderVolume(bool isLong)
	{
		var baseVolume = Volume;

		if (baseVolume <= 0)
			return 0;

		var position = Position;

		if (isLong)
		{
			if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position > 0)
				return 0;

			if (position < 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
				baseVolume += Math.Abs(position);
		}
		else
		{
			if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position < 0)
				return 0;

			if (position > 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
				baseVolume += Math.Abs(position);
		}

		return baseVolume;
	}

	private void ShiftHistory(ICandleMessage candle)
	{
		// Keep track of the last two finished candles for pattern evaluation.
		_twoBackCandle = _previousCandle;
		_previousCandle = candle;
	}
}