Estrategia de Bollinger Divergence
Bollinger Divergence busca extremos donde el precio perfora una banda mientras la banda opuesta comienza a contraerse. Esta divergencia entre el impulso del precio y la volatilidad a menudo precede a un rebote hacia el centro del rango.
Una señal larga aparece cuando una vela cierra por debajo de la banda inferior mientras la banda superior se estrecha al menos un porcentaje definido. Para los cortos, el patrón se refleja alrededor de la banda superior. Las posiciones apuntan a un movimiento rápido de regreso a la línea central de Bollinger con una toma de ganancias fija opcional.
El setup funciona mejor en mercados en rango o después de que un pico de volatilidad
comienza a desvanecerse. El parámetro CandlePercent controla cuánto debe
contraerse la banda opuesta antes de permitir una operación, ayudando a evitar
señales falsas durante tendencias fuertes.
Detalles
- Datos: Velas de precio.
- Criterios de entrada:
- Largo: Cierre por debajo de la banda inferior Y la banda superior se contrae en
CandlePercent. - Corto: Cierre por encima de la banda superior Y la banda inferior se contrae en
CandlePercent.
- Largo: Cierre por debajo de la banda inferior Y la banda superior se contrae en
- Criterios de salida:
- Retorno a la banda central O porcentaje de toma de ganancias.
- Stops: Sin stop duro; depende de la toma de ganancias o salida manual.
- Valores predeterminados:
BBLength= 20BBMultiplier= 2.0CandlePercent= 30TakeProfit= 5
- Filtros:
- Categoría: Reversión a la media
- Dirección: Largo/Corto
- Indicadores: Bollinger Bands
- Complejidad: Simple
- Nivel de riesgo: Medio
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()