BBブレイクアウト戦略
概要
BBブレイクアウト戦略は、MetaTraderエキスパートアドバイザーBreakthrough_BBをStockSharpの高レベルAPIで再現します。このシステムはボリンジャーバンドと高速な単純移動平均を組み合わせて、価格がバンド境界付近で圧縮された後に発生する爆発的なブレイクアウトを捉えます。シグナルを確定的に保ち、元のMQL5の動作を反映するために、取引は完成したロウソク足でのみ生成されます。
トレードロジック
- トレンドフィルター: 設定可能な期間の単純移動平均(SMA)がトレンド方向を検証します。戦略は最新のSMA値を4バー前のSMA値と比較します。ロング取引にはSMAの上向き傾斜が必要で、ショートには下向き傾斜が必要です。
- ボリンジャーバンドブレイクアウト: 戦略は4バー前の終値がボリンジャー上限または下限バンドとどのように相互作用したかを観察し、最新の終値と比較します。有効なブレイクアウトは、2つのタイムスタンプ間でバンド内から外へ価格が動いたときに発生します。
- シングルポジションモデル: アルゴリズムは最大1つのオープンポジションを保持します。重複したエクスポージャーを防ぐため、新規エントリーを評価する前にオープン取引をクローズします。
エントリー条件
ロング設定
- 4本前の完成したロウソク足の終値がボリンジャー上限バンドより低かった。
- 最新の終値が現在のボリンジャー上限バンドより高く終了した。
- 最新ロウソク足で計算されたSMA値が4本前のSMA値より大きい(正の傾斜)。
- 現在オープンなポジションがない。
ショート設定
- 4本前の完成したロウソク足の終値がボリンジャー下限バンドより高かった。
- 最新の終値が現在のボリンジャー下限バンドより低く終了した。
- 最新ロウソク足で計算されたSMA値が4本前のSMA値より小さい(負の傾斜)。
- 現在オープンなポジションがない。
エントリー条件が満たされると、戦略は設定されたボリュームパラメーターを使用して成行注文を送信します。
エグジットルール
- ロングポジションエグジット: ロング取引がアクティブで最新の終値がボリンジャー中央線を下回った場合、ポジションは即座に成行売り注文でクローズされます。
- ショートポジションエグジット: ショート取引がオープンで最新の終値がボリンジャー中央線を上回った場合、ポジションは成行買い注文でカバーされます。
これらのエグジットルールは、市場がバンド中央線内に戻るたびに取引を削除した元のエキスパートアドバイザーを模倣します。
インジケーター
- 単純移動平均(SMA): 方向バイアスを定義し、4ロウソク足インターバルにわたる傾斜比較を提供します。
- ボリンジャーバンド: ブレイクアウトエントリーの検出とエグジット管理に使用される上限、中央、下限エンベロープを提供します。
パラメーター
| 名前 |
説明 |
デフォルト |
最適化可能 |
MaPeriod |
トレンドフィルターに使用するSMAの長さ。 |
9 |
✔ |
BandsPeriod |
ボリンジャーバンド計算のルックバック長。 |
28 |
✔ |
Deviation |
ボリンジャーバンドに適用される標準偏差乗数。 |
1.6 |
✔ |
Volume |
注文サイズ(インストゥルメントに応じてロットまたは枚)。 |
1 |
✔ |
CandleType |
戦略が処理するロウソク足集計タイプ。 |
1時間の時間軸 |
✖ |
すべてのパラメーターはStockSharpのStrategyParamメタデータを公開しており、UIで調整したりデザイナーで最適化したりできます。
データ要件
- 選択した
CandleTypeと互換性のあるロウソク足データを提供するあらゆるインストゥルメントで機能します。
- シグナルは完成したロウソク足のみで評価されます。未完成のロウソク足はロジックを確定的に保つために無視されます。
- デフォルト設定は時間足ロウソク足を使用しますが、データソースがサポートするあらゆる時間軸を提供できます。
追加の注記
- アルゴリズムはインジケーター履歴の検索を避け、代わりに終値とSMA値のための4バーのローリングキャッシュを維持し、プロジェクトガイドラインを遵守しています。
- ストップロスやテイクプロフィットなどの保護機能は必要に応じて
StartProtectionで追加できます。元のMQL実装には含まれていないため、ここでは省略されています。
- 戦略は成行注文を発行するため、スリッページを最小化するために選択したインストゥルメントに十分な流動性があることを確認してください。
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>
/// Breakout strategy that trades Bollinger Bands breakouts with a moving average trend filter.
/// </summary>
public class BreakthroughBbStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _bandsPeriod;
private readonly StrategyParam<decimal> _deviation;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _sma;
private BollingerBands _bollingerBands;
private decimal? _closeLag0;
private decimal? _closeLag1;
private decimal? _closeLag2;
private decimal? _closeLag3;
private decimal? _maLag0;
private decimal? _maLag1;
private decimal? _maLag2;
private decimal? _maLag3;
/// <summary>
/// Moving average period that defines the long term trend.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands lookback period.
/// </summary>
public int BandsPeriod
{
get => _bandsPeriod.Value;
set => _bandsPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands width measured in standard deviations.
/// </summary>
public decimal Deviation
{
get => _deviation.Value;
set => _deviation.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="BreakthroughBbStrategy"/>.
/// </summary>
public BreakthroughBbStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Simple moving average length", "Parameters")
;
_bandsPeriod = Param(nameof(BandsPeriod), 28)
.SetGreaterThanZero()
.SetDisplay("Bands Period", "Bollinger Bands lookback", "Parameters")
;
_deviation = Param(nameof(Deviation), 1.6m)
.SetGreaterThanZero()
.SetDisplay("Deviation", "Bollinger Bands width in deviations", "Parameters")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle series processed by the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = default;
_bollingerBands = default;
_closeLag0 = null;
_closeLag1 = null;
_closeLag2 = null;
_closeLag3 = null;
_maLag0 = null;
_maLag1 = null;
_maLag2 = null;
_maLag3 = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new SimpleMovingAverage { Length = MaPeriod };
_bollingerBands = new BollingerBands
{
Length = BandsPeriod,
Width = Deviation
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_sma, _bollingerBands, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaIndValue, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
var smaValue = smaIndValue.IsFormed ? smaIndValue.ToDecimal() : 0m;
var bb = bbValue as IBollingerBandsValue;
var middleBand = bb?.MovingAverage ?? 0m;
var upperBand = bb?.UpBand ?? 0m;
var lowerBand = bb?.LowBand ?? 0m;
var close = candle.ClosePrice;
var maPrev4 = _maLag2;
var closePrev4 = _closeLag2;
if (_sma is null || _bollingerBands is null)
{
UpdateHistory(close, smaValue);
return;
}
if (!_sma.IsFormed || !_bollingerBands.IsFormed)
{
UpdateHistory(close, smaValue);
return;
}
if (Position > 0 && close < middleBand)
{
SellMarket();
UpdateHistory(close, smaValue);
return;
}
if (Position < 0 && close > middleBand)
{
BuyMarket();
UpdateHistory(close, smaValue);
return;
}
if (maPrev4 is null || closePrev4 is null)
{
UpdateHistory(close, smaValue);
return;
}
if (Position == 0)
{
if (closePrev4.Value < upperBand && close > upperBand && smaValue > maPrev4.Value)
{
BuyMarket();
UpdateHistory(close, smaValue);
return;
}
if (closePrev4.Value > lowerBand && close < lowerBand && smaValue < maPrev4.Value)
{
SellMarket();
UpdateHistory(close, smaValue);
return;
}
}
UpdateHistory(close, smaValue);
}
private void UpdateHistory(decimal close, decimal maValue)
{
_maLag3 = _maLag2;
_maLag2 = _maLag1;
_maLag1 = _maLag0;
_maLag0 = maValue;
_closeLag3 = _closeLag2;
_closeLag2 = _closeLag1;
_closeLag1 = _closeLag0;
_closeLag0 = close;
}
}
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 SimpleMovingAverage, BollingerBands
from StockSharp.Algo.Strategies import Strategy
class breakthrough_bb_strategy(Strategy):
def __init__(self):
super(breakthrough_bb_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 9)
self._bands_period = self.Param("BandsPeriod", 28)
self._deviation = self.Param("Deviation", 1.6)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._close_lag0 = None
self._close_lag1 = None
self._close_lag2 = None
self._close_lag3 = None
self._ma_lag0 = None
self._ma_lag1 = None
self._ma_lag2 = None
self._ma_lag3 = None
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def BandsPeriod(self):
return self._bands_period.Value
@BandsPeriod.setter
def BandsPeriod(self, value):
self._bands_period.Value = value
@property
def Deviation(self):
return self._deviation.Value
@Deviation.setter
def Deviation(self, value):
self._deviation.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(breakthrough_bb_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = self.MaPeriod
self._bollinger = BollingerBands()
self._bollinger.Length = self.BandsPeriod
self._bollinger.Width = self.Deviation
self._close_lag0 = None
self._close_lag1 = None
self._close_lag2 = None
self._close_lag3 = None
self._ma_lag0 = None
self._ma_lag1 = None
self._ma_lag2 = None
self._ma_lag3 = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._sma, self._bollinger, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_ind_value, bb_value):
if candle.State != CandleStates.Finished:
return
sma_val = float(sma_ind_value) if sma_ind_value.IsFormed else 0.0
mid_band = float(bb_value.MovingAverage) if bb_value.MovingAverage is not None else 0.0
upper = float(bb_value.UpBand) if bb_value.UpBand is not None else 0.0
lower = float(bb_value.LowBand) if bb_value.LowBand is not None else 0.0
close = float(candle.ClosePrice)
if not self._sma.IsFormed or not self._bollinger.IsFormed:
self._update_history(close, sma_val)
return
# Exit on middle band cross
if self.Position > 0 and close < mid_band:
self.SellMarket()
self._update_history(close, sma_val)
return
if self.Position < 0 and close > mid_band:
self.BuyMarket()
self._update_history(close, sma_val)
return
ma_prev4 = self._ma_lag2
close_prev4 = self._close_lag2
if ma_prev4 is None or close_prev4 is None:
self._update_history(close, sma_val)
return
if self.Position == 0:
if close_prev4 < upper and close > upper and sma_val > ma_prev4:
self.BuyMarket()
self._update_history(close, sma_val)
return
if close_prev4 > lower and close < lower and sma_val < ma_prev4:
self.SellMarket()
self._update_history(close, sma_val)
return
self._update_history(close, sma_val)
def _update_history(self, close, ma_value):
self._ma_lag3 = self._ma_lag2
self._ma_lag2 = self._ma_lag1
self._ma_lag1 = self._ma_lag0
self._ma_lag0 = ma_value
self._close_lag3 = self._close_lag2
self._close_lag2 = self._close_lag1
self._close_lag1 = self._close_lag0
self._close_lag0 = close
def OnReseted(self):
super(breakthrough_bb_strategy, self).OnReseted()
self._close_lag0 = None
self._close_lag1 = None
self._close_lag2 = None
self._close_lag3 = None
self._ma_lag0 = None
self._ma_lag1 = None
self._ma_lag2 = None
self._ma_lag3 = None
def CreateClone(self):
return breakthrough_bb_strategy()