Стратегия представляет собой перенос советника MetaTrader 4 «BOLINGER BAND SQUEEZE» на высокоуровневый API StockSharp. Алгоритм ищет периоды сжатия полос Боллинджера на рабочем таймфрейме и входит в позиции при расширении полос, если движение подтверждается фильтрами тренда, импульса и макро-тренда. Дополнительно логика управления позициями адаптирована под инфраструктуру Strategy.
Логика торговли
Сжатие и расширение полос
На выбранном таймфрейме рассчитываются полосы Боллинджера (по умолчанию длина 20, отклонение 2).
Ширина последней завершённой свечи сравнивается с шириной свечи RetraceCandles баров назад.
Если отношение ширин превышает SqueezeRatio, считается, что цена выходит из фазы сжатия.
Фильтр тренда
Две взвешенные скользящие средние по типичной цене (WMA 6 и WMA 85) определяют локальный тренд. Для покупки требуется, чтобы быстрая WMA была выше медленной, для продажи — ниже.
Фильтр импульса
На более старшем таймфрейме рассчитывается Momentum (длина 14). Хранится максимальное отклонение от уровня 100 за три последних завершённых свечи старшего периода.
Отклонение должно превышать порог MomentumBuyThreshold/MomentumSellThreshold в зависимости от направления. Крупные таймфреймы выбираются автоматически (M15→H1, H1→D1, D1→месяц, неделя также использует месячный сигнал). Если старший период недоступен, фильтр отключается.
Макро-фильтр
На месячном (30-дневном) таймфрейме рассчитывается MACD 12/26/9. Вход в лонг разрешён только при расположении линии MACD выше сигнальной, вход в шорт — при обратном условии.
Условия входа
Лонг: расширение полос, быстрая WMA выше медленной, месячный MACD бычий, импульс превышает порог, свечи имеют пересечение (candle[-2].Low < candle[-1].High), и текущая позиция не длинная.
Шорт: зеркальные условия (быстрая WMA ниже медленной, месячный MACD медвежий, импульс выше порога, условие candle[-1].Low < candle[-2].High).
Условия выхода
Позиция закрывается, когда цена закрытия достигает соответствующей внешней полосы Боллинджера (лонг — верхняя, шорт — нижняя). Это воспроизводит поведение блока 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(); }
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bollinger_band_squeeze_breakout_strategy(Strategy):
def __init__(self):
super(bollinger_band_squeeze_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(bollinger_band_squeeze_breakout_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(bollinger_band_squeeze_breakout_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_above and not curr_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return bollinger_band_squeeze_breakout_strategy()