Bollinger Divergenz-Strategie
Bollinger Divergence sucht nach Extremen, bei denen der Preis ein Band durchbricht, während das gegenüberliegende Band beginnt zu schrumpfen. Diese Divergenz zwischen Kursmomentum und Volatilität geht oft einer Rückkehr zur Mitte der Range voraus.
Ein Long-Signal erscheint, wenn eine Kerze unterhalb des unteren Bandes schließt, während das obere Band sich um mindestens einen festgelegten Prozentsatz verengt. Für Shorts ist das Muster um das obere Band gespiegelt. Positionen zielen auf eine schnelle Rückkehr zur mittleren Bollinger-Linie mit optionalem festem Take-Profit.
Das Setup funktioniert am besten in seitwärts laufenden Märkten oder nachdem ein
Volatilitätsanstieg beginnt, abzuebben. Der CandlePercent-Parameter steuert, wie
stark sich das gegenüberliegende Band zusammenziehen muss, bevor ein Trade erlaubt
wird, und hilft, Whipsaws bei starken Trends zu vermeiden.
Details
- Daten: Kurskerzen.
- Einstiegskriterien:
- Long: Schlusskurs unter unterem Band UND oberes Band zieht sich um
CandlePercentzusammen. - Short: Schlusskurs über oberem Band UND unteres Band zieht sich um
CandlePercentzusammen.
- Long: Schlusskurs unter unterem Band UND oberes Band zieht sich um
- Ausstiegskriterien:
- Rückkehr zum mittleren Band ODER Take-Profit-Prozentsatz.
- Stops: Kein harter Stop; basiert auf Take-Profit oder manuellem Ausstieg.
- Standardwerte:
BBLength= 20BBMultiplier= 2.0CandlePercent= 30TakeProfit= 5
- Filter:
- Kategorie: Mean Reversion
- Richtung: Long/Short
- Indikatoren: Bollinger Bands
- Komplexität: Einfach
- Risikolevel: Mittel
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>
/// Bollinger Bands Divergence Strategy.
/// Detects divergence between price and Bollinger Bands expansion.
/// </summary>
public class BollingerDivergenceStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<int> _cooldownBars;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BBMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
private BollingerBands _bollinger;
private decimal _prevUpperBand;
private decimal _prevLowerBand;
private int _cooldownRemaining;
public BollingerDivergenceStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands");
_bbMultiplier = Param(nameof(BBMultiplier), 2.0m)
.SetDisplay("BB StdDev", "Bollinger Bands standard deviation multiplier", "Bollinger Bands");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bollinger = null;
_prevUpperBand = 0;
_prevLowerBand = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands
{
Length = BBLength,
Width = BBMultiplier
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollinger, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, IIndicatorValue bollingerValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_bollinger.IsFormed)
return;
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upperBand ||
bb.LowBand is not decimal lowerBand ||
bb.MovingAverage is not decimal middleBand)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevUpperBand = upperBand;
_prevLowerBand = lowerBand;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevUpperBand = upperBand;
_prevLowerBand = lowerBand;
return;
}
var close = candle.ClosePrice;
if (_prevUpperBand > 0 && _prevLowerBand > 0)
{
// Bands expanding: upper rising, lower dropping
var bandsExpanding = upperBand > _prevUpperBand && lowerBand < _prevLowerBand;
var bullishCandle = close > candle.OpenPrice;
var bearishCandle = close < candle.OpenPrice;
// Buy: close above upper band + bands expanding + bullish candle
if (close > upperBand && bandsExpanding && bullishCandle && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: close below lower band + bands expanding + bearish candle
else if (close < lowerBand && bandsExpanding && bearishCandle && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: price returns below middle
else if (Position > 0 && close < middleBand)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: price returns above middle
else if (Position < 0 && close > middleBand)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
}
_prevUpperBand = upperBand;
_prevLowerBand = lowerBand;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class bollinger_divergence_strategy(Strategy):
"""Bollinger Bands Divergence Strategy. Detects divergence between price and Bollinger Bands expansion."""
def __init__(self):
super(bollinger_divergence_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._bb_length = self.Param("BBLength", 20) \
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands")
self._bb_multiplier = self.Param("BBMultiplier", 2.0) \
.SetDisplay("BB StdDev", "Bollinger Bands standard deviation multiplier", "Bollinger Bands")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._bollinger = None
self._prev_upper_band = 0.0
self._prev_lower_band = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bollinger_divergence_strategy, self).OnReseted()
self._bollinger = None
self._prev_upper_band = 0.0
self._prev_lower_band = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(bollinger_divergence_strategy, self).OnStarted2(time)
self._bollinger = BollingerBands()
self._bollinger.Length = int(self._bb_length.Value)
self._bollinger.Width = float(self._bb_multiplier.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._bollinger, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._bollinger)
self.DrawOwnTrades(area)
def _on_process(self, candle, bollinger_value):
if candle.State != CandleStates.Finished:
return
if not self._bollinger.IsFormed:
return
bb = bollinger_value
if bb.UpBand is None or bb.LowBand is None or bb.MovingAverage is None:
return
upper_band = float(bb.UpBand)
lower_band = float(bb.LowBand)
middle_band = float(bb.MovingAverage)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_upper_band = upper_band
self._prev_lower_band = lower_band
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_upper_band = upper_band
self._prev_lower_band = lower_band
return
close = float(candle.ClosePrice)
cooldown = int(self._cooldown_bars.Value)
if self._prev_upper_band > 0 and self._prev_lower_band > 0:
bands_expanding = upper_band > self._prev_upper_band and lower_band < self._prev_lower_band
bullish_candle = close > float(candle.OpenPrice)
bearish_candle = close < float(candle.OpenPrice)
if close > upper_band and bands_expanding and bullish_candle and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif close < lower_band and bands_expanding and bearish_candle and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and close < middle_band:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and close > middle_band:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_upper_band = upper_band
self._prev_lower_band = lower_band
def CreateClone(self):
return bollinger_divergence_strategy()