Estrategia de Compresión de Bandas de Bollinger
Esta configuración monitorea el ancho de las Bandas de Bollinger para detectar períodos de baja volatilidad. Cuando las bandas se contraen en relación con su promedio reciente, señala que una posible expansión de volatilidad está cerca.
Las pruebas indican un retorno anual promedio de aproximadamente 100%. Funciona mejor en el mercado de forex.
Una vez que se identifica una compresión, la estrategia espera que el precio rompa fuera de las bandas. Un cierre por encima de la banda superior inicia una operación larga, mientras que un cierre por debajo de la banda inferior abre una corta. La operación se cierra si el precio regresa hacia el centro de las bandas o si se activa un stop-loss.
El método está dirigido a traders que prefieren operar rupturas de volatilidad en lugar de continuaciones de tendencia. Usar el ancho de banda como filtro ayuda a evitar señales falsas durante condiciones laterales.
Detalles
- Criterios de entrada:
- Largo: Ancho de banda < ancho promedio && Cierre > banda superior
- Corto: Ancho de banda < ancho promedio && Cierre < banda inferior
- Largo/Corto: Ambos lados.
- Criterios de salida:
- Largo: Salir cuando el precio cae de vuelta dentro de las bandas
- Corto: Salir cuando el precio sube de vuelta dentro de las bandas
- Stops: Sí, típicamente a 2*ATR.
- Valores predeterminados:
BollingerPeriod = 20
BollingerMultiplier = 2
LookbackPeriod = 20
AtrPeriod = 14
AtrMultiplier = 2
El ancho medio abarca las últimas LookbackPeriod velas, incluida la actual. Un largo se cierra con un cierre de vuelta en la banda superior o por debajo y un corto con un cierre de vuelta en la banda inferior o por encima. El stop se sitúa a AtrMultiplier veces el ATR de AtrPeriod del cierre de entrada y se comprueba al cierre de las velas; 0 lo desactiva. Una señal de entrada contra una posición abierta la invierte.
CandleType = TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Ruptura
- Dirección: Ambos
- Indicadores: Bollinger Bands
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()