Bollinger Winner Pro 策略
Bollinger Winner Pro 在 Lite 基础上增加了可选过滤器和风险控制。 当价格收于布林带之外时,只有所有启用的过滤器都确认后才会开仓。
可启用的过滤器包括 RSI、Aroon 和移动平均线,并可选择使用止损。 这种模块化设计使策略能适应不同市场。
策略以均值回归为目标,当价格回到带内或触及相反带时平仓,
若启用止损则按 UseSL 参数执行。
细节
- 数据: 价格K线。
- 入场条件: 收盘价位于带外且所有启用过滤器同意。
- 离场条件: 回到中轨或相反带,或在启用时触发止损。
- 止损: 可选止损(
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 Winner Pro strategy.
/// Goes long when a candle closes below the lower band and short when it closes above the upper band, provided every
/// enabled filter agrees: RSI beyond its oversold/overbought level, Aroon up/down dominance in the trade direction and
/// the middle band on the trade side of the moving average, which confirms the trend direction. A position closes when price returns to the middle band (or reaches
/// the opposite one), and an optional percent stop-loss caps the risk.
/// </summary>
public class BollingerWinnerProStrategy : 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> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
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 longs are allowed.
/// </summary>
public decimal RSIOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// RSI level above which shorts are allowed.
/// </summary>
public decimal RSIOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.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 BollingerWinnerProStrategy()
{
_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");
_rsiOversold = Param(nameof(RSIOversold), 40m)
.SetDisplay("RSI Oversold", "RSI level below which longs are allowed", "RSI Filter");
_rsiOverbought = Param(nameof(RSIOverbought), 60m)
.SetDisplay("RSI Overbought", "RSI level above which shorts 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 the middle band 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 ||
bb.MovingAverage is not decimal middle)
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 < lower
&& (!UseRSI || rsi < RSIOversold)
&& (!UseAroon || aroonUp > aroonDown)
&& (!UseMA || middle > ma);
var shortSignal = close > upper
&& (!UseRSI || rsi > RSIOverbought)
&& (!UseAroon || aroonDown > aroonUp)
&& (!UseMA || middle < ma);
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && close >= middle)
SellMarket(Position);
else if (Position < 0 && close <= middle)
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_winner_pro_strategy(Strategy):
"""
Bollinger Winner Pro strategy.
Goes long when a candle closes below the lower band and short when it closes above the upper band, provided every
enabled filter agrees: RSI beyond its oversold/overbought level, Aroon up/down dominance in the trade direction and
the middle band on the trade side of the moving average, which confirms the trend direction. A position closes when price returns to the middle band (or reaches
the opposite one), and an optional percent stop-loss caps the risk.
"""
def __init__(self):
super(bollinger_winner_pro_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_oversold = self.Param("RSIOversold", 40.0) \
.SetDisplay("RSI Oversold", "RSI level below which longs are allowed", "RSI Filter")
self._rsi_overbought = self.Param("RSIOverbought", 60.0) \
.SetDisplay("RSI Overbought", "RSI level above 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 the middle band 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_winner_pro_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 or bollinger_value.MovingAverage 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)
middle = float(bollinger_value.MovingAverage)
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 < lower \
and (not use_rsi or rsi < float(self._rsi_oversold.Value)) \
and (not use_aroon or aroon_up > aroon_down) \
and (not use_ma or middle > ma)
short_signal = close > upper \
and (not use_rsi or rsi > float(self._rsi_overbought.Value)) \
and (not use_aroon or aroon_down > aroon_up) \
and (not use_ma or middle < 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 close >= middle:
self.SellMarket(self.Position)
elif self.Position < 0 and close <= middle:
self.BuyMarket(-self.Position)
def CreateClone(self):
return bollinger_winner_pro_strategy()