Adaptive Bollinger Breakout
Adaptive Bollinger Breakout 策略基于 that trades based on breakouts of Bollinger Bands with adaptively adjusted parameters。
当 Bollinger confirms breakout opportunities 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 MinBollingerPeriod, MaxBollingerPeriod 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
MinBollingerPeriod = 10MaxBollingerPeriod = 30MinBollingerDeviation = 1.5mMaxBollingerDeviation = 2.5mAtrPeriod = 14CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: Bollinger
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (5m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades adaptive Bollinger mean reversion selected by ATR volatility regime.
/// </summary>
public class AdaptiveBollingerBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _minBollingerPeriod;
private readonly StrategyParam<int> _maxBollingerPeriod;
private readonly StrategyParam<decimal> _minBollingerDeviation;
private readonly StrategyParam<decimal> _maxBollingerDeviation;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _fastSma = null!;
private SimpleMovingAverage _slowSma = null!;
private StandardDeviation _fastStd = null!;
private StandardDeviation _slowStd = null!;
private AverageTrueRange _atr = null!;
private decimal _atrSum;
private int _atrCount;
private int _cooldownRemaining;
/// <summary>
/// Strategy parameter: Minimum Bollinger period.
/// </summary>
public int MinBollingerPeriod
{
get => _minBollingerPeriod.Value;
set => _minBollingerPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: Maximum Bollinger period.
/// </summary>
public int MaxBollingerPeriod
{
get => _maxBollingerPeriod.Value;
set => _maxBollingerPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: Minimum Bollinger deviation.
/// </summary>
public decimal MinBollingerDeviation
{
get => _minBollingerDeviation.Value;
set => _minBollingerDeviation.Value = value;
}
/// <summary>
/// Strategy parameter: Maximum Bollinger deviation.
/// </summary>
public decimal MaxBollingerDeviation
{
get => _maxBollingerDeviation.Value;
set => _maxBollingerDeviation.Value = value;
}
/// <summary>
/// Strategy parameter: ATR period for volatility calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: Closed candles to wait between signals.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy parameter: Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdaptiveBollingerBreakoutStrategy()
{
_minBollingerPeriod = Param(nameof(MinBollingerPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Min Bollinger Period", "Short Bollinger period for volatile regimes", "Indicator Settings");
_maxBollingerPeriod = Param(nameof(MaxBollingerPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Max Bollinger Period", "Long Bollinger period for quiet regimes", "Indicator Settings");
_minBollingerDeviation = Param(nameof(MinBollingerDeviation), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Min Bollinger Deviation", "Narrow band width for quiet regimes", "Indicator Settings");
_maxBollingerDeviation = Param(nameof(MaxBollingerDeviation), 2.5m)
.SetGreaterThanZero()
.SetDisplay("Max Bollinger Deviation", "Wide band width for volatile regimes", "Indicator Settings");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR volatility calculation", "Indicator Settings");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another breakout entry", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).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();
_fastSma?.Reset();
_slowSma?.Reset();
_fastStd?.Reset();
_slowStd?.Reset();
_atr?.Reset();
_atrSum = 0m;
_atrCount = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastSma = new SimpleMovingAverage
{
Length = MinBollingerPeriod
};
_slowSma = new SimpleMovingAverage
{
Length = MaxBollingerPeriod
};
_fastStd = new StandardDeviation
{
Length = MinBollingerPeriod
};
_slowStd = new StandardDeviation
{
Length = MaxBollingerPeriod
};
_atr = new AverageTrueRange
{
Length = AtrPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastSma);
DrawIndicator(area, _slowSma);
DrawOwnTrades(area);
}
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var atrValue = _atr.Process(new CandleIndicatorValue(_atr, candle) { IsFinal = true });
var fastSmaValue = _fastSma.Process(new DecimalIndicatorValue(_fastSma, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var slowSmaValue = _slowSma.Process(new DecimalIndicatorValue(_slowSma, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var fastStdValue = _fastStd.Process(new DecimalIndicatorValue(_fastStd, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var slowStdValue = _slowStd.Process(new DecimalIndicatorValue(_slowStd, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
if (!_atr.IsFormed || !_fastSma.IsFormed || !_slowSma.IsFormed || !_fastStd.IsFormed || !_slowStd.IsFormed ||
atrValue.IsEmpty || fastSmaValue.IsEmpty || slowSmaValue.IsEmpty || fastStdValue.IsEmpty || slowStdValue.IsEmpty)
return;
var currentAtr = atrValue.ToDecimal();
_atrSum += currentAtr;
_atrCount++;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var averageAtr = _atrCount > 0 ? _atrSum / _atrCount : currentAtr;
var useFastBands = currentAtr >= averageAtr;
var middleBand = useFastBands ? fastSmaValue.ToDecimal() : slowSmaValue.ToDecimal();
var standardDeviation = useFastBands ? fastStdValue.ToDecimal() : slowStdValue.ToDecimal();
var bandWidth = useFastBands ? MaxBollingerDeviation : MinBollingerDeviation;
var upperBand = middleBand + (standardDeviation * bandWidth);
var lowerBand = middleBand - (standardDeviation * bandWidth);
var close = candle.ClosePrice;
if (Position > 0 && close >= middleBand)
{
SellMarket(Position);
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && close <= middleBand)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
else if (_cooldownRemaining == 0 && close < lowerBand && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
else if (_cooldownRemaining == 0 && close > upperBand && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, StandardDeviation, AverageTrueRange, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class adaptive_bollinger_breakout_strategy(Strategy):
"""
Strategy that trades adaptive Bollinger mean reversion selected by ATR volatility regime.
"""
def __init__(self):
super(adaptive_bollinger_breakout_strategy, self).__init__()
self._min_bollinger_period = self.Param("MinBollingerPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Min Bollinger Period", "Short Bollinger period for volatile regimes", "Indicator Settings")
self._max_bollinger_period = self.Param("MaxBollingerPeriod", 30) \
.SetGreaterThanZero() \
.SetDisplay("Max Bollinger Period", "Long Bollinger period for quiet regimes", "Indicator Settings")
self._min_bollinger_deviation = self.Param("MinBollingerDeviation", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Min Bollinger Deviation", "Narrow band width for quiet regimes", "Indicator Settings")
self._max_bollinger_deviation = self.Param("MaxBollingerDeviation", 2.5) \
.SetGreaterThanZero() \
.SetDisplay("Max Bollinger Deviation", "Wide band width for volatile regimes", "Indicator Settings")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period for ATR volatility calculation", "Indicator Settings")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another breakout entry", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_sma = None
self._slow_sma = None
self._fast_std = None
self._slow_std = None
self._atr = None
self._atr_sum = 0.0
self._atr_count = 0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(adaptive_bollinger_breakout_strategy, self).OnReseted()
self._atr_sum = 0.0
self._atr_count = 0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adaptive_bollinger_breakout_strategy, self).OnStarted2(time)
min_period = int(self._min_bollinger_period.Value)
max_period = int(self._max_bollinger_period.Value)
atr_period = int(self._atr_period.Value)
self._fast_sma = SimpleMovingAverage()
self._fast_sma.Length = min_period
self._slow_sma = SimpleMovingAverage()
self._slow_sma.Length = max_period
self._fast_std = StandardDeviation()
self._fast_std.Length = min_period
self._slow_std = StandardDeviation()
self._slow_std.Length = max_period
self._atr = AverageTrueRange()
self._atr.Length = atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast_sma)
self.DrawIndicator(area, self._slow_sma)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
aiv = CandleIndicatorValue(self._atr, candle)
aiv.IsFinal = True
atr_val = self._atr.Process(aiv)
fast_sma_val = process_float(self._fast_sma, candle.ClosePrice, candle.OpenTime, True)
slow_sma_val = process_float(self._slow_sma, candle.ClosePrice, candle.OpenTime, True)
fast_std_val = process_float(self._fast_std, candle.ClosePrice, candle.OpenTime, True)
slow_std_val = process_float(self._slow_std, candle.ClosePrice, candle.OpenTime, True)
if not self._atr.IsFormed or not self._fast_sma.IsFormed or not self._slow_sma.IsFormed or \
not self._fast_std.IsFormed or not self._slow_std.IsFormed:
return
if atr_val.IsEmpty or fast_sma_val.IsEmpty or slow_sma_val.IsEmpty or fast_std_val.IsEmpty or slow_std_val.IsEmpty:
return
current_atr = float(atr_val)
self._atr_sum += current_atr
self._atr_count += 1
if not self.IsFormedAndOnlineAndAllowTrading():
return
average_atr = self._atr_sum / self._atr_count if self._atr_count > 0 else current_atr
use_fast_bands = current_atr >= average_atr
fast_sma = float(fast_sma_val)
slow_sma = float(slow_sma_val)
fast_std = float(fast_std_val)
slow_std = float(slow_std_val)
middle_band = fast_sma if use_fast_bands else slow_sma
std_dev = fast_std if use_fast_bands else slow_std
band_width = float(self._max_bollinger_deviation.Value) if use_fast_bands else float(self._min_bollinger_deviation.Value)
upper_band = middle_band + (std_dev * band_width)
lower_band = middle_band - (std_dev * band_width)
close = float(candle.ClosePrice)
cooldown = int(self._cooldown_bars.Value)
if self.Position > 0 and close >= middle_band:
self.SellMarket(self.Position)
self._cooldown_remaining = cooldown
elif self.Position < 0 and close <= middle_band:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self._cooldown_remaining == 0 and close < lower_band and self.Position <= 0:
vol = self.Volume
if self.Position < 0:
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
self._cooldown_remaining = cooldown
elif self._cooldown_remaining == 0 and close > upper_band and self.Position >= 0:
vol = self.Volume
if self.Position > 0:
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
self._cooldown_remaining = cooldown
def CreateClone(self):
return adaptive_bollinger_breakout_strategy()