Multi-Timeframe Bollinger Bands-Strategie
Wendet Bollinger Bands sowohl auf einem primären als auch auf einem höheren Zeitrahmen an. Handelt, wenn der Kurs die Bänder des höheren Zeitrahmens durchsticht, und filtert Einstiege optional mit einem langfristigen gleitenden Durchschnitt. Das Ziel ist es, Extremwerte gegen den übergeordneten Trend zu faden.
Die Strategie unterstützt sowohl Long- als auch Short-Positionen. Ein Stop-Loss-Prozentsatz kann für das Risikomanagement aktiviert werden. Der Einsatz mehrerer Zeitrahmen hilft, Trades gegen die dominante Marktstruktur zu vermeiden.
Details
- Einstiegskriterien:
- Long: Schluss unter dem unteren Band des höheren Zeitrahmens und über dem MA-Filter (falls aktiviert).
- Short: Schluss über dem oberen Band des höheren Zeitrahmens und unter dem MA-Filter (falls aktiviert).
- Ausstiegskriterien:
- Long: Preis schließt über dem oberen Band des aktuellen Zeitrahmens.
- Short: Preis schließt unter dem unteren Band des aktuellen Zeitrahmens.
- Indikatoren:
- Bollinger Bands auf zwei Zeitrahmen (Länge 20, Multiplikator 2)
- Optionaler EMA-Filter (Periode 200)
- Stops: Optionaler Stop-Loss über StartProtection (%-basiert).
- Standardwerte:
BBLength= 20BBMultiplier= 2.0UseMaFilter= FalseMaLength= 200SLPercent= 2
- Filter:
- Gegentrend mit MTF-Kontext
- Zeitrahmen: Haupt 5m, MTF 60m standardmäßig
- Indikatoren: Bollinger Bands, EMA
- Stops: Optional
- Komplexität: Moderat
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()