Here's a small playground for fiddling with pairs.
There are three (four now) different comparison calculations you can use, price returns, RSI and ROC (and linreg). You'll have to uncomment the one you want to test.
I tested on daily only and the performance is poor. No doubt due to the poor executions available on daily. It uses schedule_function at the open but who knows if this actually applies on daily or not. You'd have to run this on minutely to test - and I don't have the patience.
What I was seeing was that this treatment looks to be finding the divergences in the spread -- not the maximum peaks of the spread. Which means that it works better buying the momentum leg and fading the lagging leg. Exactly opposite what a mean reverting pairs strat should do.
It's a place to start.
[Edit: Added a version of linear regression comparison (slope of returns)]
import numpy
import talib
import collections
# Price return settings
#periodsToConsider = 2
#minimumPercentDeltaThreshold = .03
# RSI settings
#periodsToConsider = 11
#minimumPercentDeltaThreshold = 11
# ROC settings
#periodsToConsider = 3
#minimumPercentDeltaThreshold = 1
# Linear Regression
periodsToConsider = 11
minimumPercentDeltaThreshold = 200
def initialize(context):
context.stocks = symbols('GLD', 'SLV')
context.spreadDirection = 0
set_benchmark(symbol('GLD'))
set_commission(commission.PerTrade(cost=2))
schedule_function(func=HandleDataScheduled,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open(hours=0, minutes=1))
def handle_data(context, data):
pass
def HandleDataScheduled(context, data):
prices = history(periodsToConsider+1, '1d', 'price')
### Price returns calc ###
#returns = (prices.iloc[-1] - prices.iloc[0]) / prices.iloc[0]
#returns.sort()
#returnsSpread = returns[-1] - returns[0]
###
### RSI calc ###
#returns = prices.apply(talib.RSI, timeperiod=periodsToConsider).iloc[-1]
#returns = returns.dropna()
#returns = returns.order()
#returnsSpread = returns[-1] - returns[0]
###
### ROC calc ###
#returns = prices.apply(talib.ROC, timeperiod=periodsToConsider).iloc[-1]
#returns = returns.dropna()
#returns = returns.order()
#returnsSpread = returns[-1] - returns[0]
###
### Linear regression calc ###
leg0Stock = context.stocks[0]
leg1Stock = context.stocks[-1]
leg0Returns = numpy.asarray(prices[leg0Stock]) / prices[leg0Stock][0]
leg1Returns = numpy.asarray(prices[leg1Stock]) / prices[leg1Stock][0]
returns = prices.iloc[-1] / prices.iloc[0] # a place to store the results
# Perform linear regression of these two stock returns
returns[leg0Stock] = numpy.polyfit(leg0Returns, range(0, periodsToConsider + 1), 1)[0]
returns[leg1Stock] = numpy.polyfit(leg1Returns, range(0, periodsToConsider + 1), 1)[0]
returns.sort()
returnsSpread = returns[-1] - returns[0]
###
### General logic below
requestedSpreadDirection = 1 if context.stocks[0] == returns.index[0] else -1
# Buy/Sell the spread?
if (returnsSpread >= minimumPercentDeltaThreshold):
if (context.portfolio.positions_value == 0 and context.spreadDirection != requestedSpreadDirection):
context.spreadDirection = requestedSpreadDirection
# Buy smallest return
# Sell greatest return
order_target_percent(returns.index[0], 1)
order_target_percent(returns.index[-1], -1)
print(" Buy: {0} @ {1:<7.2f} Sell: {2} @ {3:<7.2F} >>>").format(
returns.index[0].symbol,
prices[returns.index[0]][-1],
returns.index[-1].symbol,
prices[returns.index[-1]][-1])
# Close any open position if we cross the spread
elif (context.portfolio.positions_value > 0 and context.spreadDirection != requestedSpreadDirection):
order_target_percent(returns.index[0], 0)
order_target_percent(returns.index[-1], 0)
print("<<< Exit: {0} @ {1:<7.2f} Exit: {2} @ {3:<7.2F}").format(
returns.index[0].symbol,
prices[returns.index[0]][-1],
returns.index[-1].symbol,
prices[returns.index[-1]][-1])
record(Leg1=prices[context.stocks[0]][-1],
Leg2=prices[context.stocks[-1]][-1],
Leg1Return=returns[context.stocks[0]],
Leg2Return=returns[context.stocks[-1]])