Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Help coding "A Momentum Rotation Strategy for Trading VIX ETPs"

I came across this seemingly simple strategy that rotates 4 VIX ETPs: XIV, VXX, ZIV and VXZ. The idea is to go long on one of these depending on which one has the highest 83-day return. If they are all negative, then you stay in cash. I don't know how to code in Q, but was hoping someone might be able to draft a working algo that would work with Robinhood. I didn't see this particular strategy in the community so I thought I'd bring it up.

An advantage is that, as far as I can tell, no day trades would be performed so it would work well with smaller accounts. However because it analyzes entry/exit toward the end of the trading day, I thought it would make sense if it were automated as opposed to manually traded. Any help and feedback would be appreciated. Thank you Q community!

Here's the link for the strategy

Edit: added tags

14 responses

Would you ever keep money as cash or only in stocks?
Once you get the data you would use something like this:

xiv_lastprice = xiv[83]
vxx_lastprice = vxx[83]
ziv_lastprice = ziv[83]
xiv_current = xiv[0]
vxx_current = vxx[0]
ziv_current = ziv[0]
xiv return% = (xiv_current-xiv_last)/xiv_last <- repeat with all values

you if statements based on the 3 return % values you get

Hi Aamir,

I would be in cash if all 4 ETN returns were negative.

So I've made a general, non-working, framework for the algo. But I don't know the Py language enough to code the important stuff. Any help would be appreciated on how to code my missing info:

  1. Determine which of the four ETPs has the highest 83-day return.
  2. If already in the correct long ETP position, then do nothing.
  3. Any other code I'm missing in the following code.
'''
Strategy based on the paper Easy Volatility Investing from Double-Digit Numerics.

Link: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2255327

Rules: The four ETPs traded are XIV, VXX, ZIV, and VXZ. Go long at the close the ETP with the highest return over the last 83-days. If all 83-day returns are negative, move to cash. Hold until a new ETP signals a trade.  
'''

from quantopian.pipeline import Pipeline  
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline.factors import CustomFactor  
import numpy as np

def initialize(context):  
    schedule_function(enter_buy_sell_orders, date_rules.every_day(), time_rules.market_close(minutes = 30))  
    pipe = Pipeline()

#Calculate ETP returns  
def trade(context,data):  
    xiv = symbol('XIV')  
    vxx = symbol('VXX')  
    ziv = symbol('ZIV')  
    vxz = symbol('VXZ')  
    xiv_last = data.history(xiv[83])  
    xiv_current = data.history(xiv[0])  
    xiv_return = (xiv_current-xiv_last)/xiv_last 

    vxx_last = data.history(vxx[83])  
    vxx_current = data.history(vxx[0])  
    vxx_return = (vxx_current-vxx_last)/vxx_last  
    ziv_last = data.history(ziv[83])  
    ziv_current = data.history(ziv[0])  
    ziv_return = (ziv_current-ziv_last)/ziv_last  
    vxz_last = data.history(vxz[83])  
    vxz_current = data.history(vxz[0])  
    vxz_return = (vxz_current-vxz_last)/vxz_last  
# Determine which ETP has highest return  
'''
Don't know the code :) An array?  
'''

# If already in the ETP position, do nothing  
'''
Don't know the code :)  
'''

#If all ETP returns are negative, exit any open position  
    elif xiv_return<0 and vxx_return<0 and ziv_return<0 and vxz_return<0:  
        order_target_percent(xiv, 0.0)  
        order_target_percent(vxx, 0.0)  
        order_target_percent(ziv, 0.0)  
        order_target_percent(vxz, 0.0)  

you can try and see if their is a maximum function built into to quantopian

otherwise you'd need to construct i giant if statement
having it return what you want to buy.

As for the if already in position you don't really need anything. Since it will only change % returns based off

you may consider making a new def for max return where you pass it all the data you need.

I was thinking of using an array of the returns, then sorting them, then referencing the first (highest) one. But again, I don't know how to code it :)

This is probably a very simple solution, but in my code, the xiv_return is not being calculated:

def initialize(context):  
    context.assets = symbols('XIV','VXX','ZIV','VXZ')

#Calculate ETP returns  
def handle_data(context,data):  
    price_history = data.history(context.assets, "price", 90, "1d")  
    xiv_last = data.history(xiv[83])  
    xiv_current = data.history(xiv[0])  
    xiv_return = (xiv_current-xiv_last)/xiv_last  

I get a "Runtime exception: NameError: name 'xiv_return' is not defined"
How can I fix this?

Santiago,

Try something like this:

def initialize(context):  
    schedule_function(trade, date_rules.every_day(),time_rules.market_close(minutes= 1))

def trade(context, data):  
    assets =symbols( 'XIV', 'VXX', 'ZIV', 'VXZ' )  
    period = 83

    prices = data.history(assets, 'price', period + 1, '1d')  
    C = prices  
    R = C.iloc[-1] / C.iloc[0] -1.  # Returns  
    R = R.dropna()  
    R.sort_values(ascending = False,inplace=True)  
    print (R[0],R[1],R[2],R[3])  
    picks = R.head(1)  
    picks = picks[R>0]

    for asset in assets:  
        if data.can_trade(asset):  
            if asset in picks.index:  
                order_target_percent(asset, 1.)  
            else:  
                order_target(asset, 0)

Thanks Vladimir! This is a great start. I like the array you used to sort and choose the highest return asset. I did have a few questions about the code:

  1. What is the purpose of "period + 1"?
  2. I'm not too clear how the return is calculated? Could you please explain?
  3. What does the R.dropna() and the R.head mean?

Finally, I noticed in the backtest that multiple positions are held simultaneously. The intention was to only have one position any given day.

Also, how can I ensure that I sell a position before entering a new one? The transaction log shows it will purchase a second position before closing the first. Thanks!

Santiago,

What is the purpose of "period + 1"?
I'm not too clear how the return is calculated? Could you please explain?

1 day return = (price today - price yestarday)/ price yestarday
or price today/ price yesterday -1
Note: to get 1 day return you need 2 prices (price today and price yesterday)
same thing with period day return you need period+1 prices.

prices = data.history(assets, 'price', period + 1, '1d')  
C = prices  
R = C.iloc[-1] / C.iloc[0] -1.  # Returns  

To get best return we need to sort them

R.sort_values(ascending = False,inplace=True)  

and get the first one

picks = R.head(1)  

and that one should be positive

picks = picks[R>0]  

Also, how can I ensure that I sell a position before entering a new one? The transaction log shows it will purchase a second position before closing the first.

1.Lower initial capital to 5000 and give more time for trading:

schedule_function(trade, date_rules.every_day(),time_rules.market_close(minutes=30))  

2.If that will not help, disable default slippage model:

def initialize(context):  
    set_slippage(slippage.FixedSlippage(spread=0.00))  

I do not expect you will get reasonable results as some of the instruments are not liquid.

Ok, this makes more sense. So in my initial strategy I was looking for the best 83-day return. So how can I code the price from 83 days ago? The return would be calculated as:

(price of today - price 83 days ago)/price of 83 days ago

R = C.iloc[-1] / C.iloc[0] -1.  

is the same as

R =(C[-1]-C[-84])/C[-84] -1.   # 83 day return  

is the same as

R =(C[-1]-C[0])/C[0] -1.  # 83 day return  

if len(C) =84 (period+1)
and it is 83 day return

http://stockcharts.com/school/doku.php?st=roc&id=chart_school:technical_indicators:rate_of_change_roc_and_momentum

Yes, that makes sense now. Thanks Vladimir! I also added in the slippage code. It seems to help tremendously.

I'm not sure what's going on, but it doesn't seem to match the backtest of the initial article (see original post above). It starts out nicely, but then drops severely in mid-2014.

Also, is there a way to have two benchmarks? The SPY and another ticker?

Edit: corrected the severe drop date

Actually, now that I look at the original strategy, their backtest ends at the end of 2013...bummer.

The code may do well with more frequent data. Note that every test on that website is done with minute data and your test is with day data.