Dieses Setup überwacht die Breite der Bollinger Bands, um Perioden niedriger Volatilität zu erkennen. Wenn sich die Bänder im Vergleich zu ihrem jüngsten Durchschnitt zusammenziehen, signalisiert dies eine mögliche bevorstehende Volatilitätserweiterung.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 100%. Sie funktioniert am besten auf dem Forex-Markt.
Sobald ein Squeeze identifiziert wird, wartet die Strategie darauf, dass der Preis außerhalb der Bänder bricht. Ein Schluss oberhalb des oberen Bandes eröffnet eine Long-Position, während ein Schluss unterhalb des unteren Bandes eine Short-Position eröffnet. Der Trade wird geschlossen, wenn der Preis in Richtung der Mitte der Bänder zurückkehrt oder wenn ein Stop-Loss ausgelöst wird.
Die Methode richtet sich an Trader, die Volatilitätsausbrüche handeln möchten, anstatt Trendfortsetzungen. Die Verwendung der Bandbreite als Filter hilft, Fehlsignale in unruhigen Märkten zu vermeiden.
Details
Einstiegskriterien:
Long: Bandbreite < durchschnittliche Breite && Schluss > oberes Band
Short: Bandbreite < durchschnittliche Breite && Schluss < unteres Band
Long/Short: Beide Seiten.
Ausstiegskriterien:
Long: Ausstieg, wenn der Preis wieder innerhalb der Bänder fällt
Short: Ausstieg, wenn der Preis wieder innerhalb der Bänder steigt
Stops: Ja, typischerweise bei 2*ATR.
Standardwerte:
BollingerPeriod = 20
BollingerMultiplier = 2
LookbackPeriod = 20
AtrPeriod = 14
AtrMultiplier = 2
Die durchschnittliche Breite umfasst die letzten LookbackPeriod Kerzen einschließlich der aktuellen. Ein Long schließt bei einem Schluss zurück am oder unter dem oberen Band, ein Short bei einem Schluss zurück am oder über dem unteren Band. Der Stop liegt AtrMultiplier mal die ATR über AtrPeriod vom Einstiegsschluss entfernt und wird bei Kerzenschluss geprüft; 0 schaltet ihn ab. Ein Einstiegssignal gegen eine offene Position dreht sie.
CandleType = TimeSpan.FromMinutes(5)
Filter:
Kategorie: Ausbruch
Richtung: Beide
Indikatoren: Bollinger Bands
Stops: Ja
Komplexität: Mittel
Zeitrahmen: Intraday
Saisonalität: Nein
Neuronale Netze: Nein
Divergenz: Nein
Risikolevel: Mittel
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bollinger Band Squeeze strategy.
/// A squeeze is a band width below the average width of the last LookbackPeriod candles, the current one included. During a squeeze a close
/// above the upper band goes long and a close below the lower band goes short, reversing an opposite position. A long closes once price
/// closes back inside the bands and a short likewise. The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
/// </summary>
public class BollingerBandSqueezeStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerMultiplier;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _widths = [];
private decimal _stopPrice;
/// <summary>
/// Period of the Bollinger Bands.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier of the bands.
/// </summary>
public decimal BollingerMultiplier
{
get => _bollingerMultiplier.Value;
set => _bollingerMultiplier.Value = value;
}
/// <summary>
/// Candles the average band width spans.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Period of the stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BollingerBandSqueezeStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period of the Bollinger Bands", "Indicators");
_bollingerMultiplier = Param(nameof(BollingerMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Multiplier", "Standard deviation multiplier of the bands", "Indicators");
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Candles the average band width spans", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the stop ATR", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_widths.Clear();
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_widths.Clear();
_stopPrice = default;
var bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerMultiplier };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, atr);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bollingerValue.IsFormed)
return;
var bands = (BollingerBandsValue)bollingerValue;
if (bands.UpBand is not decimal upper || bands.LowBand is not decimal lower)
return;
var width = upper - lower;
_widths.Enqueue(width);
if (_widths.Count > LookbackPeriod)
_widths.Dequeue();
if (_widths.Count < LookbackPeriod || !atrValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var squeeze = width < _widths.Average();
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (squeeze && close > upper && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - AtrMultiplier * atr;
}
else if (squeeze && close < lower && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + AtrMultiplier * atr;
}
else if (Position > 0 && (close <= upper || (AtrMultiplier > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (close >= lower || (AtrMultiplier > 0 && close >= _stopPrice)))
{
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
from StockSharp.Algo.Indicators import BollingerBands, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class bollinger_band_squeeze_strategy(Strategy):
"""
Bollinger Band Squeeze strategy.
A squeeze is a band width below the average width of the last LookbackPeriod candles, the current one included. During a squeeze a close
above the upper band goes long and a close below the lower band goes short, reversing an opposite position. A long closes once price
closes back inside the bands and a short likewise. The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
"""
def __init__(self):
super(bollinger_band_squeeze_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("Bollinger Period", "Period of the Bollinger Bands", "Indicators")
self._bollinger_multiplier = self.Param("BollingerMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Bollinger Multiplier", "Standard deviation multiplier of the bands", "Indicators")
self._lookback_period = self.Param("LookbackPeriod", 20).SetGreaterThanZero().SetDisplay("Lookback Period", "Candles the average band width spans", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the stop ATR", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._widths = []
self._stop_price = Decimal(0)
def OnReseted(self):
super(bollinger_band_squeeze_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(bollinger_band_squeeze_strategy, self).OnStarted2(time)
self._reset_state()
bollinger = BollingerBands()
bollinger.Length = self._bollinger_period.Value
bollinger.Width = Decimal(self._bollinger_multiplier.Value)
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, atr)
def _process_candle(self, candle, bollinger_value, atr_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
upper = bollinger_value.UpBand
lower = bollinger_value.LowBand
width = upper - lower
period = self._lookback_period.Value
self._widths.append(width)
if len(self._widths) > period:
self._widths.pop(0)
if len(self._widths) < period or not atr_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
total = Decimal(0)
for value in self._widths:
total += value
squeeze = width < total / Decimal(period)
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
stop_atr = Decimal(self._atr_multiplier.Value)
if squeeze and close > upper and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif squeeze and close < lower and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_atr * atr
elif self.Position > 0 and (close <= upper or (stop_atr > 0 and close <= self._stop_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (close >= lower or (stop_atr > 0 and close >= self._stop_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return bollinger_band_squeeze_strategy()