Quantopian Lecture Series: Kalman Filters

Kalman Filters are used in signal processing to estimate the underlying state of a process. They are incredibly useful for finance, as we are constantly taking noisy estimates of key quantities and trading indicators. This notebook introduces Kalman Filters and shows some examples of application to quantitative finance.

The lecture will be presented at this meetup. We will be releasing a video lecture as well, watch this thread for a link.

Also in this lecture:

This is part of Quantopian’s Summer Lecture Series. We are currently developing a quant finance curriculum and will be releasing clone-able notebooks and algorithms to teach key concepts. Stay tuned for more. We are also working on a permanent home for all of our notebooks.

Credit for the notebooks goes to Evgenia 'Jenny' Nitishinskaya, and credit for the algorithms goes to David Edwards.

2522
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.

39 responses

A big thanks to one of our own users, Dr Aidan O'Mahony, for allowing us to use his Kalman Filter example. You can find more on his blog.

Nice intro on Kalman filters. One little point on the financial side though, you can use prices to estimate the hedge ratio in a cointegration relationship but you should use returns to estimate alpha and beta to avoid spurious relationships.

You're completely correct, Matthieu. We have a note near the end explaining that you'd generally want to use returns, but perhaps we should make that clearer. We used prices because the pictures are clearer due to the increased range.

I found these two sites incredibly helpful for figuring out which variables in the Kalman filter do what.

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.

Here is the companion algorithm for the Kalman filter talk. It's an implementation of the pair trade from this post that uses Kalman filters to smooth the prices and calculate the spread between the two stocks.

438
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
"""

Author: David Edwards

This algorithm pair trades two solar companies. In order to demonstrate kalman filtering,
the price series are smoothed with a kalman filter and regression parameters are estimated
with another kalman filter.

This algorithm was developed by David Edwards as part of
Quantopian's 2015 summer lecture series. Please direct any
questions, feedback, or corrections to [email protected]
"""

import numpy as np
import pandas as pd
from pykalman import KalmanFilter
import statsmodels.api as sm

def initialize(context):
# Quantopian backtester specific variables
set_symbol_lookup_date('2014-01-01')
context.X = KalmanMovingAverage(symbol('ABGB'))
context.Y = KalmanMovingAverage(symbol('FSLR'))
context.kf = None
for minute in range(10, 390, 20):
time_rule=time_rules.market_open(minutes=minute))

if context.kf is None:
initialize_filters(context, data)
return
if get_open_orders():
return
prices = np.log(history(bar_count=1, frequency='1d', field='price'))
context.X.update(prices)
context.Y.update(prices)

mu_Y = context.Y.state_means
mu_X = context.X.state_means

frame = pd.DataFrame([mu_Y, mu_X]).T

context.kf.update(frame.iloc[-1])

beta, alpha = context.kf.state_mean

spreads = (mu_Y - (beta * mu_X + alpha)).tail(500)

reference_pos = context.portfolio.positions[context.Y.asset].amount

record(
beta=beta,
alpha=alpha,
zscore=zscore
)

if reference_pos:
# Do a PNL check to make sure a reversion at least covered trading costs
# I do this because parameter drift often causes trades to be exited
# before the original spread has become profitable.
pnl = get_pnl(context, data)
if zscore > -0.75 and reference_pos > 0 and pnl > 10:
order_target(context.Y.asset, 0.0)
order_target(context.X.asset, 0.0)

elif zscore < 0.75 and reference_pos < 0 and pnl > 10:
order_target(context.Y.asset, 0.0)
order_target(context.X.asset, 0.0)

else:
if zscore > 2.0:
order_target_percent(context.Y.asset, -0.5)
order_target_percent(context.X.asset, 0.5)
if zscore < -2.0:
order_target_percent(context.Y.asset, 0.5)
order_target_percent(context.X.asset, -0.5)

def initialize_filters(context, data):
initial_bars = 10
prices = np.log(history(initial_bars, '1d', 'price'))
context.X.update(prices)
context.Y.update(prices)

# Drops the initial 0 mean value from the kalman filter
context.X.state_means = context.X.state_means.iloc[-initial_bars:]
context.Y.state_means = context.Y.state_means.iloc[-initial_bars:]

context.kf = KalmanRegression(context.Y.state_means, context.X.state_means)

def get_pnl(context, data):
x = context.X.asset
y = context.Y.asset
positions = context.portfolio.positions
dx = data[x].price - positions[x].cost_basis
dy = data[y].price - positions[y].cost_basis
return (positions[x].amount * dx +
positions[y].amount * dy)

def handle_data(context, data):
record(market_exposure=context.account.net_leverage)

class KalmanMovingAverage(object):
"""
Estimates the moving average of a price process
via Kalman Filtering.

See http://pykalman.github.io/ for docs on the
filtering process.
"""

def __init__(self, asset, observation_covariance=1.0, initial_value=0,
initial_state_covariance=1.0, transition_covariance=0.05,
initial_window=20, maxlen=300, freq='1d'):

self.asset = asset
self.freq = freq
self.initial_window = initial_window

self.kf = KalmanFilter(transition_matrices=[1],
observation_matrices=[1],
initial_state_mean=initial_value,
initial_state_covariance=initial_state_covariance,
observation_covariance=observation_covariance,
transition_covariance=transition_covariance)
self.state_means = pd.Series([self.kf.initial_state_mean], name=self.asset)
self.state_covs = pd.Series([self.kf.initial_state_covariance], name=self.asset)

def update(self, observations):
for dt, observation in observations[self.asset].iterkv():
self._update(dt, observation)

def _update(self, dt, observation):
mu, cov = self.kf.filter_update(self.state_means.iloc[-1],
self.state_covs.iloc[-1],
observation)
self.state_means[dt] = mu.flatten()[0]
self.state_covs[dt] = cov.flatten()[0]

class KalmanRegression(object):
"""
Uses a Kalman Filter to estimate regression parameters
in an online fashion.

Estimated model: y ~ beta * x + alpha
"""

def __init__(self, initial_y, initial_x, delta=1e-5):
self._x = initial_x.name
self._y = initial_y.name
trans_cov = delta / (1 - delta) * np.eye(2)
obs_mat = np.expand_dims(
np.vstack([[initial_x], [np.ones(initial_x.shape[0])]]).T, axis=1)

self.kf = KalmanFilter(n_dim_obs=1, n_dim_state=2,
initial_state_mean=np.zeros(2),
initial_state_covariance=np.ones((2, 2)),
transition_matrices=np.eye(2),
observation_matrices=obs_mat,
observation_covariance=1.0,
transition_covariance=trans_cov)
state_means, state_covs = self.kf.filter(initial_y.values)
self.means = pd.DataFrame(state_means,
index=initial_y.index,
columns=['beta', 'alpha'])
self.state_cov = state_covs[-1]

def update(self, observations):
x = observations[self._x]
y = observations[self._y]
mu, self.state_cov = self.kf.filter_update(self.state_mean, self.state_cov, y,
observation_matrix=np.array([[x, 1.0]]))
mu = pd.Series(mu, index=['beta', 'alpha'],
name=observations.name)
self.means = self.means.append(mu)

x = observations[self._x]
y = observations[self._y]
return y - (self.means.beta * x + self.means.alpha)

@property
def state_mean(self):
return self.means.iloc[-1]


There was a runtime error.

thank you very much for the lecture. It was very instructive ;). From France :)

Very glad you enjoyed it, Tao. Please don't hesitate to share any feedback.

How do read the heat map does 2012 to 2013.. is a good year.. and 2014 is a bad year..?? from blue to red?? which one.. indicates overheating..??

The heat map doesn't say anything about the quality of the year. Rather it just indicates how the relationship between two securities, and therefore regression coefficients, changes over time. There is a distinct movement through the time space as indicated by color. The point here is that you have to be careful when taking a regression, as at any given time the actual underlying conditions may be changing.

Thanks Delany for clarifying things..

What do the valleys in the equity curve represent? Times when Kalman got the underlying state completely wrong?

Hi Bharath, are you referring to the algorithm returns or something in the notebook?

Hi, did you put out the link to the video? Sorry if I'm missing it.

@Dash

Check out the Quantopian Lectures page it is a living collection of all of the Quantopian Lectures Series material including notebooks, backtests, and videos.

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.

http://nbviewer.ipython.org/format/slides/url/alphamaximus.com/assets/notebooks/kalman_filters.ipynb#/ -- looks interesting
http://arxiv.org/abs/1509.04072 -- read this on the train this morning, looks good, but I haven't worked through the maths for the actual feature transformation yet.

Also, for future readers of this thread, David shared an algo which generalizes the Kalman-based pairs trading to multiple pairs, in this thread:

I was working through the Kalman Filter notebook and I noticed in the Linear Regression portion if you regress SPY over SPY you dont get a Beta of 1 throughout and it shows that there is alpha to be gained.

It should approach 1 though, over time? It's just an estimator.

Shouldnt Alpha approach zero though? It hangs around 0.96 - 0.99

I am not sure how you got it to do that, for me, alpha approaches 0. I'm attaching my clone.

3

I am by no means an expert at python, but I am assuming that the +9.916e-1 at the top of the second graph gets added to each y interval in the second plot making it around 0.900.

Your notebook is the same exact as mine. I have attached my notebook. I changed my start and end date and it goes from 2010-2016

2

Hmm you might be right. I tried to fix this with the advice here: http://stackoverflow.com/questions/3677368/matplotlib-format-axis-offset-values-to-whole-numbers-or-specific-number but matplotlib.ticker is blocked in Quantopian.

I am no expert on kalman filters so I will have to leave that one to the crowd.

Will go ahead and run some simple regression checks tonight, but I imagine it has do due something with the calculation and not the input prices.

Attempted other pairs of the same securities, and none of them seem to approach a beta of 1 or alpha of 0 when regressed over eachother (for example, tried SPY & SPY, TSLA & TSLA, and IWM & IWM).

Miles, the issue is related to the choice of the prior mean and covariance. I'm not sure what the justification is for choosing np.ones((2,2)) as the prior covariance matrix. I found this link very helpful to understand how the filter works. In the attached notebook I ran some experiments to better understand how choice of prior affects convergence.

how kalman filter works in pictures

10

Thanks Kyle,

I will definitely look into this as I want to move away from an EMA to something that has inputs that are more appropriate

I’m curious if anyone has an opinion as to when you would exactly use a Kalman filter to calculate a moving average. For example in momentum and trend following strategies moving averages are used to smooth a time series to give an indication of a trend or change in momentum. It seems like using a Kalman filter by virtue of giving a closer fit to the actual time series reduces the smoothing effect. In the moving average example in Delaney's original notebook the 90 day MA looks smoother than the 60 day MA which is smoother than the 30 day MA which is smoother than the Kalman estimate of the MA. So applying a Kalman estimate of a moving average to momentum and trend following strategies would in general result in extra unwanted trading. What am I missing?

A Kalman filter is used to smooth out noise, and the parameters to a Kalman filter can be adjusted to make the smoothing effect more or less severe, just like in a moving average. The trade-off is that a smoother signal will lag the true state of the world, whereas a signal that follows the measurements of the world can be quite noisy. Why some people use Kalman filters is that they are effectively more mathematically generalized moving averages. You can put in other models, and the parameters to control the smoothness actually mean something (measurement error, etc.). In a moving average what does 29 days mean vs 47 days? In practice Kalman filters may be able to get you closer to a non-parametric model. A non-parametic model is one in which you don't have to tweak parameters, which is highly desirable -- parameter tweaking leads to a lot of overfitting. Again, because the parameters to a Kalman filter actually mean something, you may be able to estimate them by say measuring variance on a dataset. This would allow you to not have to tweak and pick a smoothness, like you would have to with a moving average.

One continuing question on using Kalman as a replacement for MAs. I'm not sure what the "correct" values of transition covariance and observation covariance should be. According to me, we must take

1. observation cov = variance of underlying data over some ballpark lookback window (how long?)
2. transition cov = variance of MA of underlying data over some ballpark lookback window (how long?)

Without estimating these (and setting both to some default value like 1) will likely defeat the non-parametric nature of the Kalman filter?
Is my understanding correct? And if yes, is there a better way to estimate the observation / transition covariances?

You're correct, there's no easy answer, and in the end a Kalman filter may end up being just as parametric as a MA. However, with an MA there is no hope of being able to derive the proper parameter, as the window length really means nothing. With a Kalman filter you can at least have some hope of occasionally doing so. There may be a process (less frequent in finance, more frequent in other fields) in which you can estimate observation and transition covariances through another variable rather than the data itself, or maybe even derive theoretical values. The idea is that worst case you're just as parametric as a MA, and have to optimize covariance values, best case you find another way to estimate/derive covariances in a non-parametric fashion. The way in which you would derive the covariances is probably pretty specific to each system, so I'm not sure there's a general way of doing so.

Can you please help calculate Q(t), the measurement variance prediction, in Python? I'm assuming that Q(t) can be calculated from the  state_covs array in the notebook. The square root of Q(t) can then be used to determine entry/exit signals (as described in Ernest Chan's book Algorithmic Trading). Thanks.

Hello Peter. Unfortunately my plate is very full developing more lectures, and I will not have time to look into this for a few weeks. My recommendation is to clone the notebook and mess around with the code. You should be able to see how to retrieve the information from the state matrix. If not I can take a look when I have time.

@delany it doesnt work... I've tried run all.

Hello Vlademir. I just double-checked the lecture and it should all be working fine. There was a small bug in the first moving average example plots that I corrected.

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.

Can anyone help me with the application of Kalman Filters (KF) in predicting returns vs prices - When I run KF on prices I get pretty stable coefficients but when I run the same on returns, as expected, very noisy coefficients.

Lets say daily return on day t is +2% and on t+1 is -3%. When I will use the coefficients of day t to trade on t+1 the coefficients on t+1 trading will be totally off. Is there a way to handle this or we should run KF on prices and not on returns when estimating betas.

I used the code from the following link to estimate betas -
http://www.thealgoengineer.com/2014/online_linear_regression_kalman_filter/

Hi Shivam,

I'm assuming by coefficients you mean the beta coefficients? My guess is you are using the same observation and transition covariance in both cases. Try adjusting the observation covariance to reduce the effect of the latest observation on the beta coefficients.

Hi Aidan,

Thank you for your reply. Yes, chaging covariance matrices improved the beta coefficients.

Are there any good practices for the number of iterations we should run to stablize the beta coefficients ? I have daily data for around 15 years so to calculate today's betas should I run from the first data point or should I run for the last 100 or 300 days. In these two cases since covariance matrices will be different so I may get different beta coefficients.

Hi Shivam,

I would say the more data the better but the only real way to know is to test it and see what works best.

Hi,

I was wondering if somebody have tested UnscentedKalmanFilter or AdditiveUnscentedKalmanFilter from pykalman library. I have some difficulties to use it for pair trading.

Do you think it could help for price series regression ?