基本概念
资本资产定价模型(CAPM)描述了资产的预期回报与市场系统风险之间的关系。它由威廉. 夏普 (1990 年的诺贝尔经济学奖)等人在 1960 年代提出。
CAPM 被认为是经济学的七个基本理论之一。
CAPM 表示资产的预期收益等于无风险收益加上风险溢价。 CAPM 的假设是投资者是理性的,希望获得最大化回报并尽可能降低风险。因此,CAPM 的目标是计算相对于无风险利率的给定风险溢价,投资者可以预期获得的回报。
Risk-Free Rate(无风险利率)
当投资者决定买入股票等高风险资产时,他的目的是为了获得高于无风险资产的收益。
一般认为,银行存款利率和国债收益率都是无风险的。不过,一般同期的国债收益率会高于同期银行存款,所以我们常把国债收益率当成无风险利率。
不同期限的国债利率收益不同,一般对标时,使用与风险资产预期投资时间相同就好。
比如,如果计划投资于流动性好的股票,也不打算长期持有,则我们可以使用一年期国债利率作为无风险利率。如果是不动产,则可以使用 5 年期国债利率作为对标的无风险利率。
下面的代码将演示如何通过 akshare 来获取国债收益率,并以此作为我们的无风险收益率:
import akshare as ak
import arrow
now = arrow.now()
start = now.shift(years=-1)
end = f"{now.year}{now.month:02d}{now.day:02d}"
start = f"{start.year}{start.month:02d}{start.day:02d}"
bond = ak.bond_china_yield(start_date=start,
end_date=end)
bond.set_index(keys='曲线名称', inplace=True)
bond
这样我们就得到了近一年的各种债券收益率。输出结果如下图所示:
我们可以把“中债国债收益率曲线”一年期的平均值当作一年期国债收益:
rf = bond[bond.index=='中债国债收益率曲线']['1 年'].mean()
print(rf)
rf = rf / 100
输出是 2.06。这个值应该解读为 2.06%。
市场回报 r m r_m rm
市场回报率表示为 r m r_m rm ,包括市场上的所有证券。但一般我们只使用某些指数,比如上证 50,沪深 300。如果投资偏好成长股,则可以使用中证 1000。
贝塔 𝛽
𝛽 是衡量股票相对于整体市场(例如沪深 300 指数)波动性的指标。换句话说,𝛽 代表回归线的斜率,即市场回报与个股回报的关系。
CAPM 中使用 𝛽 来描述系统风险或市场风险与资产预期回报之间的关系。根据定义,我们说整个市场的贝塔值为 1.0,个股根据其相对于市场的波动程度进行排名。
- 如果个股的 Beta = 1.0,这意味着其价格与市场完全相关
- 如果 Beta < 1.0(称为“防御性”),这表明该证券理论上的波动性低于市场
- 如果 Beta > 1.0 或“激进”,则表明资产价格比市场波动更大
CAPM 公式
该公式定义如下:
r i = r f + β ( r m − r f ) r_i = r_f + \beta(r_m - r_f) ri=rf+β(rm−rf)
这里:
- r i r_i ri是证券(个股)的预期回报
- r f r_f rf是无风险利率
- β i \beta_i βi是证券相对于市场的𝛽值
- r m − r f r_m - r_f rm−rf被称为风险溢价
我们通过一个例子来解读这个公式。如果标普 500 的整体回报率是 12.4%,无风险率利率为 0%,而 APPL 的𝛽为 1.1 的话,则投资者买入 APPL,他期望获得 13.7%的回报,以补偿承担的额外风险。
要使用 CAPM 模型,核心是计算个股相对于市场组合(指数)的𝛽。下面我们就通过 Python 来进行实现。
基于 Python 的 CAPM 实现
我们使用的市场组合是沪深 300,因此,我们也要从中抽取个股。我们将随机抽取 10 支个股来进行计算。
获取数据
为了确保所有人都能拿到数据,我们仍然使用 akshare。
首先,我们通过 akshare 获取过去一年的沪深 300 的行情数据:
import akshare as ak
hs300 = ak.stock_zh_index_daily(symbol="sz399300")
print(hs300)
我们对过去一年的沪深 300 的收益情况进行速览:
now = arrow.now()
year_ago = now.shift(years = -1)
filter = hs300.index >= np.datetime64(year_ago)
year_ago = hs300[filter].index[0]
# PRINT(YEAR_AGO)
# 计算买入并持有的收益(最近一年)
filter = hs300.index == year_ago
buy_price = hs300[filter].iloc[0]["close"]
buy_and_hold = hs300["close"][-1]/buy_price - 1
print(f"买入并持收益:{buy_and_hold:.2%}")
# 通过均值推算年化收益
market_returns = hs300.pct_change()["close"].dropna()
filter = returns.index >= year_ago
market_annual = (1 + returns[filter].mean()) ** 242 - 1
print(f"年化收益:{market_annual:.2%}")
可以看出,过去一年里,以买入并持有法计,沪深 300 的收益是-4.22%;如果按每日收益取均值,再年化,则得到收益是-3.31%,两者相差不大。
接下来,我们获取沪深 300 成份股,以便从中抽取个股进行检验:
import akshare as ak
index_stock_cons_df = ak.index_stock_cons("399300")
print(index_stock_cons_df)
我们将得到如下输出:
品种代码 品种名称 纳入日期
0 600754 锦江酒店 2023-06-12
1 600732 爱旭股份 2023-06-12
2 688223 晶科能源 2023-06-12
3 601872 招商轮船 2023-06-12
4 601607 上海医药 2023-06-12
.. ... ... ...
295 600660 福耀玻璃 2005-04-08
296 600690 青岛海尔 2005-04-08
297 600741 巴士股份 2005-04-08
298 600795 国电电力 2005-04-08
299 600900 长江电力 2005-04-08
接下来,我们随机取 10 支股票,获取行情,并计算每日收益率:
np.random.seed(78)
stocks = index_stock_cons_df['品种代码'].to_list()
stocks = random.sample(stocks, 10)
frames = {}
now = arrow.now()
start = now.shift(years = -1)
end = now.format("YYYYMMDD")
start = start.format("YYYYMMDD")
# 获取 10 支股票的行情数据
for code in stocks:
bars = ak.stock_zh_a_hist(symbol=code,
period="daily",
start_date=start,
end_date=end,
adjust="qfq")
bars.index = pd.to_datetime(bars["日期"])
frames[code] = bars["收盘"]
# 与指数行情数据合并
start = np.datetime64(now.shift(years = -1))
frames["399300"] = hs300[hs300.index >= start]["close"]
df = pd.DataFrame(frames)
# 计算每日收益
returns = df.pct_change()
# 如果存在 NAN,则后面的回归法将无法聚合
returns.dropna(how='any', inplace=True)
returns.style.format('{:,.2%}')
我们得到的每日个股及指数涨跌数据如下:
计算𝛽
我们将通过两种方式来计算𝛽。一种是回归法,一种是协方差法。
回归法
在 numpy 中有一个 polyfit 函数,可以用来进行多项式拟合。
当我们使用一次项拟合,那么得到的系数就是要求的𝛽。
cols = df.columns
for name in cols:
x = returns[name]
y = returns["399300"]
beta, alpha = np.polyfit(x, y, deg=1)
print(name, f"{beta:.2%} {alpha:.2%}")
从结果可以看出,有两支股票存在正的 alpha,同时还存在 20%以上的 beta 收益。
现在,我们就来看看,如果买入这两支股票,它们的预期收益应该是多少:
这里要注意,我们使用的 risk_free 是年化收益,因此我们最终计算出来的预期收益,也应该是年化(或者都统一到日化):
code = "601615"
beta = params[code][0]
# 回归法得到的预期收益
er = rf + beta * (market_annual - rf)
print(f"code beta: {beta:.2f}, Er: {er:.2%}")
最终我们得到 601615 的 beta 是 0.23,一年期的预期收益是 0.83 %左右。
协方差法
params = {}
for name in cols:
cov = np.cov(returns[name], returns["399300"])
beta = cov[0,1]/cov[1,1]
er = rf + beta * (market_annual - rf)
print(f"{name} beta: {beta:.2%}, Er: {er:.2%}")
params[name] = beta
code = "601615"
beta = params[code]
# 协方差法得到的预期收益
er = rf + beta * (market_annual - rf)
print(f"code beta: {beta:.2f}, Er: {er:.2%}")
这样得到 601615 的 beta 是 1.03,年化预期收益是-3.46%,与沪深 300 接近。
回顾
内容摘自《大富翁量化课程》。文章重点:
- 介绍 CAPM 模型及重点概念
- 分别用回归法和协方差法计算 CAPM 中的 β \beta β参数。
- 掌握random.sample,stock_zh_a_hist, pd.to_datetime, np.seed, np.datetime64, pct_change, dropna, df.style.format等方法。为什么要使用这些方法?在我们课程中有讲。
理解CAPM,要点是掌握:
1️⃣ 无风险利率怎么设置?
2️⃣ 所谓市场组合,究竟是指什么?
3️⃣ 标的必须是市场组合的一部分
4️⃣ 𝛽 是CAPM中最重要的参数,有哪几种方法可以计算出来?
5️⃣ 如何根据公式求标的的年化预期收益
6️⃣ 周期对齐问题。无风险利率一般以年化方式给出,而Rm和Sm一般以日的方式给出,需要进行对齐
思考
- 使用协方差与回归法,算出来的beta值不一样。哪一种方法更鲁棒?
- 使用CAPM需要满足哪些条件?你认为它的可复现性怎么样?