
“该策略基于VIX水平,在标普500期权写入与指数投资之间每月切换。若前一个月的VIX中位数高于历史中位数,则卖出标普500看跌期权;若低于历史中位数,则直接投资标普500。”
资产类别:ETF、期权 | 区域:美国 | 频率:每月 | 市场:股票 | 关键词:VIX
I. 策略概述
基本规则:
- VIX中位数判断: 每月计算VIX历史中位数(截至前一个月的完整VIX数据)。
- 期权写入 vs. 指数投资:
每月再平衡:
- 在不同VIX环境中动态切换策略,以优化风险与回报。
- 按最新的VIX数据更新历史中位数并调整仓位。
II. 策略合理性
波动率风险溢价:
- VIX与隐含波动率: 期权隐含波动率通常高于实际实现波动率,这种溢价在VIX处于高位时尤为显著。
- 写入期权的优势: 在VIX高企时期,市场对保护性期权的需求增加,导致期权价格过高,为卖方提供了有利的溢价收益。
VIX状态切换:
- 高VIX时期: 期权写入利用市场高估风险的特性获取溢价收益,同时通过持有国债降低风险。
- 低VIX时期: 市场波动较低,直接投资标普500捕捉股市正向回报,同时避免低溢价期权写入的风险。
风险调整优势:
- 动态切换的条件策略能在控制波动的同时,显著提高风险调整后的收益,优于单一投资标普500或持续期权写入的策略。
III. 论文来源
Option Writing: Using VIX to Improve Returns [点击浏览原文]
- Burton G. Malkiel、Alex Rinaudo 和 Atanu Saha
<摘要>
买入-写入(Buy-Write)和看跌-写入(Put-Write)策略已被证明能够匹配市场回报,同时降低波动性,从而实现更高的风险调整表现。这些策略受益于期权隐含波动率通常高于实际波动率的事实。本文显示,这种溢价在VIX水平较高时尤为显著。基于此发现,我们提出一种简单的条件策略:在VIX处于高位时卖出期权。基于1990年至2018年的数据,我们发现这种条件策略在绝对收益和风险调整收益方面均优于市场和持续期权写入策略。
IV. 回测表现
| 年化收益率 | 10.88% |
| 波动率 | 11.36% |
| Beta | 0.652 |
| 夏普比率 | 0.61 |
| 索提诺比率 | 0.262 |
| 最大回撤 | -31.15% |
| 胜率 | 60% |
V. 完整python代码
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)