Открыть на GitHub

Стратегия Bollinger Band Squeeze Breakout

Обзор

Стратегия представляет собой перенос советника MetaTrader 4 «BOLINGER BAND SQUEEZE» на высокоуровневый API StockSharp. Алгоритм ищет периоды сжатия полос Боллинджера на рабочем таймфрейме и входит в позиции при расширении полос, если движение подтверждается фильтрами тренда, импульса и макро-тренда. Дополнительно логика управления позициями адаптирована под инфраструктуру Strategy.

Логика торговли

  1. Сжатие и расширение полос
    • На выбранном таймфрейме рассчитываются полосы Боллинджера (по умолчанию длина 20, отклонение 2).
    • Ширина последней завершённой свечи сравнивается с шириной свечи RetraceCandles баров назад.
    • Если отношение ширин превышает SqueezeRatio, считается, что цена выходит из фазы сжатия.
  2. Фильтр тренда
    • Две взвешенные скользящие средние по типичной цене (WMA 6 и WMA 85) определяют локальный тренд. Для покупки требуется, чтобы быстрая WMA была выше медленной, для продажи — ниже.
  3. Фильтр импульса
    • На более старшем таймфрейме рассчитывается Momentum (длина 14). Хранится максимальное отклонение от уровня 100 за три последних завершённых свечи старшего периода.
    • Отклонение должно превышать порог MomentumBuyThreshold/MomentumSellThreshold в зависимости от направления. Крупные таймфреймы выбираются автоматически (M15→H1, H1→D1, D1→месяц, неделя также использует месячный сигнал). Если старший период недоступен, фильтр отключается.
  4. Макро-фильтр
    • На месячном (30-дневном) таймфрейме рассчитывается MACD 12/26/9. Вход в лонг разрешён только при расположении линии MACD выше сигнальной, вход в шорт — при обратном условии.
  5. Условия входа
    • Лонг: расширение полос, быстрая WMA выше медленной, месячный MACD бычий, импульс превышает порог, свечи имеют пересечение (candle[-2].Low < candle[-1].High), и текущая позиция не длинная.
    • Шорт: зеркальные условия (быстрая WMA ниже медленной, месячный MACD медвежий, импульс выше порога, условие candle[-1].Low < candle[-2].High).
  6. Условия выхода
    • Позиция закрывается, когда цена закрытия достигает соответствующей внешней полосы Боллинджера (лонг — верхняя, шорт — нижняя). Это воспроизводит поведение блока stop() из оригинального кода.
    • Вызов StartProtection() позволяет легко добавить дополнительные стопы/тейки через инструменты StockSharp.

Индикаторы и подписки

  • Основной таймфрейм CandleType.
  • Автоматически подобранный старший таймфрейм для Momentum.
  • Месячные свечи (30-дневная аппроксимация) для MACD.
  • Используемые индикаторы: BollingerBands, две WeightedMovingAverage по типичной цене, Momentum, MovingAverageConvergenceDivergenceSignal.

Параметры

Имя Значение по умолчанию Описание
CandleType 15-минутные свечи Рабочий таймфрейм.
BollingerPeriod 20 Длина полос Боллинджера.
BollingerWidth 2.0 Множитель стандартного отклонения.
SqueezeRatio 1.1 Минимальное отношение текущей ширины к исторической.
RetraceCandles 10 Количество свечей для сравнения ширины.
FastMaLength 6 Длина быстрой WMA (типичная цена).
SlowMaLength 85 Длина медленной WMA (типичная цена).
MomentumLength 14 Период индикатора Momentum на старшем таймфрейме.
MomentumBuyThreshold 0.3 Минимальное отклонение импульса для покупки.
MomentumSellThreshold 0.3 Минимальное отклонение импульса для продажи.

Все параметры реализованы через StrategyParam<T>, поэтому их можно оптимизировать и изменять во время работы стратегии.

Особенности реализации

  • Индикаторы подключаются через SubscribeCandles().BindEx(...), что соответствует требованиям к высокоуровневому API и избавляет от ручного управления коллекциями индикаторов.
  • Значения WMA обновляются внутри обработчика свечей по типичной цене, что повторяет использование LWMA в исходном советнике.
  • Очередь из трёх значений Momentum имитирует обращения iMomentum с лагами 1–3 в MQL.
  • Значения месячного MACD кэшируются в полях и доступны для каждой рабочей свечи без дополнительных запросов.
  • Выход по касанию внешней полосы заменяет блоки трейлинг-стопа/безубытка, сохраняя исходную идею закрытия при сильном движении.
  • Размер заявки определяется свойством Strategy.Volume; при развороте добавляется Math.Abs(Position), чтобы закрыть противоположную позицию и открыть новую.
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class BollingerBandSqueezeBreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private decimal? _prevFast, _prevSlow;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public BollingerBandSqueezeBreakoutStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 30).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = null; _prevSlow = null;
		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
		if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;
		_prevFast = fast; _prevSlow = slow;
		if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
	}
}