Стратегия отслеживает сжатие и расширение полос Боллинджера, чтобы использовать прорывы волатильности. Сжатие определяется как период, когда расстояние между верхней и нижней полосами становится узким относительно средней линии. Когда волатильность расширяется и цена закрывается за пределами полос после сжатия, система входит в позицию в направлении прорыва.
Позиции открываются рыночными ордерами. Длинная позиция создаётся, когда цена закрывается выше верхней полосы после сжатия, короткая — когда цена закрывается ниже нижней полосы. Обрабатываются только завершённые свечи, что исключает преждевременные сигналы во время их формирования.
Алгоритм отслеживает изменение ширины полос без хранения полной истории свечей. Сравнение текущей ширины с предыдущей позволяет убедиться, что расширение действительно произошло, прежде чем размещать заявки. Это избегает входа в затяжные фазы низкой волатильности, где прорыв не развивается.
По умолчанию используются полосы Боллинджера с периодом 20 и множителем ширины 2. Порог сжатия установлен на уровне 0.05, то есть полосы должны быть ближе пяти процентов к средней линии, чтобы фиксировать низкую волатильность. Таймфрейм свечи и все числовые параметры можно настраивать и оптимизировать в среде StockSharp.
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 breakout strategy.
/// Enters on breakout above/below bands, exits at middle band.
/// </summary>
public class BbSqueezeStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevUpper;
private decimal _prevLower;
private bool _hasPrev;
public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BbSqueezeStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period of Bollinger Bands", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_prevUpper = 0;
_prevLower = 0;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bb = new BollingerBands { Length = BollingerPeriod, Width = 2m };
SubscribeCandles(CandleType).BindEx(bb, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished) return;
var bbVal = (BollingerBandsValue)value;
if (bbVal.UpBand is not decimal upper ||
bbVal.LowBand is not decimal lower ||
bbVal.MovingAverage is not decimal middle)
return;
var close = candle.ClosePrice;
if (!_hasPrev)
{
_prevClose = close;
_prevUpper = upper;
_prevLower = lower;
_hasPrev = true;
return;
}
// Cross above upper band => buy
if (_prevClose <= _prevUpper && close > upper && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Cross below lower band => sell
else if (_prevClose >= _prevLower && close < lower && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
// Exit long at middle
else if (Position > 0 && close < middle)
{
SellMarket();
}
// Exit short at middle
else if (Position < 0 && close > middle)
{
BuyMarket();
}
_prevClose = close;
_prevUpper = upper;
_prevLower = lower;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class bb_squeeze_strategy(Strategy):
def __init__(self):
super(bb_squeeze_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Period of Bollinger Bands", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles used", "General")
self._prev_close = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._has_prev = False
@property
def bollinger_period(self):
return self._bollinger_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bb_squeeze_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(bb_squeeze_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self.bollinger_period
bb.Width = 2.0
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
self.DrawOwnTrades(area)
def on_process(self, candle, value):
if candle.State != CandleStates.Finished:
return
upper = float(value.UpBand)
lower = float(value.LowBand)
middle = float(value.MovingAverage)
if upper == 0 or lower == 0:
return
close = float(candle.ClosePrice)
if not self._has_prev:
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
self._has_prev = True
return
# Cross above upper band => buy
if self._prev_close <= self._prev_upper and close > upper and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Cross below lower band => sell
elif self._prev_close >= self._prev_lower and close < lower and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
# Exit long at middle
elif self.Position > 0 and close < middle:
self.SellMarket()
# Exit short at middle
elif self.Position < 0 and close > middle:
self.BuyMarket()
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
def CreateClone(self):
return bb_squeeze_strategy()