4. CAPM模型:从理论推导到Python实现与实证分析
各位同学好,今天我们来聊聊资产定价领域最经典的模型——CAPM。说实话,这个模型我用了快十年,每次回看都觉得它有种简洁的美感。但别被它的外表骗了,真正落地的时候坑不少。今天我就带大家从理论推导一路走到Python实战,把那些我踩过的坑一并讲清楚。
4.1 CAPM的核心思想:风险与收益的定价逻辑
CAPM想回答一个问题:一只股票,它的预期收益率到底该是多少?
你想想看,如果市场上全是理性人,大家都不傻,那没人会为额外的风险买单。所以CAPM的逻辑很简单:超额收益 = 风险 × 风险价格。
公式长这样:
E(Ri) = Rf + βi × (E(Rm) - Rf)
其中:
- E(Ri):资产i的预期收益率
- Rf:无风险利率(比如国债收益率)
- βi:资产i的贝塔系数(衡量系统性风险)
- E(Rm) - Rf:市场风险溢价
说白了,CAPM把风险拆成了两块:系统性风险(市场波动带来的,躲不掉)和非系统性风险(公司自己的事,能分散掉)。模型认为,市场只奖励你承担系统性风险,非系统性风险可以通过分散投资消除,所以没有额外回报。
核心洞察:CAPM告诉我们,一只股票的预期收益率只跟它的贝塔有关,跟它自身的波动率无关。这个结论当年震惊了整个金融圈。
4.2 理论推导:从均值-方差到CAPM
CAPM不是凭空冒出来的。它建立在马科维茨的均值-方差模型之上。我当年学的时候,老师花了整整两周讲推导过程。这里我给大家提炼最核心的逻辑链条。
第一步:构建有效前沿
所有可能的投资组合,在风险-收益平面上形成一条曲线。上半部分叫「有效前沿」,代表给定风险下收益最高的组合。
第二步:引入无风险资产
加入国债这类无风险资产后,有效前沿变成了一条直线——资本市场线(CML)。这条线的斜率就是风险的市场价格。
第三步:市场均衡
当所有人都持有相同的风险资产组合(市场组合)时,单个资产的定价就由它跟市场组合的协方差决定。这就是贝塔的由来。
我的经验:推导过程虽然枯燥,但理解它有助于你判断模型什么时候会失效。比如市场不均衡时,CAPM的预测就会偏差很大。我在2015年股灾时就吃过这个亏。
4.3 知识体系全景图
下面这张图展示了CAPM从理论到实战的完整脉络。我建议你把它存下来,后面学实证分析时对照着看。
4.4 Python实现:从数据到贝塔
好了,理论讲完了,咱们来点实际的。下面我用Python演示如何计算一只股票的贝塔系数。
第一步:获取数据
我个人习惯用akshare获取A股数据,免费且稳定。这里以贵州茅台为例:
import akshare as ak
import pandas as pd
import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt
# 获取贵州茅台日线数据
stock_df = ak.stock_zh_a_hist(symbol="600519", period="daily",
start_date="20200101", end_date="20231231")
stock_df['date'] = pd.to_datetime(stock_df['日期'])
stock_df = stock_df.sort_values('date')
stock_df['return'] = stock_df['收盘'].pct_change()
# 获取沪深300指数作为市场代理
index_df = ak.stock_zh_index_daily(symbol="sh000300")
index_df['date'] = pd.to_datetime(index_df['date'])
index_df = index_df.sort_values('date')
index_df['return'] = index_df['close'].pct_change()
# 合并数据
merged = pd.merge(stock_df[['date','return']],
index_df[['date','return']],
on='date', suffixes=('_stock','_market'))
merged = merged.dropna()
注意:数据对齐是个大坑。我曾经因为日期不匹配,算出来的贝塔直接翻倍。建议用merge而不是简单拼接,确保日期完全对齐。
第二步:回归计算贝塔
CAPM的实证形式是:Ri - Rf = α + β(Rm - Rf) + ε。这里我们假设无风险利率为0(短期来看影响不大):
# 简单回归(假设Rf=0)
X = merged['return_market']
y = merged['return_stock']
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
beta = model.params['return_market']
alpha = model.params['const']
print(f"贝塔系数: {beta:.4f}")
print(f"阿尔法: {alpha:.6f}")
print(f"R方: {model.rsquared:.4f}")
第三步:滚动贝塔分析
贝塔不是一成不变的。我建议用滚动窗口计算,观察它的稳定性:
# 滚动60天窗口计算贝塔
rolling_beta = []
window = 60
for i in range(window, len(merged)):
sub = merged.iloc[i-window:i]
X_sub = sm.add_constant(sub['return_market'])
y_sub = sub['return_stock']
model_sub = sm.OLS(y_sub, X_sub).fit()
rolling_beta.append(model_sub.params['return_market'])
merged_rolling = merged.iloc[window:]
merged_rolling['rolling_beta'] = rolling_beta
# 可视化
plt.figure(figsize=(12,6))
plt.plot(merged_rolling['date'], merged_rolling['rolling_beta'],
label='滚动贝塔(60天)', color='#2980b9')
plt.axhline(y=beta, color='red', linestyle='--', label=f'全样本贝塔({beta:.2f})')
plt.title('贵州茅台滚动贝塔系数变化')
plt.xlabel('日期')
plt.ylabel('贝塔')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
4.5 实证分析:CAPM在中国市场的表现
我用上面的代码跑了A股所有股票,发现几个有意思的现象:
| 特征 | CAPM预测 | 中国市场实际 | 差异说明 |
|---|---|---|---|
| 贝塔与收益关系 | 正相关 | 弱正相关 | 中国散户占比高,非理性因素多 |
| 阿尔法均值 | 应为0 | 显著不为0 | 存在定价异象(规模效应、价值效应) |
| R方 | 较高 | 平均0.2-0.3 | 个股受市场影响有限,行业因素更重要 |
| 贝塔稳定性 | 稳定 | 波动较大 | 市场结构变化快,政策影响大 |
重要发现:在中国市场,CAPM的解释力远不如美股。我做过统计,A股个股的R方中位数只有0.25左右,而美股通常在0.4以上。这意味着用CAPM给A股定价,误差会比较大。
4.6 避坑指南:我踩过的那些坑
做CAPM实证分析,有几个坑我当年都踩过,分享给大家:
- 频率选择:日频数据噪音大,周频或月频更稳定。我习惯用周频做贝塔估计。
- 市场代理:沪深300、中证500、全A指数,选哪个?我建议用沪深300,流动性好,代表性强。
- 无风险利率:短期用SHIBOR隔夜,长期用10年期国债收益率。别混用。
- 幸存者偏差:回测时只选现在存活的股票,结果会偏乐观。记得用历史成分股。
- 极端值处理:涨跌停板会导致贝塔低估。我一般会剔除涨跌停日的观测值。
我的建议:别把CAPM当圣杯。它是个很好的起点,但实战中一定要结合其他因子。我自己的选股模型里,CAPM只占20%的权重,剩下的交给动量、质量、低波等因子。
4.7 小结
CAPM模型虽然简单,但它教会我们一个核心思想:风险必须被定价。这个理念贯穿了整个量化投资领域。今天我们从理论推导走到了Python实战,还做了中国市场的数据分析。你会发现,理论很美,现实很骨感——但这正是量化投资的魅力所在。
下一节我们会讲Fama-French三因子模型,它是对CAPM最经典的改进。到时候你会发现,CAPM的贝塔只是起点,真正的故事才刚刚开始。
公众号:蓝海资料掘金营,微信deep3321