Williams VIX Fix Strategy
The Williams VIX Fix strategy adapts Larry Williams’ volatility indicator to instruments that lack a published VIX. It computes a synthetic VIX value using the distance between the highest close over a lookback period and the current low. When this value rises above a Bollinger Band threshold or the price closes below the lower Bollinger Band, the strategy considers it an oversold opportunity. An inverted calculation gauges overbought extremes.
The approach looks for mean reversion after volatility spikes. When the VIX Fix signals high fear and price is below the lower band, a long trade is opened. Conversely, when the inverse VIX Fix points to extreme complacency and price is above the upper band, existing long positions are closed. Percentile thresholds control sensitivity.
Details
- Entry Criteria:
- VIX Fix ≥ upper band or percentile and price < lower Bollinger Band.
- Long/Short: Long entries with exits on opposite signal.
- Exit Criteria:
- Inverted VIX Fix ≥ upper band or percentile and price > upper Bollinger Band.
- Stops: None.
- Default Values:
BbLength = 20
BbMultiplier = 2.0
WvfPeriod = 20
WvfLookback = 50
HighestPercentile = 0.85
LowestPercentile = 0.99
- Filters:
- Category: Volatility mean reversion
- Direction: Long
- Indicators: Bollinger Bands, Williams VIX Fix
- Stops: No
- Complexity: Medium
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Williams VIX Fix Strategy.
/// Uses Bollinger Bands width as volatility proxy.
/// Buys when price touches lower BB during high volatility (wide bands).
/// Sells when price touches upper BB during high volatility.
/// </summary>
public class WilliamsVixFixStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _cooldownBars;
private BollingerBands _bb;
private RelativeStrengthIndex _rsi;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int BbLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
public decimal BbMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public WilliamsVixFixStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_bbLength = Param(nameof(BbLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands");
_bbMultiplier = Param(nameof(BbMultiplier), 2.0m)
.SetDisplay("BB Multiplier", "BB standard deviation multiplier", "Bollinger Bands");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bb = null;
_rsi = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bb = new BollingerBands { Length = BbLength, Width = BbMultiplier };
_rsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bb, _rsi, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bb);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, IIndicatorValue bbValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_bb.IsFormed || !_rsi.IsFormed)
return;
if (bbValue.IsEmpty || rsiValue.IsEmpty)
return;
var bb = (BollingerBandsValue)bbValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower || bb.MovingAverage is not decimal mid)
return;
var rsiVal = rsiValue.ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
// Buy: price at or below lower BB + RSI oversold
if (candle.ClosePrice <= lower && rsiVal < 35 && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: price at or above upper BB + RSI overbought
else if (candle.ClosePrice >= upper && rsiVal > 65 && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: price reaches middle BB or RSI > 70
else if (Position > 0 && (candle.ClosePrice >= mid || rsiVal > 70))
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: price reaches middle BB or RSI < 30
else if (Position < 0 && (candle.ClosePrice <= mid || rsiVal < 30))
{
BuyMarket(Math.Abs(Position));
_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, CandleStates
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class williams_vix_fix_strategy(Strategy):
"""Williams VIX Fix Strategy."""
def __init__(self):
super(williams_vix_fix_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._bb_length = self.Param("BbLength", 20) \
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands")
self._bb_multiplier = self.Param("BbMultiplier", 2.0) \
.SetDisplay("BB Multiplier", "BB standard deviation multiplier", "Bollinger Bands")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._bb = None
self._rsi = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(williams_vix_fix_strategy, self).OnReseted()
self._bb = None
self._rsi = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(williams_vix_fix_strategy, self).OnStarted2(time)
self._bb = BollingerBands()
self._bb.Length = int(self._bb_length.Value)
self._bb.Width = float(self._bb_multiplier.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._bb, self._rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._bb)
self.DrawOwnTrades(area)
def _on_process(self, candle, bb_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if not self._bb.IsFormed or not self._rsi.IsFormed:
return
if bb_value.IsEmpty or rsi_value.IsEmpty:
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
upper = float(bb_value.UpBand)
lower = float(bb_value.LowBand)
mid = float(bb_value.MovingAverage)
rsi_val = float(IndicatorHelper.ToDecimal(rsi_value))
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
close = float(candle.ClosePrice)
cooldown = int(self._cooldown_bars.Value)
if close <= lower and rsi_val < 35 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif close >= upper and rsi_val > 65 and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and (close >= mid or rsi_val > 70):
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and (close <= mid or rsi_val < 30):
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
def CreateClone(self):
return williams_vix_fix_strategy()