跳转至


课程  因子投资  机器学习  Python  Poetry  ppw  tools  programming  Numpy  Pandas  pandas  算法  hdbscan  聚类  选股  Algo  minimum  numpy  algo  FFT  模式识别  配对交易  GBDT  LightGBM  XGBoost  statistics  CDF  KS-Test  monte-carlo  VaR  回测  过拟合  algorithms  machine learning  strategy  python  sklearn  pdf  概率  数学  面试题  量化交易  策略分类  风险管理  Info  interview  career  xgboost  PCA  wavelet  时序事件归因  SHAP  Figures  Behavioral Economics  graduate  arma  garch  人物  职场  Quantopian  figure  Banz  story  量化传奇  rsi  zigzag  穹顶压力  因子  pe  ORB  策略  Xgboost  Alligator  Indicator  factor  alpha101  alpha  技术指标  wave  quant  algorithm  pearson  spearman  tushare  因子分析  Alphalens  涨停板  herd-behaviour  因子策略  momentum  因子评估  review  SMC  聪明钱  trade  history  indicators  zscore  波动率  强化学习  顶背离  freshman  resources  others  AI  DeepSeek  network  量子计算  金融交易  IBM  weekly  LLT  backtest  backtrader  研报  papers  UBL  quantlib  jupyter-notebook  scikit-learn  pypinyin  qmt  xtquant  blog  static-site  duckdb  工具  colors  free resources  barra  world quant  Alpha  openbb  数据  risk-management  llm  prompt  CANSLIM  Augment  arsenal  copilot  vscode  code  量化数据存储  hdf5  h5py  cursor  augment  trae  Jupyter  jupysql  pyarrow  parquet  数据源  quantstats  实盘  clickhouse  notebook  redis  remote-agent  AI-tools  Moonshot  回测,研报,tushare 

资本资产定价模型


资本资产定价模型(CAPM)描述了资产的预期回报与市场系统风险之间的关系。它由威廉. 夏普等人在 1960 年代提出。威廉. 夏普获得了 1990 年的诺贝尔经济学奖。CAPM 被认为是经济学的七个基本理论之一。

CAPM 表示资产的预期收益等于无风险收益加上风险溢价。 CAPM 的假设是投资者是理性的,希望获得最大化回报并尽可能降低风险。因此,CAPM 的目标是计算相对于无风险利率的给定风险溢价,投资者可以预期获得的回报。

基本概念

Risk-Free Rate(无风险利率)

当投资者决定买入股票等高风险资产时,他的目的是为了获得高于无风险资产的收益。一般认为,银行存款利率和国债收益率都是无风险的。不过,一般同期的国债收益率会高于同期银行存款,所以我们常把国债收益率当成无风险利率。不同期限的国债利率收益不同,一般对标时,使用与风险资产预期投资时间相同就好。比如,如果是流动性好的股票,也不打算长期持有,则我们可以使用一年期国债利率作为无风险利率。如果是不动产,则可以使用 5 年期国债利率作为对标的无风险利率。

如何获取国债利率呢,我们可以通过 akshare:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import akshare as ak
import arrow
import numpy as np
import random
import pandas as pd

random.seed(78)

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
这样我们就得到了近一年的各种债券收益率。我们可以把“中债国债收益率曲线”一年期的平均值当作一年期国债收益:

1
2
3
4
rf = bond[bond.index=='中债国债收益率曲线']['1年'].mean()
print(rf)

rf = rf / 100
输出是 2.06。这个值应该解读为 2.06%。

市场回报 \(r_m\)

市场回报率表示为\(r_m\) ,包括市场上的所有证券。但一般我们只使用某些指数,比如上证 50,沪深 300。如果投资偏好成长股,则可以使用中证 1000。

𝛽 贝塔

𝛽 是衡量股票相对于整体市场(例如沪深 300 指数)波动性的指标。换句话说,𝛽 代表回归线的斜率,即市场回报与个股回报的关系。

CAPM 中使用 𝛽 来描述系统风险或市场风险与资产预期回报之间的关系。根据定义,我们说整个市场的贝塔值为 1.0,个股根据其相对于市场的波动程度进行排名。

  • 如果个股的 Beta = 1.0,这意味着其价格与市场完全相关
  • 如果 Beta < 1.0(称为“防御性”),这表明该证券理论上的波动性低于市场
  • 如果 Beta > 1.0 或“激进”,则表明资产价格比市场波动更大

CAPM 公式

该公式定义如下:

\[ r_i = r_f + \beta(r_m - r_f) \]

这里: * \(r_i\)是证券(个股)的预期回报 * \(r_f\)是无风险利率 * \(\beta_i\)是证券相对于市场的𝛽值 * \(r_m - r_f\)被称为风险溢价

我们通过一个例子来解读这个公式。如果标普 500 的整体回报率是 12.4%,无风险率利率为 0%,而 APPL 的𝛽为 1.1 的话,则投资者买入 APPL,他期望获得 13.7%的回报,以补偿承担的额外风险。

要使用 CAPM 模型,核心是计算个股相对于市场组合(指数)的𝛽。下面我们就通过 Python 来进行实现。

基于 Python 的 CAPM 实现

我们使用的市场组合是沪深 300,因此,我们也要从中抽取个股。我们将随机抽取 10 支个股来进行计算。

获取数据

为了确保所有人都能拿到数据,我们仍然使用 akshare。

首先,我们通过 akshare 获取过去一年的沪深 300 的行情数据:

1
2
3
4
5
import akshare as ak

hs300 = ak.stock_zh_index_daily(symbol="sz399300")
hs300.index = pd.to_datetime(hs300["date"])
print(hs300)

我们对过去一年的沪深 300 的收益情况进行速览:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
now = arrow.now()
year_ago = now.shift(years = -1)

year_ago = hs300[hs300.index >= np.datetime64(year_ago)].index[0]
# PRINT(YEAR_AGO)

# 计算买入并持有的收益(最近一年)
buy_price = hs300[hs300.index == year_ago].iloc[0]["close"]
buy_and_hold = hs300["close"][-1]/buy_price - 1
print(f"买入并持收益:{buy_and_hold:.2%}")

# 通过均值推算年化收益
market_returns = hs300["close"].pct_change().dropna()
market_annual = (1 + market_returns[market_returns.index >= year_ago].mean()) ** 242 - 1
print(f"年化收益:{market_annual:.2%}")

可以看出,过去一年里,以买入并持有法计,沪深300的收益是-4.22%;如果按每日收益取均值,再年化,则得到收益是-3.31%,两者相差不大。

接下来,我们获取沪深 300 成份股,以便从中抽取个股进行检验:

1
2
3
import akshare as ak
index_stock_cons_df = ak.index_stock_cons(symbol="399300")
print(index_stock_cons_df)

接下来,我们随机取 10 支股票,获取行情,并计算每日收益率:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
np.random.seed(78)

stocks = random.sample(index_stock_cons_df['品种代码'].to_list(), 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 函数,可以用来进行多项式拟合。当我们使用一次项拟合,那么得到的系数就是要求的𝛽。

1
2
3
4
5
6
cols = df.columns
betas = {}
for name in cols:
    beta, alpha = np.polyfit(returns[name], returns["399300"], deg=1)
    print(name, f"{beta:.2%} {alpha:.2%}")
    betas[name] = beta

从结果可以看出,有一支股票存在正的 alpha,同时还存在 10%以上的 beta 收益。

现在,我们就来看看,如果买入这支股票,它们的预期收益应该是多少:

这里要注意,我们使用的 risk_free 是年化收益,因此我们最终计算出来的预期收益,也应该是年化(或者都统一到日化):

1
2
3
4
5
6
code = "002756"
beta = betas[code]

# 回归法得到的预期收益
expected_return = rf + beta * (market_annual - rf)
print(f"code beta: {beta:.2f}, Er: {expected_return:.2%}")

最终我们得到 002756 的 beta 是 0.11,一年期的预期收益是 1.51%左右。

协方差法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
params = {}

for name in cols:
    cov = np.cov(returns["399300"], returns[name])
    beta = cov[0,1]/cov[1,1]

    expected_return = rf + beta * (market_annual - rf)
    print(f"{name} beta: {beta:.2%}, Er: {expected_return:.2%}")
    params[name] = beta

beta = params[code]

# 回归法得到的预期收益
expected_return = rf + beta * (market_annual - rf)
print(f"code beta: {beta:.2f}, Er: {expected_return:.2%}")
这样得到 002756 的 beta 是0.11,年化预期收益是1.51%。

可视化

我们可以用下面的方式,将个股与沪深300的涨跌相关性进行可视化,从而得到更直观的印象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import plotly.graph_objects as go
from plotly.subplots import make_subplots


ci = sorted(set(returns[code].index).intersection(set(returns["399300"].index)))

y = returns[code][ci]
x = returns["399300"][ci]

df = pd.DataFrame({
    "沪深300": sorted(returns["399300"][ci]),
    "标的": sorted(returns[code][ci])
})

fig = make_subplots(
    rows=1,
    cols=2
)

fig.add_trace(go.Scatter(x=df["沪深300"], y=df["标的"]))
fig2 = px.line(df)
fig.add_trace(fig2["data"][0], row=1, col=2)
fig.add_trace(fig2["data"][1], row=1, col=2)

fig['layout']['xaxis']['title']='沪深300'
fig['layout']['yaxis']['title']='标的'

fig.show()

最终我们得到下图: