Стратегия Bollinger Winner Pro
Bollinger Winner Pro дополняет версию Lite модульными фильтрами и управлением риском. Стратегия по‑прежнему реагирует на закрытие цены за пределами полос Боллинджера, но сделки совершаются только при согласии включённых фильтров.
Можно подключать фильтры RSI, Aroon и скользящей средней для подтверждения импульса и направления тренда. Также доступен встроенный стоп‑лосс, ограничивающий потери. Такая гибкость позволяет адаптировать подход под различные рынки.
Цель — возврат к средней или противоположной полосе. Благодаря множеству фильтров сигналы возникают реже, но их качество выше.
Подробности
- Данные: свечи цены.
- Условия входа: свеча закрывается вне полосы и все активные фильтры подтверждают сигнал.
- Условия выхода: возврат к средней/противоположной полосе или срабатывание стоп‑лосса при
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 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()