which algorithms did well during the aug 2015 correction?

I trade a few algorithms with hard earned cash and the last week I could see the power of a good algo: a few were able to jump out of the market (damage control) and a few destroyed cash (a volatility algo that made money for marketmakers but not for me) and one made a shitload of cash. So I decided to share the one that made money as I'm sure I'm on to something but it's not perfect yet. Anyone else has some rough gem to share we can learn from? Do you see improvements in this algo?

Cheers, Peter

377
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

def initialize(context):
# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(38054): +0.98
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.98
#,sid(8554): +1.0
}
context.stock = symbol('SPY')
fetch_csv(vixUrl,
symbol='VIX',
skiprows=1,
date_column='Date',
fetch_csv(vxstUrl,
symbol='VXST',
skiprows=3,
date_column='Date',
fetch_csv(vxvUrl,
symbol='VXV',
skiprows=2,
date_column='Date',

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > 1.0):# and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -1.0):# and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
# record(VXX=longVolFactor)
# record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

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

# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

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

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

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

df=reformat_quandl(df,'VIX Close')
df=shift_quandl(df)
return df

df=reformat_quandl(df,'Close')
df=shift_quandl(df)
return df

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

Can you explain a bit what your algo does?

In essence this ago is very simple: it calculates a number that reprents the slope of the term structure of the Vix futures in the short term. If I remember correctly it's the 9 vs 30 day structure. Then via a zscore tries to find anomalies, extreme readings, and trades accordingly. It's a volatile algo but over most periods longer then a year it makes money and most of time it chooses the correct direction. Strangely enough it is currently still long volatility while I would expect it to turn to short .... Maybe more volatility to come?

The VIX complex is still pretty strongly backwardated I think, that's why XIV is still tanking while VIX is dropping. Haven't been following it closely.

I'm a relative newbie here, but could it be that this algo is using a given day's closing VXV/VIX when it trades at 9:31 (thereby peaking into the future)? Here's a backtest using a different means of timeshifting that gives different results.

27
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

def initialize(context):
# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(38054): +0.98
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.98
#,sid(8554): +1.0
}
context.stock = symbol('SPY')
fetch_csv(vixUrl,
symbol='VIX',
skiprows=1,
date_column='Date',
post_func=my_time_shift)
fetch_csv(vxvUrl,
symbol='VXV',
skiprows=2,
date_column='Date',
post_func=my_time_shift)

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > 1.0):# and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -1.0):# and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
# record(VXX=longVolFactor)
# record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

def ordering_logic(context, data):
record(leverage=context.account.leverage*100)
if 'Close' in data['VIX'] and 'Close' in data['VXV']:
vix = data['VIX']['Close']
vxv = data['VXV']['Close']
slope = (vxv/vix)
record(slope=slope*100)

# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

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

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

def my_time_shift(df):
df = df.tshift(1, freq='b')
return df

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

df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
df=reformat_quandl(df,'VIX Close')
#    df=shift_quandl(df)
return df

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

Looks like Robert is right. Stepping through this algo with the debugger I get access to current day's VIX close at market open which is then used to calculate zscore.

Peter - are you trading this live?

Thanks for sharing Peter, that's a really cool strategy. I don't have a gem to share, but I did have an algo paper trading that popped on Monday. It's an ETF arb strategy (XLF), it doesn't work as is, but I find it useful to look at its holdings for trade suggestions.

It looks like it bought XL for $5.37, which went back up to$37.00 in the next few minutes.

18
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import pandas as pd
import statsmodels.api as sm

def initialize(context):
'''
Called once at the very beginning of a backtest (and live trading).
Use this method to set up any bookkeeping variables.

The context object is passed to all the other methods in your algorithm.

Parameters

context: An initialized and empty Python dictionary that has been
augmented so that properties can be accessed using dot
notation as well as the traditional bracket notation.

Returns None
'''
set_symbol_lookup_date('2014-01-01')
context.stocks = symbols(
'WFC', 'BRK_B', 'JPM', 'BAC', 'C', 'AIG', 'USB', 'GS', 'AXP', 'SPG',
'MET', 'MS', 'PNC', 'BK', 'COF', 'AMT', 'BLK', 'PRU', 'SCHW', 'ACE',
'TRV', 'CME', 'PSA', 'STT', 'MMC', 'BBT', 'CCI', 'CB', 'MHFI',
'EQR', 'AON', 'ICE', 'AFL', 'ALL', 'DFS', 'HCN', 'AVB', 'STI',
'AMP', 'PLD', 'HIG', 'TROW', 'MCO', 'VTR', 'BXP', 'HCP', 'GGP',
'BEN', 'FITB', 'PGR', 'MTB', 'VNO', 'NTRS', 'EQIX', 'IVZ', 'WY',
'ESS', 'PFG', 'HST', 'LNC', 'RF', 'KEY', 'XL', 'L', 'MAC', 'O',
'SLG', 'AMG', 'KIM', 'CBG', 'HBAN', 'UNM', 'CINF', 'CMA', 'ETFC',
'TMK', 'PCL', 'LUK', 'NDAQ', 'AIV', 'ZION', 'IRM', 'AIZ', 'NAVI',
'PBCT', 'HCBK', 'LM', 'GNW',
# Add the other Sector ETFs
'XLP', 'XLE', 'XLK', 'XLY', 'XLU', 'XLB', 'XLF', 'XLV','XLI'
)
context.index = symbol('XLF')
context.max_z = 2
context.min_z = -2
context.date = None

def handle_data(context, data):
'''
Called when a market event occurs for any of the algorithm's
securities.

Parameters

data: A dictionary keyed by security id containing the current
state of the securities in the algo's universe.

context: The same context object from the initialize function.
Stores the up to date portfolio as well as any state
variables defined.

Returns None
'''

record_account_info(context, data)

now = get_datetime()
if now.date() == context.date:
return
context.date = now.date()

prices = history(5000, '1m', 'price', ffill=True).dropna(axis=1)
y = prices[context.index]
models = {
stock: pd.ols(y=y, x=prices[stock], intercept=True)
for stock in prices
}
fitted_x = pd.DataFrame({stock: (models[stock].beta['x'] * prices[stock] +
models[stock].beta['intercept'])
for stock in prices})

# Store the orders in a dict, this is to cancel out
# any index ETF orders with opposite signs.
bets = {stock: 0 for stock in data}
open_orders = get_open_orders()
for stock in prices:
if stock == context.index or stock in open_orders:
continue
if z[stock] > context.max_z:
bets[stock] = fitted_x[stock][-1]
bets[context.index] -= data[context.index].price
elif z[stock] < context.min_z:
bets[stock] = -fitted_x[stock][-1]
bets[context.index] += data[context.index].price
for stock in data:
order_target_value(stock, bets[stock] * context.bet_shares)

def zscore(x):
return (x - x.mean()) / x.std()

def record_account_info(context, data):
record(leverage=context.account.leverage,
exposure=context.account.net_leverage)


There was a runtime error.

Thanks for the share, and reference to the paper. I love scanning the forums first thing learn something new everyday. I back-tested it from Jan 2009 till present and the amazing results hinted at a forward looking bias.

72
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

def initialize(context):
# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(38054): +0.98
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.98
#,sid(8554): +1.0
}
context.stock = symbol('SPY')
fetch_csv(vixUrl,
symbol='VIX',
skiprows=1,
date_column='Date',
fetch_csv(vxstUrl,
symbol='VXST',
skiprows=3,
date_column='Date',
fetch_csv(vxvUrl,
symbol='VXV',
skiprows=2,
date_column='Date',

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > 1.0):# and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -1.0):# and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
# record(VXX=longVolFactor)
# record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

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

# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

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

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

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

df=reformat_quandl(df,'VIX Close')
df=shift_quandl(df)
return df

df=reformat_quandl(df,'Close')
df=shift_quandl(df)
return df

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

It looks like there is look-ahead introduced here. When using external daily data you have to shift all the values forward by one trading day if you're using the 'Close' field, otherwise you introduce bias. You could use the 'Open' field, but that data isn't posted until the end of the day either so it's still not a good idea.

I see you add a new row for the next trading day with a copy of the last data point, which I assume is so you can use it paper/live trading. That operation should sound an alarm that you need to shift all previous observations forward to match how you'd have to do it in the live scenario.

Here is the same algo using the open price rather than the close, it technically does not have look-ahead in this backtest, but this still wouldn't be possible in live trading.

92
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

def initialize(context):
# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(38054): +0.98
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.98
#,sid(8554): +1.0
}
context.stock = symbol('SPY')
fetch_csv(vixUrl,
symbol='VIX',
skiprows=1,
date_column='Date',
fetch_csv(vxstUrl,
symbol='VXST',
skiprows=3,
date_column='Date',
fetch_csv(vxvUrl,
symbol='VXV',
skiprows=2,
date_column='Date',

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))

def handle_data(context, data):
try:
lvol = context.sidsLongVol.keys()[0]
svol = context.sidsShortVol.keys()[0]

pos = context.portfolio.positions
lweight = pos[lvol].amount * data[lvol].price / context.portfolio.portfolio_value
sweight = pos[svol].amount * data[svol].price / context.portfolio.portfolio_value
record(lweight=lweight*100, sweight=sweight*100)
except:
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore > 1.0):# and (yesterday > 1.15):
shortVolFactor = 1.0
# record(Vol=-100)
elif (zscore < -1.0):# and (yesterday < 0.97):
longVolFactor = 1.0
# record(Vol=100)
# record(VXX=longVolFactor)
# record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

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

# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

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

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

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

df=reformat_quandl(df,'VIX Open')
df=shift_quandl(df)
return df

df = df.rename(columns={'Close': 'Close_'})
df=reformat_quandl(df,'Open')
df=shift_quandl(df)
return df

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

My solution to this problem (and the problem of getting historical values) for downloaded CSV files is here: https://www.quantopian.com/posts/function-like-history-but-for-fetch-csv-data

Yeah, lookahead-bias unfortunately. I've been running a similar VXV-VXMT algo and it got destroyed pretty badly the past days (in Q paper trading). The difference between entering at close and the next open is gigantic. Enter at close (via backtest) and the algo works great, enter at the next open and you whipsaw your way down. Maybe a real-time fetcher could fix that problem - or live futures data.

(Is there a better way to share live-trading results other than screenshots?)

Backtest

Live

value did well during the correction

Yes I know there is lookahead bias (I forgot and should have mentioned), but lookahead bias is only relevant when backtesting, not when live trading. I trade the algo live with a workaround (I let the algo take a decision in paper trading and then I trade it by hand because of limitations of the quantopian platform for cash-only accounts) and I can only say from my brokerage account: I did well. This is the paper-trading screenshot:

So yes I did not make a 300% like the backtest says , but only 180 something.... Lookahead bias or not, it seems to work when trading live....

and this is the one that bombed:

This algo does not have any forward looking bias and works a bit different (It takes skew and kurtosis as signal combined with a comparison implied vs historical volatility), but .... it lost 30% in a short time. The rejected orders you see I solve by trading them manual in a connected account... its a bit of a work around as there is no easy way to trade in an account that takes 3 days to settle ;(

I would really love to solve for this lookahead bias thing ... for now I'll leave my algo trading as it is ;)

Hi Peter - are you saying that the algorithm in your first paper trading screenshot above is exactly the same algorithm you posted at the beginning of this thread?

Hey Peter,
This version should have the look-ahead issue solved. Rather than shifting just the last data point forward to the next trading day, I shift all of them. I'm pretty sure this will work for live trading without further work-arounds. The changes are all in the reformat_quandl function.

I would look into applying for a margin account, IB really doesn't ask questions and approves most accounts. The cash settlement issue makes algorithmic trading much more cumbersome.

92
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

def initialize(context):
# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(38054): +0.98
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.98
#,sid(8554): +1.0
}
context.stock = symbol('SPY')
fetch_csv(vixUrl,
symbol='VIX',
skiprows=1,
date_column='Date',
fetch_csv(vxstUrl,
symbol='VXST',
skiprows=3,
date_column='Date',
fetch_csv(vxvUrl,
symbol='VXV',
skiprows=2,
date_column='Date',

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > 1.0):# and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -1.0):# and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
# record(VXX=longVolFactor)
# record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

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

# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

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

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

df = reformat_quandl(df,'VIX Close')
return df

df = reformat_quandl(df,'Close')
return df

df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
df = reformat_quandl(df,'CLOSE')
return df

idx = tdays.searchsorted(normalized_dt)
return tdays[idx + 1]


There was a runtime error.

Yes, I keep coming back to strategies like this, but the lag in fetched VIX data is killer. I might actually revive my scala trading platform and see how it backtests there, with live VIX data.

Simon.

@ Simon, I 'm waiting for Futures, than the whole cumbersome fetcher stuff should be history
@matt, yes indeed, changed nothing
@David... when you live i Australia you are not allowed to have a margin account unless you had one before a certain point, at one point I have to get around that via European accounts

I don't think futures will necessarily include CBOE indices...?

 but lookahead bias is only relevant when backtesting, not when live trading. I trade the algo live with a workaround (I let the algo take a decision in paper trading


Peter,
I do not understand the how the lookahead bias is solved here. what do you mean " let the algo take a decision in paper trading". how do you workaround?
what is the real problem?

I added slippage protection to the original algo

103
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

def initialize(context):
# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(38054): +0.98
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.98
#,sid(8554): +1.0
}
context.stock = symbol('SPY')
fetch_csv(vixUrl,
symbol='VIX',
skiprows=1,
date_column='Date',
fetch_csv(vxstUrl,
symbol='VXST',
skiprows=3,
date_column='Date',
fetch_csv(vxvUrl,
symbol='VXV',
skiprows=2,
date_column='Date',

# default commissions and slippage
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > 1.0):# and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -1.0):# and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
# record(VXX=longVolFactor)
# record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

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

# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

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

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

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

df=reformat_quandl(df,'VIX Close')
df=shift_quandl(df)
return df

df=reformat_quandl(df,'Close')
df=shift_quandl(df)
return df

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

2015-08-26_pvr_:172INFO Profited 124,937,253 on 40,327,290 activated/transacted for PvR of 309.8%

@Peter - I noticed the algo uses leverage of 100. That seems ridiculously high...maybe I'm misinterpreting things, but isn't that unrealistic? Have you tried the algo with little to no leverage?

I just normalise all numbers to the -100/+100 band, so when it reads 100, its actually 1.

Peter: I too have live traded your algo and it works great. Any chance you have it migrated to Quantopian2?

Macro Investor's solution for removing the look ahead bias appears to be clean.
I like that it occurs at the time of fetching so that the data cannot be misapplied before being shifted.
Is there a problem with the approach reproduced below for VIX?

def rename_col0(df0):
df0 = df0.rename(columns={'Close': 'price'})
df0 = df0.fillna(method='ffill')
# Shifting data by one day to avoid forward-looking bias
return df0.shift(1)

def initialize(context):
# Pulling spot VIX
fetch_csv('https://www.quandl.com/api/v3/datasets/YAHOO/INDEX_VIX.csv',
date_column='Date',
date_format='%Y-%m-%d',
symbol='v',
post_func=rename_col0)

That works in backtest, but in live trading, you'll be ignoring the most recent value of VIX, since it got shifted off of the end. The code I use is in add_last_bar in https://www.quantopian.com/posts/function-like-history-but-for-fetch-csv-data . Note there's a bunch of other code in there for saving the history of the fetched values safely, if you don't need that, you don't need that.

That's exactly what happened to the algo when I tried to paper trade it and I am trying to fix it now.

Thanks Simon

For now for live trading I am keeping the shift to 0, but for backtesting I am keeping the shift to 1, for minimal coding effort. Does Quantopian have a flag that indicates if the algo is backtesting to live trading? Then I could use the same code and just use the flag to shift by either 0 (live) or 1 (backtesting).

@Peter B - on your original algo, did you figure out a way to limit the quantity of shares purchased? I'm sure many of you have noticed that the algo buys an immense number of shares and would not be able to liquidate such a high number with your normal broker - it would require a dark pool or off-exchange type of access for such high amounts of dumping. Do you have any idea of how you might be able to fix this? I am running a blank..

can anyone update to Q2? I'm at a loss. I keep getting an error that it is having trouble staring the backtest.

I'm completely new to this, but I'm working on the update to Q2. If I'm able to conjure something useful, I'll post here so someone else can check it out.

Peter, have you (or anybody else) migrated this to Q2?

I'm having a hard time changing moving lines 83-87.

    if 'Close' in data['VIX'] and 'Close' in data['VXV']:
vix = data['VIX']['Close']
#        temp = data.fetcher_assets
#vxst = data['VXST']['Close']
vxv = data['VXV']['Close']


Quantopian recommends using data.current to replace data[sid] but that won't work in this case because of the way this data was brought in from fetcher.

Sorry I haven't: I don't use fetcher in live trading anymore as it's notoriously unreliabe

What problems have you been having? I've been fetching CBOE stuff reliably for months.

when we make changes per Q backtest doesn't generate algorithm trade. the problem is with using data vs data.current rather than CBOE. please see below.

if 'Close' in data['VIX'] and 'Close' in data['VXV']:
vix = data['VIX']['Close']
#vxst = data['VXST']['Close']
vxv = data['VXV']['Close']

Sorry. Project kinda got dropped after my post a few months ago. This is the first and only algorithm I've attempted to work on; is the data not available internally within Quantopian? Is the fetcher the only usable method?

I do not understand the how the lookahead bias is solved here. what do you mean " let the algo take a decision in paper trading". how do you workaround?
what is the real problem?

I'm with Jeff...
I still can't grasp how those returns would be possible in real life with this strategy. Willing to let us in on some insight to how you made this happen?

I get a log when live trading that states "(ROBINHOOD) reports a holding of 0.0 shares of NBGGY; NBGGY not in Quantopian database; NBGGY not added to Quantopian blotter". The algo works when paper trading, but not live trading. How would I fix this to work with Robinhood?

That error gets logged when your brokerage account holds positions that we do not have available in the Quantopian database. If your account holds different asset classes (options, futures, fx) or securities that we don't support (like this OTC position) the security will get ignored because it can't be mapped.

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

I guess this leaves me a little more confused then. So I have identical algorithms running and I started them on the same date. The paper traded one has conducted trades, but the live one has not. Why would this happen?

You mentioned that your algo was trading with Robinhood, which leads me to believe you were trading with real money. Since Robinhood don't have paper trading accounts, I'm assuming you were using Quantopian paper trading to test your strategy.

When you use our paper trading, the algo assumes there are no existing positions in the account. There is no broker to connect to. Thus, this log message wouldn't have triggered.

Okay I understand. I am just confused why the live trading algo did not make any trades, but the paper trading algo completed trades. The only reason I am questioning this is because they are identical and I started them at the same time. So I assume that they should both be trading similar.

There could be a few pieces at play:

• Is the capital base identical between your Robinhood account and Quantopian paper algo?
• Was the account fully funded and the capital was available for trading?
• Was your Robinhood algo logged in at the expected trading time?

I have been studying this algo for sometime. A concern of mine has to do with how the algo executes buy orders. This algo sets market buys and tries to buy as much as possible. There could be an issue if the algo tries to execute a buy at lets say $32 and the price of a stock goes up to$32.10. If there is not enough extra cash in the brokerage account to cover the difference then the order will be rejected.

So is there finally a version of the Algo that can be paper trade automatically? thanks

This is a clone of Daniel's February post with slippage prevention, with a couple line edited to make Q2 happy, for what it's worth.
Simply referencing another algo. Nothing innovative, might not be implemented correctly...check me. But the backtest runs, so figured I'd drop it here for anyone to continue on.

Edits were made in the ordering logic section, where the deprecation warnings were thrown.

61
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Inspired by Risk Premia and the VIX Term Structure
# by Travis L. Johnson
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2548050
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

def initialize(context):
# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(38054): +0.98
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.98
#,sid(8554): +1.0
}
context.stock = symbol('SPY')
fetch_csv(vixUrl,
symbol='VIX',
skiprows=1,
date_column='Date',
fetch_csv(vxstUrl,
symbol='VXST',
skiprows=3,
date_column='Date',
fetch_csv(vxvUrl,
symbol='VXV',
skiprows=2,
date_column='Date',

# default commissions and slippage
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > 1.0):# and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -1.0):# and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
# record(VXX=longVolFactor)
# record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

def ordering_logic(context, data):
record(leverage=context.account.leverage*.50)
#if 'Close' in data['VIX'] and 'Close' in data['VXV']:
vix = data.current('VIX','Close')
#vxst = data['VXST']['Close']
vxv = data.current('VXV','Close')
#slope = (vix/vxst)
slope = (vxv/vix)
record(slope=slope*100)
# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

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

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

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

df=reformat_quandl(df,'VIX Close')
df=shift_quandl(df)
return df

df=reformat_quandl(df,'Close')
df=shift_quandl(df)
return df

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

Nick, did you overcome the very apparent look ahead bias that others and I pointed out and tried to reiterate? There is no point in looking at this otherwise.

Has anyone tried live trading these algos? They trade fine in paper trading. I am just not certain how it would perform live.

The data is now pulled internally. Results are very different as expected.

20
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.factors import CustomFactor
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv

def initialize(context):
my_pipe = Pipeline()
attach_pipeline(my_pipe, 'my_pipeline')

# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(41969): +0.94
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.94
#,sid(8554): +1.0
}

# default commissions and slippage
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))
class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

"""
Called every day before market open.
"""
context.output = pipeline_output('my_pipeline')
#context.vix = context.output["VixOpen"].iloc[0]
context.vix = context.output["VixClose"].iloc[0]
#context.vix = context.output["VixHigh"].iloc[0]
context.vxv = context.output["VxvClose"].iloc[0]

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > -70): # and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -105): # and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
#record(VXX=longVolFactor)
#record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

def ordering_logic(context, data):
record(leverage=context.account.leverage*.50)

vix = context.vix
print(vix, 'vix')

vxv = context.vxv
print(vxv, 'vxv')

slope = (vix/vxv)
record(slope=slope*100)
# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

def rebalance(sids,data,factor):
for sid in sids:

order_target_percent(sid, sids[sid]*factor)


There was a runtime error.

I guess because this algorithm uses history to make future decisions, a backtest from 1/1/2012 forward using the same code looks like this:

3
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import scipy.stats as stats
import datetime
import pytz
import pandas as pd
import re
from pandas import DataFrame,Series

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.factors import CustomFactor
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv

def initialize(context):
my_pipe = Pipeline()
attach_pipeline(my_pipe, 'my_pipeline')

# Define the instruments in the portfolio:
context.sidsLongVol = {
sid(41969): +0.94
#,sid(32268): +1.0
}
context.sidsShortVol = {
sid(40516): +0.94
#,sid(8554): +1.0
}

# default commissions and slippage
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

# Define the benchmark (used to get early close dates for reference).
context.spy = sid(8554)

start_date = context.spy.security_start_date
end_date   = context.spy.security_end_date

# Get the dates when the market closes early:
context.early_closes = get_early_closes(start_date, end_date).date
context.slopes = Series()

schedule_function(ordering_logic,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))
class GetVIX(CustomFactor):
window_length = 1
def compute(self, today, assets, out, vix):
out[:] = vix[-1]

"""
Called every day before market open.
"""
context.output = pipeline_output('my_pipeline')
#context.vix = context.output["VixOpen"].iloc[0]
context.vix = context.output["VixClose"].iloc[0]
#context.vix = context.output["VixHigh"].iloc[0]
context.vxv = context.output["VxvClose"].iloc[0]

def handle_data(context, data):
pass

def factors(slopes):
longVolFactor = 0.0
shortVolFactor = 0.0
if (len(slopes) >= 3):
yesterday = slopes.iloc[-2]
pscore = stats.percentileofscore(slopes,yesterday, "rank")
record(rank=((pscore-50)*2))
zscore =  stats.zscore(slopes, axis=0, ddof=1)[-1]
record(zeeskore=zscore*100)
if (zscore*100 > -70): # and (yesterday > 1.15):
shortVolFactor = 1.0
record(Vol=-100)
elif (zscore*100 < -105): # and (yesterday < 0.97):
longVolFactor = 1.0
record(Vol=100)
#record(VXX=longVolFactor)
#record(XIV=shortVolFactor)
return (longVolFactor, shortVolFactor)

def ordering_logic(context, data):
record(leverage=context.account.leverage*.50)

vix = context.vix
print(vix, 'vix')

vxv = context.vxv
print(vxv, 'vxv')

slope = (vix/vxv)
record(slope=slope*100)
# Get the current exchange time, in local timezone:
exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

context.slopes = context.slopes.append(Series(slope,index=[exchange_time]))

(longVolFactor, shortVolFactor) = factors(context.slopes)
rebalance(context.sidsLongVol, data, longVolFactor)
rebalance(context.sidsShortVol, data, shortVolFactor)

def rebalance(sids,data,factor):
for sid in sids:

order_target_percent(sid, sids[sid]*factor)


There was a runtime error.