Стратегия Williams VIX Fix
Стратегия Williams VIX Fix адаптирует индикатор волатильности Ларри Уильямса для инструментов, у которых нет собственного 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()