Sliding Linear Regression

Sometimes looking at stocks, they look trend stationary. Do people do stuff like this? Maybe take into account the residuals? It's kind of lame, but I'd be interested in what the optimal hyperparameters look like.

34
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

@batch_transform(window_length=10, refresh_period=1)
def get_LR(data, sid):
ts = data.price[sid]
slope , intercept = sm.OLS(ts, time).fit().params
return slope, intercept

def initialize(context):

context.spy = sid(8554)

#each to be optimized
#also optimize window length
context.sellThresh = -2.5
context.invested = 0

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):

myStock = context.spy
params = get_LR(data, myStock)

if params is None:
return

if(params[1] > context.buyThresh and not context.invested):
order(myStock, 10000)
if(params[1] < context.sellThresh and not context.invested):
order(myStock, -10000)
else:
order(myStock, -context.portfolio.positions[myStock].amount)


This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
7 responses

Hello Taylor,

Did you post an algorithm? I'm getting a never-ending "Loading..." message. Please post it again. I want to have a peak at how you did the linear regression.

Grant

Let me just paste the code. Weird stuff has been happening to my computer lately

import statsmodels.api as sm

@batch_transform(window_length=10, refresh_period=1)
def get_LR(data, sid):
ts = data.price[sid]
slope , intercept = sm.OLS(ts, time).fit().params
return slope, intercept

def initialize(context):
context.spy = sid(8554)
#each to be optimized
#also optimize window length
context.sellThresh = -2.5
context.invested = 0

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
myStock = context.spy
params = get_LR(data, myStock)
if params is None:
return
if(params[1] > context.buyThresh and not context.invested):
order(myStock, 10000)
if(params[1] < context.sellThresh and not context.invested):
order(myStock, -10000)
else:
order(myStock, -context.portfolio.positions[myStock].amount)



Hi Taylor and Grant, sorry for the trouble loading the backtest in the thread. We're sorting through an issue on our end and hopefully will have it fixed soon.

thanks,
Jean

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.

Hey all, that issue should be fixed now. Taylor, when you can, please run the backtest again and share that. Hopefully we'll see more than "Loading..." on that one.

-Rich
Quantopian

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.

Taylor,

Here's a version of your algorithm that I tweaked a bit. It seems to be able to profit off of dramatic down-turns in the market...not so sure about the up-swings...might require additional "tuning" or different logic.

Jean & Rich - thanks for the fix!

Grant

45
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

@batch_transform(window_length=10, refresh_period=1)
def get_LR(data, sid):
ts = data.price[sid]
slope , intercept = sm.OLS(ts, time).fit().params
return slope, intercept

def initialize(context):
context.spy = sid(8554)
#each to be optimized
#also optimize window length
context.sellThresh = 1
context.invested = False

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
myStock = context.spy
params = get_LR(data, myStock)

if params is None:
return

record(p_0 = params[0])

if(params[0] < context.buyThresh and not context.invested):
order(myStock, 10000)
context.invested = True

elif(params[0] > context.sellThresh and not context.invested):
order(myStock, -10000)
context.invested = True

else:
order(myStock, -context.portfolio.positions[myStock].amount)
context.invested = False

This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

bug maybe...doesn't sm.ols() return params in the order of intercept, slope instead of slope intercept?

edit: your logic is right. you're trading on the fitted slope. kind of stupid though because I've never in my life seen a model matrix with a ones column at the end. i'm gonna add a prepend = True to my sm.OLS call.

also your reversing of my idea makes more sense... it's like fading all the dudes that figure trend stationary will persist for a while longer.

just looked at thomas wiecki's slides on the internet that are floating around. could try running grid search on the thing below. might be messed up since it comes out with different results

import matplotlib.pyplot as plt
import statsmodels.api as sm
import datetime as dt

from zipline.transforms import batch_transform

@batch_transform
def get_LR(data, sid):
ts = data.price[sid]
time = sm.add_constant(range(0,len(ts)), prepend = True)
intercept, slope = sm.OLS(ts, time).fit().params
return intercept, slope

def initialize(self, window_length=10, buy_thresh = -1, sell_thresh = 1):
self.window_length = window_length
self.sellThresh = sell_thresh
self.invested = False
self.getRegression = get_LR(refresh_period = 1, window_length = self.window_length)

def handle_data(self, data):

params = self.getRegression.handle_data(data, globalTicker)
if params is None:
return
slope = params[1]
#place orders
if(slope < self.buyThresh and not self.invested):
self.order(globalTicker, 10000)
self.invested = True
elif(slope > self.sellThresh and not self.invested):
self.order(globalTicker, -10000)
self.invested = True
elif(abs(self.portfolio.positions[globalTicker].amount) > 0):
self.order(globalTicker, -self.portfolio.positions[globalTicker].amount)
self.invested = False

globalTicker = "SPY"
start = dt.datetime(2002,1,3)
end = dt.datetime(2013,4,5)
data = load_from_yahoo(stocks=[globalTicker], start = start, end = end, indexes={})

def run_slidingLR(window_length = 10, buy_thresh = -1, sell_thresh = 1):
results = algoObj.run(data)
#return results.portfolio_value[len(results)-1]
return results.portfolio_value

results = run_slidingLR(window_length = 10, buy_thresh = -1, sell_thresh = 1)
results.plot()
plt.show()


Hello Taylor,

My hunch is that the algorithm I posted above (the modification of yours) works because it is sensitive to over-reaction to a down-turn. Market participants get scared, etc. so the market tanks and then bounces back--a buying opportunity.

Grant