マルチタイムフレーム Bollinger Bands戦略
プライマリと上位の時間軸の両方にBollinger Bandsを適用します。価格が上位時間軸のバンドを突き抜けた時に取引し、オプションで長期移動平均によりエントリーをフィルタリングします。目標は広義のトレンドに逆らって極値をフェードすることです。
この戦略はロングとショートの両ポジションをサポートします。リスク管理のためにストップロスのパーセンテージを有効にできます。複数の時間軸を使用することで、支配的なマーケット構造に逆らった取引を避けるのに役立ちます。
詳細
- エントリー条件:
- ロング: 上位時間軸の下部バンドを下回り、MAフィルター(有効な場合)を上回って終値が付く。
- ショート: 上位時間軸の上部バンドを上回り、MAフィルター(有効な場合)を下回って終値が付く。
- エグジット条件:
- ロング: 現在の時間軸の上部バンドを上回って価格が終値を付ける。
- ショート: 現在の時間軸の下部バンドを下回って価格が終値を付ける。
- インジケーター:
- 2つの時間軸のBollinger Bands (長さ20、乗数2)
- オプションのEMAフィルター (期間200)
- ストップ: StartProtectionによるオプションのストップロス (%-ベース)。
- デフォルト値:
BBLength= 20BBMultiplier= 2.0UseMaFilter= FalseMaLength= 200SLPercent= 2
- フィルター:
- MTFコンテキストとの逆張り
- 時間軸: メイン5m、MTF 60m デフォルト
- インジケーター: Bollinger Bands、EMA
- ストップ: オプション
- 複雑さ: 中程度
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;
/// <summary>
/// Multi-timeframe Bollinger Bands strategy.
/// Bollinger Bands run on the trading timeframe and on MtfCandleType. A long opens when the close is below the
/// higher-timeframe lower band and a short when it is above the higher-timeframe upper band; with UseMaFilter the close
/// must also be above (long) or below (short) the EMA. A long exits on a close above the trading-timeframe upper band,
/// a short on a close below its lower band, and SLPercent sets a percent stop-loss.
/// </summary>
public class MtfBbStrategy : Strategy
{
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<bool> _useMaFilter;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<decimal> _slPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _mtfCandleType;
private decimal? _mtfUpper;
private decimal? _mtfLower;
/// <summary>
/// Bollinger Bands period on both timeframes.
/// </summary>
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier on both timeframes.
/// </summary>
public decimal BBMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
/// <summary>
/// Enable the EMA trend filter.
/// </summary>
public bool UseMaFilter
{
get => _useMaFilter.Value;
set => _useMaFilter.Value = value;
}
/// <summary>
/// EMA filter period.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Stop-loss percentage. 0 disables it.
/// </summary>
public decimal SLPercent
{
get => _slPercent.Value;
set => _slPercent.Value = value;
}
/// <summary>
/// Trading candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type.
/// </summary>
public DataType MtfCandleType
{
get => _mtfCandleType.Value;
set => _mtfCandleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MtfBbStrategy()
{
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands");
_bbMultiplier = Param(nameof(BBMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Bollinger Bands");
_useMaFilter = Param(nameof(UseMaFilter), false)
.SetDisplay("Use MA Filter", "Require the close on the trade side of the EMA", "Filter");
_maLength = Param(nameof(MaLength), 200)
.SetGreaterThanZero()
.SetDisplay("MA Length", "EMA filter period", "Filter");
_slPercent = Param(nameof(SLPercent), 2m)
.SetNotNegative()
.SetDisplay("SL %", "Stop-loss percentage, 0 disables", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle type", "Trading timeframe", "General");
_mtfCandleType = Param(nameof(MtfCandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("MTF Candle type", "Higher timeframe for the entry bands", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, MtfCandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_mtfUpper = null;
_mtfLower = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_mtfUpper = null;
_mtfLower = null;
var bollinger = new BollingerBands { Length = BBLength, Width = BBMultiplier };
var mtfBollinger = new BollingerBands { Length = BBLength, Width = BBMultiplier };
var ma = new ExponentialMovingAverage { Length = MaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, ma, ProcessCandle)
.Start();
SubscribeCandles(MtfCandleType)
.BindEx(mtfBollinger, ProcessMtfCandle)
.Start();
if (SLPercent > 0)
StartProtection(new Unit(), new Unit(SLPercent, UnitTypes.Percent), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessMtfCandle(ICandleMessage candle, IIndicatorValue bollingerValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bollingerValue.IsFormed)
return;
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
return;
_mtfUpper = upper;
_mtfLower = lower;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bollingerValue.IsFormed || (UseMaFilter && !maValue.IsFormed))
return;
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
return;
if (_mtfUpper is not decimal mtfUpper || _mtfLower is not decimal mtfLower)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var ma = UseMaFilter ? maValue.ToDecimal() : 0m;
var longSignal = close < mtfLower && (!UseMaFilter || close > ma);
var shortSignal = close > mtfUpper && (!UseMaFilter || close < ma);
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && close > upper)
SellMarket(Position);
else if (Position < 0 && close < lower)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class mtf_bb_strategy(Strategy):
"""
Multi-timeframe Bollinger Bands strategy.
Bollinger Bands run on the trading timeframe and on MtfCandleType. A long opens when the close is below the
higher-timeframe lower band and a short when it is above the higher-timeframe upper band; with UseMaFilter the close
must also be above (long) or below (short) the EMA. A long exits on a close above the trading-timeframe upper band,
a short on a close below its lower band, and SLPercent sets a percent stop-loss.
"""
def __init__(self):
super(mtf_bb_strategy, self).__init__()
self._bb_length = self.Param("BBLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands")
self._bb_multiplier = self.Param("BBMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Bollinger Bands")
self._use_ma_filter = self.Param("UseMaFilter", False) \
.SetDisplay("Use MA Filter", "Require the close on the trade side of the EMA", "Filter")
self._ma_length = self.Param("MaLength", 200) \
.SetGreaterThanZero() \
.SetDisplay("MA Length", "EMA filter period", "Filter")
self._sl_percent = self.Param("SLPercent", 2.0) \
.SetNotNegative() \
.SetDisplay("SL %", "Stop-loss percentage, 0 disables", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle type", "Trading timeframe", "General")
self._mtf_candle_type = self.Param("MtfCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60))) \
.SetDisplay("MTF Candle type", "Higher timeframe for the entry bands", "General")
self._mtf_upper = None
self._mtf_lower = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def MtfCandleType(self):
return self._mtf_candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, self.MtfCandleType)]
def OnReseted(self):
super(mtf_bb_strategy, self).OnReseted()
self._mtf_upper = None
self._mtf_lower = None
def OnStarted2(self, time):
super(mtf_bb_strategy, self).OnStarted2(time)
self._mtf_upper = None
self._mtf_lower = None
bollinger = BollingerBands()
bollinger.Length = self._bb_length.Value
bollinger.Width = Decimal(self._bb_multiplier.Value)
mtf_bollinger = BollingerBands()
mtf_bollinger.Length = self._bb_length.Value
mtf_bollinger.Width = Decimal(self._bb_multiplier.Value)
ma = ExponentialMovingAverage()
ma.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bollinger, ma, self._process_candle).Start()
self.SubscribeCandles(self.MtfCandleType).BindEx(mtf_bollinger, self._process_mtf_candle).Start()
sl = float(self._sl_percent.Value)
if sl > 0:
self.StartProtection(Unit(), Unit(Decimal(sl), UnitTypes.Percent), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_mtf_candle(self, candle, bollinger_value):
if candle.State != CandleStates.Finished:
return
if not bollinger_value.IsFormed:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
self._mtf_upper = float(bollinger_value.UpBand)
self._mtf_lower = float(bollinger_value.LowBand)
def _process_candle(self, candle, bollinger_value, ma_value):
if candle.State != CandleStates.Finished:
return
use_ma = self._use_ma_filter.Value
if not bollinger_value.IsFormed or (use_ma and not ma_value.IsFormed):
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
if self._mtf_upper is None or self._mtf_lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
upper = float(bollinger_value.UpBand)
lower = float(bollinger_value.LowBand)
close = float(candle.ClosePrice)
ma = float(ma_value.GetValue[Decimal](None)) if use_ma else 0.0
long_signal = close < self._mtf_lower and (not use_ma or close > ma)
short_signal = close > self._mtf_upper and (not use_ma or close < ma)
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and close > upper:
self.SellMarket(self.Position)
elif self.Position < 0 and close < lower:
self.BuyMarket(-self.Position)
def CreateClone(self):
return mtf_bb_strategy()