3 min read

使用卡尔曼滤波计算个股的动态贝塔

引言

在金融领域,贝塔(β)系数是资本资产定价模型(CAPM)中的核心参数,用于衡量单个资产或证券组合相对于整个市场(通常以市场指数代表)的系统性风险或波动性。具体而言,贝塔反映了市场收益率变动一个单位时,个体资产收益率的预期变动幅度。准确估计贝塔对于投资组合管理、风险控制、资产定价以及绩效评估都具有重要意义。例如,投资者可通过贝值判断资产的风险属性,进行合理的资产配置;基金经理也可依据贝塔调整投资组合的市场暴露程度。

传统的贝塔估计方法主要依赖于最小二乘(OLS)回归。通过在给定时间窗口(如1年、3年或5年)内对资产收益率与市场收益率进行线性回归,所得斜率系数即为贝塔估计值。此外,为降低短期波动带来的误差,也常采用滚动窗口回归或调整贝塔(Adjusted Beta)等方法。

然而,这些传统方法存在明显不足。首先,它们隐含假设贝塔在估计期间内是固定不变的,但事实上,受公司资本结构、经营策略、行业竞争及宏观经济环境等因素影响,资产的系统性风险可能随时间变化,即贝塔具有时变性。其次,滚动窗口回归中窗口长度的选择缺乏统一标准,较短窗口对噪声敏感,较长窗口则可能无法及时捕捉最新变化。再者,这些方法无法提供实时更新的贝塔估计,难以满足动态风险管理的需求。

为克服上述不足,本研究引入卡尔曼滤波(Kalman Filter)方法进行时变贝塔的估计。卡尔曼滤波是一种递归状态估计算法,能够从带有噪声的观测数据中最优地估计动态系统的内部状态。其核心优势在于能够实时更新状态估计,并通过建模状态变量的动态演化过程(如随机游走)来捕捉参数的时变特性。将卡尔曼滤波应用于贝塔估计,不仅可以得到随时间平滑变化的贝塔序列,还能通过状态空间模型灵活描述贝塔的动态行为,从而提供更准确、适应性的风险度量。

卡尔曼滤波原理及其在计算贝塔中的应用

卡尔曼滤波是一种最优递归数据处理算法,用于从一系列存在噪声的观测数据中估计动态系统的内部状态。其基本思想是通过结合系统模型的预测(先验估计)和当前观测值的更新,递归地得到状态的最优估计(后验估计)。该算法主要包含两个步骤:预测(时间更新)和更新(测量更新)。

基本原理

  1. 状态空间模型
    卡尔曼滤波建立在状态空间模型之上,包括状态方程和观测方程:
    • 状态方程(系统模型):描述状态向量如何随时间演化
      \[x_t = F_t x_{t-1} + B_t u_t + w_t\]
      其中,\(x_t\)\(t\)时刻的状态向量,\(F_t\)为状态转移矩阵,\(u_t\)为控制输入向量(可选),\(B_t\)为控制输入矩阵,\(w_t \sim N(0, Q_t)\)为过程噪声。
    • 观测方程(测量模型):描述观测值与状态向量的关系
      \[z_t = H_t x_t + v_t\]
      其中,\(z_t\)\(t\)时刻的观测向量,\(H_t\)为观测矩阵,\(v_t \sim N(0, R_t)\)为观测噪声。
  2. 算法步骤
    • 预测步骤
      先验状态估计:
      \[\hat{x}_t^{-} = F_t \hat{x}_{t-1} + B_t u_t\]
      先验误差协方差:
      \[P_t^{-} = F_t P_{t-1} F_t^T + Q_t\]
    • 更新步骤
      卡尔曼增益:
      \[K_t = P_t^{-} H_t^T (H_t P_t^{-} H_t^T + R_t)^{-1}\]
      后验状态估计:
      \[\hat{x}_t = \hat{x}_t^{-} + K_t (z_t - H_t \hat{x}_t^{-})\]
      后验误差协方差:
      \[P_t = (I - K_t H_t) P_t^{-}\]

在计算贝塔时的应用

在时变贝塔估计中,我们将CAPM模型置于状态空间框架下。假设在时刻\(t\),资产超额收益\(r_{a,t}\)与市场超额收益\(r_{m,t}\)满足:
\[r_{a,t} = \alpha_t + \beta_t r_{m,t} + \epsilon_t\]
其中,\(\alpha_t\)\(\beta_t\)为时变参数,\(\epsilon_t\)为观测噪声。

定义状态向量为\(x_t = [\alpha_t, \beta_t]^T\),则状态空间模型可表示为:
- 状态方程(假设参数遵循随机游走):
\[x_t = x_{t-1} + w_t\]
其中,\(w_t\)为过程噪声,协方差矩阵为\(Q\)
- 观测方程:
\[z_t = H_t x_t + v_t\]
其中,\(z_t = r_{a,t}\)\(H_t = [1, r_{m,t}]\)\(v_t = \epsilon_t\)为观测噪声,方差为\(R\)

通过卡尔曼滤波递归处理数据,可得到状态向量(即\(\alpha_t\)\(\beta_t\))的最优估计。滤波结果(Filtered)基于当前及过去信息,适用于实时估计;平滑结果(Smoothed)利用全部样本信息,适用于事后分析。

R代码实例

# 安装必要的包(如果尚未安装)
if (!require("quantmod")) install.packages("quantmod")  # 用于获取数据
if (!require("dlm")) install.packages("dlm")            # 用于卡尔曼滤波
if (!require("ggplot2")) install.packages("ggplot2")    # 用于绘图
if (!require("reshape2")) install.packages("reshape2")  # 用于数据重塑

# 加载包
library(quantmod)
library(dlm)
library(ggplot2)
library(reshape2)

# 设置时间范围
start_date <- as.Date("2020-01-01")
end_date <- Sys.Date() # 使用当前日期

# 下载AAPL和SPY的日收盘价数据
getSymbols("AAPL", src = "yahoo", from = start_date, to = end_date)
## [1] "AAPL"
getSymbols("SPY", src = "yahoo", from = start_date, to = end_date)
## [1] "SPY"
# 提取收盘价并合并数据
aapl_close <- Cl(AAPL)
spy_close <- Cl(SPY)
merged_data <- merge(aapl_close, spy_close)
colnames(merged_data) <- c("AAPL", "SPY")

# 计算日对数收益率
returns <- na.omit(data.frame(
  Date = index(merged_data),
  AAPL_Return = as.numeric(ROC(merged_data$AAPL)),
  SPY_Return = as.numeric(ROC(merged_data$SPY))
))

# 使用dlm构建卡尔曼滤波模型
buildCapm <- function(parm) {
  dlmMod <- dlm(
    FF = matrix(c(1, 0), nrow=1),
    V = exp(parm[1]),
    GG = diag(2),
    W = diag(exp(parm[2:3])),
    m0 = c(0, 1),
    C0 = diag(2)
  )
  dlmMod$JFF <- matrix(c(0, 1), nrow=1)
  dlmMod$X <- returns$SPY_Return
  return(dlmMod)
}

# 初始参数猜测(取对数后的方差)
init_parm <- c(log(0.01), log(0.0001), log(0.0001))

# 使用dlmMLE优化参数
mle_result <- dlmMLE(
  y = returns$AAPL_Return,
  parm = init_parm,
  build = buildCapm
)

# 检查优化是否成功
if (mle_result$convergence != 0) {
  warning("参数优化可能未完全收敛。")
}

# 用优化后的参数构建最终的dlm模型
final_model <- buildCapm(mle_result$par)

# 运行卡尔曼滤波和平滑
filtered_result <- dlmFilter(returns$AAPL_Return, final_model)
smoothed_result <- dlmSmooth(filtered_result)

# 提取平滑后的状态估计
alpha_smoothed <- smoothed_result$s[-1, 1]
beta_smoothed <- smoothed_result$s[-1, 2]

# 提取滤波后的状态估计
alpha_filtered <- filtered_result$m[-1, 1]
beta_filtered <- filtered_result$m[-1, 2]

# 将结果添加到数据框中
returns$Alpha_Smoothed <- alpha_smoothed
returns$Beta_Smoothed <- beta_smoothed
returns$Alpha_Filtered <- alpha_filtered
returns$Beta_Filtered <- beta_filtered

# 绘制时变贝塔系数
ggplot(returns, aes(x = Date)) +
  geom_line(aes(y = Beta_Smoothed, color = "Smoothed β"), linewidth = 0.8) +
  geom_line(aes(y = Beta_Filtered, color = "Filtered β"), linewidth = 0.5, linetype = "dashed", alpha = 0.7) +
  geom_hline(yintercept = 1, linetype = "dotted", color = "black") +
  labs(title = "AAPL vs SPY: Dynamic Beta Coefficient (Kalman Filter)",
       x = "Date",
       y = "Beta Value",
       color = "Estimation Type") +
  theme_bw() +
  scale_color_manual(values = c("Smoothed β" = "darkgreen", "Filtered β" = "darkred"))

# 绘制时变阿尔法系数
ggplot(returns, aes(x = Date)) +
  geom_line(aes(y = Alpha_Smoothed, color = "Smoothed α"), linewidth = 0.8) +
  geom_hline(yintercept = 0, linetype = "dotted", color = "black") +
  labs(title = "AAPL vs SPY: Dynamic Alpha Coefficient (Kalman Filter)",
       x = "Date",
       y = "Alpha Value",
       color = "Estimation Type") +
  theme_bw() +
  scale_color_manual(values = c("Smoothed α" = "darkgreen"))