Стратегия является портом эксперт-советника BollingerBandsEA (ver. 3.0) с платформы MetaTrader. Она ищет возврат к среднему после экстремальных выходов цены за полосы Боллинджера внутри активной сессии.
Логика торговли
Подписывается на основную внутридневную серию свечей (по умолчанию 15 минут) и на дневные свечи для трендового фильтра.
Рассчитывает полосы Боллинджера (период 20, множитель 2.0) на внутридневных данных и простую среднюю с периодом 100 на дневных закрытиях.
Отслеживает текущие и предыдущие дневные экстремумы, а также сохраняет значения полос с предыдущей свечи.
Открывает сделки только в торговом окне: с момента SessionStartOffsetMinutes минут после открытия рынка до SessionEndOffsetMinutes минут до закрытия.
Прекращает открывать новые позиции, когда суммарный результат за текущий день (включая плавающий PnL) становится положительным.
Условия для продажи: предыдущая свеча медвежья, закрылась выше верхней полосы, текущая цена остаётся выше полосы, ширина полос достаточна, цена ниже дневной SMA, и цена обновляет текущий или предыдущий дневной максимум.
Условия для покупки: предыдущая свеча бычья, закрылась ниже нижней полосы, текущая цена остаётся ниже полосы, ширина полос достаточна, цена выше дневной SMA, и цена обновляет текущий или предыдущий дневной минимум.
Размер позиции может быть фиксированным или рассчитываться по заданному проценту риска с учётом дистанции до стоп-лосса в пунктах.
Выход из позиции контролируется стоп-лоссом, тейк-профитом, опциональным закрытием на средней полосе, трейлинг-стопом, переходом в безубыток и принудительным закрытием убыточных позиций по истечении заданного времени.
Параметры
Параметр
Описание
CandleType
Тип свечей для основной логики.
BollingerLength
Период расчёта полос Боллинджера.
BollingerWidth
Множитель ширины полос.
DailyMaLength
Период дневной SMA для фильтра тренда.
StopLossPoints
Дистанция стоп-лосса в пунктах.
UseRiskVolume
Использовать ли риск-ориентированный размер позиции.
RiskPercent
Доля капитала, которой рискнет сделка.
FixedVolume
Фиксированный объём при отключённом риск-менеджменте.
SessionStartOffsetMinutes
Количество минут после открытия рынка, когда разрешены входы.
SessionEndOffsetMinutes
Количество минут до закрытия рынка, когда входы запрещены.
CloseOnMiddleBand
Закрывать позицию при пересечении средней полосы.
EnableTrailing
Включить трейлинг-стоп.
TrailingFactor
Требуемое движение (в кратных стопу) для активации трейлинга.
EnableBreakEven
Переносить ли стоп в безубыток.
BreakEvenFactor
Необходимый профит (в кратных стопу) для перехода в безубыток.
CloseLosingAfterMinutes
Время в минутах до принудительного закрытия убыточной позиции.
Дополнительно
Стоп-лосс и тейк-профит моделируются по экстремумам свечей. При необходимости можно добавить регистрацию реальных защитных заявок.
Расчёт риск-объёма требует корректных Security.Step и Security.StepPrice. При их отсутствии стратегия использует фиксированный объём.
Ограничение дневной прибыли основано на PnL стратегии, поэтому важно соответствие валюты портфеля и параметров риска.
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>
/// Trades reversals from Bollinger Bands.
/// Buys when price closes below lower band and sells when price closes above upper band.
/// </summary>
public class BollingerBandsSessionReversalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerLength;
private readonly StrategyParam<decimal> _bollingerWidth;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Length of the Bollinger Bands moving average.
/// </summary>
public int BollingerLength
{
get => _bollingerLength.Value;
set => _bollingerLength.Value = value;
}
/// <summary>
/// Width multiplier for Bollinger Bands.
/// </summary>
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public BollingerBandsSessionReversalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "General");
_bollingerLength = Param(nameof(BollingerLength), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Length", "MA period for Bollinger Bands", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Width", "Band width multiplier", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bollinger = new BollingerBands
{
Length = BollingerLength,
Width = BollingerWidth
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
if (bbValue is not IBollingerBandsValue bb)
return;
var middle = bb.MovingAverage ?? 0m;
var upper = bb.UpBand ?? 0m;
var lower = bb.LowBand ?? 0m;
if (middle == 0m || upper == 0m || lower == 0m)
return;
var price = candle.ClosePrice;
// Reversal: buy when price falls below lower band
if (price < lower && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Reversal: sell when price rises above upper band
else if (price > upper && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
// Exit at middle band
else if (Position > 0 && price >= middle)
{
SellMarket();
}
else if (Position < 0 && price <= middle)
{
BuyMarket();
}
}
}
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 BollingerBands
from StockSharp.Algo.Strategies import Strategy
class bollinger_bands_session_reversal_strategy(Strategy):
def __init__(self):
super(bollinger_bands_session_reversal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary candle series", "General")
self._bollinger_length = self.Param("BollingerLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Length", "MA period for Bollinger Bands", "Indicators")
self._bollinger_width = self.Param("BollingerWidth", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Width", "Band width multiplier", "Indicators")
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerLength(self):
return self._bollinger_length.Value
@BollingerLength.setter
def BollingerLength(self, value):
self._bollinger_length.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
def OnStarted2(self, time):
super(bollinger_bands_session_reversal_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self.BollingerLength
bollinger.Width = self.BollingerWidth
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bollinger, self._process_candle).Start()
def _process_candle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
upper = bb_value.UpBand
lower = bb_value.LowBand
middle = bb_value.MovingAverage
if upper is None or lower is None or middle is None:
return
up = float(upper)
lo = float(lower)
mid = float(middle)
if up == 0 or lo == 0 or mid == 0:
return
price = float(candle.ClosePrice)
if price < lo and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif price > up and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self.Position > 0 and price >= mid:
self.SellMarket()
elif self.Position < 0 and price <= mid:
self.BuyMarket()
def CreateClone(self):
return bollinger_bands_session_reversal_strategy()