Estrategia Williams VIX Fix
La estrategia Williams VIX Fix adapta el indicador de volatilidad de Larry Williams a instrumentos que carecen de un VIX publicado. Calcula un valor VIX sintético utilizando la distancia entre el cierre más alto durante un período de referencia y el mínimo actual. Cuando este valor sube por encima de un umbral de Bollinger Band o el precio cierra por debajo de la banda inferior de Bollinger, la estrategia lo considera una oportunidad de sobreventa. Un cálculo invertido mide los extremos de sobrecompra.
El enfoque busca reversión a la media después de picos de volatilidad. Cuando el VIX Fix señala alto miedo y el precio está por debajo de la banda inferior, se abre una operación larga. Por el contrario, cuando el VIX Fix inverso apunta a complacencia extrema y el precio está por encima de la banda superior, las posiciones largas existentes se cierran. Los umbrales de percentil controlan la sensibilidad.
Detalles
- Criterios de entrada:
- VIX Fix ≥ banda superior o percentil y precio < banda inferior de Bollinger.
- Largo/Corto: Entradas largas con salidas en señal opuesta.
- Criterios de salida:
- VIX Fix invertido ≥ banda superior o percentil y precio > banda superior de Bollinger.
- Stops: Ninguno.
- Valores predeterminados:
BbLength= 20BbMultiplier= 2.0WvfPeriod= 20WvfLookback= 50HighestPercentile= 0.85LowestPercentile= 0.99
- Filtros:
- Categoría: Reversión a la media de volatilidad
- Dirección: Largo
- Indicadores: Bollinger Bands, Williams VIX Fix
- Stops: No
- Complejidad: Medio
- Marco temporal: Cualquiera
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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.
/// VIX Fix = (highest close of WvfPeriod - low) / highest close * 100; the inverted VIX Fix uses (high - lowest close) / lowest close.
/// Each value is extreme when it reaches its own Bollinger upper band (BbLength, BbMultiplier) or the highest value of the last
/// WvfLookback bars times its percentile (HighestPercentile for the VIX Fix, LowestPercentile for the inverted one).
/// A long opens on an extreme VIX Fix with the close below the lower price Bollinger Band and closes on an extreme inverted
/// VIX Fix with the close above the upper band.
/// </summary>
public class WilliamsVixFixStrategy : Strategy
{
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<int> _wvfPeriod;
private readonly StrategyParam<int> _wvfLookback;
private readonly StrategyParam<decimal> _highestPercentile;
private readonly StrategyParam<decimal> _lowestPercentile;
private readonly StrategyParam<DataType> _candleType;
private Highest _highestClose;
private Lowest _lowestClose;
private SimpleMovingAverage _wvfMean;
private StandardDeviation _wvfDeviation;
private Highest _wvfRange;
private SimpleMovingAverage _invMean;
private StandardDeviation _invDeviation;
private Highest _invRange;
/// <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>
/// Lookback of the highest and lowest close in the VIX Fix.
/// </summary>
public int WvfPeriod
{
get => _wvfPeriod.Value;
set => _wvfPeriod.Value = value;
}
/// <summary>
/// Lookback of the percentile thresholds.
/// </summary>
public int WvfLookback
{
get => _wvfLookback.Value;
set => _wvfLookback.Value = value;
}
/// <summary>
/// Percentile of the highest VIX Fix value that marks fear.
/// </summary>
public decimal HighestPercentile
{
get => _highestPercentile.Value;
set => _highestPercentile.Value = value;
}
/// <summary>
/// Percentile of the highest inverted VIX Fix value that marks complacency.
/// </summary>
public decimal LowestPercentile
{
get => _lowestPercentile.Value;
set => _lowestPercentile.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public WilliamsVixFixStrategy()
{
_bbLength = Param(nameof(BbLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger");
_bbMultiplier = Param(nameof(BbMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Bollinger");
_wvfPeriod = Param(nameof(WvfPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("WVF Period", "Lookback of the highest and lowest close", "VIX Fix");
_wvfLookback = Param(nameof(WvfLookback), 50)
.SetGreaterThanZero()
.SetDisplay("WVF Lookback", "Lookback of the percentile thresholds", "VIX Fix");
_highestPercentile = Param(nameof(HighestPercentile), 0.85m)
.SetGreaterThanZero()
.SetDisplay("Highest Percentile", "Percentile of the highest VIX Fix value", "VIX Fix");
_lowestPercentile = Param(nameof(LowestPercentile), 0.99m)
.SetGreaterThanZero()
.SetDisplay("Lowest Percentile", "Percentile of the highest inverted VIX Fix value", "VIX Fix");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "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 };
_highestClose = new Highest { Length = WvfPeriod };
_lowestClose = new Lowest { Length = WvfPeriod };
_wvfMean = new SimpleMovingAverage { Length = BbLength };
_wvfDeviation = new StandardDeviation { Length = BbLength };
_wvfRange = new Highest { Length = WvfLookback };
_invMean = new SimpleMovingAverage { Length = BbLength };
_invDeviation = new StandardDeviation { Length = BbLength };
_invRange = new Highest { Length = WvfLookback };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue)
{
if (candle.State != CandleStates.Finished)
return;
var time = candle.OpenTime;
var highestClose = _highestClose.Process(candle.ClosePrice, time, true).ToDecimal();
var lowestClose = _lowestClose.Process(candle.ClosePrice, time, true).ToDecimal();
if (!_highestClose.IsFormed || !_lowestClose.IsFormed || highestClose <= 0m || lowestClose <= 0m)
return;
var wvf = (highestClose - candle.LowPrice) / highestClose * 100m;
var inv = (candle.HighPrice - lowestClose) / lowestClose * 100m;
var fear = IsExtreme(wvf, time, _wvfMean, _wvfDeviation, _wvfRange, HighestPercentile);
var complacency = IsExtreme(inv, time, _invMean, _invDeviation, _invRange, LowestPercentile);
if (fear is null || complacency is null)
return;
if (bollingerValue is not IBollingerBandsValue { UpBand: decimal upper, LowBand: decimal lower })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (fear == true && close < lower && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (complacency == true && close > upper && Position > 0)
SellMarket(Position);
}
private bool? IsExtreme(decimal value, DateTime time, SimpleMovingAverage mean, StandardDeviation deviation, Highest range, decimal percentile)
{
var middle = mean.Process(value, time, true).ToDecimal();
var spread = deviation.Process(value, time, true).ToDecimal();
var highest = range.Process(value, time, true).ToDecimal();
if (!mean.IsFormed || !deviation.IsFormed || !range.IsFormed)
return null;
var upperBand = middle + BbMultiplier * spread;
return value >= upperBand || value >= highest * percentile;
}
}
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, Highest, Lowest, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class williams_vix_fix_strategy(Strategy):
"""
Williams VIX Fix Strategy.
VIX Fix = (highest close of WvfPeriod - low) / highest close * 100; the inverted VIX Fix uses (high - lowest close) / lowest close.
Each value is extreme when it reaches its own Bollinger upper band (BbLength, BbMultiplier) or the highest value of the last
WvfLookback bars times its percentile (HighestPercentile for the VIX Fix, LowestPercentile for the inverted one).
A long opens on an extreme VIX Fix with the close below the lower price Bollinger Band and closes on an extreme inverted
VIX Fix with the close above the upper band.
"""
def __init__(self):
super(williams_vix_fix_strategy, self).__init__()
self._bb_length = self.Param("BbLength", 20).SetGreaterThanZero().SetDisplay("BB Length", "Bollinger Bands period", "Bollinger")
self._bb_multiplier = self.Param("BbMultiplier", 2.0).SetGreaterThanZero().SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Bollinger")
self._wvf_period = self.Param("WvfPeriod", 20).SetGreaterThanZero().SetDisplay("WVF Period", "Lookback of the highest and lowest close", "VIX Fix")
self._wvf_lookback = self.Param("WvfLookback", 50).SetGreaterThanZero().SetDisplay("WVF Lookback", "Lookback of the percentile thresholds", "VIX Fix")
self._highest_percentile = self.Param("HighestPercentile", 0.85).SetGreaterThanZero().SetDisplay("Highest Percentile", "Percentile of the highest VIX Fix value", "VIX Fix")
self._lowest_percentile = self.Param("LowestPercentile", 0.99).SetGreaterThanZero().SetDisplay("Lowest Percentile", "Percentile of the highest inverted VIX Fix value", "VIX Fix")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._highest_close = None
self._lowest_close = None
self._wvf_set = None
self._inv_set = None
@property
def candle_type(self):
return self._candle_type.Value
def _create_set(self):
mean = SimpleMovingAverage()
mean.Length = self._bb_length.Value
deviation = StandardDeviation()
deviation.Length = self._bb_length.Value
rng = Highest()
rng.Length = self._wvf_lookback.Value
return (mean, deviation, rng)
def OnStarted2(self, time):
super(williams_vix_fix_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self._bb_length.Value
bollinger.Width = Decimal(float(self._bb_multiplier.Value))
self._highest_close = Highest()
self._highest_close.Length = self._wvf_period.Value
self._lowest_close = Lowest()
self._lowest_close.Length = self._wvf_period.Value
self._wvf_set = self._create_set()
self._inv_set = self._create_set()
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def _process_candle(self, candle, bollinger_value):
if candle.State != CandleStates.Finished:
return
time = candle.OpenTime
highest_close = float(process_float(self._highest_close, candle.ClosePrice, time, True).GetValue[Decimal](None))
lowest_close = float(process_float(self._lowest_close, candle.ClosePrice, time, True).GetValue[Decimal](None))
if not self._highest_close.IsFormed or not self._lowest_close.IsFormed or highest_close <= 0 or lowest_close <= 0:
return
wvf = (highest_close - float(candle.LowPrice)) / highest_close * 100.0
inv = (float(candle.HighPrice) - lowest_close) / lowest_close * 100.0
fear = self._is_extreme(wvf, time, self._wvf_set, float(self._highest_percentile.Value))
complacency = self._is_extreme(inv, time, self._inv_set, float(self._lowest_percentile.Value))
if fear is None or complacency is None:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
upper = float(bollinger_value.UpBand)
lower = float(bollinger_value.LowBand)
if fear and close < lower and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif complacency and close > upper and self.Position > 0:
self.SellMarket(self.Position)
def _is_extreme(self, value, time, indicators, percentile):
mean, deviation, rng = indicators
middle = float(process_float(mean, value, time, True).GetValue[Decimal](None))
spread = float(process_float(deviation, value, time, True).GetValue[Decimal](None))
highest = float(process_float(rng, value, time, True).GetValue[Decimal](None))
if not mean.IsFormed or not deviation.IsFormed or not rng.IsFormed:
return None
upper_band = middle + float(self._bb_multiplier.Value) * spread
return value >= upper_band or value >= highest * percentile
def CreateClone(self):
return williams_vix_fix_strategy()