Открыть на GitHub

Стратегия Breakthrough BB

Общее описание

Стратегия Breakthrough BB переносит экспертный советник Breakthrough_BB из MetaTrader в инфраструктуру StockSharp. Алгоритм сочетает полосы Боллинджера и простую скользящую среднюю, чтобы фиксировать импульсные прорывы, возникающие после сжатия цены у границ канала. Сигналы формируются только на закрытых свечах, что полностью повторяет логику оригинала на MQL5.

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

  • Фильтр тренда. Простая скользящая средняя (SMA) с настраиваемым периодом определяет преобладающее направление. Текущее значение SMA сравнивается со значением четырёх свечей назад. Для длинных сделок требуется восходящий наклон, для коротких — нисходящий.
  • Прорыв полос Боллинджера. Стратегия анализирует, где находилась цена закрытия четыре свечи назад относительно верхней/нижней полосы, и сравнивает это с последним закрытием. Прорыв считается валидным, если цена переместилась изнутри канала за его пределы за указанный промежуток.
  • Одна позиция. Алгоритм удерживает не более одной позиции одновременно. Любая открытая сделка закрывается до оценки новых входов, что предотвращает накопление позиций.

Условия входа

Покупка

  1. Цена закрытия четыре свечи назад находилась ниже верхней полосы Боллинджера.
  2. Последняя закрытая свеча завершилась выше текущей верхней полосы Боллинджера.
  3. Значение SMA на последней свече больше значения SMA четырёх свечей назад (наклон вверх).
  4. Открытых позиций нет.

Продажа

  1. Цена закрытия четыре свечи назад находилась выше нижней полосы Боллинджера.
  2. Последняя закрытая свеча завершилась ниже текущей нижней полосы Боллинджера.
  3. Значение SMA на последней свече меньше значения SMA четырёх свечей назад (наклон вниз).
  4. Открытых позиций нет.

При выполнении условий отправляется рыночная заявка на объём, указанный в параметре Volume.

Условия выхода

  • Закрытие покупок. Если открыта длинная позиция и последняя свеча закрылась ниже средней полосы Боллинджера, позиция немедленно закрывается рыночной продажей.
  • Закрытие продаж. Если открыта короткая позиция и последняя свеча закрылась выше средней полосы Боллинджера, позиция покрывается рыночной покупкой.

Тем самым соблюдается логика MQL5-версии, закрывающей сделки при возврате цены к средней линии полос Боллинджера.

Используемые индикаторы

  • SMA: задаёт направление тренда и служит для оценки наклона на интервале в четыре свечи.
  • Полосы Боллинджера: предоставляют верхнюю, среднюю и нижнюю границы для поиска пробоев и сопровождения позиции.

Параметры

Имя Назначение Значение по умолчанию Оптимизация
MaPeriod Период SMA для фильтра тренда. 9
BandsPeriod Длина расчёта полос Боллинджера. 28
Deviation Множитель стандартного отклонения для полос. 1.6
Volume Объём заявки (лоты или контракты). 1
CandleType Тип свечей, используемых в расчётах. Таймфрейм 1 час

Параметры реализованы через StrategyParam, благодаря чему их можно менять из интерфейса или задействовать в оптимизации.

Требования к данным

  • Подходит для любых инструментов, предоставляющих свечные данные выбранного таймфрейма.
  • Логика обрабатывает только завершённые свечи, что гарантирует повторяемость сигналов.
  • По умолчанию используется часовой таймфрейм, однако можно задать любую доступную периодичность.

Дополнительные замечания

  • Исторические значения индикаторов не считываются напрямую; стратегия хранит компактный буфер последних четырёх закрытий и значений SMA, что соответствует проектным ограничениям.
  • Защитные механизмы (стоп-лосс, тейк-профит) не активированы, поскольку отсутствовали в исходной реализации. Их можно добавить через StartProtection при необходимости.
  • Поскольку используются рыночные заявки, рекомендуется выбирать инструменты с достаточной ликвидностью для минимизации проскальзывания.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy that trades Bollinger Bands breakouts with a moving average trend filter.
/// </summary>
public class BreakthroughBbStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _bandsPeriod;
	private readonly StrategyParam<decimal> _deviation;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private BollingerBands _bollingerBands;

	private decimal? _closeLag0;
	private decimal? _closeLag1;
	private decimal? _closeLag2;
	private decimal? _closeLag3;

	private decimal? _maLag0;
	private decimal? _maLag1;
	private decimal? _maLag2;
	private decimal? _maLag3;

	/// <summary>
	/// Moving average period that defines the long term trend.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands lookback period.
	/// </summary>
	public int BandsPeriod
	{
		get => _bandsPeriod.Value;
		set => _bandsPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands width measured in standard deviations.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}


	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="BreakthroughBbStrategy"/>.
	/// </summary>
	public BreakthroughBbStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Simple moving average length", "Parameters")
			;

		_bandsPeriod = Param(nameof(BandsPeriod), 28)
			.SetGreaterThanZero()
			.SetDisplay("Bands Period", "Bollinger Bands lookback", "Parameters")
			;

		_deviation = Param(nameof(Deviation), 1.6m)
			.SetGreaterThanZero()
			.SetDisplay("Deviation", "Bollinger Bands width in deviations", "Parameters")
			;


		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle series processed by the strategy", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_sma = default;
		_bollingerBands = default;

		_closeLag0 = null;
		_closeLag1 = null;
		_closeLag2 = null;
		_closeLag3 = null;

		_maLag0 = null;
		_maLag1 = null;
		_maLag2 = null;
		_maLag3 = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_sma = new SimpleMovingAverage { Length = MaPeriod };
		_bollingerBands = new BollingerBands
		{
			Length = BandsPeriod,
			Width = Deviation
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(_sma, _bollingerBands, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaIndValue, IIndicatorValue bbValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var smaValue = smaIndValue.IsFormed ? smaIndValue.ToDecimal() : 0m;
		var bb = bbValue as IBollingerBandsValue;
		var middleBand = bb?.MovingAverage ?? 0m;
		var upperBand = bb?.UpBand ?? 0m;
		var lowerBand = bb?.LowBand ?? 0m;

		var close = candle.ClosePrice;
		var maPrev4 = _maLag2;
		var closePrev4 = _closeLag2;

		if (_sma is null || _bollingerBands is null)
		{
			UpdateHistory(close, smaValue);
			return;
		}

		if (!_sma.IsFormed || !_bollingerBands.IsFormed)
		{
			UpdateHistory(close, smaValue);
			return;
		}

		if (Position > 0 && close < middleBand)
		{
			SellMarket();
			UpdateHistory(close, smaValue);
			return;
		}

		if (Position < 0 && close > middleBand)
		{
			BuyMarket();
			UpdateHistory(close, smaValue);
			return;
		}

		if (maPrev4 is null || closePrev4 is null)
		{
			UpdateHistory(close, smaValue);
			return;
		}

		if (Position == 0)
		{
			if (closePrev4.Value < upperBand && close > upperBand && smaValue > maPrev4.Value)
			{
				BuyMarket();
				UpdateHistory(close, smaValue);
				return;
			}

			if (closePrev4.Value > lowerBand && close < lowerBand && smaValue < maPrev4.Value)
			{
				SellMarket();
				UpdateHistory(close, smaValue);
				return;
			}
		}

		UpdateHistory(close, smaValue);
	}

	private void UpdateHistory(decimal close, decimal maValue)
	{
		_maLag3 = _maLag2;
		_maLag2 = _maLag1;
		_maLag1 = _maLag0;
		_maLag0 = maValue;

		_closeLag3 = _closeLag2;
		_closeLag2 = _closeLag1;
		_closeLag1 = _closeLag0;
		_closeLag0 = close;
	}
}