技术:卡尔曼滤波在量化交易中应用及进展 (含源码)

大家好, 我是Lucy@FinTech社区。

今天的文章将首先简介卡尔曼滤波基本原理;接着用Python和R分别实现卡尔曼滤波计算两种ETF配对交易的对冲比率;最后介绍卡尔曼滤波在量化交易中的最近的研究应用成果。希望对大家有所帮助!

欢迎大家添加微信fintech78,加入FinTech社区,提认知,攒人脉,求职招聘!

 

前言

传统的配对交易策略,其配对比率一般固定在某一个恒定的值,但是随着时间的推移,最优的对冲比率必然会发生变化。如果继续使用某一段时间估计出的最优对冲比率继续进行交易,会出现样本内过拟合的情况。为了克服这个缺点,算法工程师们引入了Kalman Filter的思想,来改善配对交易策略的表现。

  • 本文首先简介卡尔曼滤波基本原理(若对卡尔曼滤波不太熟悉,请阅读文末参考"How a Kalman filter works, in pictures");
  • 接着用Python和R分别实现卡尔曼滤波计算两种ETF配对交易的对冲比率
  • 最后介绍了卡尔曼滤波在量化交易中的最近的研究应用成果。

一、卡尔曼滤波

卡尔曼滤波的基本思想:

卡尔曼滤波器是一个最优化的自回归数据处理算法。

简而言之,就是以上一时刻状态变量的最优估值为准,预测下一时刻状态变量,同时对下一时刻的状态进行观测,得到观察变量,再用观测量对预测测量修正,从而得到下一时刻的最有效状态估计

下图来自wiki,可辅助理解整个过程:

卡尔曼滤波器有5条核心的公式。卡尔曼滤波器对于解决大部分问题,是最优效率最高甚至最有用的。

核心方程如下图所示:

卡尔曼滤波算法基本流程:

  1. 根据当前状态和状态转换模型,预测隐藏变量的下一个状态;
  2. 更新状态协方差预测值;
  3. 给定隐藏变量和观察模型的预测,预测观察变量的下一个值;
  4. 更新测得的协方差预测值;
  5. 计算观测变量的观测值与预测值之间的误差;
  6. 计算卡尔曼增益;
  7. 更新隐藏变量的估算值;
  8. 更新状态协方差预测;

 

二、配对交易中卡尔曼滤波的应用

卡尔曼滤波器应用于国债ETF配对交易(Python):

这里使用两种固定收益ETF,一种是iShares 20年以上国债ETF(TLT)iShares 3到7年国债ETF(IEI)

这里考虑两个ETF的线性组合,所以两个变量的数学上的关系是线性方程,我们通过使用卡尔曼滤波器,动态估计这对ETF之间的斜率和截距(即hedge ration)。

这里我们使用pykalman库提供的卡尔曼滤波算法。以及numpy、pandas、matplotlib等库。

首先通过绘制TLT和IEI的散点图,探索两种ETF之间的关系。

下一步是使用pykalman的卡尔曼滤波函数,动态计算TFT和IEI之间的截距和斜率。具体实现算法:

最后,使用上面calc_slope_intercept_kalman计算的值绘制斜率和截距的的关系图。

计算结果如下图所示:

现在,我们已经使用卡尔曼滤波建立两个ETF之间的动态关系,接下来就可以根据这些信息制定国债ETF的交易策略,由于不是本文重点,这里不做展开(全部源码见附录)。
 

卡尔曼滤波器应用于黄金ETF配对交易(R语言):

这里我们使用的两个价格系列是:GLDGDX。也是考虑两个ETF的线性关系,这里用R语言实现。

首先,也是看一下两种ETF的价格关系,这里使用时序图。

接下来用卡尔曼滤波器计算对冲比率的代码:

上面算法计算结果如下图所示:

 

现在我们已经得到这对ETF之间的对冲比率随时间的变化,至此我们就可以利用这个,来构建我们的黄金ETF对冲的交易策略。由于不是本文重点,这里不做展开。

 

三、近期进展

Carlos Eduardo de Moura等人(参考链接中论文)提出了一种基于线性状态空间模型的资产对的交易策略。

该模型用于对一对资产形成的价差进行建模。一旦估计出足够的价差状态空间模型,会使用卡尔曼滤波器来计算条件概率,当这些条件概率的值很大时就会激活策略:进行相应地价差买卖。

作者通过对美国和巴西市场的真实数据测试,尽管可能数据量有限,但测试结果表明,由单一利差组成的基本的投资组合的表现优于某些主要市场基准

本期的内容就到这里,希望大家有所收获!
 

参考:

  1. How a Kalman filter works, in pictures
    https://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/
  2. Dynamic Hedge Ratio Between ETF Pairs Using the Kalman Filter
    https://www.quantstart.com/articles/Dynamic-Hedge-Ratio-Between-ETF-Pairs-Using-the-Kalman-Filter/
  3. Carlos Eduardo de Moura等 A pairs trading strategy based on linear state space models and the Kalman filter
    https://www.researchgate.net/publication/301644668_A_pairs_trading_strategy_based_on_linear_state_space_models_and_the_Kalman_filter
  4. 策略不给力?来一发卡尔曼滤波
    https://zhuanlan.zhihu.com/p/21294526
     

参考:Python全部源码

import matplotlib.pyplot as plt

import numpy as np

import pandas as pd

import pandas_datareader.data as web

from pykalman import KalmanFilter


 

def draw_date_coloured_scatterplot(etfs, prices):

"""

展示两种ETF价格关系的散点图

"""

# 分别用红和黄色代表两种ETF

plen = len(prices)

colour_map = plt.cm.get_cmap('YlOrRd')

colours = np.linspace(0.1, 1, plen)

# 创建散点图对象

scatterplot = plt.scatter(

prices[etfs[0]], prices[etfs[1]],

s=30, c=colours, cmap=colour_map,

edgecolor='k', alpha=0.8

)


 

colourbar = plt.colorbar(scatterplot)

colourbar.ax.set_yticklabels(

[str(p.date()) for p in prices[::plen//9].index]

)

plt.xlabel(prices.columns[0])

plt.ylabel(prices.columns[1])

plt.show()

def calc_slope_intercept_kalman(etfs, prices):

"""

利用pyKalman软件包中的Kalman过滤器,计算ETF对的价格回归的斜率和回归的截距。

"""

delta = 1e-5 # 控制过渡协方差矩阵的噪音

trans_cov = delta / (1 - delta) * np.eye(2) # 过渡协方差矩阵

# 创建观测矩阵:一个一维矩阵存储TFT的值

obs_mat = np.vstack(

[prices[etfs[0]], np.ones(prices[etfs[0]].shape)]

).T[:, np.newaxis]

# 创建卡尔曼滤波器实例

kf = KalmanFilter(

n_dim_obs=1,

n_dim_state=2,#状态,这里是2,我们要求的是线性回归的斜率和截距

initial_state_mean=np.zeros(2),#斜率和截距的状态均值初始化为0

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 = kf.filter(prices[etfs[1]].values)

return state_means, state_covs

def draw_slope_intercept_changes(prices, state_means):

"""

从卡尔曼滤波器计算结果,绘制斜率和截距的变化

"""

pd.DataFrame(

dict(

slope=state_means[:, 0],

intercept=state_means[:, 1]

), index=prices.index

).plot(subplots=True)

plt.show()

if __name__ == "__main__":


 

etfs = ['TLT', 'IEI']

start_date = "2010-8-01"

end_date = "2016-08-01"

# 从雅虎金融获取数据

prices = web.DataReader(

etfs, 'yahoo', start_date, end_date

)['Adj Close']

draw_date_coloured_scatterplot(etfs, prices)

state_means, state_covs = calc_slope_intercept_kalman(etfs, prices)

draw_slope_intercept_changes(prices, state_means)