以下代码是在计算每只股票相对等权股票池的多日滚动CAPM beta
def compute_market_beta(returns, window=BETA_WINDOW, minp=BETA_MINP): """60d trailing CAPM beta of each stock vs the equal-weight universe.""" r_mkt = returns.mean(axis=1) var = r_mkt.rolling(window, min_periods=minp).var() cov = returns.rolling(window, min_periods=minp).cov(r_mkt) return cov.div(var, axis=0)实现了如下功能:
1. 用等权股票池的横截面平均收益作为市场收益;
2. 计算市场收益的滚动方差;
3. 计算每只股票与市场收益的滚动协方差;
4. 协方差除以方差,得到每只股票的滚动 beta。
金融上,它度量的是个股对“等权市场组合”的系统性风险暴露。核心公式是:
其中是个股收益,
是市场组合收益。
代码用 pandas 的滚动协方差除以滚动方差,得到随时间变化的 beta。
这里进一步分析其计算逻辑,适应场景,以及可能需要注意或修正的事项。
1 代码逻辑
1.1 输入假设
这里先对输入假设进行分析
returns是一个DataFrame:
- 行:日期或时间序列
- 列:股票
- 值:收益率,不是价格
window=BETA_WINDOW:滚动窗口,例如 60 个交易日。
minp=BETA_MINP:窗口内最少需要的有效观测数,用于控制 NaN。
1.2 计算逻辑
这里进一步分析代码的计算逻辑
r_mkt = returns.mean(axis=1)
按行求均值,得到每一天的市场收益。
如果 returns有 N 只股票,那么:
这相当于一个每日再平衡的等权组合。
注意它不等于市值加权指数,也不是真正的市场组合。
var = r_mkt.rolling(window, min_periods=minp).var()
对市场收益 r_mkt做滚动方差:
pandas 默认ddof=1,即样本方差。
min_periods控制窗口内至少有多少个非 NaN 值才计算。
cov = returns.rolling(window, min_periods=minp).cov(r_mkt)
对每一只股票收益与市场收益 r_mkt做滚动协方差:
返回结果通常是一个DataFrame,列与原returns相同,表示每只股票在每个日期的滚动协方差。
return cov.div(var, axis=0)
把每只股票的滚动协方差除以市场滚动方差:
axis=0表示按行索引对齐,也就是按日期对齐。
因为var是Series,索引为日期,所以每一行所有股票都除以当天的市场方差。
最终返回一个 beta 矩阵:
- 行:日期
- 列:股票
- 值:该股票当天的滚动 CAPM beta
2. 金融相关
这里从金融的角度,分析以上计算逻辑。
2.1 CAPM与beta
CAPM 的基本形式:
或者:
其中:
-
:无风险利率;
-
:市场风险溢价;
-
:股票 i 对市场风险的暴露。
beta 的定义:
也可以写成:
含义说明如下
-
:股票与市场同步波动;
-
:进攻型、周期型,市场涨跌时波动更大;
-
:防御型,波动小于市场;
-
:与市场反向;
-
:理论上无系统性风险暴露。
2.2 beta 是OLS回归斜率
如果把个股收益对市场收益做单变量回归:
那么 OLS 估计的斜率就是:
这正是代码中cov / var做的事。
因此,这段代码本质上是在做滚动单因子回归,但没有显式估计alpha。
2.3 滚动beta的意义
rolling(window=60)表示用过去 60 个交易日的数据估计beta,即:
这叫做时变beta或滚动历史beta。
金融含义:
- 短窗口:对近期市场变化更敏感,但噪声大;
- 长窗口:更稳定,但对结构变化反应慢;
- 60 日约等于一个季度,是常见折中。
2.4 市场组合代理
理论上,CAPM 中的市场组合应包括所有可投资资产,并按市值加权。
现实中无法直接观测,所以常用指数代理。
这段代码用:
r_mkt = returns.mean(axis=1)
即等权股票池作为市场代理。
这意味着:
- 每只股票权重相同;
- 小盘股、波动大的股票权重可能被高估;
- 与真实市值加权市场组合有差异;
- 如果股票池本身就是当前股票池,还可能存在幸存者偏差。
2.5 beta的用途
beta经常可能用于如下用途。
计算股权资本成本:;
组合风险管理:组合 beta 是成分股 beta 的加权平均;
市场中性策略:根据 beta 调整多空敞口;
对冲:用股指期货对冲时,合约数量与组合 beta 有关;
绩效归因:区分 alpha 和市场 beta 收益;
因子模型:作为市场因子暴露。
3. 潜在问题
3.1 等权假设问题
等权市场包含个股自身,可能造成偏差
因为:
所以:
个股与市场协方差中包含了自身方差项。
若股票池很小,beta 会被高估。更严谨的做法是:
- 使用外部市场指数;
- 或计算市场收益时排除该股票自身;
- 或使用市值加权。
3.2 缺失值处理可能不一致
returns.mean(axis=1)
默认skipna=True,所以某天某些股票缺失时,市场收益由剩余股票平均得到。
但 returns.rolling(...).cov(r_mkt)对每只股票与市场收益计算协方差时,会忽略该股票自身的 NaN。
这样可能出现:
- 市场方差var使用了某些日期;
- 个股协方差cov使用了另一些日期;
- 分子分母样本不完全一致。
更严谨的做法是:
对每只股票,
先把该股票收益与市场收益对齐、dropna(),
再计算同一组样本上的协方差和方差。
3.3 无风险利率问题
CAPM 严格使用超额收益:
代码直接使用原始收益。
若日频无风险利率变化很小,近似可以接受;但严谨的资产定价应使用超额收益。
3.4 历史beta不等于未来beta
滚动 beta 是历史估计。
beta 会随公司杠杆、行业、宏观环境变化。实际中常用:
- Blume 调整:;
- 贝叶斯收缩;
- 多因子模型;
- DCC-GARCH、Kalman 滤波等动态 beta 方法。
3.5 非同步交易与微观结构
不同市场、不同时区、停牌、涨跌停、流动性差,
都会导致收益率不同步,从而低估或高估 beta。
3.6 异常值与窗口选择
极端收益会显著影响协方差和方差。可考虑:
- winsorize;
- 使用稳健回归;
- 调整窗口长度;
- 使用指数加权
3.7 beta不需要年化
beta 是斜率,频率一致时,日频、周频、月频估计的理论 beta 相同。
把收益年化不会改变 beta,因为协方差和方差同比例缩放。
4 更严谨实现
如果希望分子分母使用完全相同的样本,可以这样写:
import pandas as pd def compute_market_beta(returns, window=60, minp=30): r_mkt = returns.mean(axis=1) betas = {} for col in returns.columns: sub = pd.concat([returns[col], r_mkt], axis=1).dropna() sub.columns = ['stock', 'mkt'] cov = sub['stock'].rolling(window, min_periods=minp).cov(sub['mkt']) var = sub['mkt'].rolling(window, min_periods=minp).var() betas[col] = cov / var return pd.DataFrame(betas)如果要排除自身影响,可以计算除该股票外的等权市场收益:
sum_ret = returns.sum(axis=1) count = returns.notna().sum(axis=1) for col in returns.columns: r_mkt_ex = (sum_ret - returns[col]) / (count - returns[col].notna()) # 再用 r_mkt_ex 与 returns[col] 计算滚动 betareference
---
滚动单因子回归的计算和代码示例
https://blog.csdn.net/liliang199/article/details/165297581