Back to Community
Poor man's "Risk Premia and the VIX Term Structure"

This is an algo inspired by "Risk Premia and the VIX Term Structure" by Travis L. Johnson, http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050 .

The backtest looked way better before I got rid of all the lookahead bias I accidentally introducted with the use of fetch_csv. This slope idea has some merit though, I think - perhaps it needs to be combined with the "variance risk premium" signal between implied volatility and realized volatility. I have also wondered whether these sorts of strategies' performance varies with the exact theoretical composition of the futures months of the VXX/XIV ETPs. Perhaps they decay/grow fastest when they hold mostly one VIX futures term, or perhaps when they hold a perfect balance?

Anyway, hope this provides some food for thought! I'll be continuing to play with this one, but I might have to switch to using intraday VIX/VXV/VXST/VXMT data (outside of Quantopian).

Clone Algorithm
186
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson 
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050

import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series
from zipline.utils.tradingcalendar import get_early_closes
from zipline.utils import tradingcalendar
vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
vxstUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vxstcurrent.csv'
vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vxvdailyprices.csv'

def initialize(context):
    # Define the instruments in the portfolio:
    context.sidsLongVol = {
        sid(38054): +1.0
        #,sid(32268): +1.0
    }
    context.sidsShortVol = {
        sid(40516): +1.0
        #,sid(8554): +1.0
    }
    context.stock = symbol('SPY')
    fetch_csv(vixUrl, 
              symbol='VIX', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX)
    fetch_csv(vxstUrl, 
              symbol='VXST', 
              skiprows=3,
              date_column='Date', 
              pre_func=addFieldsVXST)
    fetch_csv(vxvUrl, 
              symbol='VXV', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV)
    
    # Define the benchmark (used to get early close dates for reference).
    context.spy = sid(8554)
    
    start_date = context.spy.security_start_date
    end_date   = context.spy.security_end_date

    # no trading costs, but shouldn't make a big difference
    set_slippage(slippage.FixedSlippage(spread=0.00))
    set_commission(commission.PerShare(cost=0.0, min_trade_cost=None))
    
    # Get the dates when the market closes early:
    context.early_closes = get_early_closes(start_date, end_date).date
    context.slopes = Series()

    schedule_function(ordering_logic,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_open(hours=0, minutes=5))
    
def handle_data(context, data):
    pass

def factors(slopes):
    longVolFactor = 0.0
    shortVolFactor = 0.0
    if (len(slopes) >= 3):
        yesterday = slopes.iloc[-2]
        # MAGIC NUMBERS - calibrate from rolling history! TODO
        # short the ETPs rather than go long to benefit from 
        # the friction, if any.  
        if (yesterday > 1.15):
            longVolFactor = -1.0
        elif (yesterday < 0.97):
            shortVolFactor = -1.0
    return (longVolFactor, shortVolFactor)
 

def ordering_logic(context, data):
    if 'Close' in data['VIX'] and 'Close' in data['VXV']:
        vix = data['VIX']['Close']
        #vxst = data['VXST']['Close']
        vxv = data['VXV']['Close']
        #slope = (vix/vxst)
        slope = (vxv/vix)
        record(slope=slope)    

        # Get the current exchange time, in local timezone: 
        exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
        
        context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))
        
        (longVolFactor, shortVolFactor) = factors(context.slopes)
        rebalance(context.sidsLongVol, data, longVolFactor) 
        rebalance(context.sidsShortVol, data, shortVolFactor) 
   

def rebalance(sids,data,factor):    
    for sid in sids:
        # this check is stupid, there's no reason we shouldn't be able to place
        # orders in minutes where there were not trades.
        if 'price' in data[sid]: 
            order_target_percent(sid, sids[sid]*factor)          

def reformat_quandl(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    df['Date'] = df.Date.apply(lambda dt: pd.Timestamp(re.sub('\*','',dt), tz='US/Eastern'))
    df = df.sort(columns='Date', ascending=True)
    df.index = range(df.shape[0])
    return df

def shift_quandl(df):
    tdays = tradingcalendar.trading_days
    last_bar = df.iloc[-1].copy()
    last_date = pd.to_datetime(last_bar['Date'])
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    last_bar['Date'] = next_dt
    last_bar.name = df.index[-1] + 1
    df = df.append(last_bar)
    return df

def addFieldsVIX(df):
    df=reformat_quandl(df,'VIX Close')
    df=shift_quandl(df)
    return df

def addFieldsVXST(df):
    df=reformat_quandl(df,'Close')
    df=shift_quandl(df)
    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df=reformat_quandl(df,'CLOSE')
    df=shift_quandl(df)
    return df
There was a runtime error.
11 responses

Is there any surrogate for vix (other than vxx) in quantopian? The data on vxx seems wrong.

VXX is not at all a surrogate for VIX. VIX is an index derived from the implied volatilities of the options on the S&P 500 index. There are futures traded on the VIX index. VXX is an ETP which tracks an index which is calculated as a constant-maturity daily-rebalanced combination of VIX futures.

Just to add to what Simon said, the VIX spot value itself isn't tradable anywhere.

Yes. It isn't. So what is the best proxy for VIX. All these days I thought VXX is a decent proxy (adjusted for time to expiry). Apparently, it's not.

Hi Simon, thanks for sharing. This is awesome!

I've been playing around with Vola-ETPs lately as well. Have you had a look at "http://volatilitymadesimple.com/category/strategy-backtests/"? It seems those guys did some serious work back-testing various (non-academic) Vola strategies.

Yeah I follow his work, it would be good to get backtests/notebooks made of some of those strategies. I did one of Tony Cooper's VRP strategies a couple of years ago on here, I don't know if it still works...

Tony Cooper warned people that his strategies were data-mined, and said as much on my blog.

In spite of Johnson's decent explanation of the explanatory power of vix slope for all sorts of risk prema Equity Risk Premia and the VIX Term Structure
the results cannot easily be replicated for the slope of implied vol in other currencies, (e.g., VDAX and NKY vol). It suggests that this is somewhat anomalous and instead there are other uses of VIX which may prove more fruitful for estimating risk premia more robustly.

Which is all well and good but what conclusions are you drawing as to the use of VIX instruments? Do the futures represent any sort of insurance for long equity holders of the S&P 500 stocks? Will the risk premium collected collapse? Hence will contango be reduced? What conclusions are you drawing here?

All I said is that the VIX slope may not be as predictive of risk premia as the author suggests. I never said anything about the risk premia collapsing, futures representing insurance for long equity holders etc. Contango be reduced....

The paper states if the vol is inverted, you aren't being well compensated for the risk, basically. Vix slope is meant to be an indicator. That's the primary focus and it uses VIX slope as an indicator for a wide range of risk premia (not just the vol risk premium).

My claim is the VDAX slope does not have similar predictive power over vol risk premia in Europe.

VIX inversions tend to coincide with a number of other things, like vol spiking upwards, vol at high levels, changes to risk reversals, etc. Each of these is a viable signal instead of the VIX slope alone as to whether one is being adequately compensated for Vol risk.

I never made conclusions about the use of VIX instruments. I never said anything about futures representing insurance or risk premia collapsing.

Inverted vix slopes do not occur frequently and tend to indicate the market is stressed. They are typically short lived.

I simply wanted clarification and thanks for giving it. Agreed on short-lived-ness of inversion. I have not looked at VDAX or Jap volatility but intended to do so - I assume whatever futures and options exists for German and Jap vol may not be as well traded as in the US which may account for the difference?

My questions surrounding the use of the VIX as regards the US markets were really more rhetorical than anything. I am amazed by the severity and over-reaction of the huge VIX spikes and keep meaning to look at the actual vol during those periods rather than the implied.

Just interested in the VIX in general. As for Contango lasting, god knows. Some contango must survive since otherwise why bother selling options, why take the risk of offering "insurance" to fund managers. But whether contango has been too generous since 2004 I really don't know.

All I'm saying is its a great trade and I hope it lasts!