スモール・インサイド・バー戦略
概要
スモール・インサイド・バー戦略は、2本の連続したローソク足の間のモメンタムシフトに続くコンパクトなインサイドバーパターンを探します。オリジナルのMetaTrader 5のエキスパートがStockSharpの高レベルAPIに変換され、現在は完成したローソク足のみで動作します。このアプローチは、ボラティリティが圧縮されたフェーズによってトリガーされるブレイクアウトスタイルのエントリーを好むトレーダー向けに設計されています。
パターン定義
戦略は最新の2本の完成したローソク足を評価します:
- インサイドバー条件 – 最後に完成したローソク足は前のローソク足の範囲内に完全に収まっていなければなりません。
- レンジ比フィルター – マザーバー(2本前のバー)のレンジはインサイドバーのレンジの設定可能な倍数以上でなければなりません。デフォルトの比率は2:1です。
- 方向フィルター –
- ロングセットアップは、弱気のマザーバーとともにマザーバーの下半分に形成される強気のインサイドバーを必要とします。
- ショートセットアップは、強気のマザーバーとともにマザーバーの上半分に形成される弱気のインサイドバーを必要とします。
- オプションの反転は同じ幾何学的要件を維持しながらロングとショートの解釈を入れ替えます。
ポジション管理
OpenMode パラメーターはオリジナルEAの動作を反映します:
- AnySignal – すべてのシグナルで新しい成行注文を送信します。反対のポジションが存在する場合、StockSharpはネッティングアカウントを使用するため部分的に相殺されます。
- SwingWithRefill – エントリー前に反対のエクスポージャーをフラットにし、同じ方向への複数の追加を許可します。
- SingleSwing – 最大1つの方向取引を維持します。整合したポジションがオープンの間は新しいシグナルは無視されます。
ロングとショートの両エントリーは独立して有効化できます。リバーサル取引は単純にどのセットアップがロングまたはショートの注文を生成するかを反転させます。
パラメーター
| 名前 | デフォルト | 説明 |
|---|---|---|
CandleType |
1時間の時間軸 | パターン検出に使用されるローソク足サブスクリプション。 |
RangeRatioThreshold |
2.0 | 最小マザー対インサイドレンジ比。 |
EnableLong |
true | 強気取引を許可する。 |
EnableShort |
true | 弱気取引を許可する。 |
ReverseSignals |
false | ロングとショートのパターン方向を入れ替える。 |
OpenMode |
SwingWithRefill | 新しいシグナルで既存のエクスポージャーをどう処理するかを制御する。 |
取引ロジック
- 設定されたローソク足シリーズをサブスクライブして完成したバーを待ちます。
- パターンを評価するために最後の2本の完成したローソク足を維持します。
- パターンとレンジフィルターが整合したら、方向シグナルを決定し、オプションでリバーサルを適用します。
- 取引が許可されていること(
IsFormedAndOnlineAndAllowTrading)と関連する方向が有効になっていることを確認します。 - 選択された
OpenModeに基づいて注文サイズを計算し、戦略のベースボリュームを使用して成行注文を送信します。 - 最新のローソク足が次の評価サイクルの一部になるよう内部のローソク足履歴を更新します。
実装メモ
- 戦略は組み込みのリスクマネージャーを有効化するために
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class small_inside_bar_strategy(Strategy):
def __init__(self):
super(small_inside_bar_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._range_ratio_threshold = self.Param("RangeRatioThreshold", 2.25)
self._enable_long = self.Param("EnableLong", True)
self._enable_short = self.Param("EnableShort", True)
self._reverse_signals = self.Param("ReverseSignals", False)
self._prev_candle_high = None
self._prev_candle_low = None
self._prev_candle_open = None
self._prev_candle_close = None
self._two_back_high = None
self._two_back_low = None
self._two_back_open = None
self._two_back_close = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(small_inside_bar_strategy, self).OnStarted2(time)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._prev_candle_high is None:
self._cache_prev(candle)
return
if self._two_back_high is None:
self._shift_history(candle)
return
inside_high = self._prev_candle_high
inside_low = self._prev_candle_low
mother_high = self._two_back_high
mother_low = self._two_back_low
if inside_high <= inside_low or mother_high <= mother_low:
self._shift_history(candle)
return
if not (inside_high < mother_high and inside_low > mother_low):
self._shift_history(candle)
return
inside_range = inside_high - inside_low
mother_range = mother_high - mother_low
ratio = mother_range / inside_range if inside_range != 0 else 1e18
if ratio <= self._range_ratio_threshold.Value:
self._shift_history(candle)
return
midpoint = (mother_high + mother_low) / 2.0
bullish_inside = (self._prev_candle_close > self._prev_candle_open and
inside_high < midpoint and
self._two_back_close < self._two_back_open)
bearish_inside = (self._prev_candle_close < self._prev_candle_open and
inside_low < midpoint and
self._two_back_close > self._two_back_open)
if self._reverse_signals.Value:
bullish_inside, bearish_inside = bearish_inside, bullish_inside
should_open_long = bullish_inside and self._enable_long.Value
should_open_short = bearish_inside and self._enable_short.Value
if should_open_long:
volume = self._calc_order_volume(True)
if volume > 0:
self.BuyMarket(volume)
if should_open_short:
volume = self._calc_order_volume(False)
if volume > 0:
self.SellMarket(volume)
self._shift_history(candle)
def _calc_order_volume(self, is_long):
base_volume = float(self.Volume)
if base_volume <= 0:
return 0
position = self.Position
if is_long:
if position < 0:
base_volume += abs(position)
else:
if position > 0:
base_volume += abs(position)
return base_volume
def _cache_prev(self, candle):
self._prev_candle_high = float(candle.HighPrice)
self._prev_candle_low = float(candle.LowPrice)
self._prev_candle_open = float(candle.OpenPrice)
self._prev_candle_close = float(candle.ClosePrice)
def _shift_history(self, candle):
self._two_back_high = self._prev_candle_high
self._two_back_low = self._prev_candle_low
self._two_back_open = self._prev_candle_open
self._two_back_close = self._prev_candle_close
self._cache_prev(candle)
def OnReseted(self):
super(small_inside_bar_strategy, self).OnReseted()
self._prev_candle_high = None
self._prev_candle_low = None
self._prev_candle_open = None
self._prev_candle_close = None
self._two_back_high = None
self._two_back_low = None
self._two_back_open = None
self._two_back_close = None
def CreateClone(self):
return small_inside_bar_strategy()