BBSR Extreme
Стратегия возврата внутрь полос с фильтром наклона EMA и защитой через ATR. C, L, U, E — обычное закрытие, нижняя полоса, верхняя полоса и EMA; p — предыдущая сформированная свеча.
Правила торговли
Лонг: C[p] < L[p] && C > L && E > E[p]. Шорт: C[p] > U[p] && C < U && E < E[p]. Равенство не проходит. Вход объёмом Volume только из нулевой позиции; без наращивания, разворота и фиксированной паузы в свечах.
До отправки входа фиксируется A = ATR сигнальной свечи. Нативная локальная защита рыночными заявками привязана к фактической цене исполнения: лонг — стоп/цель fill - 2A / fill + 3A, шорт — fill + 2A / fill - 3A. Множители настраиваются. Последующий ATR уровни не меняет. Подписки bid/ask позволяют закрыть фактически исполненный объём между сигнальными свечами.
Входы разрешены только по завершённым свечам после прогрева индикаторов. Открытая позиция сопровождается до выхода; незавершённая рыночная заявка блокирует повторную отправку.
Отклонение Боллинджера — стандартное отклонение генеральной совокупности скользящего окна закрытий, включая текущую завершённую свечу.
Параметры
| Parameter | Default |
|---|---|
BollingerPeriod |
20 |
BollingerMultiplier |
2 |
MaLength |
7 (EMA) |
AtrLength |
14 (Wilder) |
AtrStopMultiplier |
2 |
AtrProfitMultiplier |
3 |
CandleType |
5m |
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>
/// Strategy uses Bollinger Bands breakout with moving average trend filter and ATR-based exits.
/// </summary>
public class BbsrExtremeStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerMultiplier;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrStopMultiplier;
private readonly StrategyParam<decimal> _atrProfitMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevLower;
private decimal _prevUpper;
private decimal _prevClose;
private decimal _prevMa;
private bool _isInitialized;
private Order _pending;
private bool IsPending => _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed);
/// <summary>
/// Period for Bollinger Bands calculation.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for Bollinger Bands.
/// </summary>
public decimal BollingerMultiplier
{
get => _bollingerMultiplier.Value;
set => _bollingerMultiplier.Value = value;
}
/// <summary>
/// Moving average length.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiplier for stop calculation.
/// </summary>
public decimal AtrStopMultiplier
{
get => _atrStopMultiplier.Value;
set => _atrStopMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier for take profit.
/// </summary>
public decimal AtrProfitMultiplier
{
get => _atrProfitMultiplier.Value;
set => _atrProfitMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes the strategy.
/// </summary>
public BbsrExtremeStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Bollinger Bands length", "Indicators");
_bollingerMultiplier = Param(nameof(BollingerMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Multiplier", "Standard deviation multiplier", "Indicators");
_maLength = Param(nameof(MaLength), 7)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Length for moving average", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR indicator period", "Risk Management");
_atrStopMultiplier = Param(nameof(AtrStopMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Stop Multiplier", "ATR multiplier for stop", "Risk Management");
_atrProfitMultiplier = Param(nameof(AtrProfitMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("ATR Profit Multiplier", "ATR multiplier for take profit", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevLower = default;
_prevUpper = default;
_prevClose = default;
_prevMa = default;
_isInitialized = false;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bollinger = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerMultiplier
};
var ma = new EMA
{
Length = MaLength
};
var atr = new AverageTrueRange
{
Length = AtrLength
};
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Start();
}
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx([bollinger, ma, atr], ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished
|| values[0] is not BollingerBandsValue bb || !values[0].IsFormed
|| bb.UpBand is not decimal upper || bb.LowBand is not decimal lower
|| !values[1].IsFormed || !values[2].IsFormed)
return;
var ma = values[1].GetValue<decimal>();
var atr = values[2].GetValue<decimal>();
var bull = _isInitialized && _prevClose < _prevLower && candle.ClosePrice > lower && ma > _prevMa;
var bear = _isInitialized && _prevClose > _prevUpper && candle.ClosePrice < upper && ma < _prevMa;
_prevLower = lower;
_prevUpper = upper;
_prevClose = candle.ClosePrice;
_prevMa = ma;
_isInitialized = true;
if (Position != 0m || IsPending || atr <= 0m || !IsFormedAndOnlineAndAllowTrading())
return;
if (!bull && !bear)
return;
// Freeze distances before registering the entry; native protection anchors them to actual fills.
StartProtection(new Unit(atr * AtrProfitMultiplier, UnitTypes.Absolute),
new Unit(atr * AtrStopMultiplier, UnitTypes.Absolute), useMarketOrders: true, isLocalStop: true);
_pending = CreateOrder(bull ? Sides.Buy : Sides.Sell, 0m, Volume);
_pending.Type = OrderTypes.Market;
_pending.Comment = "0556Entry";
RegisterOrder(_pending);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.BusinessEntities import Subscription
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderStates, OrderTypes, Sides, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands, ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class bbsr_extreme_strategy(Strategy):
def __init__(self):
super(bbsr_extreme_strategy, self).__init__()
self._bb_period = self.Param("BollingerPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Period", "Bollinger Bands length", "Indicators")
self._bb_mult = self.Param("BollingerMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Multiplier", "Standard deviation multiplier", "Indicators")
self._ma_length = self.Param("MaLength", 7) \
.SetGreaterThanZero() \
.SetDisplay("MA Length", "Length for moving average", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR indicator period", "Risk Management")
self._atr_stop_mult = self.Param("AtrStopMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Stop Multiplier", "ATR multiplier for stop", "Risk Management")
self._atr_profit_mult = self.Param("AtrProfitMultiplier", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Profit Multiplier", "ATR multiplier for take profit", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._clear_state()
def _clear_state(self):
self._previous = None
self._pending = None
def OnReseted(self):
super(bbsr_extreme_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(bbsr_extreme_strategy, self).OnStarted2(time)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Start()
self._bb = BollingerBands()
self._bb.Length = self._bb_period.Value
self._bb.Width = self._bb_mult.Value
self._ma = ExponentialMovingAverage()
self._ma.Length = self._ma_length.Value
self._atr = AverageTrueRange()
self._atr.Length = self._atr_length.Value
candles = self.SubscribeCandles(self._candle_type.Value)
candles.BindEx(self._bb, self._ma, self._atr, self._process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawIndicator(area, self._ma)
self.DrawIndicator(area, self._bb)
self.DrawOwnTrades(area)
def _process(self, candle, bb, ma, atr):
if candle.State != CandleStates.Finished or not self._bb.IsFormed or not self._ma.IsFormed or not self._atr.IsFormed or bb.UpBand is None or bb.LowBand is None:
return
average = ma.GetValue[Decimal](None)
distance = atr.GetValue[Decimal](None)
close = candle.ClosePrice
bull = self._previous is not None and self._previous[0] < self._previous[1] and close > bb.LowBand and average > self._previous[3]
bear = self._previous is not None and self._previous[0] > self._previous[2] and close < bb.UpBand and average < self._previous[3]
self._previous = (close, bb.LowBand, bb.UpBand, average)
pending = self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed)
if self.Position != 0 or pending or distance <= 0 or not self.IsFormedAndOnlineAndAllowTrading() or not (bull or bear):
return
self.StartProtection(Unit(distance * Decimal(self._atr_profit_mult.Value), UnitTypes.Absolute),
Unit(distance * Decimal(self._atr_stop_mult.Value), UnitTypes.Absolute),
useMarketOrders=True, isLocalStop=True)
self._pending = self.CreateOrder(Sides.Buy if bull else Sides.Sell, Decimal(0), self.Volume)
self._pending.Type = OrderTypes.Market
self._pending.Comment = "0556Entry"
self.RegisterOrder(self._pending)
def CreateClone(self):
return bbsr_extreme_strategy()