Bollinger Breakout 策略
Bollinger Breakout 策略旨在捕捉突破布林带后继续发展的行情。 当收盘价高于上轨或低于下轨时,如启用的过滤器全部同意,便在突破方向开仓。
可选过滤器包括 RSI、Aroon 和移动平均线,用于验证动量和趋势。 还可以启用止损以控制风险。平仓发生在价格触及相反带或止损触发时。
该策略适用于趋势性市场,在此环境中带的突破通常会延续。
细节
- 数据: 价格K线。
- 入场条件:
- 多头: 收盘价高于上轨且所有启用过滤器确认。
- 空头: 收盘价低于下轨且所有启用过滤器确认。
- 离场条件: 触及相反带或在
UseSL启用时触发止损。 - 止损: 可选止损(
UseSL)。 - 默认参数:
UseRSI= TrueUseAroon= FalseUseMA= TrueUseSL= True
- 过滤器:
- 类型: 突破
- 方向: 多空皆可
- 指标: Bollinger Bands, RSI, Aroon, Moving Average
- 复杂度: 中等
- 风险级别: 高
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;
/// <summary>
/// Bollinger Breakout strategy.
/// Goes long when a candle closes above the upper band and short when it closes below the lower band, provided every
/// enabled filter agrees: RSI beyond its long/short momentum level, Aroon up/down dominance in the trade direction and
/// price on the trade side of the moving average. A long closes when price touches the lower band and a short when it
/// touches the upper band; an optional percent stop-loss caps the risk.
/// </summary>
public class BollingerBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<bool> _useRsi;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiShortLevel;
private readonly StrategyParam<decimal> _rsiLongLevel;
private readonly StrategyParam<bool> _useAroon;
private readonly StrategyParam<int> _aroonLength;
private readonly StrategyParam<bool> _useMa;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<bool> _useSl;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BBMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
/// <summary>
/// Enable the RSI filter.
/// </summary>
public bool UseRSI
{
get => _useRsi.Value;
set => _useRsi.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RSILength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// RSI level below which shorts are allowed.
/// </summary>
public decimal RSIShortLevel
{
get => _rsiShortLevel.Value;
set => _rsiShortLevel.Value = value;
}
/// <summary>
/// RSI level above which longs are allowed.
/// </summary>
public decimal RSILongLevel
{
get => _rsiLongLevel.Value;
set => _rsiLongLevel.Value = value;
}
/// <summary>
/// Enable the Aroon filter.
/// </summary>
public bool UseAroon
{
get => _useAroon.Value;
set => _useAroon.Value = value;
}
/// <summary>
/// Aroon period.
/// </summary>
public int AroonLength
{
get => _aroonLength.Value;
set => _aroonLength.Value = value;
}
/// <summary>
/// Enable the moving average filter.
/// </summary>
public bool UseMA
{
get => _useMa.Value;
set => _useMa.Value = value;
}
/// <summary>
/// Moving average period.
/// </summary>
public int MALength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Enable the stop-loss.
/// </summary>
public bool UseSL
{
get => _useSl.Value;
set => _useSl.Value = value;
}
/// <summary>
/// Stop-loss percentage from the entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BollingerBreakoutStrategy()
{
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands");
_bbMultiplier = Param(nameof(BBMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("BB StdDev", "Bollinger Bands standard deviation multiplier", "Bollinger Bands");
_useRsi = Param(nameof(UseRSI), true)
.SetDisplay("Use RSI", "Require RSI confirmation", "RSI Filter");
_rsiLength = Param(nameof(RSILength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI Filter");
_rsiShortLevel = Param(nameof(RSIShortLevel), 45m)
.SetDisplay("RSI Short Level", "RSI level below which shorts are allowed", "RSI Filter");
_rsiLongLevel = Param(nameof(RSILongLevel), 55m)
.SetDisplay("RSI Long Level", "RSI level above which longs are allowed", "RSI Filter");
_useAroon = Param(nameof(UseAroon), false)
.SetDisplay("Use Aroon", "Require Aroon confirmation", "Aroon Filter");
_aroonLength = Param(nameof(AroonLength), 14)
.SetGreaterThanZero()
.SetDisplay("Aroon Length", "Aroon period", "Aroon Filter");
_useMa = Param(nameof(UseMA), true)
.SetDisplay("Use MA", "Require price on the trade side of the moving average", "Moving Average");
_maLength = Param(nameof(MALength), 50)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average period", "Moving Average");
_useSl = Param(nameof(UseSL), true)
.SetDisplay("Use Stop Loss", "Enable the percent stop-loss", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop-loss percentage from the entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bollinger = new BollingerBands
{
Length = BBLength,
Width = BBMultiplier
};
var rsi = new RelativeStrengthIndex { Length = RSILength };
var aroon = new Aroon { Length = AroonLength };
var ma = new ExponentialMovingAverage { Length = MALength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, rsi, aroon, ma, ProcessCandle)
.Start();
if (UseSL)
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue rsiValue, IIndicatorValue aroonValue, IIndicatorValue maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bollingerValue.IsFormed || !rsiValue.IsFormed || !aroonValue.IsFormed || !maValue.IsFormed)
return;
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
return;
var aroon = (AroonValue)aroonValue;
if (aroon.Up is not decimal aroonUp || aroon.Down is not decimal aroonDown)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var rsi = rsiValue.ToDecimal();
var ma = maValue.ToDecimal();
var close = candle.ClosePrice;
var longSignal = close > upper
&& (!UseRSI || rsi > RSILongLevel)
&& (!UseAroon || aroonUp > aroonDown)
&& (!UseMA || close > ma);
var shortSignal = close < lower
&& (!UseRSI || rsi < RSIShortLevel)
&& (!UseAroon || aroonDown > aroonUp)
&& (!UseMA || close < ma);
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && candle.LowPrice <= lower)
SellMarket(Position);
else if (Position < 0 && candle.HighPrice >= upper)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex, ExponentialMovingAverage, Aroon
from StockSharp.Algo.Strategies import Strategy
class bollinger_breakout_strategy(Strategy):
"""
Bollinger Breakout strategy.
Goes long when a candle closes above the upper band and short when it closes below the lower band, provided every
enabled filter agrees: RSI beyond its long/short momentum level, Aroon up/down dominance in the trade direction and
price on the trade side of the moving average. A long closes when price touches the lower band and a short when it
touches the upper band; an optional percent stop-loss caps the risk.
"""
def __init__(self):
super(bollinger_breakout_strategy, self).__init__()
self._bb_length = self.Param("BBLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands")
self._bb_multiplier = self.Param("BBMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("BB StdDev", "Bollinger Bands standard deviation multiplier", "Bollinger Bands")
self._use_rsi = self.Param("UseRSI", True) \
.SetDisplay("Use RSI", "Require RSI confirmation", "RSI Filter")
self._rsi_length = self.Param("RSILength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period", "RSI Filter")
self._rsi_long_level = self.Param("RSILongLevel", 55.0) \
.SetDisplay("RSI Long Level", "RSI level above which longs are allowed", "RSI Filter")
self._rsi_short_level = self.Param("RSIShortLevel", 45.0) \
.SetDisplay("RSI Short Level", "RSI level below which shorts are allowed", "RSI Filter")
self._use_aroon = self.Param("UseAroon", False) \
.SetDisplay("Use Aroon", "Require Aroon confirmation", "Aroon Filter")
self._aroon_length = self.Param("AroonLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("Aroon Length", "Aroon period", "Aroon Filter")
self._use_ma = self.Param("UseMA", True) \
.SetDisplay("Use MA", "Require price on the trade side of the moving average", "Moving Average")
self._ma_length = self.Param("MALength", 50) \
.SetGreaterThanZero() \
.SetDisplay("MA Length", "Moving average period", "Moving Average")
self._use_sl = self.Param("UseSL", True) \
.SetDisplay("Use Stop Loss", "Enable the percent stop-loss", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop-loss percentage from the entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation", "General")
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnStarted2(self, time):
super(bollinger_breakout_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self._bb_length.Value
bollinger.Width = Decimal(self._bb_multiplier.Value)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
aroon = Aroon()
aroon.Length = self._aroon_length.Value
ma = ExponentialMovingAverage()
ma.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bollinger, rsi, aroon, ma, self._process_candle).Start()
if self._use_sl.Value:
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, bollinger_value, rsi_value, aroon_value, ma_value):
if candle.State != CandleStates.Finished:
return
if not bollinger_value.IsFormed or not rsi_value.IsFormed or not aroon_value.IsFormed or not ma_value.IsFormed:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
if aroon_value.Up is None or aroon_value.Down is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
upper = float(bollinger_value.UpBand)
lower = float(bollinger_value.LowBand)
aroon_up = float(aroon_value.Up)
aroon_down = float(aroon_value.Down)
rsi = float(rsi_value.GetValue[Decimal](None))
ma = float(ma_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
use_rsi = self._use_rsi.Value
use_aroon = self._use_aroon.Value
use_ma = self._use_ma.Value
long_signal = close > upper \
and (not use_rsi or rsi > float(self._rsi_long_level.Value)) \
and (not use_aroon or aroon_up > aroon_down) \
and (not use_ma or close > ma)
short_signal = close < lower \
and (not use_rsi or rsi < float(self._rsi_short_level.Value)) \
and (not use_aroon or aroon_down > aroon_up) \
and (not use_ma or close < ma)
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and float(candle.LowPrice) <= lower:
self.SellMarket(self.Position)
elif self.Position < 0 and float(candle.HighPrice) >= upper:
self.BuyMarket(-self.Position)
def CreateClone(self):
return bollinger_breakout_strategy()