Hello Everyone,

I have a few problems with my algorithm. I just recently started to code.

1. I was trying to implement a hedge spread ratio using OLS regression, but no orders were executed.
2. Often times the program has run time errors, such as nontype for context.slope[t], even in context.zscore[t].
3. All I aim for is to get the zscore based on the hedge spread ratio and use it as a trading signal.
4. Without the zscore part, the program would trade and cover position like it should do.

I am sure there are more problems in the codes, please help me to solve my problems!

Sincerely,
Terence.

9
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 math
import numpy as np
import datetime as dt
import statsmodels.tsa.stattools as ts
import statsmodels.api as sm
import pytz
import pandas as pd

window_length=15

def initialize(context):
context.std_multiplier = 2.5
context.Pair_list =  [
[sid(46216), sid(46220)],#last-1
[sid(14517), sid(14516)],#last
[sid(5484), sid(6119)],
[sid(24), sid(5773)],
[sid(24), sid(7671)],
[sid(20088), sid(1335)],
[sid(12691), sid(698)],
[sid(46631),sid(32146)],
[sid(46222), sid(46221)],
[sid(46217),sid(46223)],
[sid(46170), sid(46220)],
[sid(46170), sid(46222)],
[sid(46216), sid(46222)],
[sid(46216), sid(46220)]
]

context.bounds=[None]*len(context.Pair_list)
context.avg_ratio_data=[None]*len(context.Pair_list)
context.current_ratio=[None]*len(context.Pair_list)
context.high_bound=[None]*len(context.Pair_list)
context.low_bound=[None]*len(context.Pair_list)
context.vwap1= [None]*len(context.Pair_list)
context.vwap2= [None]*len(context.Pair_list)
context.mavg1= [None]*len(context.Pair_list)
context.mavg2= [None]*len(context.Pair_list)
context.slope= [None]*len(context.Pair_list)
context.zscore= [None]*len(context.Pair_list)

def compute_zscore(context, data, t):

price_history = history(15, '1d', 'price')
price_history = price_history.fillna(method='ffill')
slope=ols_transform(price_history, context.Pair_list[t][0], context.Pair_list[t][1])
spread = data[context.Pair_list[t][0]].price - (slope * data[context.Pair_list[t][1]].price)

return zscore

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
price_history = history(15, '1d', 'price')
price_history = price_history.fillna(method='ffill')

for t in range(0,(len(context.Pair_list))):
#t=0
#while(t<len(context.Pair_list)):
if context.Pair_list[t][0] not in data or context.Pair_list[t][1] not in data:
return
context.vwap1[t] = data[context.Pair_list[t][0]].vwap(5)
context.vwap2[t] = data[context.Pair_list[t][1]].vwap(5)
context.mavg1[t] = data[context.Pair_list[t][0]].mavg(5)
context.mavg2[t] = data[context.Pair_list[t][1]].mavg(5)

context.current_ratio[t] = data[context.Pair_list[t][0]].price / data[context.Pair_list[t][1]].price
context.avg_ratio_data[t] = avg_ratio(price_history, context,context.Pair_list[t][0],context.Pair_list[t][1],t)

if context.avg_ratio_data[t] is None:
return

if context.bounds[t] is None:
return
context.high_bound[t], context.low_bound[t] = context.bounds[t]

#context.slope[t] = Ols(price_history, context, context.Pair_list[t][0], context.Pair_list[t][1], t)
"""
if context.slope[t] is None:
return
context.slope[t] = ols_transform(price_history, context.Pair_list[t][0], context.Pair_list[t][1])

return

return
"""
if context.zscore[t] is None:
return
context.zscore[t]=compute_zscore(context,data,t)

#Buys the undervalued stock and sells the overvalues stock
if (context.current_ratio[t] > context.high_bound[t] and context.last_trade[t] is not "high") or (context.zscore[t] > 1):
# if context.vwap1[t] < data[context.Pair_list[t][0]].price and context.vwap2[t] < data[context.Pair_list[t][1]].price:
order_value(context.Pair_list[t][1], +(context.portfolio.portfolio_value/(2*len(context.Pair_list))))
order_value(context.Pair_list[t][0], -(context.portfolio.portfolio_value/(2*len(context.Pair_list))))
elif context.current_ratio[t] < context.low_bound[t] and context.last_trade[t] is not "low" or (context.zscore[t] < 1):
# if context.vwap1[t] > data[context.Pair_list[t][0]].price and context.vwap2[t] > data[context.Pair_list[t][1]].price:
order_value(context.Pair_list[t][0], +(context.portfolio.portfolio_value/(2*len(context.Pair_list))))
order_value(context.Pair_list[t][1], -(context.portfolio.portfolio_value/(2*len(context.Pair_list))))
elif context.last_trade[t] is not "mid" and context.last_trade[t] is "high" and context.current_ratio[t] < context.avg_ratio_data[t]:
order_target(context.Pair_list[t][1], 0)
order_target(context.Pair_list[t][0], 0)
elif context.last_trade[t] is not "mid" and context.last_trade[t] is "low" and context.current_ratio[t] > context.avg_ratio_data[t]:
order_target(context.Pair_list[t][1], 0)
order_target(context.Pair_list[t][0], 0)

record(value=context.portfolio.positions_value)

#@batch_transform(window_length=15, refresh_period=1)
"""
def Ols(prices,context,sid0,sid1,t):

prices = prices.fillna(method='bfill')
sid0=context.Pair_list[t][0]
sid1=context.Pair_list[t][1]

p0 = prices[sid0].values
p1 = prices[sid1].values

return sm.OLS(p0, p1).fit().params

#@batch_transform(window_length=15, refresh_period=1)
"""

def ols_transform(prices, sid1, sid2):
"""
Computes regression coefficient (slope and intercept)
via Ordinary Least Squares between two SIDs.
"""
p0 = prices[sid1]

slope=sm.OLS(p0, p1).fit().params
return slope
def avg_ratio(prices,context,sid0,sid1,t):

prices = prices.fillna(method='bfill')
sid0=context.Pair_list[t][0]
sid1=context.Pair_list[t][1]

p0 = prices[sid0].values
p1 = prices[sid1].values

p0_arr = np.array(p0)
p1_arr = np.array(p1)

if p0_arr is None or p1_arr is None:
return None

return (p0_arr/p1_arr).mean()

#@batch_transform(window_length=15, refresh_period=1)

prices = prices.fillna(method='bfill')
sid0=context.Pair_list[t][0]
sid1=context.Pair_list[t][1]

p0 = prices[sid0].values
p1 = prices[sid1].values

p0_arr = np.array(p0)
p1_arr = np.array(p1)

if p0_arr is None or p1_arr is None:
return None

std_amt = (p0_arr/ p1_arr).std()*context.std_multiplier

high_bound = mean + std_amt
low_bound = mean - std_amt

return (high_bound, low_bound)

# elif context.vwap1 > data[context.Pair_list[t][0]] and context.last_trade[t] is "low"
# and context.current_ratio[t] > context.avg_ratio_data[t]

# elif context.vwap2 > data[context.Pair_list[t][1]] and context.last_trade[t] is "low"
# and context.current_ratio[t] > context.avg_ratio_data[t]

# elif context.vwap1 < data[context.Pair_list[t][0]] and context.last_trade[t] is "high"
# and context.current_ratio[t] < context.avg_ratio_data[t]

# elif context.vwap2 < data[context.Pair_list[t][1]] and context.last_trade[t] is "high"
# and context.current_ratio[t] < context.avg_ratio_data[t]


There was a runtime error.
18 responses

I was trying to something similar but my Python knowledge got on the way. Will be also interested in the algorithm when done.

Partly experimental code:

import numpy as np
import pandas as pd
import collections
import copy

from sklearn import linear_model

def initialize(context):
set_commission(commission.PerShare(0.01))
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.01))
c = context
c.SPDR = {}

c.SPDR['index'] = [symbol('SPY')]
c.SPDR['sectors'] = [symbol('XLY'), symbol('XLP'), symbol('XLE'), symbol('XLF'), symbol('XLV'), symbol('XLI'), symbol('XLB'), symbol('XLK'), symbol('XLU')]

c.secs = c.SPDR['index'] + c.SPDR['sectors']
c.to_200 = pd.DataFrame({i : np.linspace(1, 200, 200) for i in c.secs})
c.to_100 = pd.DataFrame({i : np.linspace(1, 100, 100) for i in c.secs})
c.to_050 = pd.DataFrame({i : np.linspace(1, 50, 50) for i in c.secs})
c.to_030 = pd.DataFrame({i : np.linspace(1, 30, 30) for i in c.secs})
c.to_015 = pd.DataFrame({i : np.linspace(1, 15, 15) for i in c.secs})
c.to_007 = pd.DataFrame({i : np.linspace(1, 7, 7) for i in c.secs})
c.regr = linear_model.LinearRegression()
c.numsecs = float(len(c.secs))
c.margine = 0.30
def handle_data(context, data):
c = context
p200 = history(200, '1d', 'price', ffill=True)
p100 = p200.iloc[-100:, :]
p050 = p200.iloc[-50:, :]
p030 = p200.iloc[-30:, :]
p015 = p200.iloc[-15:, :]
p007 = p200.iloc[-7:, :]
if len(p200.columns) != len(c.secs) or \
len(p100.columns) != len(c.secs) or \
len(p050.columns) != len(c.secs) or \
len(p030.columns) != len(c.secs) or \
len(p015.columns) != len(c.secs) or \
len(p007.columns) != len(c.secs) or \
len(data) != len(c.secs):
log.warn('No complete data')
return
lnp200 = np.log(p200)
lnp100 = lnp200.iloc[-100:, :]
lnp050 = lnp200.iloc[-50:, :]
lnp030 = lnp200.iloc[-30:, :]
lnp015 = lnp200.iloc[-15:, :]
lnp007 = lnp200.iloc[-7:, :]

def sgn(x):
return 0.0 if x is None or x == 0.0 else np.copysign(1.0, x)
def initiate_trade(s, w, force = False):
poss = float(c.portfolio.positions[s].amount)
oo = get_open_orders(s)
if force and len(oo) == 0:
order_target_percent(s, w)
elif poss == 0.0 and w != 0.0:
[cancel_order(i) for i in oo]
order_target_percent(s, w)
elif sgn(poss) != sgn(w):
[cancel_order(i) for i in oo]
order_target_percent(s, w)

c.to_200.index = lnp200.index
c.to_100.index = lnp100.index
c.to_050.index = lnp050.index
c.to_030.index = lnp030.index
c.to_015.index = lnp015.index
c.to_007.index = lnp007.index
regc200 = { i : c.regr.fit(pd.DataFrame(c.to_200[i], columns = [i]), pd.DataFrame(lnp200[i], columns = [i]), -1).coef_[-1][-1] for i in c.secs }
regc100 = { i : c.regr.fit(pd.DataFrame(c.to_100[i], columns = [i]), pd.DataFrame(lnp100[i], columns = [i]), -1).coef_[-1][-1] for i in c.secs }
regc050 = { i : c.regr.fit(pd.DataFrame(c.to_050[i], columns = [i]), pd.DataFrame(lnp050[i], columns = [i]), -1).coef_[-1][-1] for i in c.secs }
regc030 = { i : c.regr.fit(pd.DataFrame(c.to_030[i], columns = [i]), pd.DataFrame(lnp030[i], columns = [i]), -1).coef_[-1][-1] for i in c.secs }
regc015 = { i : c.regr.fit(pd.DataFrame(c.to_015[i], columns = [i]), pd.DataFrame(lnp015[i], columns = [i]), -1).coef_[-1][-1] for i in c.secs }
regc007 = { i : c.regr.fit(pd.DataFrame(c.to_007[i], columns = [i]), pd.DataFrame(lnp007[i], columns = [i]), -1).coef_[-1][-1] for i in c.secs }

dsig200 = { i : regc200[i] for i in c.secs }
dsig100 = { i : regc100[i] for i in c.secs }
dsig050 = { i : regc050[i] for i in c.secs }
dsig030 = { i : regc030[i] for i in c.secs }
dsig015 = { i : regc015[i] for i in c.secs }
dsig007 = { i : regc007[i] for i in c.secs }

sig200 = { i : regc200[i] - regc200[c.SPDR['index'][-1]] for i in c.secs }
sig100 = { i : regc100[i] - regc100[c.SPDR['index'][-1]] for i in c.secs }
sig050 = { i : regc050[i] - regc050[c.SPDR['index'][-1]] for i in c.secs }
sig030 = { i : regc030[i] - regc030[c.SPDR['index'][-1]] for i in c.secs }
sig015 = { i : regc015[i] - regc015[c.SPDR['index'][-1]] for i in c.secs }
sig007 = { i : regc007[i] - regc007[c.SPDR['index'][-1]] for i in c.secs }
sig200[c.SPDR['index'][-1]] = 0.0
sig100[c.SPDR['index'][-1]] = 0.0
sig050[c.SPDR['index'][-1]] = 0.0
sig030[c.SPDR['index'][-1]] = 0.0
sig015[c.SPDR['index'][-1]] = 0.0
sig007[c.SPDR['index'][-1]] = 0.0

dw200 = { i : sgn(dsig200[i]) / c.numsecs for i in c.secs }
dw100 = { i : sgn(dsig100[i]) / c.numsecs for i in c.secs }
dw050 = { i : sgn(dsig050[i]) / c.numsecs for i in c.secs }
dw030 = { i : sgn(dsig030[i]) / c.numsecs for i in c.secs }
dw015 = { i : sgn(dsig015[i]) / c.numsecs for i in c.secs }
dw007 = { i : sgn(dsig007[i]) / c.numsecs for i in c.secs }

w200 = { i : sgn(sig200[i]) / c.numsecs for i in c.secs }
w100 = { i : sgn(sig100[i]) / c.numsecs for i in c.secs }
w050 = { i : sgn(sig050[i]) / c.numsecs for i in c.secs }
w030 = { i : sgn(sig030[i]) / c.numsecs for i in c.secs }
w015 = { i : sgn(sig015[i]) / c.numsecs for i in c.secs }
w007 = { i : sgn(sig007[i]) / c.numsecs for i in c.secs }
w200[c.SPDR['index'][-1]] = 0.0
w100[c.SPDR['index'][-1]] = 0.0
w050[c.SPDR['index'][-1]] = 0.0
w030[c.SPDR['index'][-1]] = 0.0
w015[c.SPDR['index'][-1]] = 0.0
w007[c.SPDR['index'][-1]] = 0.0
w200[c.SPDR['index'][-1]] = -np.sum(w200.values())
w100[c.SPDR['index'][-1]] = -np.sum(w100.values())
w050[c.SPDR['index'][-1]] = -np.sum(w050.values())
w030[c.SPDR['index'][-1]] = -np.sum(w030.values())
w015[c.SPDR['index'][-1]] = -np.sum(w015.values())
w007[c.SPDR['index'][-1]] = -np.sum(w007.values())
#w200[c.SPDR['index'][-1]] += sgn(dw200[c.SPDR['index'][-1]])
#w100[c.SPDR['index'][-1]] += sgn(dw100[c.SPDR['index'][-1]])
#w050[c.SPDR['index'][-1]] += sgn(dw050[c.SPDR['index'][-1]])
#w030[c.SPDR['index'][-1]] += sgn(dw030[c.SPDR['index'][-1]])
#w015[c.SPDR['index'][-1]] += sgn(dw015[c.SPDR['index'][-1]])
#w007[c.SPDR['index'][-1]] += sgn(dw007[c.SPDR['index'][-1]])
w = { i : np.array([w007[i], w015[i] / 2., w030[i] / 4., w050[i] / 8., w100[i] / 16., w200[i] / 32.]) for i in c.secs }
dw = { i : np.array([dw007[i], dw015[i] / 2., dw030[i] / 4., dw050[i] / 8., dw100[i] * 16., dw200[i] / 32.]) for i in c.secs }
#fw = { i : dw007[i] * (1 + c.margine) if sgn(np.sum(w[i]) + np.sum(dw[i])) == dw007[i] else dw007[i] for i in c.SPDR['sectors'] }
#fw.update({ i : dw007[i] * (1 + c.margine) if sgn(np.sum(w[i]) + np.sum(dw[i])) == dw007[i] else dw007[i] for i in c.SPDR['index'] })
fw = { i : (sgn(np.sum(dw[i] + w[i])) / c.numsecs) if abs(np.sum(dw[i] + w[i])) > (1. / c.numsecs) else 0.0 for i in c.secs }
for i in c.secs:
wsum = np.sum(np.abs(fw.values()))
fw[i] = (fw[i] / wsum) if wsum != 0.0 else fw[i]
[initiate_trade(i, fw[i]) for i in c.SPDR['sectors']]
[initiate_trade(i, fw[i]) for i in c.SPDR['index']]



Terence,
Do you think you could get this strategy working (or close to it) with a single pair? If so, I could help you expand the model to support several pairs. You can check out this post to get an idea of how we would go about it. This kind of strategy will be much less messy to code if you take a class based approach. It will also be easier to pass different settings to different pairs and to know that everything is working properly. I know you are pretty new to programming, but Python's bread and butter is in it's object oriented features, it's never too early to introduce yourself to them.

EDIT:
At first glance I noticed two things.

1. In lines 68, 78, 82, and 100 you use 'return' to kick out of the for loop. My guess is that you just want to skip that pair and move onto the next one. You will want to change the 'return' to 'continue' if that's the case. Using return will kick you completely out of the handle_data function, if the first pair triggers that, the rest of them don't get checked. Switching to 'continue' will make the loop stop processing that pair and move onto the next one.

2. Your ols_transform is returning the slope and intercept of the OLS regression. That is probably messing up later calculations. See below for returning the slope only

# Select the slope only
slope = sm.OLS(p0, p1).fit().params[sid2]

# return both the slope and intercept separately
intercept, slope = sm.OLS(p0, p1).fit().params


David

Hello David,

Thank you for your feedback! I wrote something with the class you provided. I figure my code is really messy and I'd restart it again. However, I am really stuck at line 33 and line 83. The reason why it doesn't run is because line 83 has an error called:
"Runtime exception: AttributeError: 'float' object has no attribute '_data'"

The program ran but it did not generate any trade, perhaps because there was no data. I would be grateful if you can tell me a bit on how I should resolve the problem with line 83.

Terence

Here is the code:
import math
import numpy as np
import scipy as sp
import datetime as dt
import statsmodels.tsa.stattools as ts
import statsmodels.api as sm

window_length = 30
lev = 1.2

def initialize(context):
context.std_multiplier = 1.5
context.pairs =[ [sid(24), sid(32146)],
[sid(46216), sid(46220)],
[sid(14517), sid(14516)],
[sid(5484), sid(6119)],
[sid(24), sid(5773)],
[sid(46170), sid(46220)],
[sid(46170), sid(46222)],
[sid(46216), sid(46222)],
[sid(46216), sid(46220)]
]

# Set the allocation per stock
pct_per_algo = 1.0 / (2*len(context.pairs))

# Make a separate algo for each stock.


def handle_data(context, data):
for algo in context.algo:
algo.handle_data(context, data)

# Initialize with a single stock and assign a proportion of the account.
def __init__(self, ticker1, ticker2, allocation):
self.ticker1 = ticker1
self.ticker2 = ticker2
self.allocation = allocation

@batch_transform(window_length=30, refresh_period=1)
def avg_ratio(self, price1, price2, datapanel):

price1 = datapanel['price'][self.ticker1]
price2 = datapanel['price'][self.ticker2]
sid1_arr = np.array(price1)
sid2_arr = np.array(price2)

if sid1_arr is None or sid2_arr is None:
return None

return (sid1_arr/sid2_arr).mean()

@batch_transform(window_length=30, refresh_period=1)
def bounds(self, price1, price2, mean, context, datapanel):

price1 = datapanel['price'][self.ticker1]
price2 = datapanel['price'][self.ticker2]
sid1_arr = np.array(price1)
sid2_arr = np.array(price2)

if sid1_arr is None or sid2_arr is None:
return None

std_amt = (sid1_arr/sid2_arr).std()*context.std_multiplier
high_bound = mean + std_amt
low_bound = mean - std_amt

return (high_bound, low_bound)

def handle_data(self, context, data):

cash = context.portfolio.portfolio_value/(2*len(context.pairs))
price1 = data[self.ticker1].price
price2 = data[self.ticker2].price

current_ratio = price1/price2
avgratio = self.avg_ratio(price1, price2, data)
bounds= self.bounds(price1, price2, avgratio, context, data)
high_bound, low_bound = bounds

if current_ratio > high_bound and last_trade is not "high":
order_value(self.ticker2, +lev*cash)
order_value(self.ticker1, -lev*cash)
elif current_ratio < low_bound and last_trade is not "low":
order_value(self.ticker1, +lev*cash)
order_value(self.ticker2, -lev*cash)
elif last_trade is not "mid" and last_trade is "high" and current_ratio < avgratio:
order_target(self.ticker1, 0)
order_target(self.ticker2, 0)
elif last_trade is not "mid" and last_trade is "low" and current_ratio > avgratio:
order_target(self.ticker1, 0)
order_target(self.ticker2, 0)
"""


def __init__(self, ticker1, ticker2, allocation):
self.ticker1 = ticker1
self.ticker2 = ticker2
self.allocation = allocation

def ols(self, prices, price1, price2):

p0 = prices[price1]
slope=sm.OLS(p0, p1).fit().params[0]
return slope

def zscore(self, price1, price2, slope, context):

slope = self.ols(prices, price1, price2)
spread = price1 - (slope * price2)
return zscore

def handle_data(self, context, data):

cash = context.portfolio.portfolio_value/(2*len(context.pairs))
slope = self.ols(prices, price1, price2)
zscore = self.zscore(price1, price2, slope, context)

if zscore > 2:
order_value(self.ticker2, +lev*cash)
order_value(self.ticker1, -lev*cash)
elif zscore < -2:
order_value(self.ticker1, +lev*cash)
order_value(self.ticker2, -lev*cash)
elif zscore < 0.5 and zscore > -0.5:
order_target(self.ticker1, 0)
order_target(self.ticker2, 0)

"""


If you format the code  it would be easy. Use 3 instead of 4. Ctrl + K also does the formatting.

I cleaned up your version and got it running, there is probably some conflicting orders going on because you have the same stock in several pairs. You will want to check your accounting, maybe aggregate the orders so that you can submit as few as possible.

It looks to trade every 30 min and I switched you from batch_transform back to history because batch_transform is being depreciated in favor of history. Feel free to message me if you have any questions.

David

7
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 math
import numpy as np
import scipy as sp
import datetime as dt
import statsmodels.tsa.stattools as ts
import statsmodels.api as sm

window_length = 30
lev = 1.2

def initialize(context):
context.std_multiplier = 1.5
context.pairs =[
[sid(24),    sid(32146)],
[sid(46216), sid(46220)],
[sid(14517), sid(14516)],
[sid(5484),  sid(6119)],
[sid(24),    sid(5773)],
[sid(46170), sid(46220)],
[sid(46170), sid(46222)],
[sid(46216), sid(46222)],
[sid(46216), sid(46220)]
]

# Set the allocation per stock
pct_per_algo = 1.0 / (2*len(context.pairs))

# Make a separate algo for each stock.
context.pairs[t][1], pct_per_algo)
for t in range (0, (len(context.pairs)))]

def handle_data(context, data):
for algo in context.algo:
try:
algo.handle_data(context, data)
except Exception as e:
log.debug(e)
log.info([algo.ticker1.symbol, algo.ticker2.symbol])

# Initialize with a single stock and assign a proportion of the account.
def __init__(self, ticker1, ticker2, allocation):
self.ticker1 = ticker1
self.ticker2 = ticker2
self.allocation = allocation

def avg_ratio(self):
prices = history(30, '1d', 'price')
price1 = prices[self.ticker1]
price2 = prices[self.ticker2]
return (price1/price2).mean()

def bounds(self, context):
prices = history(30, '1d', 'price')
price1 = prices[self.ticker1]
price2 = prices[self.ticker2]
std_amt = (price1 / price2).std() * context.std_multiplier
mean = self.avg_ratio()
high_bound = mean + std_amt
low_bound = mean - std_amt
return (high_bound, low_bound)

def handle_data(self, context, data):
now = get_datetime()

# Constrain to only trade every half hour
if now.minute % 30:
return

if 'price' not in data[self.ticker1] or 'price' not in data[self.ticker2]:
log.info('No price data for pair: (%s, %s)'%(
self.ticker1.symbol, self.ticker2.symbol
))
return

open_orders = get_open_orders()
if self.ticker1 in open_orders or self.ticker2 in open_orders:
return

cash = context.portfolio.portfolio_value * self.allocation

price1 = data[self.ticker1].price
price2 = data[self.ticker2].price

current_ratio = price1 / price2
avgratio = self.avg_ratio()
high_bound, low_bound = self.bounds(context)

# lev is undefined
lev = 1

if current_ratio > high_bound and last_trade is not "high":
order_value(self.ticker2, +lev*cash)
order_value(self.ticker1, -lev*cash)
elif current_ratio < low_bound and last_trade is not "low":
order_value(self.ticker1, +lev*cash)
order_value(self.ticker2, -lev*cash)
elif last_trade is not "mid" and last_trade is "high" and current_ratio < avgratio:
order_target(self.ticker1, 0)
order_target(self.ticker2, 0)
elif last_trade is not "mid" and last_trade is "low" and current_ratio > avgratio:
order_target(self.ticker1, 0)
order_target(self.ticker2, 0)

#
# def __init__(self, ticker1, ticker2, allocation):
# self.ticker1 = ticker1
# self.ticker2 = ticker2
# self.allocation = allocation
#
# def ols(self, prices, price1, price2):
#
# p0 = prices[price1]
# slope=sm.OLS(p0, p1).fit().params[0]
# return slope
#
# def zscore(self, price1, price2, slope, context):
#
# slope = self.ols(prices, price1, price2)
# spread = price1 - (slope * price2)
# return zscore
#
# def handle_data(self, context, data):
#
# cash = context.portfolio.portfolio_value/(2*len(context.pairs))
# slope = self.ols(prices, price1, price2)
# zscore = self.zscore(price1, price2, slope, context)
#
# if zscore > 2:
# order_value(self.ticker2, +lev*cash)
# order_value(self.ticker1, -lev*cash)
# elif zscore < -2:
# order_value(self.ticker1, +lev*cash)
# order_value(self.ticker2, -lev*cash)
# elif zscore < 0.5 and zscore > -0.5:
# order_target(self.ticker1, 0)
# order_target(self.ticker2, 0)  
There was a runtime error.

Added cash and capital to the plots to make sure there is excess leverage. A good start I think.

1
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 math
import numpy as np
import scipy as sp
import datetime as dt
import statsmodels.tsa.stattools as ts
import statsmodels.api as sm

window_length = 30
lev = 1.2

def initialize(context):
context.std_multiplier = 1.5
context.pairs =[
[sid(24),    sid(32146)],
[sid(46216), sid(46220)],
[sid(14517), sid(14516)],
[sid(5484),  sid(6119)],
[sid(24),    sid(5773)],
[sid(46170), sid(46220)],
[sid(46170), sid(46222)],
[sid(46216), sid(46222)],
[sid(46216), sid(46220)]
]

# Set the allocation per stock
pct_per_algo = 1.0 / (2*len(context.pairs))

# Make a separate algo for each stock.
context.pairs[t][1], pct_per_algo)
for t in range (0, (len(context.pairs)))]

def handle_data(context, data):
for algo in context.algo:
try:
algo.handle_data(context, data)
except Exception as e:
log.debug(e)
log.info([algo.ticker1.symbol, algo.ticker2.symbol])

# Initialize with a single stock and assign a proportion of the account.
def __init__(self, ticker1, ticker2, allocation):
self.ticker1 = ticker1
self.ticker2 = ticker2
self.allocation = allocation

def avg_ratio(self):
prices = history(30, '1d', 'price')
price1 = prices[self.ticker1]
price2 = prices[self.ticker2]
return (price1/price2).mean()

def bounds(self, context):
prices = history(30, '1d', 'price')
price1 = prices[self.ticker1]
price2 = prices[self.ticker2]
std_amt = (price1 / price2).std() * context.std_multiplier
mean = self.avg_ratio()
high_bound = mean + std_amt
low_bound = mean - std_amt
return (high_bound, low_bound)

def handle_data(self, context, data):
now = get_datetime()

# Constrain to only trade every half hour
if now.minute % 30:
return

if 'price' not in data[self.ticker1] or 'price' not in data[self.ticker2]:
log.info('No price data for pair: (%s, %s)'%(
self.ticker1.symbol, self.ticker2.symbol
))
return

open_orders = get_open_orders()
if self.ticker1 in open_orders or self.ticker2 in open_orders:
return

cash = context.portfolio.portfolio_value * self.allocation

price1 = data[self.ticker1].price
price2 = data[self.ticker2].price

current_ratio = price1 / price2
avgratio = self.avg_ratio()
high_bound, low_bound = self.bounds(context)

# lev is undefined
lev = 1

if current_ratio > high_bound and last_trade is not "high":
order_value(self.ticker2, +lev*cash)
order_value(self.ticker1, -lev*cash)
elif current_ratio < low_bound and last_trade is not "low":
order_value(self.ticker1, +lev*cash)
order_value(self.ticker2, -lev*cash)
elif last_trade is not "mid" and last_trade is "high" and current_ratio < avgratio:
order_target(self.ticker1, 0)
order_target(self.ticker2, 0)
elif last_trade is not "mid" and last_trade is "low" and current_ratio > avgratio:
order_target(self.ticker1, 0)
order_target(self.ticker2, 0)

record(cash = context.portfolio.cash, capital = context.portfolio.capital_used)

#
# def __init__(self, ticker1, ticker2, allocation):
# self.ticker1 = ticker1
# self.ticker2 = ticker2
# self.allocation = allocation
#
# def ols(self, prices, price1, price2):
#
# p0 = prices[price1]
# slope=sm.OLS(p0, p1).fit().params[0]
# return slope
#
# def zscore(self, price1, price2, slope, context):
#
# slope = self.ols(prices, price1, price2)
# spread = price1 - (slope * price2)
# return zscore
#
# def handle_data(self, context, data):
#
# cash = context.portfolio.portfolio_value/(2*len(context.pairs))
# slope = self.ols(prices, price1, price2)
# zscore = self.zscore(price1, price2, slope, context)
#
# if zscore > 2:
# order_value(self.ticker2, +lev*cash)
# order_value(self.ticker1, -lev*cash)
# elif zscore < -2:
# order_value(self.ticker1, +lev*cash)
# order_value(self.ticker2, -lev*cash)
# elif zscore < 0.5 and zscore > -0.5:
# order_target(self.ticker1, 0)
# order_target(self.ticker2, 0)  
There was a runtime error.

Hello David and Suminda,

Thank you both for helping me out! I combined with what you both suggested and did a back-test, for a short-term period. I'm not sure whether the program will trade excessive leverage again. Please let me know how I can improve this model, I would be grateful for any feedback!

Terence

59
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 statsmodels.api as sm
import numpy as np
import pandas as pd

def initialize(context):
context.std_multiplier = 2
context.pair_list=[
[sid(1335),sid(20088)],
[sid(122),sid(20680)],
[sid(5484),sid(6119)],
[sid(2214),sid(6868)]
]

pct_per_algo= 1.0 / len(context.pair_list)

context.algos=pd.Series([Pair_Trade(context.pair_list[t][0], context.pair_list[t][1], pct_per_algo) for t in range(0, (len(context.pair_list)))])

def handle_data(context, data):
context.algos.apply(lambda algo: algo.handle_data(context, data))

def __init__(self, stk0, stk1, pct_of_account):
self.stk0=stk0
self.stk1=stk1
self.pct=pct_of_account

def handle_data(self, context, data):
cash=context.portfolio.cash*self.pct
price_history = history(bar_count=756, frequency='1d', field='price') #3 years
price_history = price_history.fillna(method='ffill')
position0=context.portfolio.positions[self.stk0].amount
position1=context.portfolio.positions[self.stk1].amount
price1 = data[self.stk0].price
price2 = data[self.stk1].price
share0 = int(cash / data[self.stk0].price)
share1 = int(cash / data[self.stk1].price)
#get the hedge spread ratio and its zscore
beta = self.ols_transform(price_history, self.stk0, self.stk1)
rVal = self.getMeanStd(price_history, beta, self.stk0, self.stk1)
meanPrice, stdPrice = rVal

#get the spread ratio and its zscore, as well as high, low bounds
values = self.getZscore(price_history, self.stk0, self.stk1)
mean, std = values
current_ratio=price1/price2
zscore = (current_ratio - mean)/std
avgratio = self.avg_ratio()
high_bound, low_bound = self.bounds(context)

if z>=1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / data[self.stk1].price)
order(self.stk1, num_shares)
order(self.stk0, -1 * num_shares/beta)

elif z<=-1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / data[self.stk0].price)
order(self.stk0, num_shares)
order(self.stk1, -1 * num_shares*beta)

elif zscore >=2.33 and position0 != 0 and  position1 != 0 and current_ratio > high_bound and last_trade is not "high":
order(self.stk1, share1*(2/3))
order(self.stk0, -1*share0*(2/3))

elif zscore<-2.33 and position0 != 0 and position1 != 0 and current_ratio < low_bound and last_trade is not "low":
order(self.stk0, share0*(2/3))
order(self.stk1, -1*share1*(2/3))

elif zscore >=1.65 and zscore <2.33 and position0 != 0 and position1 != 0:
order(self.stk1, share1*(1/3))
order(self.stk0, -1*share0*(1/3))

elif zscore <=-1.65 and zscore >-2.33 and position0 != 0 and  position1 != 0:
order(self.stk0, share0*(1/3))
order(self.stk1, -1*share1*(1/3))

elif abs(zscore)<=0.5 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "high" and current_ratio < avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)

elif abs(zscore)<=0.5 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "low" and current_ratio > avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)

elif abs(z)<=1 and abs(zscore)>=0.5 and position0 != 0 and position1 != 0:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)

record(cash=context.portfolio.cash, capital = context.portfolio.capital_used)

def avg_ratio(self):
prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
return (price1/price2).mean()

def bounds(self, context):
prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
std_amt = (price1 / price2).std() * context.std_multiplier
mean = self.avg_ratio()
high_bound = mean + std_amt
low_bound = mean - std_amt
return (high_bound, low_bound)

#get the zscore of the spread ratio
def getZscore(self, prices, sid0, sid1):
return mean, std

#get the zscore of the hedge spread ratio
def getMeanStd(self, prices, beta, sid0, sid1):
meanPrice = np.mean(prices[sid1] - beta*prices[sid0])
stdPrice= np.std(prices[sid1] - beta*prices[sid0])
if meanPrice is not None and stdPrice is not None :
return (meanPrice, stdPrice)
else:
return None
#get the slope for the hedge spread ratio
def ols_transform(self, prices, sid0, sid1):
prices = prices.fillna(method='bfill')
p0 = prices[sid0].values
p1 = prices[sid1].values

slope = sm.OLS(p0, p1).fit().params[0]
return slope

There was a runtime error.

On lines 67,68, 72,73, and the other similar ones, you need to make either the numerator or denominator a float in the ratio at the end. Python 2.x uses floor division for integers, so 2/3 = 0, but 2.0/3 = 0.66666, that is going to give you odd results.

Hi David,

I edited the code and ran a lot more back-tests. I realized that the trading signal can maintain for a long period of time, making the algorithm to trade non-stop to buy over 100k, short 100k. As for line 61:

context.portfolio.cash + context.portfolio.capital_used> sum(abs(price1*position0) + abs(price2*position1) for t in range (0,)):


I am trying to get a sum of my positions so that it does not exceed my total amount of cash and capital used, but I don't know how to define the range..

I hope you can give me some feedback on whether or not this program will still trade non-stop in the future and defining the range.

Thank you for helping me!

Sincerely,
Terence

59
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 statsmodels.api as sm
import numpy as np
import pandas as pd

def initialize(context):
context.std_multiplier = 2
context.notional = 5000
context.pair_list=[
[sid(1335),sid(20088)],
[sid(122),sid(20680)],
[sid(5484),sid(6119)],
[sid(2214),sid(6868)],
[sid(12691),sid(698)],
[sid(41969),sid(8554)]
]

pct_per_algo= 1.0 / len(context.pair_list)

context.algos=pd.Series([Pair_Trade(context.pair_list[t][0], context.pair_list[t][1], pct_per_algo) for t in range(0, (len(context.pair_list)))])

def handle_data(context, data):
context.algos.apply(lambda algo: algo.handle_data(context, data))

def __init__(self, stk0, stk1, pct_of_account):
self.stk0=stk0
self.stk1=stk1
self.pct=pct_of_account

def handle_data(self, context, data):
#cash=context.portfolio.cash*self.pct
cash=context.notional*self.pct
price_history = history(bar_count=500, frequency='1d', field='price') #2 years
price_history = price_history.fillna(method='ffill')
position0=context.portfolio.positions[self.stk0].amount
position1=context.portfolio.positions[self.stk1].amount
price1 = data[self.stk0].price
price2 = data[self.stk1].price
share0 = int(cash / data[self.stk0].price)
share1 = int(cash / data[self.stk1].price)
# Pair_val = sum(abs(price1*position0 + price2*position1)) get the value of the pair, make sure that it is smaller than the available cash.

#get the hedge spread ratio and its zscore
beta = self.ols_transform(price_history, self.stk0, self.stk1)
rVal = self.getMeanStd(price_history, beta, self.stk0, self.stk1)
meanPrice, stdPrice = rVal

#get the spread ratio and its zscore, as well as high, low bounds
values = self.getZscore(price_history, self.stk0, self.stk1)
mean, std = values
current_ratio=price1/price2
zscore = (current_ratio - mean)/std
avgratio = self.avg_ratio(price_history)
high_bound, low_bound = self.bounds(context,price_history)

if cash > (abs(price1*position0) + abs(price2*position1)) and cash > sum(abs(price1*position0) + abs(price2*position1) for t in range (0,)):
if z>=1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / price2)
order(self.stk1, num_shares)
order(self.stk0, -1 * num_shares/beta)
elif z<=-1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / price1)
order(self.stk0, num_shares)
order(self.stk1, -1 * num_shares*beta)
elif zscore >=2 and position0 == 0 and  position1 == 0 and current_ratio > high_bound and last_trade is not "high":
order(self.stk1, share1)
order(self.stk0, -1*share0)
elif zscore<-2 and position0 == 0 and position1 == 0 and current_ratio < low_bound and last_trade is not "low":
order(self.stk0, share0)
order(self.stk1, -1*share1)
elif abs(zscore)<=0.5 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "high" and current_ratio < avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
elif abs(zscore)<=0.5 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "low" and current_ratio > avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
elif abs(z)<=1 and abs(zscore)>=0.5 and position0 != 0 and position1 != 0:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
record(cash=context.portfolio.cash, capital = context.portfolio.capital_used, position_val=sum(abs(price1*position0) +abs( price2*position1) for t in range (0,)))

elif cash <= (abs(price1*position0) + abs(price2*position1)) and cash <= sum((abs(price1*position0) + abs(price2*position1)) for t in range (0,)):
return

def avg_ratio(self, prices):
#prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
return (price1/price2).mean()

def bounds(self,context,prices):
#prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
std_amt = (price1 / price2).std() * context.std_multiplier
mean = self.avg_ratio(prices)
high_bound = mean + std_amt
low_bound = mean - std_amt
return (high_bound, low_bound)

#get the zscore of the spread ratio
def getZscore(self, prices, sid0, sid1):
return mean, std

#get the zscore of the hedge spread ratio
def getMeanStd(self, prices, beta, sid0, sid1):
meanPrice = np.mean(prices[sid1] - beta*prices[sid0])
stdPrice= np.std(prices[sid1] - beta*prices[sid0])
if meanPrice is not None and stdPrice is not None :
return (meanPrice, stdPrice)
else:
return None

#get the slope for the hedge spread ratio
def ols_transform(self, prices, sid0, sid1):
prices = prices.fillna(method='bfill')
p0 = prices[sid0].values
p1 = prices[sid1].values

slope = sm.OLS(p0, p1).fit().params[0]
return slope
"""
elif zscore >=1.65 and zscore <2.33 and position0 != 0 and position1 != 0:
order(self.stk1, share1/3.0)
order(self.stk0, -1*share0/3.0)

elif zscore <=-1.65 and zscore >-2.33 and position0 != 0 and  position1 != 0:
order(self.stk0, share0/3.0)
order(self.stk1, -1*share1/3.0)

elif cash <= (abs(price1*position0) + abs(price2*position1)) and cash <= sum((abs(price1*position0) + abs(price2*position1)) for t in range (0,)):
return

"""
There was a runtime error.

Terence, it looks like you are getting closer. I added a few lines and record statements to help track how money is spent.

I commented out all but one pair and It looks like it only traded on that first day. I'd go through and test it with one pair while you get the PairTrade class working properly, then add the other pairs. My guess is that it goes outside of its leverage requirement and doesn't place any more trades as a result.

2
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 statsmodels.api as sm
import numpy as np
import pandas as pd

def initialize(context):
context.std_multiplier = 2
context.notional = 5000
context.pair_list=[
[sid(1335),sid(20088)],
# [sid(122),sid(20680)],
# [sid(5484),sid(6119)],
# [sid(2214),sid(6868)],
# [sid(12691),sid(698)],
# [sid(41969),sid(8554)]
]

pct_per_algo= 1.0 / len(context.pair_list)

for pair in context.pair_list])

def handle_data(context, data):
#
# Record your total portfolio leverage and market exposure
#
P = context.portfolio
positions = P.positions
market_value = sum(abs(positions[stock].amount * data[stock].price)
for stock in data)

leverage = market_value / P.portfolio_value
exposure = P.positions_value / P.portfolio_value

record(leverage=leverage, exposure=exposure)

context.algos.apply(lambda algo: algo.handle_data(context, data))

def __init__(self, stk0, stk1, pct_of_account):
self.stk0=stk0
self.stk1=stk1
self.pct=pct_of_account

def handle_data(self, context, data):
#cash=context.portfolio.cash*self.pct
cash=context.notional*self.pct
price_history = history(bar_count=500, frequency='1d', field='price') #2 years
price_history = price_history.fillna(method='ffill')
position0=context.portfolio.positions[self.stk0].amount
position1=context.portfolio.positions[self.stk1].amount
price1 = data[self.stk0].price
price2 = data[self.stk1].price
share0 = int(cash / data[self.stk0].price)
share1 = int(cash / data[self.stk1].price)

market_value = abs(position0 * price1) + abs(position1 * price2)

leverage = market_value / cash
# record(algo_leverage=leverage)
# Pair_val = sum(abs(price1*position0 + price2*position1)) get the value of the pair, make sure that it is smaller than the available cash.

#get the hedge spread ratio and its zscore
beta = self.ols_transform(price_history, self.stk0, self.stk1)
rVal = self.getMeanStd(price_history, beta, self.stk0, self.stk1)
meanPrice, stdPrice = rVal

#get the spread ratio and its zscore, as well as high, low bounds
values = self.getZscore(price_history, self.stk0, self.stk1)
mean, std = values
current_ratio=price1/price2
zscore = (current_ratio - mean)/std
avgratio = self.avg_ratio(price_history)
high_bound, low_bound = self.bounds(context,price_history)

# if cash > (abs(price1*position0) + abs(price2*position1)) and cash > sum(abs(price1*position0) + abs(price2*position1) for t in range (0,)):
if leverage <= 1:
if z>=1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / price2)
order(self.stk1, num_shares)
order(self.stk0, -1 * num_shares/beta)
elif z<=-1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / price1)
order(self.stk0, num_shares)
order(self.stk1, -1 * num_shares*beta)
elif zscore >=2 and position0 == 0 and  position1 == 0 and current_ratio > high_bound and last_trade is not "high":
order(self.stk1, share1)
order(self.stk0, -1*share0)
elif zscore<-2 and position0 == 0 and position1 == 0 and current_ratio < low_bound and last_trade is not "low":
order(self.stk0, share0)
order(self.stk1, -1*share1)
elif abs(zscore)<=0.5 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "high" and current_ratio < avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
elif abs(zscore)<=0.5 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "low" and current_ratio > avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
elif abs(z)<=1 and abs(zscore)>=0.5 and position0 != 0 and position1 != 0:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
# record(cash=context.portfolio.cash, capital = context.portfolio.capital_used, position_val=sum(abs(price1*position0) +abs( price2*position1) for t in range (0,)))

# elif cash <= (abs(price1*position0) + abs(price2*position1)) and cash <= sum((abs(price1*position0) + abs(price2*position1)) for t in range (0,)):
else:
return

def avg_ratio(self, prices):
#prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
return (price1/price2).mean()

def bounds(self,context,prices):
#prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
std_amt = (price1 / price2).std() * context.std_multiplier
mean = self.avg_ratio(prices)
high_bound = mean + std_amt
low_bound = mean - std_amt
return (high_bound, low_bound)

#get the zscore of the spread ratio
def getZscore(self, prices, sid0, sid1):
return mean, std

#get the zscore of the hedge spread ratio
def getMeanStd(self, prices, beta, sid0, sid1):
meanPrice = np.mean(prices[sid1] - beta*prices[sid0])
stdPrice= np.std(prices[sid1] - beta*prices[sid0])
if meanPrice is not None and stdPrice is not None :
return (meanPrice, stdPrice)
else:
return None

#get the slope for the hedge spread ratio
def ols_transform(self, prices, sid0, sid1):
prices = prices.fillna(method='bfill')
p0 = prices[sid0].values
p1 = prices[sid1].values

slope = sm.OLS(p0, p1).fit().params[0]
return slope
"""
elif zscore >=1.65 and zscore <2.33 and position0 != 0 and position1 != 0:
order(self.stk1, share1/3.0)
order(self.stk0, -1*share0/3.0)

elif zscore <=-1.65 and zscore >-2.33 and position0 != 0 and  position1 != 0:
order(self.stk0, share0/3.0)
order(self.stk1, -1*share1/3.0)

elif cash <= (abs(price1*position0) + abs(price2*position1)) and cash <= sum((abs(price1*position0) + abs(price2*position1)) for t in range (0,)):
return

"""
There was a runtime error.

Hello David,

I tried again with a few more backtests. I realized that my tests would all have an error issue towards october 2006 when running the test from 2002-2014. However, there is no error issue if I run from 2006-2014. This is the backtest for one year period. It trades normally but I am sure that the leverage problem is not fixed. In this problem, I realized the leverage is only based on one pair position and its allocated cash sum. I made it so that it takes into account for the portfolio value in another version.

I wonder if you can help me on limiting gross value, based on the sum of each pair's gross position, so that the program will not trade excessively.

Thank you again for your inputs and feedback!

Sincerely,
Terence Liu

7
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 statsmodels.api as sm
import numpy as np
import pandas as pd

def initialize(context):
context.std_multiplier = 1.5
#context.notional = 5000
context.pair_list=[
[sid(1335),sid(20088)],
[sid(122),sid(20680)],
[sid(5484),sid(6119)],
[sid(2214),sid(6868)],
[sid(12691),sid(698)],
[sid(41969),sid(8554)]
]

pct_per_algo= 1.0 / len(context.pair_list)

for pair in context.pair_list])

def handle_data(context, data):
#
# Record your total portfolio leverage and market exposure
#
P = context.portfolio
positions = P.positions
market_value = sum(abs(positions[stock].amount * data[stock].price)
for stock in data)

leverage = market_value / P.portfolio_value
exposure = P.positions_value / P.portfolio_value

record(leverage=leverage, exposure=exposure)

context.algos.apply(lambda algo: algo.handle_data(context, data))

def __init__(self, stk0, stk1, pct_of_account):
self.stk0=stk0
self.stk1=stk1
self.pct=pct_of_account

def handle_data(self, context, data):

freecash=context.portfolio.cash - context.portfolio.positions_value
cash=context.portfolio.cash*self.pct
price_history = history(bar_count=750, frequency='1d', field='price') #3 years
price_history = price_history.fillna(method='ffill')
position0=context.portfolio.positions[self.stk0].amount
position1=context.portfolio.positions[self.stk1].amount
price1 = data[self.stk0].price
price2 = data[self.stk1].price
share0 = int(cash / data[self.stk0].price)
share1 = int(cash / data[self.stk1].price)

market_value = abs(position0 * price1) + abs(position1 * price2)

leverage = market_value / cash
# record(algo_leverage=leverage)
# Pair_val = sum(abs(price1*position0 + price2*position1)) get the value of the pair, make sure that it is smaller than the available cash.

#get the hedge spread ratio and its zscore
beta = self.ols_transform(price_history, self.stk0, self.stk1)
rVal = self.getMeanStd(price_history, beta, self.stk0, self.stk1)
meanPrice, stdPrice = rVal

#get the spread ratio and its zscore, as well as high, low bounds
values = self.getZscore(price_history, self.stk0, self.stk1)
mean, std = values
current_ratio=price1/price2
zscore = (current_ratio - mean)/std
avgratio = self.avg_ratio(price_history)
high_bound, low_bound = self.bounds(context,price_history)

# if cash > (abs(price1*position0) + abs(price2*position1)) and cash > sum(abs(price1*position0) + abs(price2*position1) for t in range (0,)):
if leverage <= 1.01 and leverage >= 0 and context.portfolio.cash > 0 and freecash > 0:

if z>=1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / price2)
order(self.stk1, num_shares)
order(self.stk0, -1 * num_shares/beta)
elif z<=-1.65 and position0 == 0 and position1 == 0:
num_shares = int(cash / price1)
order(self.stk0, num_shares)
order(self.stk1, -1 * num_shares*beta)
elif zscore >=2 and position0 == 0 and  position1 == 0 and current_ratio > high_bound and last_trade is not "high":
order(self.stk1, share1)
order(self.stk0, -1*share0)
elif zscore<-2 and position0 == 0 and position1 == 0 and current_ratio < low_bound and last_trade is not "low":
order(self.stk0, share0)
order(self.stk1, -1*share1)
else:
return

if abs(zscore)<=0.75 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "high" and current_ratio < avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
elif abs(zscore)<=0.75 and position0 != 0 and position1 != 0 and last_trade is not "mid" and last_trade is "low" and current_ratio > avgratio:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
elif abs(z)<=1 and position0 != 0 and position1 != 0:
order_target_value(self.stk0, 0)
order_target_value(self.stk1, 0)
record(cash=context.portfolio.cash, freecash=freecash)
# record(cash=context.portfolio.cash, capital = context.portfolio.capital_used, position_val=sum(abs(price1*position0) +abs( price2*position1) for t in range (0,)))

# elif cash <= (abs(price1*position0) + abs(price2*position1)) and cash <= sum((abs(price1*position0) + abs(price2*position1)) for t in range (0,)):

def avg_ratio(self, prices):
#prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
return (price1/price2).mean()

def bounds(self,context,prices):
#prices = history(756, '1d', 'price')
price1 = prices[self.stk0]
price2 = prices[self.stk1]
std_amt = (price1 / price2).std() * context.std_multiplier
mean = self.avg_ratio(prices)
high_bound = mean + std_amt
low_bound = mean - std_amt
return (high_bound, low_bound)

#get the zscore of the spread ratio
def getZscore(self, prices, sid0, sid1):
return mean, std

#get the zscore of the hedge spread ratio
def getMeanStd(self, prices, beta, sid0, sid1):
meanPrice = np.mean(prices[sid1] - beta*prices[sid0])
stdPrice= np.std(prices[sid1] - beta*prices[sid0])
if meanPrice is not None and stdPrice is not None :
return (meanPrice, stdPrice)
else:
return None

#get the slope for the hedge spread ratio
def ols_transform(self, prices, sid0, sid1):
prices = prices.fillna(method='bfill')
p0 = prices[sid0].values
p1 = prices[sid1].values

slope = sm.OLS(p0, p1).fit().params[0]
return slope
"""
elif zscore >=1.65 and zscore <2.33 and position0 != 0 and position1 != 0:
order(self.stk1, share1/3.0)
order(self.stk0, -1*share0/3.0)

elif zscore <=-1.65 and zscore >-2.33 and position0 != 0 and  position1 != 0:
order(self.stk0, share0/3.0)
order(self.stk1, -1*share1/3.0)

elif cash <= (abs(price1*position0) + abs(price2*position1)) and cash <= sum((abs(price1*position0) + abs(price2*position1)) for t in range (0,)):
return

"""
There was a runtime error.

Terence,
It looks like the way the exposure and leverage are calculated take the entire portfolio into account, not just one pair. I think this might be working correctly, or really close to it, I don't see any glaring problems. It never gets more than 1.4x leveraged, which is totally reasonable, and the overall exposure to the market stays pretty low. It doesn't seem like it trades excessively either.

It looks like it's coming together well, I would say that now you should just go back and test with one pair to confirm each pair is working correctly. Then add one or two pairs at a time and make sure the transition goes as expected. If all that goes well, put any finishing touches on it and paper trade it, ideally on IB, that will give you a good idea of what to expect.

Nice work,
David

I am having a similar problem as the OP, possibly also due to thinly traded pairs. My entry/exit logic should be considerably simpler. I want to trade for reversion on any pair getting a certain distance from the prior day's closing values, closing the position if the pair gets close to reversion or close to end of day, whichever comes first.

I can successfully run the strategy with a single pair (hard-coded SIDs to context.s1 and context.s2) but whenever I run with multiple thinly traded symbols using the dict structure, I get an error that says I can't trade more than 10000000000 shares or something to that effect

Anything obvious I'm doing wrong here? What's best practice for pairs trading of multiple symbols?

3
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 numpy as np
import pytz
from brokers.ib import IBExchange

def initialize(context):
# context.s1 = symbol('EOI')
#  context.s2 = symbol('EOS')
# set_max_position_size(max_notional=50000.0)
# set_max_position_size(max_notional=50000.0)

context.spy = symbol('SPY')
context.pair_list=[
[sid(36816),sid(36006)],
[sid(40533),sid(42096)]
]

context.pClose1 = 0
context.pClose2 = 0

context.threshold = 0.01

set_commission(commission.PerShare(cost=0))

def handle_data(context, data):
exchange_time = get_datetime().astimezone(pytz.timezone('US/Eastern'))
open = get_open_orders()
if len(open) > 0:
log.debug('at ' + str(exchange_time) + ' open order count is ' + str(len(open)))

for pair in context.pair_list:
context.s1 = pair[0]
context.s2 = pair[1]
#      log.info(context.s1,data[context.s1].price)
#     print context.s1
if exchange_time.hour == 16 and exchange_time.minute == 0:
context.pClose1 = np.log(data[context.s1].close_price)
context.pClose2 = np.log(data[context.s2].close_price)
#  print context.s1 + context.pClose1 + context.s2 + context.pClose2
log.info(context.pClose1, context.pClose2)
continue

# calculate change vs prior close
dayChg1 = np.log(data[context.s1].close_price) - (context.pClose1)
dayChg2 = np.log(data[context.s2].close_price) - (context.pClose2)

# exit logic
if exchange_time.hour == 15 and exchange_time.minute >= 55:
order_target(context.s1, 0,style=MarketOrder(exchange=IBExchange.SMART))
order_target(context.s2, 0,style=MarketOrder(exchange=IBExchange.SMART))
continue
# exit if
elif abs(dayChg2 - dayChg1) < context.threshold/4:
order_target(context.s1, 0,style=MarketOrder(exchange=IBExchange.SMART))
order_target(context.s2, 0,style=MarketOrder(exchange=IBExchange.SMART))

# entry logic
if dayChg1 - dayChg2 > context.threshold:
order_target_percent(context.s1, -0.1,style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(context.s2, 0.1,style=MarketOrder(exchange=IBExchange.SMART))

elif dayChg2 - dayChg1 > context.threshold:
order_target_percent(context.s1, 0.1,style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(context.s2, -0.1,style=MarketOrder(exchange=IBExchange.SMART))


There was a runtime error.

Chad, it looks like you are running into thinly traded stock issues. I replaced "data[stock].close_price" with "data[stock].price" and it seems to work. When using the 'close_price' field, the prices are not forward filled, meaning that if there was no trades that minute, data[stock].close_price will be NaN. Changing to data[stock].price makes it use whatever the last trade price was.

2
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 numpy as np
import pytz
from brokers.ib import IBExchange

def initialize(context):
# context.s1 = symbol('EOI')
#  context.s2 = symbol('EOS')
# set_max_position_size(max_notional=50000.0)
# set_max_position_size(max_notional=50000.0)

context.spy = symbol('SPY')
context.pair_list=[
[sid(36816),sid(36006)],
[sid(40533),sid(42096)]
]

context.pClose1 = 0
context.pClose2 = 0

context.threshold = 0.01

set_commission(commission.PerShare(cost=0))

def handle_data(context, data):
exchange_time = get_datetime().astimezone(pytz.timezone('US/Eastern'))
open_orders = get_open_orders()
if len(open_orders) > 0:
log.debug('at ' + str(exchange_time) + ' open order count is ' + str(len(open_orders)))
return

for pair in context.pair_list:
context.s1 = pair[0]
context.s2 = pair[1]
#      log.info(context.s1,data[context.s1].price)
#     print context.s1
if exchange_time.hour == 16 and exchange_time.minute == 0:
context.pClose1 = np.log(data[context.s1].price)
context.pClose2 = np.log(data[context.s2].price)
#  print context.s1 + context.pClose1 + context.s2 + context.pClose2
log.info(context.pClose1, context.pClose2)
continue

# calculate change vs prior close
dayChg1 = np.log(data[context.s1].price) - (context.pClose1)
dayChg2 = np.log(data[context.s2].price) - (context.pClose2)

# exit logic
if exchange_time.hour == 15 and exchange_time.minute >= 55:
order_target(context.s1, 0,
style=MarketOrder(exchange=IBExchange.SMART))
order_target(context.s2, 0,
style=MarketOrder(exchange=IBExchange.SMART))
continue
# exit if
elif abs(dayChg2 - dayChg1) < context.threshold/4:
order_target(context.s1, 0,
style=MarketOrder(exchange=IBExchange.SMART))
order_target(context.s2, 0,
style=MarketOrder(exchange=IBExchange.SMART))

# entry logic
if dayChg1 - dayChg2 > context.threshold:
order_target_percent(context.s1, -0.1,
style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(context.s2, 0.1,
style=MarketOrder(exchange=IBExchange.SMART))

elif dayChg2 - dayChg1 > context.threshold:
order_target_percent(context.s1, 0.1,
style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(context.s2, -0.1,
style=MarketOrder(exchange=IBExchange.SMART))


There was a runtime error.

Thanks for the fix, David. However, I tried adding some more logging and a few extra conditionals to make the exits more precise and it broke again. I did set some breakpoints and step through for a few bars and noted that the "positions" were all 0 shares, and the exception came from one of the code blocks that's supposed to flatten (order_target) to 0 shares.

In previous backtesting applications I've used, this has been the generalized structure:
1 calculate whatever signals are needed
2 Load a collection of position objects. Foreach, evaluate exit criteria and close if necessary
3 else, loop through each symbol in the universe without a position open and check entry rules

I'd love if there were a smarter way to access the collection of open positions and handle exits, generally controlling the flow smarter. Like I said, I'm new at this platform

4
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 numpy as np
import pytz
from brokers.ib import IBExchange

def initialize(context):
# context.s1 = symbol('EOI')
#  context.s2 = symbol('EOS')
# set_max_position_size(max_notional=50000.0)
# set_max_position_size(max_notional=50000.0)

context.spy = symbol('SPY')
context.pair_list=[
[sid(26744),sid(26975)],
[sid(27843),sid(26746)]
]

context.pClose1 = 0
context.pClose2 = 0

context.threshold = 0.005

set_commission(commission.PerShare(cost=0))

def handle_data(context, data):
exchange_time = get_datetime().astimezone(pytz.timezone('US/Eastern'))
'''
open_orders = get_open_orders()
if len(open_orders) > 0:
log.debug('at ' + str(exchange_time) + ' open order count is ' + str(len(open_orders)))
return
'''

for pair in context.pair_list:
context.s1 = pair[0]
context.s2 = pair[1]
#      log.info(context.s1,data[context.s1].price)
#     print context.s1
if exchange_time.hour == 16 and exchange_time.minute == 0:
context.pClose1 = np.log(data[context.s1].price)
context.pClose2 = np.log(data[context.s2].price)
#  print context.s1 + context.pClose1 + context.s2 + context.pClose2
#log.info(context.pClose1, context.pClose2)
continue

# calculate change vs prior close
dayChg1 = np.log(data[context.s1].price) - (context.pClose1)
dayChg2 = np.log(data[context.s2].price) - (context.pClose2)

# exit logic
if exchange_time.hour == 15 and exchange_time.minute == 59:
order_target(context.s1, 0,
style=MarketOrder(exchange=IBExchange.SMART))
order_target(context.s2, 0,
style=MarketOrder(exchange=IBExchange.SMART))
log.info("Exited %s / %s at %s for end of day" %(context.s1,context.s2,exchange_time))
continue

# exit long/short pair if indicator crosses zero
elif context.portfolio.positions[context.s1].amount > 0 and  (spread) > 0:
order_target(context.s1, 0,
style=MarketOrder(exchange=IBExchange.SMART))
order_target(context.s2, 0,
style=MarketOrder(exchange=IBExchange.SMART))
log.info("Exited long %s /short %s at %s for intraday crossover" %(context.s1,context.s2,exchange_time))
# exit short/long pair if indicator crosses zero

elif context.portfolio.positions[context.s1].amount < 0 and  (spread) < 0:
order_target(context.s1, 0,
style=LimitOrder(data[context.s1].price))
order_target(context.s2, 0,
style=LimitOrder(data[context.s1].price))
log.info("Exited short %s /long %s at %s for intraday crossover" %(context.s1,context.s2,exchange_time))

# entry logic
if context.portfolio.positions[context.s1].amount == 0 and spread > context.threshold:
order_target_percent(context.s1, -0.1,
style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(context.s2, 0.1,
style=MarketOrder(exchange=IBExchange.SMART))
log.info("Entered short %s /long %s at %s " %(context.s1,context.s2,exchange_time))

elif context.portfolio.positions[context.s1].amount == 0 and spread < -context.threshold:
order_target_percent(context.s1, 0.1,
style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(context.s2, -0.1,
style=MarketOrder(exchange=IBExchange.SMART))
log.info("Entered long %s /short %s at %s " %(context.s1,context.s2,exchange_time))


There was a runtime error.

Also, why is my return (at the point of failure) 4000%? is the market neutral position screwing up the denominator of my return calculation by making account equity equal to longPos - shortPos, and not considering the cash generated from the shorting? Small denominator would make for big RoR

The crazy returns were due to new orders being placed when there was already open orders on the books. I made a few changes, the biggest one is that you were actually overwriting data in your main for loop. Defining "context.s1" and "context.s2" meant that the context.pClose variables were being overwritten on each pass of that for loop. Another change I made is that the algo now cancels any stale limit orders on every bar, the thought process is that it will submit a new updated order if the criteria is still met. Very cool strategy, I love that drawdown. Feel free to message me if you have further questions.

David

25
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 numpy as np
import pytz
from brokers.ib import IBExchange

def initialize(context):
# context.s1 = symbol('EOI')
#  context.s2 = symbol('EOS')
# set_max_position_size(max_notional=50000.0)
# set_max_position_size(max_notional=50000.0)

context.spy = symbol('SPY')
context.pair_list=[
(sid(26744),sid(26975)),
(sid(27843),sid(26746))
]

context.pClose1 = 0
context.pClose2 = 0

context.threshold = 0.005

set_commission(commission.PerShare(cost=0))

def handle_data(context, data):
exchange_time = get_datetime().astimezone(pytz.timezone('US/Eastern'))

# Cancel stale LOs.
cancel_open_orders()
if get_open_orders():
# Shouldn't trigger
log.debug("Open Orders: Skipping bar.....")
return

# Use history, if the freq is '1d', the current price,
# and yesterdays close prices are returned.
gaps = np.log(history(2, '1d', 'price')).diff().iloc[-1]

for pair in context.pair_list:
s1 = pair[0]
s2 = pair[1]
# calculate change vs prior close
dayChg1 = gaps[s1]
dayChg2 = gaps[s2]
# exit logic
if exchange_time.hour == 15 and exchange_time.minute == 59:
order_target(s1, 0, style=MarketOrder(exchange=IBExchange.SMART))
order_target(s2, 0, style=MarketOrder(exchange=IBExchange.SMART))
log.info("Exited %s / %s at %s for end of day" %(
s1, s2,exchange_time))
continue

# exit long/short pair if indicator crosses zero
elif context.portfolio.positions[s1].amount > 0 and (spread > 0):
order_target(s1, 0, style=MarketOrder(exchange=IBExchange.SMART))
order_target(s2, 0, style=MarketOrder(exchange=IBExchange.SMART))
log.info("Exited long %s /short %s at %s for intraday crossover" %(
s1, s2,exchange_time))

# exit short/long pair if indicator crosses zero
elif context.portfolio.positions[s1].amount < 0 and  (spread < 0):
order_target(s1, 0, style=LimitOrder(data[s1].price))
order_target(s2, 0, style=LimitOrder(data[s1].price))
log.info("Exited short %s /long %s at %s for intraday crossover" %(
s1,s2,exchange_time))

# entry logic
if context.portfolio.positions[s1].amount == 0 and spread > context.threshold:
order_target_percent(s1, -0.1, style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(s2, 0.1,  style=MarketOrder(exchange=IBExchange.SMART))
log.info("Entered short %s /long %s at %s " %(
s1, s2,exchange_time))

elif context.portfolio.positions[s1].amount == 0 and spread < -context.threshold:
order_target_percent(s1, 0.1, style=MarketOrder(exchange=IBExchange.SMART))
order_target_percent(s2, -0.1, style=MarketOrder(exchange=IBExchange.SMART))
log.info("Entered long %s /short %s at %s " %(
s1, s2, exchange_time))

def cancel_open_orders():
open_orders = get_open_orders()
for ords in open_orders.itervalues():
for oo in ords:
log.info("Cancelling %s"%oo)
cancel_order(oo)

def market_open(dt):