Bollinger Williams R Strategy
Strategy based on Bollinger Bands and Williams %R indicators. Enters long when price is at lower band and Williams %R is oversold (< -80) Enters short when price is at upper band and Williams %R is overbought (> -20)
Testing indicates an average annual return of about 103%. It performs best in the stocks market.
Bollinger bands expose volatility breakouts and Williams %R ensures momentum is extreme. Positions open when price closes outside a band with a matching Williams %R reading.
Best for volatility expansion traders. ATR stops handle adverse turns.
Details
- Entry Criteria:
- Long:
Close < LowerBand && WilliamsR < -80 - Short:
Close > UpperBand && WilliamsR > -20
- Long:
- Long/Short: Both
- Exit Criteria: Price returns to middle band
- Stops: ATR-based using
AtrMultiplier - Default Values:
BollingerPeriod= 20BollingerDeviation= 2.0mWilliamsRPeriod= 14WilliamsROversold= -80WilliamsROverbought= -20AtrPeriod= 14AtrMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() The -80 and -20 in the rules are the defaults of WilliamsROversold and WilliamsROverbought. The stop lies AtrMultiplier ATRs (AtrPeriod) from the entry close and is checked on candle closes. An entry signal against an open position reverses it.
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: Bollinger Bands, Williams %R, R
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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 Williams %R strategy.
/// A close below the lower band with Williams %R below WilliamsROversold goes long and a close above the upper band with %R above
/// WilliamsROverbought goes short, reversing an opposite position. The position closes when price returns to the middle band.
/// The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
/// </summary>
public class BollingerWilliamsRStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _williamsRPeriod;
private readonly StrategyParam<decimal> _williamsROversold;
private readonly StrategyParam<decimal> _williamsROverbought;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
/// <summary>
/// Period of the Bollinger Bands.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier of the bands.
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// Period of Williams %R.
/// </summary>
public int WilliamsRPeriod
{
get => _williamsRPeriod.Value;
set => _williamsRPeriod.Value = value;
}
/// <summary>
/// Williams %R level for longs.
/// </summary>
public decimal WilliamsROversold
{
get => _williamsROversold.Value;
set => _williamsROversold.Value = value;
}
/// <summary>
/// Williams %R level for shorts.
/// </summary>
public decimal WilliamsROverbought
{
get => _williamsROverbought.Value;
set => _williamsROverbought.Value = value;
}
/// <summary>
/// Period of the stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BollingerWilliamsRStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period of the Bollinger Bands", "Indicators");
_bollingerDeviation = Param(nameof(BollingerDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier of the bands", "Indicators");
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Period of Williams %R", "Indicators");
_williamsROversold = Param(nameof(WilliamsROversold), -80m)
.SetDisplay("Williams %R Oversold", "Williams %R level for longs", "Indicators");
_williamsROverbought = Param(nameof(WilliamsROverbought), -20m)
.SetDisplay("Williams %R Overbought", "Williams %R level for shorts", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the stop ATR", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "Risk");
_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();
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stopPrice = default;
var bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerDeviation };
var williams = new WilliamsR { Length = WilliamsRPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, williams, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, williams);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue williamsValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bollingerValue.IsFormed || !williamsValue.IsFormed || !atrValue.IsFormed)
return;
var bands = (BollingerBandsValue)bollingerValue;
if (bands.UpBand is not decimal upper || bands.LowBand is not decimal lower || bands.MovingAverage is not decimal middle)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
var williams = williamsValue.GetValue<decimal>();
if (close < lower && williams < WilliamsROversold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - AtrMultiplier * atr;
}
else if (close > upper && williams > WilliamsROverbought && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + AtrMultiplier * atr;
}
else if (Position > 0 && (close >= middle || (AtrMultiplier > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (close <= middle || (AtrMultiplier > 0 && close >= _stopPrice)))
{
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
from StockSharp.Algo.Indicators import BollingerBands, WilliamsR, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class bollinger_williams_r_strategy(Strategy):
"""
Bollinger Williams %R strategy.
A close below the lower band with Williams %R below WilliamsROversold goes long and a close above the upper band with %R above
WilliamsROverbought goes short, reversing an opposite position. The position closes when price returns to the middle band.
The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
"""
def __init__(self):
super(bollinger_williams_r_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("Bollinger Period", "Period of the Bollinger Bands", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0).SetGreaterThanZero().SetDisplay("Bollinger Deviation", "Standard deviation multiplier of the bands", "Indicators")
self._williams_r_period = self.Param("WilliamsRPeriod", 14).SetGreaterThanZero().SetDisplay("Williams %R Period", "Period of Williams %R", "Indicators")
self._williams_r_oversold = self.Param("WilliamsROversold", -80.0).SetDisplay("Williams %R Oversold", "Williams %R level for longs", "Indicators")
self._williams_r_overbought = self.Param("WilliamsROverbought", -20.0).SetDisplay("Williams %R Overbought", "Williams %R level for shorts", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the stop ATR", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._stop_price = Decimal(0)
def OnReseted(self):
super(bollinger_williams_r_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(bollinger_williams_r_strategy, self).OnStarted2(time)
self._reset_state()
bollinger = BollingerBands()
bollinger.Length = self._bollinger_period.Value
bollinger.Width = Decimal(self._bollinger_deviation.Value)
williams = WilliamsR()
williams.Length = self._williams_r_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, williams, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, williams)
def _process_candle(self, candle, bollinger_value, williams_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not bollinger_value.IsFormed or not williams_value.IsFormed or not atr_value.IsFormed:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None or bollinger_value.MovingAverage is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
upper = bollinger_value.UpBand
lower = bollinger_value.LowBand
middle = bollinger_value.MovingAverage
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
williams = williams_value.GetValue[Decimal](None)
stop_atr = Decimal(self._atr_multiplier.Value)
if close < lower and williams < Decimal(self._williams_r_oversold.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif close > upper and williams > Decimal(self._williams_r_overbought.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_atr * atr
elif self.Position > 0 and (close >= middle or (stop_atr > 0 and close <= self._stop_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (close <= middle or (stop_atr > 0 and close >= self._stop_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return bollinger_williams_r_strategy()