Открыть на GitHub

Стратегия BB Squeeze

Стратегия отслеживает сжатие и расширение полос Боллинджера, чтобы использовать прорывы волатильности. Сжатие определяется как период, когда расстояние между верхней и нижней полосами становится узким относительно средней линии. Когда волатильность расширяется и цена закрывается за пределами полос после сжатия, система входит в позицию в направлении прорыва.

Позиции открываются рыночными ордерами. Длинная позиция создаётся, когда цена закрывается выше верхней полосы после сжатия, короткая — когда цена закрывается ниже нижней полосы. Обрабатываются только завершённые свечи, что исключает преждевременные сигналы во время их формирования.

Алгоритм отслеживает изменение ширины полос без хранения полной истории свечей. Сравнение текущей ширины с предыдущей позволяет убедиться, что расширение действительно произошло, прежде чем размещать заявки. Это избегает входа в затяжные фазы низкой волатильности, где прорыв не развивается.

По умолчанию используются полосы Боллинджера с периодом 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;
	}
}