Using VIX to Time Options Writing
Log in to collectAcademic paper
Strategy in a nutshell
Invests in S&P 500 options or the index based on VIX levels, selling puts when VIX is high and holding the index when VIX is low. Positions are adjusted monthly using the historical median VIX to optimize risk and return.
Economic rationale
Exploits the volatility risk premium, capitalizing on overpriced options during high VIX periods and benefiting from low-volatility, positive-return environments. Conditional positioning reduces losses and is robust across time periods and market conditions.
Backtest performance
Annualised return10.88%
Volatility11.36%
Beta0.652
Sharpe ratio0.61
Sortino ratio0.262
Maximum drawdown-31.15%
Win rate60%
Full Python code
from collections import deque
from AlgorithmImports import *
import numpy as np
from QuantConnect.Python import PythonQuandl
class UsingVIXtoTimeOptionsWriting(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
self.symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
data = self.AddEquity("BIL", Resolution.Minute)
data.SetLeverage(2)
self.bills = data.Symbol
# SPY options.
option = self.AddOption("SPY", Resolution.Minute)
option.SetFilter(-20, 20, 25, 35)
# Vix spot.
self.vix_spot = self.AddData(CBOE, 'VIX', Resolution.Daily).Symbol
# VIX historical monthly data.
self.data = None
# Get vix history.
history = self.History(self.vix_spot, 10*12*30, Resolution.Daily)
if 'close' in history.columns:
closes = history['close']
self.data = deque(closes)
# Next expiration date.
self.expiration_date = None
def OnData(self, slice):
# store VIX price
if self.vix_spot in slice and slice[self.vix_spot]:
price = slice[self.vix_spot].Value
self.data.append(price)
# Open new trades only on market close.
if not (self.Time.hour == 15 and self.Time.minute == 59):
return
# At least year of data is ready.
if len(self.data) < 12 * 30: return
if self.expiration_date:
if self.Time.date() < self.expiration_date.date():
return
if self.Portfolio.Invested:
self.Liquidate()
vix_median = np.median(self.data)
# Last month VIX median.
vix_median_t1 = np.median([x for x in self.data][-21:])
for i in slice.OptionChains:
chains = i.Value
if not self.Portfolio.Invested:
puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
if not puts: return
underlying_price = self.Securities[self.symbol].Price
expiries = [i.Expiry for i in puts]
# Determine expiration date nearly one month.
expiry = min(expiries, key=lambda x: abs((x.date()-self.Time.date()).days-30))
strikes = [i.Strike for i in puts]
# determine at-the-money strike
strike = min(strikes, key=lambda x: abs(x-underlying_price))
atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]
if atm_put:
if not self.expiration_date:
self.expiration_date = atm_put[0].Expiry
return
self.expiration_date = atm_put[0].Expiry
if vix_median_t1 < vix_median:
self.SetHoldings(self.symbol, 1)
return
options_q = int(self.Portfolio.MarginRemaining / (underlying_price * 100))
self.Securities[atm_put[0].Symbol].MarginModel = BuyingPowerModel(5)
self.SetHoldings(self.bills, 1)
self.Sell(atm_put[0].Symbol, options_q)
if self.Portfolio.Invested:
self.Liquidate(self.symbol)