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.
Все числовые параметры оформлены через StrategyParam<T> и доступны для оптимизации в Designer/Runner.
Особенности реализации
Стратегия подписывается на три потока свечей: базовый, старший для полос/моментума и ещё один для MACD.
Моментум рассчитывается стандартным индикатором StockSharp, а последние три отклонения сохраняются для повторения MQL-логики.
Для корректной работы защитных правил необходимо, чтобы инструмент предоставлял PriceStep; иначе конвертация пунктов в цену невозможна.
StockSharp использует модель единой чистой позиции. Поведение с наращиванием серии (Max_Trades) из оригинала упрощено до одного агрегированного объёма.
Контроль баланса, рассылка e-mail/Push и другие сервисные функции советника не реализованы, чтобы сохранить нейтральность относительно брокера.
Использование
Подключите стратегию к инструменту, который предоставляет часовые, дневные и месячные свечи (либо скорректируйте параметры).
Убедитесь, что у инструмента задан PriceStep — тогда пункты корректно превращаются в ценовые смещения.
Настройте объём и параметры риска до запуска стратегии.
Запустите стратегию: она подпишется на данные, будет анализировать закрытые свечи и управлять позицией по заданным правилам.
Отличия от советника 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;
}
}
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 one_h_bollinger_bands_strategy(Strategy):
def __init__(self):
super(one_h_bollinger_bands_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(one_h_bollinger_bands_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(one_h_bollinger_bands_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return one_h_bollinger_bands_strategy()