Updated algorithm: Buy XIV when VIX is above 20, sell XIV when VIX is below 12.

Is it a good trade in real market? On paper it looks great, and it's so simple. Any suggestions?

1512
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
import pandas
import pandas as pd

"""
Geting VIX price
"""
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
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 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 = data.current(context.VIX, 'price')

"""
if 'price' in data['VIX']:
vix_price = data['VIX'].price

if 'mean' in data['VIX']:
vix_mean = (data['VIX']['mean'])
"""
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price > 20):
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price < 12):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

There was a runtime error.
158 responses

@James Could u explain the line Correct look-ahead bias in mapping data to times

  df = df.tshift(1, freq='b')


That line I am not sure, but rename_col method is only for getting VIX everyday:

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


I just updated the algorithm and the return is amazing.

If you check my Transaction Details after you clone my code and run it, these transactions are very clear based on my simple algorithm, buy low and sell high on both XIV and UVXY.

Simple algorithm: Buy XIV and sell UVXY when VIX is above 20, sell XIV and buy UVXY when VIX is below 12.

I noticed that this algo suffers a failure to launch when testing in the current year.

10
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
import pandas
import pandas as pd

"""
Geting VIX price
"""
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
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 VIX data
fetch_csv('https://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 = data.current(context.VIX, 'price')

"""
if 'price' in data['VIX']:
vix_price = data['VIX'].price

if 'mean' in data['VIX']:
vix_mean = (data['VIX']['mean'])
"""
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price > 20):
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price < 12):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

There was a runtime error.

when I get VIX, sometimes it is not that accurate, so this year, it didn't get a VIX below 12, which is not true.

Now only trade with XIV, no UVXY.

1512
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
import pandas
import pandas as pd

"""
Geting VIX price
"""
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
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)
context.FB = sid(42950)

# 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'

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)
'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):
# We get VIX's current price.
current_price = data.current(context.VIX, 'price')

'''
if 'price' in data['VIX']:
vix_price = data['VIX'].price

if 'mean' in data['VIX']:
vix_mean = (data['VIX']['mean'])

# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price >= 18 and vix_mean >= current_price): #17.7
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.
if (current_price >= 19.8): #19.8
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
#order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))


There was a runtime error.

Notebook Analysis.

11
Notebook previews are currently unavailable.

http://seekingalpha.com/article/3892536-xiv-earns-value-hint-contango

My thinking is very simple: VIX stays around 12-14 most times, once in a while(2-3 times a year) it will go up to 20, 25, 30 or even higher but eventually it will come down to below 15. XIV is inverse of VIX, when VIX go up to 20, XIV will be at its low point, however XIV will not stay at its low very long, just like VIX will be not be that high very long, as VIX come down, XIV will go up.

So you just need to be patient and wait for VIX go up above 20 or more, then you wait until VIX stop going up and start to come down a bit, then you buy XIV, and hold until VIX drop below 13 or 14, then you sell it. This will work because XIV is not UVXY, it gains through time, so hold it for long term will actually profit for you. The same goes for shorting UVXY, because of Contango, but shorting is more risky so that's why I prefer to trade only XIV.

Interesting algo - thanks for sharing. The URL (http://www.quandl.com/api/v1/datasets/CBOEFE/INDEX_VIX.csv?trim_start=2012-01-01 ) seems to only pull VIX pricing info through 2015. How are the post-2015 prices calculated and compared?

I change to pipeline to pull VIX data, and now it works after 2015.

1512
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

"""
Geting VIX price
"""
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
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)

pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

# 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'

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)
'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):
# We get VIX's current price.
#current_price = data.current(context.VIX, 'price')
current_price = context.vix

'''
if 'price' in data['VIX']:
vix_price = data['VIX'].price

if 'mean' in data['VIX']:
vix_mean = (data['VIX']['mean'])

# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price >= 18 and vix_mean >= current_price): #17.7
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.
if (current_price >= 19.8): #19.8
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
#order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))


There was a runtime error.

Hi James, great algo. So you're still not using the UVXY, correct?

UVXY is too risky, only way to trade it is to short it.

Hi James, from a previous post you mentioned:

So you just need to be patient and wait for VIX go up above 20 or more, then you wait until VIX stop going up and start to come down a bit, then you buy XIV, and hold until VIX drop below 13 or 14, then you sell it.

So I'm wondering if your algo would have less drawdown if you waited for the VIX to cross the 20 mark "from above" before entering the XIV position. Otherwise you'll enter XIV at 20 for example, but if VIX is increasing, you'll be losing on the XIV position. I'm just not sure if that can be coded into the algo?

yes, I did try it, but the result somehow is a little worse than this. I think the reason is that when VIX spikes it will quickly drop again, the window to buy VIX is only a few days, so that few days will not cost us too much, but changing the algorithm might miss a few entry windows to buy.

http://investing.kuchita.com/2012/06/28/xiv-data-and-pricing-model-since-vix-futures-available-2004/

The site above has calculated XIV etc. prices going back to 2004. It would be a good idea to figure out how to
down load it and run your algorithm on it. XIV has not always been as profitable and there are huge
drops (8:1) in the price according to this site. The transitions seem to have become much sharper in the
last few years. VIX almost hit 60 in 2008, You would have been in trouble if you bought at 20.

So if someone wants to implement this would you suggest your latest algo?

Because XIV is being traded in this algo, and XIV is rebalanced everyday, does the algo account for beta slippage? In instances where the long XIV position is held for months, the slippage may start eating away at profit? Or is the slippage negligible?

XIV will drop while you are holding it, but you will not sell it until VIX go below 12, as you can see from my transaction records, every trade of XIV I made money, every single one of them, that means as long as you hold it and not sell, eventually you can make profit and sell at a profit when VIX reach below 12. This algorithm works!

So the slippage is negligible, that's what I thought with such high returns. This is cool James, congrats man! Are you going live with it soon?

I do notice I get this log when I run it in Q:

1969-12-31 19:00 WARN requests_csv.py:56: UserWarning: Quandl has deprecated unencrypted url functionality. Please format all urls using https://
1969-12-31 19:00 WARN <string>:29: FutureWarning: pd.rolling_mean is deprecated for Series and will be removed in a future version, replace with
Series.rolling(window=2,center=False).mean()


Any thoughts?

I switched to use pipeline to get VIX value, so there is no more deprecated functions. Updated algorithm attached.

1512
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

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

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]
context.vix_mean = context.output["vix_mean"].iloc[0]

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):
# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price >= 18 and vix_mean >= current_price): #17.7
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 40, we buy XIV. In case of a huge spike.
if (current_price >= 40):
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is above 30, we buy XIV. In case of a big spike.
elif (current_price >= 30):
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is above 20, we buy XIV. In case of a normal spike.
elif (current_price >= 19.8): #19.8
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is below 12, we sell XIV.
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
#order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))


There was a runtime error.

This is nice James! Do you trade with Robinhood?

I am not trading it live now, I will do it with IB soon.

Hey all, he is right UVXY is too risky, but TZA isn't. Check this out

26
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

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

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]
context.vix_mean = context.output["vix_mean"].iloc[0]

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):
# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price >= 18 and vix_mean >= current_price): #17.7
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 40, we buy XIV. In case of a huge spike.
if (current_price >= 40):
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is above 30, we buy XIV. In case of a big spike.
elif (current_price >= 30):
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is above 20, we buy XIV. In case of a normal spike.
elif (current_price >= 19.8): #19.8
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is below 12, we sell XIV.
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)

order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))


There was a runtime error.

Hey Guys,
Did a little more backtesting. Just be careful if trading live. It did not handle the 2011 crash very well. James, Are you putting in any other trading guards?

26
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

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

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]
context.vix_mean = context.output["vix_mean"].iloc[0]

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):
# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days
xiv_price = data.current(context.XIV, 'price')
tza_price = data.current(context.UVXY, 'price')

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price >= 18 and vix_mean >= current_price): #17.7
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 40, we buy XIV. In case of a huge spike.
if (current_price >= 40):
order_target_percent(context.XIV, 1)

order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is above 30, we buy XIV. In case of a big spike.
elif (current_price >= 30):
order_target_percent(context.XIV, 1)

order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is above 20, we buy XIV. In case of a normal spike.
elif (current_price >= 19.8): #19.8
order_target_percent(context.XIV, 1)

order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
# If VIX index is below 12, we sell XIV.
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)

log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))


There was a runtime error.

TZA? I didn't see it in the backtests.

Oh, I see it in the sid(). Sorry :) The UVXY threw me off. I imagine the beta slippage is considerably greater using a 3x leveraged ETF in TZA.

yeah, I was just lazy and threw it in there. Also, If you are using about 50% leverage for long only, you can get this puppy to almost 10000% if you discount the crash in 2011. say maybe order(context.VIX, int(10000/current_price-10)) ? just check it out. I' working on putting in trading guards for XIV because this doesn't catch the crash in 2011

I have made some improvements on the algorithm, and the return has been improved a lot!

1512
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

"""
The initialize function sets any data or variables that
It's only called once at the beginning of your algorithm.
"""
def initialize(context):
# set up XIV
context.XIV = sid(40516)
context.UVXY = sid(41969)
context.SPXS = sid(37083)
context.sell_price = 0
context.stop_price = 100
context.vix_last_price = 0;

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]
context.vix_mean = context.output["vix_mean"].iloc[0] # last 20 days average of VIX

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):
# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days
if (context.vix_last_price == 0):
context.vix_last_price = current_price # set our last trading VIX price

xiv_current = data.current(context.XIV, 'price')
price_history = data.history(context.XIV, "price", 20, "1d")
xiv_mean = price_history.mean() # get the last 20 days average of XIV
'''
# If XIV is currently listed on a major exchange
# If VIX index is above 20, we buy XIV and sell UVXY.
# If VIX index is below 12, we sell XIV and buy UVXY.

if (current_price >= 18 and vix_mean >= current_price): #17.7
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
elif (current_price <= 12.2): #12.2
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX: %s" % current_price)
log.info("Selling %s" % (context.XIV.symbol))

'''
# If XIV is currently listed on a major exchange
# If VIX index is above 59, we buy XIV.
if (current_price >= 59):
if (context.sell_price == 0):
context.sell_price = 12.8
context.stop_price = 63 # Set a stop price, if VIX goes above it, we sell
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
# sell XIV if VIX goes above the stop price we set last time
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
# If VIX index is above 55, we buy XIV.
elif (current_price >= 55):
if (context.sell_price == 0):
context.sell_price = 12.5
context.stop_price = 59
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
elif (current_price >= 51):
if (context.sell_price == 0):
context.sell_price = 12.5
context.stop_price = 55
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
elif (current_price >= 43):
if (context.sell_price == 0):
context.sell_price = 12.2
context.stop_price = 47
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
elif (current_price >= 39):
if (context.sell_price == 0):
context.sell_price = 12.2
context.stop_price = 43
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
elif (current_price >= 35):
if (context.sell_price == 0):
context.sell_price = 12.2
context.stop_price = 39
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
elif (current_price >= 31):
if (context.sell_price == 0):
context.sell_price = 12.2
context.stop_price = 35
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
elif (current_price >= 27):
if (context.sell_price == 0):
context.sell_price = 12.2
context.stop_price = 31
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
elif (current_price >= 23.6): # second buy price at 23.6
if (context.sell_price == 0):
context.sell_price = 12.2
context.stop_price = 27
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
# If VIX index is above 19.8, we buy XIV. In case of a normal spike.
elif (current_price >= 19.8):
if (context.sell_price == 0):
context.sell_price = 12.2 # Set a sell price at 12.2
context.stop_price = 23.6 # Set a stop price at 23.6
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100
# If VIX index is below sell price and xiv below xiv_20d_mean + 1.6, we sell XIV.
elif (current_price <= context.sell_price and xiv_current <= xiv_mean+1.6):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_mean_______________ %s" % vix_mean)
log.info("xiv_current____________ %s" % xiv_current)
log.info("xiv_mean_______________ %s" % xiv_mean)
context.sell_price = 0
context.stop_price = 100
context.vix_last_price = current_price

There was a runtime error.

Hey James, I've been following this thread and have spent quite a few hours playing around with your algo trying to increase the gains while decreasing the drawdown. Haven't had much luck thus far. Your latest update is blowing my mind!

Question for you. When I run the new algo YTD it buys nothing - the reason being that the current VIX price is below 19.80. But, when I run it from 01/01/16 thu yesterday, it's still holding XIV from November. Wondering your thoughts on this. XIV is up 32% so far YTD so it would be nice to capture these gains if you had gone live with this algo on the first of the year. If you were to start live trading this with capital say, tomorrow, would you just deploy it and sit tight and wait for the VIX to get back up to 19.80?

Safest way is to wait until VIX go up again and then buy XIV, but the algorithm bought it on Nov 2016 and hold it until now, so like you said, you can buy XIV now and hold, and wait until the algo say it's time to sell. I bought XIV a couple days ago in my real account and we will see how it goes.

James,

Referring to you latest algo with the mad gains. Do you think you are overfitting since it does not trade much?

All of my improvements are for one goal, which is to buy XIV at lowest and sell it at highest, if you play around with my algo, change a few parameters, you will find out the return will get a little worse, maybe 1/5 of what the return is now, but still very good!

Here is a list of parameters you can change:
1. elif (current_price <= context.sell_price and xiv_current <= xiv_mean+1.6) ------> change 1.6 to 2
2. elif (current_price >= 19.8) --------> change 19.8 to 20
3. context.stop_price = 23.6 and elif (current_price >= 23.6) ----------> change 23.6 to 24
4. price_history = data.history(context.XIV, "price", 20, "1d") --------------> change 20 to 25
5. pipe.add(SimpleMovingAverage(inputs=[cboe_vix.vix_open], window_length=20), 'vix_mean') ---------> change 20 to 25

These are the parameters I have been testing all the time, the ones that I am using now gave the best result, but if you spend some time and play around with them, you will find out that as long as you change those numbers within a certain range(say change 19.8 to any number between 19 and 20), the result are still going to be great.

What that means is the idea of this algorithm works, i select those specific numbers based on past VIX and XIV values, but I think the future results will still be good since all those numbers within a certain range gave a pretty good return, as long as you pick a number in the middle of those ranges, you will be getting good result. I tested all those numbers and their corresponding ranges, I picked the numbers in the middle of their ranges, therefore even if the future VIX and XIV are going to be different, these numbers will be able to handle it.

So test those parameters and find out what ranges these parameters are in will still give decent returns and pick the best numbers for yourself. Read my comments if you don't understand what those parameters are for.

I agree with both Nathan and James. I think there may be some overfitting, but in James's defence the algo's basic concepts seem valid. I created a version that should have less risk of overfitting. I deleted much of James's code as it looks like he was experimenting a fair bit so there was quite a bit of stuff in there that either wasn't being used, or wasn't contributing significantly to gains. There was also some stuff that created risk of overfitting. The gains aren't as high but that seems a reasonable tradeoff for an algo that should be overfit less. Good job though James, you win the award for most intriguing backtest given the sheer magnitude of the returns.

52
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
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor
from quantopian.pipeline.data.quandl import cboe_vix

def initialize(context):
context.XIV = symbol('XIV')
context.UVXY = symbol('UVXY')

pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

def rebalance(context, data):

xiv_current = data.current(context.XIV, 'price')
price_history = data.history(context.XIV, "price", 20, "1d")
xiv_mean = price_history.mean()

if (context.vix >= 19.8):
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)

elif (context.vix <= 12.2 and xiv_current <= xiv_mean+2):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)

record(Leverage=context.account.leverage)


There was a runtime error.

I believe I am right in saying you can user fetcher_csv to load your own data? That being the case my suggestion would be to create ersatz equity curves going back to 2004 using vix futures which approximate VXX and XIV. Upload these and see how these particular parameters performed since 2004.

Returns as spectacular, but the drawdowns are huge - I wouldn't be able to stomach 50% drawdowns.

Could we implement a stoploss some how?

Returns as spectacular, but the drawdowns are huge - I wouldn't be able to stomach 50% drawdowns.
Could we implement a stoploss some how?

According to my backtesting the system described in this thread (in particular the last implementation by Warren Harding above) is a horrendous disaster when back tested against XIV/VXX simulated prices since 2004.

The VIX spiked up to 80 in late 2008 and this system as drafted was short from 19.80. The drawdown for the system was 98%and it did not recover until late 2013. CAGR was 37% for the period and standard deviation (annualised) of daily returns was 75%.

Anthony can you publish your result here?

Correct me if I'm wrong, but the Algo that James posted would switch over to UVXY once the VIX hits 63:

# If VIX index is above 59, we buy XIV.
if (current_price >= 59):
if (context.sell_price == 0):
context.sell_price = 12.8
context.stop_price = 63 # Set a stop price, if VIX goes above it, we sell
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("vix_last_price %s" % context.vix_last_price)
# sell XIV if VIX goes above the stop price we set last time
elif (current_price >= context.stop_price):
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_stop_price_________ %s" % context.stop_price)
log.info("xiv_current____________ %s" % xiv_current)
context.sell_price = 0
context.stop_price = 100

Thanks for pointing that out to people Anthony.

1). This algo is not for the faint of heart in the first place.

2). My draft does not include the necessary guardrails, it's just a quick simple draft not a finished trading system.

3). I still think there is potential here and I am going to attempt to install some guardrails.

I think VIX is a very good short trade. I am basing my trading at IB on a switch between Contango and a level of backwardation. That does away with guessing "ranges". In my view you are most unlikely to be able to contain DD while maintaining CAGR at very high levels.

If I actually make money on this one I will aim to rebalance periodically between the system and cash. So, a 50/50 rebalancing halves CAGR and DD. Needless to say it seems a good idea to trade very small relative to your net worth!

That way horrible DD's should be almost bearable.

Sorry I can't post the backtest - I have not uploaded my futures data. I have done the tests in my own python backtester locally.

In back testing CAGR 98% since 2004, max dd 76%. Using the front month only. XIV/VXX use a sliding combination of front and second.

Its a handsome return even if you allocate 30/70 in favour of cash. What happens going forward? No idea! You can bet it will be nothing like as good as that. Still, here's hoping.

Out of interest you never, ever see that sort of MAR over the long term in CTAs. CAGR/Max DD

And no doubt you won't see such a good MAR with this thing either over the next decade. XIV will go bust if the spike is big enough. Hence the importance of rebalancing just in case of disaster.

Anthony I've seen posts here and elsewhere that says if there is a movement large enough for XIV to be shutdown then there will be bigger issues than losing money. I'm not 100% sure, but what I was referring to relates the only crash large enough to do that of no recover was the 1929 crash.

Warren, I also see there are moments where leverage goes above 1. Do you know of a way to limit that so it could be a set it and forget it algo?

Sure, here's a version that rebalances more often to keep the leverage steady.

49
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
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor
from quantopian.pipeline.data.quandl import cboe_vix

def initialize(context):
context.XIV = symbol('XIV')
context.UVXY = symbol('UVXY')
context.HoldXIVMode=True

pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

def rebalance(context, data):

xiv_current = data.current(context.XIV, 'price')
price_history = data.history(context.XIV, "price", 20, "1d")
xiv_mean = price_history.mean()

if (context.vix >= 19.8):
context.holdXIVMode=True
elif (context.vix <= 12.2 and xiv_current <= xiv_mean+2):
context.holdXIVMode=False

if context.holdXIVMode:
order_target_percent(context.XIV, 0.98)
order_target_percent(context.UVXY, 0)
else:
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 0.98)

record(Leverage=context.account.leverage)


There was a runtime error.

Looks ok. It turns $10k into$3M. I thought with apparent returns like that I was going to have to tell you your intraday leverage hits the sky. Only 1.31. This is that code with PvR added. I turned on PnL because there was room since there were no shorts (on by default) to worry about (in record). PvR has max leverage built-in, or for minimal intraday leverage code, use this. [This backtest starts earlier and runs a few extra days]

20
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
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor
from quantopian.pipeline.data.quandl import cboe_vix

def initialize(context):
context.XIV = symbol('XIV')
context.UVXY = symbol('UVXY')
context.HoldXIVMode=True

pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

def rebalance(context, data):

xiv_current = data.current(context.XIV, 'price')
price_history = data.history(context.XIV, "price", 20, "1d")
xiv_mean = price_history.mean()

if (context.vix >= 19.8):
context.holdXIVMode=True
elif (context.vix <= 12.2 and xiv_current <= xiv_mean+2):
context.holdXIVMode=False

if context.holdXIVMode:
order_target_percent(context.XIV, 0.98)
order_target_percent(context.UVXY, 0)
else:
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 0.98)

#record(Leverage=context.account.leverage)

def pvr(context, data):
''' Custom chart and/or log of profit_vs_risk returns and related information
'''
# # # # # # # # # #  Options  # # # # # # # # # #
record_pvr      = 0            # Profit vs Risk returns (percentage)
record_pvrp     = 1            # PvR (p)roportional neg cash vs portfolio value
record_cash     = 1            # Cash available
record_max_lvrg = 1            # Maximum leverage encountered
record_risk_hi  = 1            # Highest risk overall
record_shorting = 0            # Total value of any shorts
record_cash_low = 0            # Any new lowest cash level
record_q_return = 0            # Quantopian returns (percentage)
record_pnl      = 1            # Profit-n-Loss
record_risk     = 0            # Risked, max cash spent or shorts beyond longs+cash
record_leverage = 0            # Leverage (context.account.leverage)
record_overshrt = 0            # Shorts beyond longs+cash
logging         = 0            # Also to logging window conditionally (1) or not (0)
if record_pvrp: record_pvr = 0 # if pvrp is active, straight pvr is off

import time
from datetime import datetime
from pytz import timezone   # Python will only do once, makes this portable.
#   Move to top of algo for better efficiency.
c = context  # Brevity is the soul of wit -- Shakespeare [for efficiency, readability]
if 'pvr' not in c:
date_strt = get_environment('start').date()
date_end  = get_environment('end').date()
cash_low  = c.portfolio.starting_cash
c.pvr = {
'pvr'        : 0,      # Profit vs Risk returns based on maximum spent
'max_lvrg'   : 0,
'risk_hi'    : 0,
'days'       : 0.0,
'date_prv'   : '',
'date_end'   : date_end,
'cash_low'   : cash_low,
'cash'       : cash_low,
'start'      : cash_low,
'begin'      : time.time(),  # For run time
'log_summary': 126,          # Summary every x days
'run_str'    : '{} to {}  ${} {} US/Eastern'.format(date_strt, date_end, int(cash_low), datetime.now(timezone('US/Eastern')).strftime("%Y-%m-%d %H:%M")) } log.info(c.pvr['run_str']) def _pvr_(c): ptype = 'PvR' if record_pvr else 'PvRp' log.info('{} {} %/day {}'.format(ptype, '%.4f' % (c.pvr['pvr'] / c.pvr['days']), c.pvr['run_str'])) log.info(' Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % (c.portfolio.portfolio_value - c.pvr['start']), '%.0f' % c.pvr['risk_hi'], '%.1f' % c.pvr['pvr'])) log.info(' QRet {} PvR {} CshLw {} MxLv {} RskHi {} Shrts {}'.format('%.2f' % q_rtrn, '%.2f' % c.pvr['pvr'], '%.0f' % c.pvr['cash_low'], '%.2f' % c.pvr['max_lvrg'], '%.0f' % c.pvr['risk_hi'], '%.0f' % shorts)) def _minut(): # To preface each line with the minute of the day. dt = get_datetime().astimezone(timezone('US/Eastern')) minute = (dt.hour * 60) + dt.minute - 570 # (-570 = 9:31a) return str(minute).rjust(3) date = get_datetime().date() if c.pvr['date_prv'] != date: c.pvr['days'] += 1.0 do_summary = 0 if c.pvr['log_summary'] and c.pvr['days'] % c.pvr['log_summary'] == 0 and _minut() == '100': do_summary = 1 # Log summary every x days c.pvr['date_prv'] = date # next line for speed if c.pvr['cash'] == c.portfolio.cash and not do_summary and date != c.pvr['date_end']: return c.pvr['cash'] = c.portfolio.cash longs = 0 # Longs value shorts = 0 # Shorts value overshorts = 0 # Shorts value beyond longs plus cash new_cash_low = 0 # To trigger logging in cash_low case new_risk_hi = 0 q_rtrn = 100 * (c.portfolio.portfolio_value - c.pvr['start']) / c.pvr['start'] cash = c.portfolio.cash cash_dip = int(max(0, c.pvr['start'] - cash)) if record_pvrp and cash < 0: # Let negative cash ding less when portfolio is up. cash_dip = int(max(0, c.pvr['start'] - cash * c.pvr['start'] / c.portfolio.portfolio_value)) # Imagine: Start with 10, grows to 1000, goes negative to -10, shud not be 200% risk. if int(cash) < c.pvr['cash_low']: # New cash low new_cash_low = 1 c.pvr['cash_low'] = int(cash) if record_cash_low: record(CashLow = int(c.pvr['cash_low'])) # Lowest cash level hit if record_max_lvrg: if c.account.leverage > c.pvr['max_lvrg']: c.pvr['max_lvrg'] = c.account.leverage record(MaxLv = c.pvr['max_lvrg']) # Maximum leverage #log.info('Max Lvrg {}'.format('%.2f' % c.pvr['max_lvrg'])) for p in c.portfolio.positions: if not data.can_trade(p): continue shrs = c.portfolio.positions[p].amount if shrs < 0: shorts += int(abs(shrs * data.current(p, 'price'))) elif shrs > 0: longs += int( shrs * data.current(p, 'price')) if shorts > longs + cash: overshorts = shorts # Shorts when too high if record_overshrt: record(OvrShrt = overshorts) # Shorts beyond payable if record_shorting: record(Shorts = shorts) # Shorts value as a positve if record_leverage: record(Lvrg = c.account.leverage) # Leverage if record_cash: record(Cash = int(cash)) # Cash risk = int(max(cash_dip, shorts)) if record_risk: record(Risk = risk) # Amount in play, maximum of shorts or cash used if risk > c.pvr['risk_hi']: c.pvr['risk_hi'] = risk new_risk_hi = 1 if record_risk_hi: record(RiskHi = c.pvr['risk_hi']) # Highest risk overall if record_pnl: # "Profit and Loss" in dollars record(PnL = min(0, c.pvr['cash_low']) + context.portfolio.pnl ) if record_pvr or record_pvrp: # Profit_vs_Risk returns based on max amount actually spent (risk high) if c.pvr['risk_hi'] != 0: # Avoid zero-divide c.pvr['pvr'] = 100 * (c.portfolio.portfolio_value - c.pvr['start']) / c.pvr['risk_hi'] ptype = 'PvRp' if record_pvrp else 'PvR' record(**{ptype: c.pvr['pvr']}) if record_q_return: record(QRet = q_rtrn) # Quantopian returns to compare to pvr returns curve if logging: if new_risk_hi or new_cash_low: qret = ' QRet ' + '%.1f' % q_rtrn lv = ' Lv ' + '%.1f' % c.account.leverage if record_leverage else '' pvr = ' PvR ' + '%.1f' % c.pvr['pvr'] if record_pvr else '' pnl = ' PnL ' + '%.0f' % c.portfolio.pnl if record_pnl else '' csh = ' Cash ' + '%.0f' % cash if record_cash else '' shrt = ' Shrt ' + '%.0f' % shorts if record_shorting else '' ovrshrt = ' Shrt ' + '%.0f' % overshorts if record_overshrt else '' risk = ' Risk ' + '%.0f' % risk if record_risk else '' mxlv = ' MaxLv ' + '%.2f' % c.pvr['max_lvrg'] if record_max_lvrg else '' csh_lw = ' CshLw ' + '%.0f' % c.pvr['cash_low'] if record_cash_low else '' rsk_hi = ' RskHi ' + '%.0f' % c.pvr['risk_hi'] if record_risk_hi else '' log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(), lv, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, ovrshrt, risk, rsk_hi)) if do_summary: _pvr_(c) if date == c.pvr['date_end']: # Summary on last day once. if 'pvr_summary_done' not in c: c.pvr_summary_done = 0 if not c.pvr_summary_done: _pvr_(c) elapsed = (time.time() - c.pvr['begin']) / 60 # minutes log.info( '\nRuntime {} hr {} min End: {} {}'.format( int(elapsed / 60), '%.1f' % (elapsed % 60), datetime.now(timezone('US/Eastern')).strftime("%Y-%m-%d %H:%M"), 'US/Eastern')) c.pvr_summary_done = 1 def handle_data(context, data): pvr(context, data)  There was a runtime error. Anthony I've seen posts here and elsewhere that says if there is a movement large enough for XIV to be shutdown then there will be bigger issues than losing money. I'm not 100% sure, but what I was referring to relates the only crash large enough to do that of no recover was the 1929 crash. Always best to do the back testing oneself. In 2008/2009 buy and hold of the XIV would have lost 93% or more of its value - I used futures contracts, front month only, to replicate buy and hold performance since 2004. During that crash VIX rose to 80 and the S&P 500 lost 55% at its worst. In 1929 and following at its worst the US stock market was down 90% ish. So, if XIV can lose 93% with the VIX at 80 and the S&P 500 down 55%...it does not have to get an awful lot worse for XIV to go bust. But look, here is what people so often miss. Almost everyone on these sort of forums is trying to shoot the lights out. To grow rich and retire on the proceeds of speculation. And nothing wrong with that. I believe the mistake they usually make however is that they assume huge returns will come with modest volatility and drawdown - I do not believe that to be the case. Even the great Medallion Fund is a pretty hairy ride. The real problem for people with less experience is that they invent rules and change parameters so that in back testing their goal is achieved - high return for low vol and dd. In my own experience at least, this does not come to pass. I have experienced the horrors of curve fitting at first hand. I have been guilty of leading myself up this naive garden path in the past. Instead of cooking the books, in my view one is better off doing one of two things. Or a combination of both. Above I quoted a backtest yielding a CAGR of 98% for a mad DD of 76%. Such impressive results may well not come to pass, but since the tests were based on one simple parameter, provided option sellers continue to demand a reasonable premium for selling call options the VIX, I am working on the assumption that the trade will not entirely fail. That being the case if you can't survive a 76% or worse drawdown (who can?) you have two reasonable options. Trade for peanuts - maybe that is$10,00 in my case, or $100,000 in your case. If you compound, your money grows 30 times in 5 years (in your dreams!). At that stage 76% drawdowns get very destabilising so take some money off the table. Or look at it another way. Take your entire trading capital and reckon you will devote 10% to this high risk scheme. Leave the rest in a bunch of low risk, short dated bond funds. Every month, quarter or year rebalance to maintain the 10/90 split. Over the long term you would achieve 10 or 11% on your capital for single digit volatility drawdown and single digit volatility. In short my own feeling is that it is misguided to twist the parameters and add rules. Accept such a scheme for what it is (horribly risky and volatile) and trade small. Not many people will care for my suggestion. You make the real money in a number of different ways. If VIX is flattish for a period you will benefit from contango on the short. In a spike up you aim to benefit from going long the vix. After the spike huge profits were made in 2009 going short when vix had hit 80. The algo in this thread fails on all counts other than when VIX trades within a range. It is a disaster in a huge spike. People will only be able to see this if they back test back through a period which includes a vast spike such as that in 2008/2009. True in the long term VIX has traded within a fairly predictable range but you will lose your pants in the odd Black Swan event. People have assumed they can not test back beyond when the ETFs started trading. Incorrect: they can use the futures contracts back to 2004. Having traded futures for years it was simple for me. Not so simple if you have not spent time thinking about the concatenation of futures contracts and how to do it. Nonetheless not to do so is to make a grave error. As can be seen here. 1. XIV go up when VIX is flat 2. It will lose money when a big crisis happen(vix hit above 60), once in every 10 years? In other words, VIX will be in a 10 - 25 range most of the time. This algorithm works most times, but when there is a fundamental flaw in our economy, a real crisis with a real cause happens, you get out of this algorithm, and wait until the crisis pass. Until VIX fall back into that range again, you can continue trade with the algo. a real crisis with a real cause happens, you get out of this algorithm, and wait until the crisis pass Didn't look at your algo James only Warren's. And that ain't what Warren's does. Warren's is short right the way up the spike. Maybe yours is different? Or are you relying on manual intervention to stop trading? Manually stop it, my algo is not good enough to capitalize a huge spike in VIX. "For traders who short volatility with multi-day long positions in XIV, they would be wise to instead use short positions in VXX." Got it from this article: http://seekingalpha.com/article/4040136-long-term-price-action-xiv-just-contango Ah, understood. Manually stop it, my algo is not good enough to capitalize a huge spike in VIX. Maybe no one's algo is! Certainly not always and in all circumstances anyway. Warren, I tried to do a backtest with your version and it had an error with holdXIVmode on recent time spans. Here's a bug fix. 49 Loading... 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 from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor from quantopian.pipeline.data.quandl import cboe_vix def initialize(context): context.XIV = symbol('XIV') context.UVXY = symbol('UVXY') context.holdXIVMode=True pipe = Pipeline() attach_pipeline(pipe, 'my_pipeline') pipe.add(GetVIX(inputs=[cboe_vix.vix_open]), 'VixOpen') schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=1) ) def before_trading_start(context,data): context.output = pipeline_output('my_pipeline') context.vix = context.output["VixOpen"].iloc[0] class GetVIX(CustomFactor): window_length = 1 def compute(self, today, assets, out, vix): out[:] = vix[-1] def rebalance(context, data): xiv_current = data.current(context.XIV, 'price') price_history = data.history(context.XIV, "price", 20, "1d") xiv_mean = price_history.mean() if data.can_trade(context.XIV): if (context.vix >= 19.8): context.holdXIVMode=True elif (context.vix <= 12.2 and xiv_current <= xiv_mean+2): context.holdXIVMode=False if context.holdXIVMode: order_target_percent(context.XIV, 0.98) order_target_percent(context.UVXY, 0) else: order_target_percent(context.XIV, 0) order_target_percent(context.UVXY, 0.98) record(Leverage=context.account.leverage)  There was a runtime error. Has anyone tried live trading this algo on either IB or Robinhood? Also any thoughts on this futurewarning? "1969-12-31 19:00 WARN requests_csv.py:56: UserWarning: Quandl has deprecated unencrypted url functionality. Please format all urls using https:// 1969-12-31 19:00 WARN :29: FutureWarning: pd.rolling_mean is deprecated for Series and will be removed in a future version, replace with Series.rolling(window=2,center=False).mean() " I changed the algorithm to only trade XIV again and removed a lot of risky over fitting stuff. You can try to test in any time frame within last 5 years, every single trade this algorithm made is a profitable trade. Check the Transaction Details, you will see how much each trade makes, some make 20%, some 50%, some even 200%, but the most important thing is that it never lose money! You just need to be patient, sometimes one trade will take a year or longer, but it will always turn into a profitable trade in the end. 1512 Loading... 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 import pandas import pandas as pd import datetime import numpy as np import talib from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor, Latest from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix from quantopian.pipeline.factors import SimpleMovingAverage class GetVIX(CustomFactor): window_length = 1 def compute(self, today, assets, out, vix): out[:] = vix[-1] """ 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.UVXY = sid(41969) context.SPXS = sid(37083) context.sell_price = 0 context.vix_last_price = 0 # fetch VIX data pipe = Pipeline() attach_pipeline(pipe, 'my_pipeline') #get VIX at market open pipe.add(GetVIX(inputs=[cboe_vix.vix_open]), 'VixOpen') #get VIX average in the last 2 days pipe.add(SimpleMovingAverage(inputs=[cboe_vix.vix_open], window_length=20), 'vix_mean') set_slippage(slippage.FixedSlippage(spread=.02)) # add a slippage set_commission(commission.PerShare(cost=0.0135, min_trade_cost=1)) # add a commission schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=1) ) ''' # total minutes in a trading day total_minutes = 6*60 + 30 for i in range(1, total_minutes): # Every 30 minutes run schedule if i % 30 == 0: # This will start at 9:31AM and will run every 30 minutes # Specify that we want the 'rebalance' method to run schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=i), True ) ''' def before_trading_start(context,data): context.output = pipeline_output('my_pipeline') context.vix = context.output["VixOpen"].iloc[0] context.vix_mean = context.output["vix_mean"].iloc[0] # last 20 days average of VIX """ Rebalance function scheduled to run once per day (at market open). """ def rebalance(context, data): # We get VIX's current price. current_price = context.vix # vix current value at market open vix_mean = context.vix_mean # vix mean value of last 2 days if (context.vix_last_price == 0): context.vix_last_price = current_price # set our last trading VIX price xiv_current = data.current(context.XIV, 'price') price_history = data.history(context.XIV, "price", 20, "1d") xiv_mean = price_history.mean() # get the last 20 days average of XIV # If XIV is currently listed on a major exchange if data.can_trade(context.XIV): # If VIX index is above 59, we buy XIV. if (current_price >= 59): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 55): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 51): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 47): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 43): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 39): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 35): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 31): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 27): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 23.6): # second buy price at 23.6 if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) # If VIX index is above 19.8, we buy XIV. In case of a normal spike. elif (current_price >= 19.8): if (context.sell_price == 0): context.sell_price = 12.2 # Set a sell price at 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) # If VIX index is below sell price and xiv below xiv_20d_mean + 1.6, we sell XIV. elif (current_price <= context.sell_price and xiv_current <= xiv_mean+1.6): # Sell all of our shares by setting the target position to zero order_target_percent(context.XIV, 0) #order_target_percent(context.UVXY, 1) log.info("VIX_current____________ %s" % current_price) log.info("VIX_mean_______________ %s" % vix_mean) log.info("xiv_current____________ %s" % xiv_current) log.info("xiv_mean_______________ %s" % xiv_mean) context.sell_price = 0 context.vix_last_price = current_price record(Leverage=context.account.leverage)  There was a runtime error. and make sure you have a nice stiff drink when you hit that 50% drawdown or 99% during 08' Reading the ETF prospectus, Velocityshares strongly recomends to don't hold XIV for long periods. Here's a copy/paste of "Long holding period risk" section : ...The ETNs are only suitable for a very short investment horizon. The relationship between the level of the VIX Index and the underlying futures on the VIX Index will begin to break down as the length of an investorâ€™s holding period increases... ...The ETNs are not long term substitutes for long or short positions in the futures underlying the VIX Index... ...The long term expected value of your ETNs is zero. If you hold your ETNs as a long term investment, it is likely that you will lose all or a substantial portion of your investment... XIV is the inverse of VIX, it's one of the few VIX ETNs that you can actually hold for long and not going to zero in the end. Go check what XIV does and how it defers from other VIX ETFs and ETNs. I will be very thankful if you can prove more details. I just checked with the official information provided by the issuer, and it does not say that. In summary it says "The ETNs ...may not be suitable for investors who plan to hold them for longer than one day..." The issuer says the long term expected value is zero. I don't know what other information could be better. Just find "long term" in the official prospectus. If you have more info, it will be more than welcome. XIV will go up in a calm, less volatile bull market(1990-1997, 2002-2008, 2011-2017, 6-7 out of every 10 years maybe?). XIV will go down in a more volatile bear market(1997 - 2002, 2008 - 2011). That's how XIV works, and in the long term it will grow if we are at a not-so-volatile bull market, like the one we are in right now. Hi James, I like the algorithm. I agree with you that the VIX will have its black swan moment and spike up during a crisis but that will be a rare once-in-a-decade event which will require manual intervention. Other than that, this algo seems to work great the majority of time. Is the latest version of the algorithm tradable live? The trouble with this algorithm is the hard coded 1.6. This needs to be changed into the standard deviation for XIV. Other than that, like the algorithm. I am not sure, you probably need to change a few things depend on what your broker is, IB or robinhood. Tried to use the standard deviation instead of 1.6, thoughts? 28 Loading... 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 import pandas import pandas as pd import datetime import numpy as np import talib from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor, Latest from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix from quantopian.pipeline.factors import SimpleMovingAverage class GetVIX(CustomFactor): window_length = 1 def compute(self, today, assets, out, vix): out[:] = vix[-1] """ 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.UVXY = sid(41969) context.SPXS = sid(37083) context.sell_price = 0 context.vix_last_price = 0 # fetch VIX data pipe = Pipeline() attach_pipeline(pipe, 'my_pipeline') #get VIX at market open pipe.add(GetVIX(inputs=[cboe_vix.vix_open]), 'VixOpen') #get VIX average in the last 2 days pipe.add(SimpleMovingAverage(inputs=[cboe_vix.vix_open], window_length=20), 'vix_mean') set_slippage(slippage.FixedSlippage(spread=.02)) # add a slippage set_commission(commission.PerShare(cost=0.0135, min_trade_cost=1)) # add a commission schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=1) ) ''' # total minutes in a trading day total_minutes = 6*60 + 30 for i in range(1, total_minutes): # Every 30 minutes run schedule if i % 30 == 0: # This will start at 9:31AM and will run every 30 minutes # Specify that we want the 'rebalance' method to run schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=i), True ) ''' def before_trading_start(context,data): context.output = pipeline_output('my_pipeline') context.vix = context.output["VixOpen"].iloc[0] context.vix_mean = context.output["vix_mean"].iloc[0] # last 20 days average of VIX """ Rebalance function scheduled to run once per day (at market open). """ def rebalance(context, data): # We get VIX's current price. current_price = context.vix # vix current value at market open vix_mean = context.vix_mean # vix mean value of last 2 days if (context.vix_last_price == 0): context.vix_last_price = current_price # set our last trading VIX price xiv_current = data.current(context.XIV, 'price') price_history = data.history(context.XIV, "price", 20, "1d") xiv_mean = price_history.mean() # get the last 20 days average of XIV xiv_std = price_history.std() # get the standard deviation of XIV # If XIV is currently listed on a major exchange if data.can_trade(context.XIV): # If VIX index is above 59, we buy XIV. if (current_price >= 59): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 55): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 51): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 47): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 43): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 39): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 35): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 31): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 27): if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 23.6): # second buy price at 23.6 if (context.sell_price == 0): context.sell_price = 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) # If VIX index is above 19.8, we buy XIV. In case of a normal spike. elif (current_price >= 19.8): if (context.sell_price == 0): context.sell_price = 12.2 # Set a sell price at 12.2 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) # If VIX index is below sell price and xiv below xiv_20d_mean + standard deviation, we sell XIV. elif (current_price <= context.sell_price and xiv_current <= xiv_mean+xiv_std): # Sell all of our shares by setting the target position to zero order_target_percent(context.XIV, 0) #order_target_percent(context.UVXY, 1) log.info("VIX_current____________ %s" % current_price) log.info("VIX_mean_______________ %s" % vix_mean) log.info("xiv_current____________ %s" % xiv_current) log.info("xiv_mean_______________ %s" % xiv_mean) context.sell_price = 0 context.vix_last_price = current_price record(Leverage=context.account.leverage)  There was a runtime error. I think this is more reasonable. I did something a little different. Firstly, I am benchmarking against XIV since it seems to me we need to beat a buy and hold strategy. I then looked at two market modes - one normal and one during a collapse. This strategy goes to cash when vix is about 25 to try avoid crashes. In normal trading conditions it holds a percentage in XIV based on the level of VIX and the rest in cash. This feels a little less like overfitting to me than putting in specific values that are not likely to be repeated. This seems to beat XIV in the long term - and clearly beats the market by a large percentage. Any suggestions for improvements would be greatly appreciated. 10 Loading... 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 from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor from quantopian.pipeline.data.quandl import cboe_vix def initialize(context): set_benchmark(symbol('XIV')) context.XIV = symbol('XIV') context.TLT = symbol('TLT') context.HoldXIVMode=True pipe = Pipeline() attach_pipeline(pipe, 'my_pipeline') pipe.add(GetVIX(inputs=[cboe_vix.vix_open]), 'VixOpen') schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=1) ) def before_trading_start(context,data): context.output = pipeline_output('my_pipeline') context.vix = context.output["VixOpen"].iloc[0] class GetVIX(CustomFactor): window_length = 1 def compute(self, today, assets, out, vix): out[:] = vix[-1] def rebalance(context, data): xiv_current = data.current(context.XIV, 'price') price_history = data.history(context.XIV, "price", 20, "1d") xiv_mean = price_history.mean() if data.can_trade(context.XIV): # Normal Range Trading Rules context.xiv_adjust = context.vix - 12 if context.xiv_adjust < 0.0: context.xiv_adjust = 0.0 context.xiv_weigth = context.xiv_adjust/8 if context.xiv_weigth > 1.0: context.xiv_weigth = 1.0 if context.xiv_weigth < 0.0: context.xiv_weigth = 0.0 # Market Collapse Guard if context.vix > 25: context.xiv_weigth = 0.0 order_target_percent(context.XIV, context.xiv_weigth) # order_target_percent(context.TLT, 1.0-context.xiv_weigth) record(Leverage=context.account.leverage)  There was a runtime error. What if we threw in a small holding or 75% XIV 25% TLT to try and reduce the DD. Of course this will directly impact the returns, but the DDs are rediculous and that is during the bull run from 2009-present. These 40-50% DD during this timeframe would be >80% during 2008 James, Thanks for sharing the algo! Couple of q's: - is there any reason the backtest didn't go back to the xiv inception date (2010-11-30)? - it seems the vix signal is day lag, e.g., on 2011-10-05, current_price is 46.18, while that's Oct 04 open vix, and Oct 05 open vix is actually 40.73 You are right about the VIX price, I used a pipeline to get VIX at opening price every day, but it did gave me the opening price of previous day, I am look into fixing this problem. I have been reading quite a bit about how to get current day VIX price. The closest I got is to use the closing price from previous day, since Quantopian does not support intraday trading on VIX, so we can't get it's intraday opening price. Anyone have a better idea? So I made a few changes in the last couple days: 1. Instead of getting VIX opening price from previous day, I get its closing price from previous day. 2. buy UVXY when you are not long XIV, but only use 10% of my portfolio to buy UVXY. 3. Changed the XIV selling condition to when XIV <= XIV_30_days_average + 2.7. The reason: sell XIV at a higher price. This algo always buy XIV at the lowest price, because I only buy it after a VIX spike. So the question is when to sell it? How to sell it just before another VIX spike, and that is the million dollar question. Maybe set a 30% or higher stop gain? Any idea is appreciated. 1512 Loading... 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 import pandas import pandas as pd import datetime import numpy as np import talib from scipy import stats from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor, Latest from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix from quantopian.pipeline.data.quandl import yahoo_index_vix from quantopian.pipeline.factors import SimpleMovingAverage class GetVIX(CustomFactor): window_length = 1 def compute(self, today, assets, out, vix): out[:] = vix[-1] """ 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.UVXY = sid(41969) context.sell_price = 0 context.vix_last_price = 0 # fetch VIX data pipe = Pipeline() attach_pipeline(pipe, 'my_pipeline') #get VIX at market open pipe.add(GetVIX(inputs=[cboe_vix.vix_close]), 'VixOpen') #get VIX average in the last 2 days pipe.add(SimpleMovingAverage(inputs=[cboe_vix.vix_close], window_length=2), 'vix_mean') set_slippage(slippage.FixedSlippage(spread=.02)) # add a slippage set_commission(commission.PerShare(cost=0.0135, min_trade_cost=1)) # add a commission schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=1) ) ''' # total minutes in a trading day total_minutes = 6*60 + 30 for i in range(1, total_minutes): # Every 30 minutes run schedule if i % 30 == 0: # This will start at 9:31AM and will run every 30 minutes # Specify that we want the 'rebalance' method to run schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=i), True ) ''' def before_trading_start(context,data): output = pipeline_output('my_pipeline') context.vix = output["VixOpen"].iloc[0] context.vix_mean = output["vix_mean"].iloc[0] # last 2 days average of VIX """ Rebalance function scheduled to run once per day (at market open). """ def rebalance(context, data): # We get VIX's current price. current_price = context.vix # vix current value at market open vix_mean = context.vix_mean # vix mean value of last 2 days if (context.vix_last_price == 0): context.vix_last_price = current_price # set our last trading VIX price xiv_current = data.current(context.XIV, 'price') price_history = data.history(context.XIV, "price", 30, "1d") xiv_mean = price_history.mean() # get the last 30 days average of XIV xiv_std = price_history.std() # get the standard deviation of XIV #log.info("xiv_std, xiv, vix______ %s, %s, %s" % (xiv_std, xiv_current, current_price)) # If XIV is currently listed on a major exchange if data.can_trade(context.XIV): # If VIX index is above 59, we buy XIV. if (current_price >= 59): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 55): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 51): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 47): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 43): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 39): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 35.3): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 31.4): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 27.5): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 23.6): # second buy price at 23.6 if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) # If VIX index is above 19.5, we buy XIV. In case of a normal spike. elif (current_price >= 19.5): if (context.sell_price == 0): context.sell_price = 12.4 # Set a sell price at 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX__________ %s" % current_price) log.info("VIX_mean_____ %s" % vix_mean) log.info("xiv_current__ %s" % xiv_current) # If VIX index is below sell price and xiv below xiv_30d_mean + 2.7, we sell XIV. elif (current_price <= context.sell_price and xiv_current <= xiv_mean + 2.7): # Sell all of our shares by setting the target position to zero order_target_percent(context.XIV, 0) order_target_percent(context.UVXY, 0.1) log.info("VIX_current____________ %s" % current_price) log.info("VIX_mean_______________ %s" % vix_mean) log.info("xiv_current____________ %s" % xiv_current) log.info("xiv_mean_______________ %s" % xiv_mean) context.sell_price = 0 context.vix_last_price = current_price #record(Leverage=context.account.leverage) record(VIX=current_price) record(XIV=xiv_current)  There was a runtime error. James, I feel this algo is prone to overfitting... you may want to get replicated prices for xiv/uvxy/vxx since 2004 when vix futures started trading...the current vix regime is very different from what it was. Simply going back to the xiv inception date would expose a lot more risks than current test scenario. You may fetch vix from csv files. Good luck. Can you test it? I am not sure how to fetch vix from a local csv file. This is the testing result with trading only XIV from 2010-11-30 to now. I removed UVXY because it only started trading after 2011. Again, even with a -76% drawdown, every single trade is profitable. The first XIV buy waited more than 2 years to sell, but it still made money in the end. 1512 Loading... 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 import pandas import pandas as pd import datetime import numpy as np import talib from scipy import stats from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor, Latest from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix from quantopian.pipeline.data.quandl import yahoo_index_vix from quantopian.pipeline.factors import SimpleMovingAverage class GetVIX(CustomFactor): window_length = 1 def compute(self, today, assets, out, vix): out[:] = vix[-1] """ 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.UVXY = sid(41969) context.sell_price = 0 context.vix_last_price = 0 # fetch VIX data pipe = Pipeline() attach_pipeline(pipe, 'my_pipeline') #get VIX at market open pipe.add(GetVIX(inputs=[cboe_vix.vix_close]), 'VixOpen') #get VIX average in the last 2 days pipe.add(SimpleMovingAverage(inputs=[cboe_vix.vix_close], window_length=2), 'vix_mean') set_slippage(slippage.FixedSlippage(spread=.02)) # add a slippage set_commission(commission.PerShare(cost=0.0135, min_trade_cost=1)) # add a commission schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=1) ) ''' # total minutes in a trading day total_minutes = 6*60 + 30 for i in range(1, total_minutes): # Every 30 minutes run schedule if i % 30 == 0: # This will start at 9:31AM and will run every 30 minutes # Specify that we want the 'rebalance' method to run schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=i), True ) ''' def before_trading_start(context,data): output = pipeline_output('my_pipeline') context.vix = output["VixOpen"].iloc[0] context.vix_mean = output["vix_mean"].iloc[0] # last 2 days average of VIX """ Rebalance function scheduled to run once per day (at market open). """ def rebalance(context, data): # We get VIX's current price. current_price = context.vix # vix current value at market open vix_mean = context.vix_mean # vix mean value of last 2 days if (context.vix_last_price == 0): context.vix_last_price = current_price # set our last trading VIX price xiv_current = data.current(context.XIV, 'price') price_history = data.history(context.XIV, "price", 30, "1d") xiv_mean = price_history.mean() # get the last 30 days average of XIV xiv_std = price_history.std() # get the standard deviation of XIV #log.info("xiv_std, xiv, vix______ %s, %s, %s" % (xiv_std, xiv_current, current_price)) # If XIV is currently listed on a major exchange if data.can_trade(context.XIV): # If VIX index is above 59, we buy XIV. if (current_price >= 59): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 55): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 51): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 47): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 43): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 39): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 35.3): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 31.4): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 27.5): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 23.6): # second buy price at 23.6 if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) # If VIX index is above 19.5, we buy XIV. In case of a normal spike. elif (current_price >= 19.5): if (context.sell_price == 0): context.sell_price = 12.4 # Set a sell price at 12.4 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX__________ %s" % current_price) log.info("VIX_mean_____ %s" % vix_mean) log.info("xiv_current__ %s" % xiv_current) # If VIX index is below sell price and xiv below xiv_30d_mean + 2.7, we sell XIV. elif (current_price <= context.sell_price and xiv_current <= xiv_mean + 2.7): # Sell all of our shares by setting the target position to zero order_target_percent(context.XIV, 0) #order_target_percent(context.UVXY, 0.1) log.info("VIX_current____________ %s" % current_price) log.info("VIX_mean_______________ %s" % vix_mean) log.info("xiv_current____________ %s" % xiv_current) log.info("xiv_mean_______________ %s" % xiv_mean) context.sell_price = 0 context.vix_last_price = current_price #record(Leverage=context.account.leverage) record(VIX=current_price) record(XIV=xiv_current)  There was a runtime error. Local csv is not supported as I know. You can use dropbox or google drive, I use http to my own server. Notice the drawdown comes to -76%? I tried to do that too, didn't work. Big drawdown is not a problem if the final return is still positive. Why not allocate 100% of portfolio to UVXY when going long VIX? 27 Loading... 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 import pandas import pandas as pd import datetime import numpy as np import talib from scipy import stats from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor, Latest from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix from quantopian.pipeline.data.quandl import yahoo_index_vix from quantopian.pipeline.factors import SimpleMovingAverage class GetVIX(CustomFactor): window_length = 1 def compute(self, today, assets, out, vix): out[:] = vix[-1] """ 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.UVXY = sid(41969) context.sell_price = 0 context.vix_last_price = 0 # fetch VIX data pipe = Pipeline() attach_pipeline(pipe, 'my_pipeline') #get VIX at market open pipe.add(GetVIX(inputs=[cboe_vix.vix_close]), 'VixOpen') #get VIX average in the last 2 days pipe.add(SimpleMovingAverage(inputs=[cboe_vix.vix_close], window_length=2), 'vix_mean') set_slippage(slippage.FixedSlippage(spread=.02)) # add a slippage set_commission(commission.PerShare(cost=0.005, min_trade_cost=1)) # add a commission schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=1) ) ''' # total minutes in a trading day total_minutes = 6*60 + 30 for i in range(1, total_minutes): # Every 30 minutes run schedule if i % 30 == 0: # This will start at 9:31AM and will run every 30 minutes # Specify that we want the 'rebalance' method to run schedule_function( rebalance, date_rules.every_day(), time_rules.market_open(minutes=i), True ) ''' def before_trading_start(context,data): output = pipeline_output('my_pipeline') context.vix = output["VixOpen"].iloc[0] context.vix_mean = output["vix_mean"].iloc[0] # last 2 days average of VIX """ Rebalance function scheduled to run once per day (at market open). """ def rebalance(context, data): # We get VIX's current price. current_price = context.vix # vix current value at market open vix_mean = context.vix_mean # vix mean value of last 2 days if (context.vix_last_price == 0): context.vix_last_price = current_price # set our last trading VIX price xiv_current = data.current(context.XIV, 'price') price_history = data.history(context.XIV, "price", 30, "1d") xiv_mean = price_history.mean() # get the last 30 days average of XIV xiv_std = price_history.std() # get the standard deviation of XIV #log.info("xiv_std, xiv, vix______ %s, %s, %s" % (xiv_std, xiv_current, current_price)) # If XIV is currently listed on a major exchange if data.can_trade(context.XIV): # If VIX index is above 59, we buy XIV. if (current_price >= 59): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 55): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 51): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 47): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) order_target_percent(context.VXZ, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 43): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 39): if (context.sell_price == 0): context.sell_price = 12.3 order_target_percent(context.XIV, 1) #order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 35.3): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 31.4): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 27.5): if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) elif (current_price >= 23.6): # second buy price at 23.6 if (context.sell_price == 0): context.sell_price = 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX: %s" % current_price) log.info("xiv_current %s" % xiv_current) # If VIX index is above 19.5, we buy XIV. In case of a normal spike. elif (current_price >= 19.5): if (context.sell_price == 0): context.sell_price = 12.4 # Set a sell price at 12.4 order_target_percent(context.XIV, 1) order_target_percent(context.UVXY, 0) log.info("VIX__________ %s" % current_price) log.info("VIX_mean_____ %s" % vix_mean) log.info("xiv_current__ %s" % xiv_current) # If VIX index is below sell price and xiv below xiv_30d_mean + 2.7, we sell XIV. elif (current_price <= context.sell_price and xiv_current <= xiv_mean + 2.7): # Sell all of our shares by setting the target position to zero order_target_percent(context.XIV, 0) order_target_percent(context.UVXY, 1) log.info("VIX_current____________ %s" % current_price) log.info("VIX_mean_______________ %s" % vix_mean) log.info("xiv_current____________ %s" % xiv_current) log.info("xiv_mean_______________ %s" % xiv_mean) context.sell_price = 0 context.vix_last_price = current_price #record(Leverage=context.account.leverage) record(VIX=current_price) record(XIV=xiv_current)  There was a runtime error. Hi James, fantastic stuff you have here. Question: I ran multiple back tests on your 100,000%+ algorithm and upon analyzing the log outputs, I noticed that I got a couple 'WARN' signs indicating that X amount of shares of UVXY/XIV were partially filled. What are the repercussions of this if it were to be traded live? It doesn't seem to have partial fills early on, but they start recurring with frequency (3 times between August 2016 and November 2016) late into the algorithm's lifecycle. @Mark Trader I do like the idea of standard deviation, but how it sits isn't robinhood proof due to leverage going above 1. Also is that pulling the past morning vix price or closing price? @Alex Paz Quantopian is simulating the product's liquidity. In backtests, I think they use the product's dollar volume to determine a realistic order amount that can be filled. So as the algo makes more money and therefore places larger orders, not all orders can be filled at once because the product doesn't have enough liquidity. More info on dollar volume here: https://www.quantopian.com/help#ide-dollar-volume Great returns, well done! A question: Once strategy are holding UVXY, for example at 23.6, but vix drop down to below 19.8 in one day, and then stop in the area that vix is between 12.2 and 19.8 for a long time, what will happen? Do we have some logic to know this and avoid it happen? If you bought UVXY when VIX is below 12.2, and hold it for a very long time, your UVXY will go to zero, that's why I removed uvxy or only allocate 10% of my portfolio for it. Long UVXY is very risky! So James, yes you just mentioned one situation of holding UVXY, another possible situation in this strategy, which I am concerning is that the strategy holding UVXY from 23.6 and then vix drop quickly to below 19.8, and right now in strategy there is not logic dealing with this. So you final decision will be removing UVXY? It seems the returns are affected very much. Holding UVXY like a gambling game, sometimes lose but a few chance gain bigger. The only time I will buy UVXY is when VIX drop below 12.4, and only time hold it is when VIX is between 12.4 -19.5. When VIX is above 19.5 I will always sell UVXY, so when VIX is at 23.6, I sell UVXY, not hold it. That's how my strategy works, please double check. Another way to trade UVXY, use a small portion of your portfolio, set a stop loss, so if you lose, you lose small, if you win, you can potentially win bigly. HI James, sorry that I did not study your last code before, and I just checked it out. Actually you could make your code much simple if do so: always gather XIV when vix higher than 19.8. And if you do so, the strategy becomes a very simple task which manual operation will be better and safe, via Option Spread. You are right, I am going to do that now. Thanks. @Josh Nielsen Thank you for clarifying. That brings up a second question: Is there any way around this issue? As the algorithm matures and grows in size, wouldn't that be hindering the potential profits it's designed to return? In other words, at one point it'll get so big that it will, by nature, be too big to run at full capacity. And by full capacity I mean 100% of all orders executed are filled completely. Too big = More than 10 Million dollars worth of shares trade per day. You don't need to worry about that! thanks for posting this... anyone live trading it? the drawdown is pretty steep.... also, since there's a huge dd when the market tanks, which we expect to do sooner or later, would a stop loss or trailing stop work here? there must be some way to exit a position that moves against without suffering such a large dd... many of these algos have done well the last couple of years as there has been little volatility and the market as shot straight up more or less... I suspect this may not do well in a choppy or more volatile trading environment which we could see soon enough... thoughts? seems too risky to put in play as it is.. @tony I am not trading it as of now. Still reading into it more and learning more about it. @James Thanks for your thoughts. I have another question: This may be a Quantopian thing but I noticed that in the transaction details that it buys on the first day (whichever day that may be). Then on the next transaction day, it buys on and sells each security in the same minute but not in the right order. At first it seems illogical because there wouldn't be enough capital to buy again and then sell. For example: 1 May 9:32am - Buy 123 Quantity of XIV @$'X'
2 May 9:32am - Buy 123 Quantity of UVXY @ $'X' 2 May 9:32am - Sell 123 Quantity of XIV @$'X'

There isn't enough capital to buy again first on May 2. Shouldn't the order go 'buy, sell, buy'? Again, this may be a silly Quantopian thing putting the securities in alphabetical order, but wanted to be sure.

@tony
It won't work in a more volatile bear market, like 2008, you should get out when a big crash happened.

@alex
I think it's just the order displayed on Quantopian, in reality it should be buy, sell then buy.

1512
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from scipy import stats
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
from quantopian.pipeline.data.quandl import yahoo_index_vix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

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

context.sell_price = 0
context.vix_last_price = 0

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

output = pipeline_output('my_pipeline')
context.vix = output["VixOpen"].iloc[0]
context.vix_mean = output["vix_mean"].iloc[0] # last 2 days average of VIX

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):

# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days
if (context.vix_last_price == 0):
context.vix_last_price = current_price # set our last trading VIX price

xiv_current = data.current(context.XIV, 'price')
price_history = data.history(context.XIV, "price", 30, "1d")
xiv_mean = price_history.mean() # get the last 30 days average of XIV
xiv_std = price_history.std() # get the standard deviation of XIV
uvxy_current = data.current(context.UVXY, 'price')
#log.info("xiv_std, xiv, vix______ %s, %s, %s" % (xiv_std, xiv_current, current_price))

# If XIV is currently listed on a major exchange
# If VIX index is above 19.5, we buy XIV. In case of a VIX spike.
if (current_price >= 19.5):
if (context.sell_price == 0):
context.sell_price = 12.4 # Set a sell price at 12.4
order_target_percent(context.XIV, 1)
order_target_percent(context.UVXY, 0)
log.info("VIX__________ %s" % current_price)
log.info("VIX_mean_____ %s" % vix_mean)
log.info("xiv_current__ %s" % xiv_current)

# If VIX index is below sell price and xiv below xiv_30d_mean + 2.7, we sell XIV.
elif (current_price <= context.sell_price and xiv_current <= xiv_mean + 2.7):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_mean_______________ %s" % vix_mean)
log.info("xiv_current____________ %s" % xiv_current)
log.info("xiv_mean_______________ %s" % xiv_mean)
context.sell_price = 0
context.vix_last_price = current_price

record(Leverage=context.account.leverage)
#record(VIX=current_price)
#record(XIV=xiv_current)
#record(UVXY=uvxy_current)

There was a runtime error.

James, this algorithm seems to have a forward looking bias, in that you are using the VIX closing value at the start of the day. You should look back a day when pulling VIX close value.

Can't get intraday VIX, so the closing price of VIX is actually from previous day.

You are right, there is no look ahead. Very interesting. The only issue I have with this is the hardcoded 2.7 number, which is very significant in the early days of XIV when it is small, and not significant at all in the current times.

Try testing using 2.3, 2.4, .... 3.8, the worst return is 16391.3%, with a -61.3% drawdown. 2.7 is a number in the middle of that range which gives the best return. So what this means is in the future even if 2.7 will not give the best result, it will still give a pretty good one.

James, You didn't understand my point. What you are doing is overfitting the model. There is no rationale behind the 2.7, except that it works. Overfitted models perform poorly out of sample.

Um yes, wholeheartedly agreed.

Thanks James, like the algo. And I appreciate the points made about the hard coded 2.7 brought up by Macro and Anthony. Since you're using the VIX close from the day before, do you guys think it would be helpful to execute this function in premarket trading once that is made available in Q?

It's not overfitting if ALL those numbers (2.3 - 3.8) produce consistent good return. It is overfitting if only one or two of those numbers give good return and the rest give not so good return.

It should be good if we can get rid of the 2.7 and instead using some standard deviation or some formula which take XIV, UVXY, VIX info as input. 2.7 might be good for the past data,
but might not work well in future

Another suggestion is if we can minimize the drawdown, several suggestions
1. when VIX is above 19.5, instead of blindly buy XIV, we only buy when we see the VIX decreases, and we might monitor the VIX price in hour granularity if it is above 19.5
2 Introduce a cool down timer = 5-10 days which do not trade when we see dramatically decline from past 3 days

saw leverage go above 1 a few times, is this still an issue say using it with Robinhood without leverage?

@haiqin Liu
Go try it out yourself, and let me know.

@elsid
you can changeorder_target_percent(context.XIV, 1) to order_target_percent(context.XIV, 0.95) to make the leverage below 1.

Thanks James yea I just wanted to make sure also to my understanding when you are long XIV when VIX Is above say 19.5. You don't sell those shares correct? So even in an 08' scenario you'll get a massive spike to say to 80-90 your drawdown will go down to say 50-80% but will soon recover when VIX come second crashing back down to normal levels correct?

@Elsid
YES! however 08' situation will take a few years to recover.

I think if you add back in uxvy to this and possibly add in this https://www.quantopian.com/posts/record-pnl-per-stock then it could possible be an algo that has the crazy gains you'd like and is decently tradeable.

85
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

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

context.sell_price = 0
context.vix_last_price = 0

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]
context.vix_mean = context.output["vix_mean"].iloc[0] # last 20 days average of VIX

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):
# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days
if (context.vix_last_price == 0):
context.vix_last_price = current_price # set our last trading VIX price

xiv_current = data.current(context.XIV, 'price')
price_history = data.history(context.XIV, "price", 20, "1d")
xiv_mean = price_history.mean() # get the last 20 days average of XIV
xiv_std = price_history.std() # get the standard deviation of XIV

# If XIV is currently listed on a major exchange
# If VIX index is above 59, we buy XIV.
if (current_price >= 59):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 55):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 51):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 47):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 43):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 39):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 35):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 31):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 27):
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

elif (current_price >= 23.6): # second buy price at 23.6
if (context.sell_price == 0):
context.sell_price = 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

# If VIX index is above 19.8, we buy XIV. In case of a normal spike.
elif (current_price >= 19.8):
if (context.sell_price == 0):
context.sell_price = 12.2 # Set a sell price at 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)

# If VIX index is below sell price and xiv below xiv_20d_mean + standard deviation, we sell XIV.
elif (current_price <= context.sell_price and xiv_current <= xiv_mean+xiv_std):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
#order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_mean_______________ %s" % vix_mean)
log.info("xiv_current____________ %s" % xiv_current)
log.info("xiv_mean_______________ %s" % xiv_mean)
context.sell_price = 0
context.vix_last_price = current_price

record(Leverage=context.account.leverage)

There was a runtime error.

Hi James - thanks for the algo. Were you able to sell XIV this Friday, when it turned? Are you using IB, RH or another?

@guillermo
The algo sold XIV on Feb. 24, but I sold it on Feb. 23 on my personal account. I am not live trading with this aglo yet.

Hi James,

Nice algo. BTW , your code had me puzzled a bit at the beginning because of all the if clauses.
You can make it more understandable (and elegant) by simply removing all except 2nd to last if clause and replacing with

if (current_price >= 19.8):
if (context.sell_price == 0):
context.sell_price = 12.2 # Set a sell price at 12.2
order_target_percent(context.XIV, 1)
#order_target_percent(context.UVXY, 0)
log.info("VIX: %s" % current_price)
log.info("xiv_current %s" % xiv_current)
# If VIX index is below sell price and xiv below xiv_20d_mean + standard deviation, we sell XIV.
elif (current_price <= context.sell_price and xiv_current <= xiv_mean+xiv_std):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.XIV, 0)
#order_target_percent(context.UVXY, 1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_mean_______________ %s" % vix_mean)
log.info("xiv_current____________ %s" % xiv_current)
log.info("xiv_mean_______________ %s" % xiv_mean)
context.sell_price = 0
context.vix_last_price = current_price


Try it. The results are identical.

Cheers,
Serge

James,
i'm a new user with Quantopian so pardon me if i'm wrong... Just cloned your last algo and ran the full backtest out of the box. Something weird with the UVXY transactions: While the sells XIV balance perfectly the buys, it's not the case with UVXY. Sometimes, but not always, the sells represent a minor fraction of the buys. For example, first UVXY buy : 4886, then 488 sold. Am I missing something ?

Because of UVXY Reverse Split

Thank you for the this, James! I've deployed your algo and livetrading it with Robinhood.

Does anyone trade this live? If so has it performed similar to backtesting? Any issues?

I'm having an issue now where it is trying to buy uvxy, but the orders are being rejected and cancelled. One reason being that it tries to order more than allotted. Although even if it did buy it, I would be losing money due to the decay of uvxy.

Guys, like I said, uvxy is too risky, don't buy it, comment it out in my algorithm.

How so? The largest drawdown in your backtest is from XIV - not UVXY?

There is bound to be something that pops the VIX up every now and then and if you're holding UVXY it will be a nice little run (like the election)

Hi I am new with quantopian, I run James' algo backtest and daily position and gains appear to stop at 2017-2-14. Why is that?

The BETA value of this algo is simply too high. Realy quite risky for live trading.

Quick question, guys.. could any of the variations of the algorithm in this thread get me flagged as a day trader?

Thank you.

** Also, first post, been browsing quite a bit, running backtests amany, and learning a TON. I plan to stick around awhile ;) Thanks for all of your contributions. I hope I can be of assistance where needed.

Hi Kern,

If you could make the -0.5 < BETA < 0.5, that will be great. :-)

Thomas, this algo is designed to trade XIV and UVXY exclusively (they're essentially just invertions of one another).

If you're looking for low beta, and thus for your earnings not to be so dependent on which way the market is headed, you're probably going to be after algos that select from a broader range of stocks/tickers.

Hello guys,
thx for sharing your code. I've got some finance/python experience, but pretty new to Quantopian API so your code was really helpful.
I've modified the algo in this post with the aim to make it more robust and, above all, a bit safer (also done some off-line back-testing from 2007 using underlying indices when ETNs were not available - results are encouranging).
In a nutshell, the algo either
1. goes short vol (i.e. long XIV): if both i. VIX futures in contango (VXV > VIX) & ii. VIX is declining (not just when high enough like in the previous algos), or
2. goes long vol (i.e. long VXX): if both i. VIX futures in backwardation (VXV < VIX) & ii. VIX is increasing.
3. Otherwise, the algo take all position off (flat).
Thoughts?

149
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
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv
import pandas as pd

###################### work around for VIX / VXV data
class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

class GetVXV(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vxv):
out[:] = vxv[-1]

class ViX_avg(CustomFactor):
inputs = [cboe_vix.vix_open]
window_length = 60
def compute(self, today, assets, out, vix):
vix = vix.ravel()
vix_avg = pd.Series(vix).rolling(window=60).mean()
out[:] = vix_avg.iloc[-1]

###############
def initialize(context):
set_benchmark(symbol('XIV')) # comment to use SPY as bmk
context.XIV = symbol('XIV') # rather than sid()
context.VXX = symbol('VXX')
# context.sell_price = 12 # Set a sell price at 12

pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

## no need for the class Vix_avg(), see below

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

context.output = pipeline_output('my_pipeline')
context.vix = context.output["VixOpen"].iloc[0]
context.vxv = context.output["VxvOpen"].iloc[0]
context.vix_avg = context.output["ViX_avg"].iloc[0]

def rebalance(context, data):

# xiv_current = data.current(context.XIV, 'price')
# xiv_mean = data.history(context.XIV, "price", 20, "1d").mean()
vix_moma =  1 - (context.vix_avg / context.vix)

# params
r1 = 0.1
r2 = 0.1

# short vol (i.e. long XIV)
if ( (context.vxv / context.vix > (1+r1)) & (vix_moma < r2)):
order_target_percent(context.XIV, 1)
order_target_percent(context.VXX, 0)
# long vol  (i.e. long VXX)
elif ((context.vxv / context.vix < (1-r1)) & (vix_moma > r2)):
order_target_percent(context.XIV, 0)
order_target_percent(context.VXX, 1)
# flat (no position)
else:
order_target_percent(context.XIV, 0)
order_target_percent(context.VXX, 0)

log.info("VIX: %s" % context.vix)
log.info("xiv_current %s" % data.current(context.XIV, 'price'))

record(Leverage=context.account.leverage)

There was a runtime error.

@Kern Winn,
not sure if you are still interested in answer to your recent question

If your portfolio value is always above $25,000, then you are always exempt from day trade restrictions. Generally, buy / sell or sell / buy of same stock during same trading session counts as a day trade. If portfolio under$25,000, then you must avoid becoming tagged as a pattern day trader.
"A pattern day trader is a stock market trader who executes four or more day trades in five business days in a margin account, provided the number of day trades are more than six percent of the customer's total trading activity for that same five-day period." - from https://en.wikipedia.org/wiki/Pattern_day_trader

My current algo Robin Hood VIX Mix tends to trade infrequently enough to completely avoid pattern day trader difficulties.
Of course, the nature of these trades and the securities involved mean you can lose a lot of money.
If you are more interested in low risk, then probably not appropriate for you.

James, have you started live trading this algo yet? And if so, what version are you using?

Which version is the best to live trade? I would love to start using this.

None, I've been paper trading this since january and it has been long UVXY from ~$21 (pre reverse split 4:1) Drawdown is around 60-70 so far. My oppinion: Any algo which is not backtesting include the year of 2008, should be very very careful! Many people call UVXY poisson. They must have right. :-) only if you are trying to go long UVXY - it is the holy grail if you're shorting it and the market doesn't crash We need an update on this to pull VIX data from CBOE or pipeline instead of Yahoo, since that function was deprecated. Has anyone converted their algorithm to work with iBridgePy, Zipline or QuantConnect? If not I plan to soon or would be happy to help anyone convert theirs. @Michael Very nice! But I would prefer to hear what's your experience on these different platforms. Pros and Cons. :-) Especially I want to know how to do multiple account trading. I have a primary account and sereval linked accounts by IB. Formerly by QuantOpian I open several live accounts separately. But by QuantConnect, the first account costs 20$/month, each further costs 10$/month. By using IBridgePy.com since one uses the IB Gateway, if I trade on separat account, I have to start several instances of IB Gateway. This is not possible on the same machine or it will make many problems. So I have to buy several VPS. Anotherway is I login to my primory account but trade on linked account. This is possible. But if I use the IBridgePy.com, they will ask me for 1000$ yearly (the first year 600$). So I wonder if one can do this with the zipline-live. or I have to pay for that? Zipline-live will likely have the same issues with setting up multiple instances. It sounds like you're further along in research than me. I'm not as worried about multiple accounts, my main concern currently is porting the code and getting a stable build/configuration. @ Alessandro Muci Interesting algorithm and risk metrics look very attractive. I just have one thing to suggest: holding TLT (20yr bond etf) while holding no position in XIV or VXX. It will boost all relevant risk metrics (Sharpe from 1.14 to 1.40, Alpha from 28% to 40%, Drawdown from -33% to -28%). Seemed like a promising, simple strategy. Unfortunate that the events over Feb 2-5, 2018 (a cripping -80% destruction in the fund's value during after-hours trading) demonstrated that the VIX instrument is subject to exaggerated fluctuations that do not represent true market sentiments. The ETN is being liquidated. Credit Suisse says it will end trading in the volatility security that's become the focus of this sell-off https://www.quantopian.com/posts/trade-xiv-based-on-vix-1#58a10efd20736215dda33783 I wrote in this forum that this kind of issues can happened, and that the long term price of the asset is zero. Of course, I lost more than 100% of profit in this period. Same here warned about this on a few VIX strategies on here, especially the faulty backtests that most were showing, thinking that somehow 50%-60% DD in the lowest volatile period in history, with limited backtesting history was going to end well. Felt like a very amateurish environment of not taking risk into consideration at all. The irony it was almost prophetic one year ago haha. Elsid Aliaj Feb 3, 2017 Edit Delete or 99% during 08' Since the Black Swan incident on February 6, 2018, this post has not been updated. Have all guys abandoned this algorithm? But I think it is a good strategy. The only issure is how to avoid the black swan event. The 10% 90% rebalance strategy between SVXY&TLT may be a good choice. Well XIV died, and the other volatility instruments were heavily reduced in terms of % change when VIX changes. So many people lost everything during that, and now there is really nothing similar. JNUG/JDST is hot right now with the insane gold run up. If you have capital, a bet on JDST is not bad. Or short GC futures. Another one is natural gas. The 3x pairs UGAZ/DGAZ are always one of the most volatile. And gas is due for a run like every year until Nov. Good luck. I tried to use SVXY & TLT to do 50 50 balance base on the James algo. Something wired. The backtest shows that bought SVXY at 71.28$ in Feb 06 2018 and sold at 13.61\$ in Jun 06 2018.

The total returns between 2011-2019 should be 134%. But the backtest result is 779.61%.

Who can tell me why?

The same thing happened when I backtest the original James algo which only change the XIV to SVXY.

Base on James original algo, the total returns between 2011-2019 only 4.3% (because the Black Swan DD in Feb 2018). But the backtest shows 2084.14%.

8
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from scipy import stats
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
#from quantopian.pipeline.data.quandl import yahoo_index_vix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

"""
The initialize function sets any data or variables that
It's only called once at the beginning of your algorithm.
"""
def initialize(context):
# set up XIV
context.UVXY = sid(41969)
context.SVXY = sid(41968)
context.TLT = sid(23921)

context.sell_price = 0
context.vix_last_price = 0

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

output = pipeline_output('my_pipeline')
context.vix = output["VixOpen"].iloc[0]
context.vix_mean = output["vix_mean"].iloc[0] # last 2 days average of VIX

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):

# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days
if (context.vix_last_price == 0):
context.vix_last_price = current_price # set our last trading VIX price

svxy_current = data.current(context.SVXY, 'price')
tlt_current = data.current(context.TLT, 'price')

price_history_svxy = data.history(context.SVXY, "price", 30, "1d")
svxy_mean = price_history_svxy.mean() # get the last 30 days average of SVXY

#if xiv not in trading list,change to svxy
# If VIX index is above 59, we buy XIV.
if (current_price >= 59):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
order_target_percent(context.SVXY, 0.5)
#order_target_percent(context.VXZ, 0)
log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 55):
if (context.sell_price == 0):
context.sell_price = 12.3
order_target_percent(context.TLT, 0.5)
order_target_percent(context.SVXY, 0.5)

#order_target_percent(context.VXZ, 0)
log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 51):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.VXZ, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 47):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)

order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 43):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 39):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 35.3):
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 31.4):
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 27.5):
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 23.6): # second buy price at 23.6
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

# If VIX index is above 19.5, we buy XIV. In case of a normal spike.
elif (current_price >= 19.5):
if (context.sell_price == 0):
context.sell_price = 12.4 # Set a sell price at 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX__________ %s" % current_price)
log.info("VIX_mean_____ %s" % vix_mean)
log.info("svxy_current__ %s" % svxy_current)

# If VIX index is below sell price and xiv below xiv_30d_mean + 2.7, we sell XIV.
elif (current_price <= context.sell_price and svxy_current <= svxy_mean + 2.7):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.SVXY, 0)
order_target_percent(context.TLT, 1)
log.info("Sell" )
#order_target_percent(context.UVXY, 0.1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_mean_______________ %s" % vix_mean)
log.info("svxy_current____________ %s" % svxy_current)
log.info("svxy_mean_______________ %s" % svxy_mean)
context.sell_price = 0
context.vix_last_price = current_price

#record(Leverage=context.account.leverage)
record(VIX=current_price)
record(TLT=tlt_current)
record(SVXY=svxy_current)


There was a runtime error.

Hi guys,

I update the algo:
1. SVXY and TLT 55 balance
2. When VIX lower than 12.3, buy VXX to hedge the Black Swan

Thoughts?

6
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
import pandas
import pandas as pd
import datetime
import numpy as np
import talib
from scipy import stats
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
#from quantopian.pipeline.data.quandl import yahoo_index_vix
from quantopian.pipeline.factors import SimpleMovingAverage

class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

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

context.sell_price = 0
context.vix_last_price = 0

context.last_vxx_position = 0

# fetch VIX data
pipe = Pipeline()
attach_pipeline(pipe, 'my_pipeline')

#get VIX at market open
#get VIX average in the last 2 days

schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=1)
)

'''
# total minutes in a trading day
total_minutes = 6*60 + 30
for i in range(1, total_minutes):
# Every 30 minutes run schedule
if i % 30 == 0:
# This will start at 9:31AM and will run every 30 minutes
# Specify that we want the 'rebalance' method to run
schedule_function(
rebalance,
date_rules.every_day(),
time_rules.market_open(minutes=i),
True
)
'''

output = pipeline_output('my_pipeline')
context.vix = output["VixOpen"].iloc[0]
context.vix_mean = output["vix_mean"].iloc[0] # last 2 days average of VIX

"""
Rebalance function scheduled to run once per day (at market open).
"""
def rebalance(context, data):

# We get VIX's current price.
current_price = context.vix  # vix current value at market open
vix_mean = context.vix_mean  # vix mean value of last 2 days
if (context.vix_last_price == 0):
context.vix_last_price = current_price # set our last trading VIX price

svxy_current = data.current(context.SVXY, 'price')
vxx_current = data.current(context.VXX, 'price')

price_history_svxy = data.history(context.SVXY, "price", 30, "1d")
svxy_mean = price_history_svxy.mean() # get the last 30 days average of SVXY

if (current_price >=19.5 and context.last_vxx_position > 0):
order_target_percent(context.VXX, 0)
context.last_vxx_position = 0
log.info("Sell VXX")
log.info("VXX_Current: %s" % vxx_current)

#if xiv not in trading list,change to svxy
# If VIX index is above 59, we buy XIV.
if (current_price >= 59):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
order_target_percent(context.SVXY, 0.5)
#order_target_percent(context.VXZ, 0)
log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 55):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
order_target_percent(context.SVXY, 0.5)

#order_target_percent(context.VXZ, 0)
log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 51):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.VXZ, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 47):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 43):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 39):
if (context.sell_price == 0):
context.sell_price = 12.3

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 35.3):
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 31.4):
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 27.5):
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

elif (current_price >= 23.6): # second buy price at 23.6
if (context.sell_price == 0):
context.sell_price = 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX: %s" % current_price)
log.info("svxy_current %s" % svxy_current)

# If VIX index is above 19.5, we buy XIV. In case of a normal spike.
elif (current_price >= 19.5):
if (context.sell_price == 0):
context.sell_price = 12.4 # Set a sell price at 12.4

order_target_percent(context.TLT, 0.5)
#order_target_percent(context.UVXY, 0)
order_target_percent(context.SVXY, 0.5)

log.info("VIX__________ %s" % current_price)
log.info("VIX_mean_____ %s" % vix_mean)
log.info("svxy_current__ %s" % svxy_current)

# If VIX index is below sell price and xiv below xiv_30d_mean + 2.7, we sell XIV.
elif (current_price <= context.sell_price and svxy_current <= svxy_mean + 2.7):
# Sell all of our shares by setting the target position to zero
order_target_percent(context.SVXY, 0)
order_target_percent(context.TLT, 0)
log.info("Sell" )
#order_target_percent(context.UVXY, 0.1)
log.info("VIX_current____________ %s" % current_price)
log.info("VIX_mean_______________ %s" % vix_mean)
log.info("svxy_current____________ %s" % svxy_current)
log.info("svxy_mean_______________ %s" % svxy_mean)
order_target_percent(context.VXX, 1)
context.last_vxx_position = 1
log.info("VXX_Current: %s" % vxx_current)

context.sell_price = 0
context.vix_last_price = current_price

#record(Leverage=context.account.leverage)
record(VIX=current_price)
record(VXX=vxx_current)
record(SVXY=svxy_current)
There was a runtime error.

The total returns between 2011-2019 should be 134%. But the backtest result is 779.61%.

Who can tell me why?

Maybe it's bcz. you use the order_target_percent()? This means the profit will be invested again, the so-called 'compound interest'? Einstein said it is the eighth wonder of the world.

The second equity curve looks quite "ugly". :-))

This is what happened in 2017 - VIX stayed at all time lows for mostly the whole year so you got destroyed holding VXX until in Decmeber when the VIX spiked back up

@Thomas
"The total returns between 2011-2019 should be 134%. " This result is manually calculated by using the same algo as order_target_percent(). I'm afraid there is a bug.

Have to admit that the DD is too much. The algo is not practical in real trading.

I'm still holding SVXY position and totally dazed. Seems the black swan will happen at any time.

SVXY reversed split 1:4 in Sept 2018. Looks like the" svxy_current 71.28" is the 5/2 close.
but in the transaction, it buys at 6/2 lately with price 11.13.

@Vidal boreal
Buddy, you are right.
In the algo, the transaction is based on the open price.
At Feb 5th 2018, the VIX spike happened during the intraday so that the transaction actually happened in Feb 6th.

The algo avoid the Black Swan perfectly, haha...

The algo avoid the Black Swan perfectly...
Your algo avoided the black swan on Feb 5th 2018. But will your algo avoid another swan in the future?