Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Trade XIV and VXX based on XIV and VXV ratio

I attempted to recreate this algorithm:
http://volatilitymadesimple.com/vix-mores-vixvxv-ratio/
with the use of this as a secondary resource
http://vixandmore.blogspot.com/2013/09/revisiting-vixvxv-ratio.html

The first article provided the bulk of the information along with decisions to invest vs to withdraw. I attempted to recreate the algorithm in the attached back test. I may have messed up somewhere in the algorithm because i'm not getting similar results to those that they included in their tests.

Did I make an error somewhere?

*not letting me attach the backtest

import pandas  
import pandas as pd  
VxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vxvdailyprices.csv'  
"""
Geting VIX price  
"""

#reference article http://volatilitymadesimple.com/vix-mores-vixvxv-ratio/  
#reference article2 http://vixandmore.blogspot.com/2013/09/revisiting-vixvxv-ratio.html  
def rename_col(df):  
    df = df.rename(columns={'CLOSE': 'price'})  
    df = df.rename(columns={'VIX Close': 'price'})  
    df = df.rename(columns={'Equity Put Volume': 'price'})  
    df = df.fillna(method='ffill')  
    df = df[['price', 'sid']]  
    # Correct look-ahead bias in mapping data to times  
    df = df.tshift(1, freq='b')  
    df['mean'] = pd.rolling_mean(df['price'], 2)  
    log.info(' \n %s ' % df.head())  
    return df  

"""
The initialize function sets any data or variables that  
you'll use in your algorithm.  
It's only called once at the beginning of your algorithm.  
"""
def initialize(context):  
    # set up XIV  
    context.XIV = sid(40516)  
    context.VXX = sid(38054)  
    context.UVXY = sid(41969)  

    #fetch VXV data  
    fetch_csv('https://www.quandl.com/api/v1/datasets/CBOEFE/INDEX_VXV.csv?trim_start=2012-01-01',  
        date_column='Date',  
        symbol='VXV',  
        post_func=rename_col,  
        date_format='%d-%m-%Y')  
    context.VXV = 'VXV'

    # fetch VIX data  
    fetch_csv('http://www.quandl.com/api/v1/datasets/CBOEFE/INDEX_VIX.csv?trim_start=2012-01-01',  
        date_column='Date',  
        symbol='VIX',  
        post_func=rename_col,  
        date_format='%d-%m-%Y')  
    context.VIX = 'VIX'  

    # Specify that we want the 'rebalance' method to run once a day  
    schedule_function(rebalance, date_rule=date_rules.every_day())

"""
Rebalance function scheduled to run once per day (at market open).  
"""
def rebalance(context, data):  
    # We get VIX's current price.  
    current_price_xiv = data.current(context.XIV, 'price')  
    current_price_vxv = data.current(context.VXV, 'price')  
    ratio = current_price_xiv/current_price_vxv #xiv/vxv  
    current_price_vxx = data.current(context.VXX, 'price')  
    """  
    invests on xiv and vxx depending on the ratio  
    """  
    # If XIV and VXX is currently listed on a major exchange  
    if data.can_trade(context.XIV) and data.can_trade(context.VXX):  
        #long xiv when ratio <0.92  
        if ratio < 0.92:  
            order_target_percent(context.XIV, 1)  
            order_target_percent(context.VXX, 0)  
            log.info("XIV: %s" % current_price_xiv)  
        #long vxx when ratio >1.08  
        if ratio > 1.08:  
            order_target_percent(context.XIV, 0)  
            order_target_percent(context.VXX, 1)  
            log.info("VXX: %s" % current_price_vxx)  
    record(ratio = ratio,  
           xiv = current_price_xiv,  
           vxv = current_price_vxv  
          )  
58 responses

Try this:

def rebalance(context, data):  
    current_price_vix = data.current(context.VIX, 'price')  
    current_price_vxv = data.current(context.VXV, 'price')  
    ratio = current_price_vix /current_price_vxv #vix/vxv  
    if all(data.can_trade([context.XIV,context.VXX])):  
        if ratio < 0.92:  
            order_target_percent(context.XIV, 1)  
            order_target_percent(context.VXX, 0)  
        elif ratio > 1.08:  
            order_target_percent(context.XIV, 0)  
            order_target_percent(context.VXX, 1)  
        else:  
            order_target(context.XIV, 0)  
            order_target(context.VXX, 0)  
    record(ratio = ratio,  
           vix = current_price_vix,  
           vxv = current_price_vxv  
          )  

I tried that set of code as well. I'm not seeing results like what the article claims. It wins pretty big from january to june 2016, but then proceeds to lose.
With that code I'm seeing ~-31% return, -3.33 beta, and a -66.4% drawdown.

Is this not a functional trading strategy?

Work it all out in a spreadsheet, far simpler. Spent ten days testing the strategy and now translating it to Python. Very simple indeed. You need to understand the underlying instruments - that is the key. Buy and hold XIV max DD 93%. Tricky game. Use futures contracts in a spreadsheet if you really want to understand what this is all about. Take XIV back to 2004 with futures.

I've ran it in an excel. It seems like it would work
This is a link to my excel to test:
https://www.dropbox.com/s/63sinkyoh33w9z2/buy%20and%20sell%20test.xlsx?dl=0

Aamir,
You are using XIV/VXV ratio.

 ratio = current_price_xiv/current_price_vxv #xiv/vxv  

In the original source they use VIX/VXV ratio.
Also check if you fetched vix,vxv data correctly.

I set it up that way because in the article they say:
"Bloggers VIX & More and Six Figure Investing have previously suggested only trading when the VIX:VXV ratio was sufficiently high or low, and remaining in cash when the ratio was middling. Both have suggested a ratio in the vicinity of 0.92/1.08 as better thresholds (i.e. long XIV when ratio < 0.92, long VXX when ratio > 1.08)."

@Vladimir thank you for catching that

This is the main logic in the code right now:

def rebalance(context, data):  
    # We get VIX's current price.  
    current_price_xiv = data.current(context.XIV, 'price')  
    current_price_vxv = data.current(context.VXV, 'price')  
    current_price_vix = data.current(context.VIX, 'price')  
    ratio = current_price_vix/current_price_vxv #xiv/vxv  
    #current_price_vxx = data.current(context.VXX, 'price')  
    """  
    invests on xiv and vxx depending on the ratio  
    """  
    if all(data.can_trade([context.XIV,context.VXX])):  
        if ratio < 0.92:  
            order_target_percent(context.XIV, 1)  
            order_target_percent(context.VXX, 0)  
        elif ratio > 1.08:  
            order_target_percent(context.XIV, 0)  
            order_target_percent(context.VXX, 1)  
        else:  
            order_target(context.XIV, 0)  
            order_target(context.VXX, 0)  
    record(ratio = ratio,  
           xiv = current_price_xiv,  
           vxv = current_price_vxv  
          )  

Backtest from 1/1/12 to current

Aamir,

Did you pay attention that ratio remain constant last year.
Check if you fetched vix,vxv data correctly.

Vladimir;

Thank you for pointing that out. I copied the fetching parts from other algorithms. I think it is correct. In my files that i downloaded from the links in the algorithm, it should be fetching and parsing them. If it is some error in the rename col function, I can't resolve that. I copied that from peter baker's file. It seems like its just undermining the first 244 lines of the file its fetching from. Do you have any idea why it might not be working?

I looked at some of the articles you linked and tried to apply the same data parsing code to the algorithm. Every time i try to implement the extra defined classes/tasks I end up with an error.

The components of the VIX/VXV pipeline critical thread i am adding to the algorithm is:

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

I am getting an error with re.sub components of that code.
I have also changed the vxv fetching to be from cboe instead of quandl.
I am however still getting some runtime errors

Here is a copy of my code:

import pandas  
import pandas as pd  
vxvurl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vxvdailyprices.csv'  
from quantopian.pipeline.data.quandl import cboe_vxv  
"""
Geting VIX price  
"""

#reference article http://volatilitymadesimple.com/vix-mores-vixvxv-ratio/  
#reference article2 http://vixandmore.blogspot.com/2013/09/revisiting-vixvxv-ratio.html  
def rename_col(df):  
    df = df.rename(columns={'CLOSE': 'price'})  
    df = df.rename(columns={'VIX Close': 'price'})  
    df = df.rename(columns={'Equity Put Volume': 'price'})  
    df = df.fillna(method='ffill')  
    df = df[['price', 'sid']]  
    # Correct look-ahead bias in mapping data to times  
    df = df.tshift(1, freq='b')  
    df['mean'] = pd.rolling_mean(df['price'], 2)  
    log.info(' \n %s ' % df.head())  
    return df  

"""
The initialize function sets any data or variables that  
you'll use in your algorithm.  
It's only called once at the beginning of your algorithm.  
"""
def initialize(context):  
    # set up XIV  
    context.XIV = sid(40516)  
    context.VXX = sid(38054)  
    fetch_csv(vxvurl,  
        date_column='Date',  
        symbol='VXV',  
        post_func=rename_col,  
        date_format='%m/%d/%y'  
             )  
    context.VXV = 'VXV'

    # fetch VIX data  
    fetch_csv('http://www.quandl.com/api/v1/datasets/CBOEFE/INDEX_VIX.csv?trim_start=2012-01-01',  
        date_column='Date',  
        symbol='VIX',  
        post_func=rename_col,  
        date_format='%d-%m-%Y')  
    context.VIX = 'VIX'  

    # Specify that we want the 'rebalance' method to run once a day  
    schedule_function(rebalance, date_rule=date_rules.every_day())

"""
Rebalance function scheduled to run once per day (at market open).  
"""
def rebalance(context, data):  
    # We get VIX's current price.  
    current_price_xiv = data.current(context.XIV, 'price')  
    current_price_vxv = data.current(context.VXV, 'price')  
    current_price_vix = data.current(context.VIX, 'price')  
    ratio = current_price_vix/current_price_vxv #xiv/vxv  
    #current_price_vxx = data.current(context.VXX, 'price')  
    """  
    invests on xiv and vxx depending on the ratio  
    """  
    if all(data.can_trade([context.XIV,context.VXX])):  
        if ratio < 0.92:  
            order_target_percent(context.XIV, 1)  
            order_target_percent(context.VXX, 0)  
        elif ratio > 1.08:  
            order_target_percent(context.XIV, 0)  
            order_target_percent(context.VXX, 1)  
        else:  
            order_target(context.XIV, 0)  
            order_target(context.VXX, 0)  
    record(ratio = ratio,  
           xiv = current_price_xiv,  
           vxv = current_price_vxv  
          )  


Hi Aamir,
I was getting an error when using the above code, so I think I fixed it here. I believe it was calling the incorrect VXV url.

Is there, or should there be, a sell order if the ratio approaches 1? Wouldn't this help lower the draw down?

@santiago

I looked at your back test, and it has the same problem that mine had. look closely at the custom data starting on jan 1 2016. You will notice that it no longer is querying vxv after that point so it never has an accurate ratio value after that date.

As for your second point:

f ratio < 0.92:  
            order_target_percent(context.XIV, 1)  
            order_target_percent(context.VXX, 0)  
        elif ratio > 1.08:  
            order_target_percent(context.XIV, 0)  
            order_target_percent(context.VXX, 1)  
        else:  
            order_target(context.XIV, 0)  
            order_target(context.VXX, 0)  

I am intending it to buy xiv if the ratio is < 0.92, and to buy vxx if the ratio is > 1.08. The original article that's commented at the top said not to hold any position if the ratio is not in those two areas. Effectively it should own no shares if the ratio is 0.92-1.08, hence the else order_target to be 0 for both of the two stocks. The triggering values can be adjusted up or down depending. It may be useful to change the trigger to 0.9 and 1.1 respectively, but I can't test that accurately due to the fact that It won't receive any data after jan 1, 2016 for vxv.

Instead of calling a url, can you use data.history for these sids?

I looked on here, and I couldn't find it.

So your original VXV url is here but it only goes until 12/31/15
But this one here has current price data. Would this work?

This might be a naive question, but why do you need to fetch the data if you're already getting the current price via data.current?

My other problem is that I don't know how to parse properly. I believe data.current will only get stock price for stocks that are built into to the quantopian universe, and since VXV VIX and other volatility indexes are not built in most people have to fetch data from a csv file.

Let me try to clear it up. data.current works with SID. VIX VXV and other volatility indexes don't by default have those through quantopian atm. Hence why almost all users take it from a csv file on another website.

I see. So, would the CSV that I linked to above with current VXV data work?

Pipeline version:

Yeah, that file would work but I don't know how to parse that link, or why it's not working.
i have changed the fetch portion of the code to:

fetch_csv(vxvurl,  
        date_column='Date',  
        symbol='VXV',  
        post_func=rename_col,  
        date_format='%m%d/%y')  
    context.VXV = 'VXV'  

where the vxv url is
vxvurl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vxvdailyprices.csv'

I'm having trouble in particular with the Date portion of the csv. If somebody could resolve that part it would be good to use / paper trade test.

@Valdimir do you think this strategy would work with minute/hourly data as well?
also if you look at the log with the pipeline version you linked, you'd get this error/warning on 12-3-2013, about your order failing. Do you know why that occurs.

I changed some of the trigger criteria in the following back test

Pipeline using now only daily data.

this is with a 0.90 and a 1.10 trigger

this is a 0.95 and a 1.08 trigger

How could Robinhood trading/testing be added to the algo?

I don't understand. It should be fine to trade through robinhood, though I wouldn't recommend it as of yet.

Agreed, however I thought when depolying an algo it needed specific code, especially for Robinhood. I just didn't know if that extra code would "break" the code in the algo for whatever reason. I'm new to Quantopian so my insights may be pretty naive :)

Quantopian and robinhood have a great agreement that says you don't need any specific commands to be run.

Does this algo take into account the expense ratios associated with the VXX and/or XIV?

Would adjusting this code to use tvix instead work?
@santiago, no it doesnt its a simple ratio

So I adjusted this to long SPY when the ratio is between 0.9 and 1.10.

What does it mean if the leverage is greater than 1?

Changed the ratio cutoffs to 0.95 and 1.05 to increase the returns even more without significantly increasing the DD.

I notice in the back tests that two positions are held at once. Can this be prevented using "slippage" so only one position is held at any one time?

I notices you added investing in the SPY . any reason why you implemented this?

On average the SPY increases over time. I reasoned that small increase would be better than staying in cash, which seems to be profitable as seen in the back tests.

50% shorts

0.90 1.10 triggers hold spy etf when conditions aren't met

0.90 1.10 triggers with 1/2 shorts and hedge against spy

ratio 0.92 1.08 1/2 short logic

The common practice of recording context.account.leverage misses 389 of the 390 minutes of the day.
Try https://www.quantopian.com/posts/max-intraday-leverage

How would you implement the code to not leverage any excess capital?

I'm not sure. I was just thinking for those of us who use Robinhood we cannot use leverage or short positions.

I just don't know the code to to keep only one position at a time.

Any suggestions on going long on an inverse instead of shorting?

You could trade that original version. but you wouldn't be acquiring more risk by shorting.

How would you implement the code to not leverage any excess capital?

Ideas to work with. This holds leverage to 1 (max intraday) while keeping the real returns (around 185%) mainly by scheduling a retry to open positions only after close is done. It is also helped some by reducing initial capital for fewer partial fills. You might want to try adding some similar securities to increase capital with less downside. I'd guess lots of room for improvement.
A mind-bender: Leverage is ~1 yet amount put at risk is still over 6 times initial cash (previous algo was over 9). That's from shorting I think.
Broker would have shorting restrictions and may charge fees, here they are unaccounted for.

Note although returns appear lower here than previous backtest, that is only because returns ignore margin, -3.5M in the previous code. See the top of the Source code (tab) for PvR which is apples-to-apples accommodating margin.

Could you elaborate more about why your version has a return of 12x whereas my version had 24x, and how that is affected by leverage/Loans?
Also given the current political climate of the US, do you think this set of code would continue to work for the coming months?
Do you have any suggestions regarding adjustments to the code?
If I was to implement this strategy using intraday data would you expect the beta to increase or decrease given the exposure time/duration held?

Based off of blues edits i wanted to change the ratio calculation to involve xiv and vxv instead of vix and vxv. This would allow me to easily fetch a more frequently updated csv file to quantopian, and would potentially be more profitable. Unfortunately I don't know about how the get ratio class would be changed with a fetched csv. I wanted to see if anybody would be able to help with it later on. Here is also the last backtest.

I was wondering if someone could help me understand this portion of code from the aforementioned algos? In noob language please :)

class Get_Ratio(CustomFactor):  
    inputs = [cboe_vix.vix_close, cboe_vxv.close]  
    window_length = 1  
    def compute(self, today, assets, out, vix, vxv):  
        out[:] = (vix[-1]/vxv[-1]) * np.ones(len(assets))      

def before_trading_start(context, data):  
    output = pipeline_output('vix_vxv_pipeline')  
    output = output.dropna()  
    context.ratio = output.loc[symbol('SPY')]['vix_vxv']  

Any explanation of the above portion of code would be helpful. Thanks!

Get_ratio outputs a array. for the two values divided by each other. Then it inserts -1 for yesterdays/todays values.
it then outputs that as a value.

Could you help me with this portion of the code:

class Get_Ratio_Current(CustomFactor):  
    inputs = [cboe_vix.vix_close, cboe_vxv.close]  
    window_length = 1  
    def compute(self, today, assets, out, vix, vxv):  
        out[:] = (vix[-1]/vxv[-1])# * np.ones(len(assets))

class Get_Ratio_Last(CustomFactor):  
    inputs = [cboe_vix.vix_close, cboe_vxv.close]  
    window_length = 2  
    def compute(self, today, assets, out, vix, vxv):  
        out[:] = (vix[-2]/vxv[-2])# * np.ones(len(assets))  

I need some help getting it to output todays ratio, and yesterday's ratio.

def open(context,data):  
    if context.bot_today: return    # Only once a day  
    if get_open_orders(): return    # Wait for close to finish  
    #xiv  
    xiv = symbol('XIV')  
    xiv_price = data.current(xiv,'price')  
    xiv_history = data.history(xiv,'price',2,'1d')  
    xiv_last = xiv_history[-1]

    #vxx  
    vxx  = symbol('VXX')  
    vxx_price = data.current(vxx ,'price')  
    '''  
    #vxv  
    vxv = symbol('VXV')  
    vxv_price = data.current(vxv, 'price')  
    #current ratio  
    #current_vix = Get_Ratio_Current(cboe_vix.vix_close[-1])  
    current_vxv = Get_Ratio_Current(cboe_vxv.close)  
    current_ratio = current_vix/current_vxv  
    print(current_vix)  
    print(current_vxv)  
    print(current_ratio)  
    '''  
    #calculating last ratio  
    last_ratio = Get_Ratio_Last()  
    #calculating current ratio  
    current_ratio = Get_Ratio_Current()  
    current_ratio_val = current_ratio

    #calculating % of each  
    total = xiv_price + vxx_price  
    xiv_percent = (xiv_price/total)  
    vxx_percent = (vxx_price/total)  

    record(current = current_ratio_val)  
    record(last = last_ratio)

'''
    #investing in stocks based on the % of prices  
    if context.ratio < 0.98 and current_ratio < last_ratio and all(data.can_trade([xiv,vxx])):  
        order_target_percent(xiv,   xiv_percent)  
        order_target_percent(vxx,  -vxx_percent)        

    record(Ratio = context.ratio*10000) #done to make charting of the ratio easier to read  
    context.bot_today = 1

    if context.ratio < 0.98 and all(data.can_trade([xiv,vxx])) and ratio1 > context.ratio:  
        order_target_percent(xiv,   xiv_percent)  
        order_target_percent(vxx,  -vxx_percent)

    elif context.ratio > 1.02 and all(data.can_trade([xiv,vxx])) and ratio1 < context.ratio:  
        order_target_percent(xiv,  -xiv_percent)  
        order_target_percent(vxx,   vxx_percent)

    # When ratio is between 0.9 and 1.10, long SPY  
    elif all(data.can_trade([xiv,vxx])):  
        order_target_percent(xiv,  0.0)  
        order_target_percent(vxx,  0.0)  
        order_target_percent(sid(8554) , 1)  
    record(Ratio = context.ratio*10000) #done to make charting of the ratio easier to read  
    context.bot_today = 1  
        '''  
class Get_Ratio_Current(CustomFactor):  
    inputs = [cboe_vix.vix_close, cboe_vxv.close]  
    window_length = 1  
    def compute(self, today, assets, out, vix, vxv):  
        out[:] = (vix[-1]/vxv[-1])# * np.ones(len(assets))

class Get_Ratio_Last(CustomFactor):  
    inputs = [cboe_vix.vix_close, cboe_vxv.close]  
    window_length = 2 #rekt  
    def compute(self, today, assets, out, vix, vxv):  
        out[:] = (vix[-2]/vxv[-2]) * np.ones(len(assets))  

My main logic portion

Finished the get_ratio_last, with 0 leverage.

Having trouble holding stocks, it enters and exists the positions it has every day:
http://i.imgur.com/PrB93DA.png

Did you figure out the issue yet? I'll try and figure out the problem within the next few days. Has anyone been paper or live trading this algo?

All of the versions on this thread trade far more than the original algo suggests. "VIX:VXV < .92 buy XIV hold till VIX:VXV > .92 then sell VIX and buy SPY" and so on.

Can some one please tell me why this algo is getting in and out almost every day. Even the original version. What am I missing?

@Bodhi Fly The original strategy was designed to only trade in those ranges, which works if you have a continually updating data set. Given that in quantopian you can only update the values 1x/day. In the last back test, i gave it exits and enters a position everyday (not as intended). I don't see how to change the code to prevent it from reallocating funds in and out every day Screen Shot. The other part is because i added some components to the original strategy (last ratio), to minimize drawdowns on muti-day losses, by allowing money to be moved around more rapidly.

@Nick Sulzer I have been testing my last copy of the code since Feb 22 (~1 month) if you look at this Screen Shot, between everyday the code goes in and out of the stock almost completely, and then readjusts for the next day. I'm not sure if that's just how Quantopian does the back tests, or if there is some error in my code

I have tried live trading this system and went bust. I think vixandmore have other criteria they are not sharing.