Открыть на GitHub

Стратегия 1H Bollinger Bands

Обзор

1H Bollinger Bands Strategy — адаптация советника MetaTrader «1H Bolinger Bands» под высокоуровневый API StockSharp. Идея состоит в торговле отскоков от дневных полос Боллинджера при условии, что часовой тренд направлен в ту же сторону, а месячный MACD подтверждает направление. По умолчанию рабочий таймфрейм — H1, дополнительно используются более старшие интервалы данных.

Торговая логика

  • Фильтр тренда. На базовом таймфрейме рассчитываются две линейно-взвешенные средние (LWMA 250 и 500). Сделки разрешаются только в сторону господствующего тренда.
  • Паттерн отскока. На старшем таймфрейме (по умолчанию дневном) стратегия следит за свечой, минимум которой пробивает нижнюю полосу Боллинджера, и следующей свечой, открывшейся выше этой полосы. Для продаж используется зеркальное условие через верхнюю полосу.
  • Подтверждение моментума. Моментум (период 14) рассчитывается на старшем таймфрейме. Хотя бы одно из трёх последних абсолютных отклонений от уровня 100 должно превышать заданный порог (по умолчанию 0.3).
  • Фильтр MACD. Месячный MACD (12/26/9) должен находиться выше сигнальной линии для покупок и ниже — для продаж.
  • Вход. При совпадении всех фильтров открывается рыночная сделка. Если уже есть противоположная позиция, объём заявки закрывает её и переворачивает направление.

Управление позицией

Страховка реализована непосредственно в стратегии и оперирует расстояниями в пунктах, пересчитанными через Security.PriceStep:

  • Стоп-лосс. Позиция закрывается, когда цена проходит заданное количество пунктов против входа.
  • Тейк-профит. При достижении целевого расстояния в пунктах прибыль фиксируется.
  • Трейлинг-стоп (опция). При включении и достаточном движении вперёд внутренняя трейлинг-линия подтягивается вслед за ценой. Пробой этой линии закрывает сделку.
  • Перевод в безубыток (опция). После достижения прибыли, равной порогу срабатывания, стоп переносится на цену входа плюс заданное смещение (минус для продаж). Возврат к этому уровню завершает позицию.

Денежный трейлинг и контроль общей просадки, реализованные в исходном советнике, не переносятся — версия для StockSharp делает упор на универсальные ценовые правила.

Параметры

Параметр Описание Значение по умолчанию
CandleType Базовый таймфрейм для поиска сигналов. Свечи 1 час
HigherTimeFrame Таймфрейм для полос Боллинджера и моментума. Свечи 1 день
MacdTimeFrame Таймфрейм для подтверждающего MACD. Свечи 30 дней
FastMaPeriod / SlowMaPeriod Периоды быстрых/медленных LWMA на базе. 6 / 85
TrendFastPeriod / TrendSlowPeriod Долгосрочные LWMA-фильтры. 250 / 500
MomentumPeriod Длина моментума на старшем таймфрейме. 14
MomentumThreshold Минимальное абсолютное отклонение моментума от 100. 0.3
BollingerPeriod / BollingerWidth Настройки дневных полос Боллинджера. 20 / 2.0
TradeVolume Базовый объём сделки. 1
StopLossPips / TakeProfitPips Стоп-лосс и тейк-профит в пунктах. 20 / 50
EnableTrailing / TrailingStopPips Включение трейлинг-стопа и расстояние. true / 40
EnableBreakEven / BreakEvenTriggerPips / BreakEvenOffsetPips Безубыток: включение, порог и смещение. true / 30 / 30

Все числовые параметры оформлены через StrategyParam<T> и доступны для оптимизации в Designer/Runner.

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

  • Стратегия подписывается на три потока свечей: базовый, старший для полос/моментума и ещё один для MACD.
  • Моментум рассчитывается стандартным индикатором StockSharp, а последние три отклонения сохраняются для повторения MQL-логики.
  • Для корректной работы защитных правил необходимо, чтобы инструмент предоставлял PriceStep; иначе конвертация пунктов в цену невозможна.
  • StockSharp использует модель единой чистой позиции. Поведение с наращиванием серии (Max_Trades) из оригинала упрощено до одного агрегированного объёма.
  • Контроль баланса, рассылка e-mail/Push и другие сервисные функции советника не реализованы, чтобы сохранить нейтральность относительно брокера.

Использование

  1. Подключите стратегию к инструменту, который предоставляет часовые, дневные и месячные свечи (либо скорректируйте параметры).
  2. Убедитесь, что у инструмента задан PriceStep — тогда пункты корректно превращаются в ценовые смещения.
  3. Настройте объём и параметры риска до запуска стратегии.
  4. Запустите стратегию: она подпишется на данные, будет анализировать закрытые свечи и управлять позицией по заданным правилам.

Отличия от советника MQL

  • Денежный трейлинг и общий стоп по эквити отсутствуют — используются только ценовые ограничения.
  • В оригинале присутствовали оповещения по почте и пуш-уведомления; в версии StockSharp они убраны.
  • Вместо набора ордеров используется чистая позиция, автоматически переворачиваемая при появлении противоположного сигнала.

Такие изменения делают стратегию более естественной для инфраструктуры 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;

public class OneHBollingerBandsStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public OneHBollingerBandsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}