Williams VIX Fix策略
Williams VIX Fix策略将Larry Williams的波动率指标应用于没有官方VIX的品种。该指标通过计算过去一段时间最高收盘价与当前最低价之间的距离来构造合成VIX。当该值上穿基于布林带的阈值或价格跌破下轨时,被视为超卖信号;通过反向计算可以识别超买情形。
该方法寻找波动率冲击后的均值回归。当VIX Fix显示极度恐慌且价格低于下轨时开多仓;当反向VIX Fix提示极度乐观且价格高于上轨时,平掉现有多仓。百分位阈值用于调节敏感度。
细节
- 入场条件:
- VIX Fix ≥ 上轨或百分位,且价格 < 布林带下轨。
- 方向:仅做多,反向信号用于平仓。
- 出场条件:
- 反向VIX Fix ≥ 上轨或百分位,且价格 > 布林带上轨。
- 止损:无。
- 默认参数:
BbLength= 20BbMultiplier= 2.0WvfPeriod= 20WvfLookback= 50HighestPercentile= 0.85LowestPercentile= 0.99
- 过滤器:
- 类型:波动率均值回归
- 方向:多头
- 指标:布林带、Williams VIX Fix
- 止损:无
- 复杂度:中等
- 时间框架:任意
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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()