View on GitHub

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;
		}
	}
}