Python金融分析(五):CAPM模型简介

前言

资本资产定价模型(Capital Asset Pricing Model,CAPM),是由美国学者威廉·夏普(William Sharpe)、林特尔(John Lintner)、特里诺(Jack Treynor)和莫辛(Jan Mossin)等人在现代投资组合理论的基础上发展起来的,是现代金融市场价格理论的支柱,广泛应用于投资决策和公司理财领域。资本资产定价模型中,所谓资本资产主要指的是股票资产,而定价则试图解释资本市场如何决定股票收益率,进而决定股票价格。

CAPM大概是资产定价中使用最广泛的模型, 它的之所以如此流行有一下几个原因。

  • 作为单因素线性模型,无论是理解还是实现都非常简单。只要下载上市公司的历史价格数据和市场指数数据,先计算收益,然后估算股票的市场风险。
  • 这个最简单的单因子资产定价模型可以作为其他更复杂的定价模型(例如Fama-French 3因子模型)的基础。

接下来,我们将简要介绍CAPM模型,然后利用Yahoo Finance的数据作为样例,介绍beta的估计和调整方法。

CAPM简要介绍

马科维茨(Markowitz,1952)的分散投资与效率组合投资理论第一次以严谨的数理工具为手段向人们展示了一个风险厌恶的投资者在众多风险资产中如何构建最优资产组合的方法。

CAPM阐述了在投资者都采用马科维茨的理论进行投资管理的条件下市场均衡状态的形成,其核心在于资产的预期收益与预期风险具有正相关关系。具体而言,根据资本资产定价模型,对于一个给定的资产ii,它的期望收益率和市场投资组合的期望收益率之间的关系可以表示为:

E(Ri)=Rf+βi(E(Rm)Rf)(1)E(R_i) = R_f + \beta_i(E(R_m)-R_f)\tag{1}

其中,

  • E(Ri)E(R_i)–资产ii的期望收益率;
  • RfR_f–无风险收益率,通常以短期国债的利率来近似替代;
  • βi\beta_i – 资产ii的Beta(系统性风险)系数;
  • E(Rm)E(R_m) – 市场期望回报率 (Expected Market Return)。

实际上,如果我们去掉公式的期望(1)(1),则可以得到:

RiRf=α+βi(RmRf)R_i - R_f = \alpha + \beta_i(R_m-R_f)

其中的α\alpha就代表资产ii本身的超额收益。

毫无疑问,CAPM中,Beta系数是至关重要的:

βi=Cov(Ri,Rm)Var(Rm)(2)\beta_i = \frac{Cov(R_i, R_m)}{Var(R_m)}\tag{2}

从公式(1)(1)(3)(3)可以发现,Beta系数的含义为,市场风险溢价每提高1%,资产的收益率提高β%\beta\%,所以Beta系数越大,则该资产相对市场的波动性也越大。

yahoofinance

CAPM计算示例

数据获取

我们以苹果公司在2014年1月1日~2018年12月31日期间的股票作为研究对象,首先读取相关数据,包括个股(AAPL)和市场(^GSPC, Yahoo Finance中标普500的代码):

1
%matplotlib inline
1
2
3
4
5
6
7
8
9
10
11
12
13
from scipy import stats
import numpy as np
import pandas as pd

import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas_datareader.data as pddata
import altair as alt
import statsmodels.api as sm

plt.style.use('ggplot')
mpl.rcParams['figure.figsize']= [15, 9]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
beg_date = '2014-01-01'
end_date = '2018-12-31'

ticker = 'AAPL'
stock_apple = pddata.DataReader(name=ticker, data_source='yahoo',
start=beg_date, end=end_date,
retry_count=3, pause=0.1,
session=None, access_key=None)
return_apple = stock_apple['Adj Close'] / stock_apple['Adj Close'].shift(1) - 1
return_apple.dropna(inplace=True)
return_apple = return_apple.to_frame()
return_apple.columns = ['ret']

ticker = '^GSPC'
sp500 = pddata.DataReader(name=ticker, data_source='yahoo',
start=beg_date, end=end_date,
retry_count=3, pause=0.1,
session=None, access_key=None)
return_market = sp500['Adj Close'] / sp500['Adj Close'].shift(1) - 1
return_market.dropna(inplace=True)
return_market = return_market.to_frame()
return_market.columns = ['ret']
sp500.head()
High Low Open Close Volume Adj Close
Date
2013-12-31 1849.439941 1842.410034 1842.609985 1848.359985 2312840000 1848.359985
2014-01-02 1845.859985 1827.739990 1845.859985 1831.979980 3080600000 1831.979980
2014-01-03 1838.239990 1829.130005 1833.209961 1831.369995 2774270000 1831.369995
2014-01-06 1837.160034 1823.729980 1832.310059 1826.770020 3294850000 1826.770020
2014-01-07 1840.099976 1828.709961 1828.709961 1837.880005 3511750000 1837.880005
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fig, ax1 = plt.subplots()

color = 'tab:red'
ax1.set_xlabel('Date')
ax1.set_ylabel('Stock Price', color=color)
ax1.plot(stock_apple.index, stock_apple['Adj Close'], color=color)
ax1.tick_params(axis='y', labelcolor=color)

ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis

color = 'tab:blue'
ax2.set_ylabel('SP500 Index', color=color) # we already handled the x-label with ax1
ax2.plot(sp500.index, sp500['Adj Close'], color=color)
ax2.tick_params(axis='y', labelcolor=color)

fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()

output_10_1

1
2
3
4
5
6
7
8
9
fig, ax = plt.subplots()

color = 'tab:red'
ax.set_xlabel('Date')
ax.set_ylabel('Return')
ax.plot(return_apple.index, return_apple.ret, label='AAPL Return')
ax.plot(return_market.index, return_market.ret, label='Market Return')
ax.legend()
plt.show()

output_11_0

估计Beta系数

从前面的分析可以知道,Beta系数就是线性回归的一个参数(斜率),所以通过线性回归就可以得到。在python中至少有三种方法可以进行闲心回归的参数估计。

scipy包的linregress方法

1
2
results = stats.linregress(return_market['ret'], return_apple['ret'])
print(results)
LinregressResult(slope=1.1272452267207456, intercept=0.00046898978447086414, rvalue=0.620220452518429, pvalue=1.311085503675735e-134, stderr=0.040228176150808315)

numpy的polyfit方法

1
np.polyfit(return_market['ret'],return_apple['ret'],deg=1)
array([1.12724523e+00, 4.68989784e-04])

statsmodel的OLS方法

1
2
3
x = sm.add_constant(return_market['ret'])
ols_results = sm.OLS(return_apple['ret'], x).fit()
print(ols_results.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                    ret   R-squared:                       0.385
Model:                            OLS   Adj. R-squared:                  0.384
Method:                 Least Squares   F-statistic:                     785.2
Date:                Mon, 25 Mar 2019   Prob (F-statistic):          1.31e-134
Time:                        10:37:47   Log-Likelihood:                 3792.1
No. Observations:                1258   AIC:                            -7580.
Df Residuals:                    1256   BIC:                            -7570.
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const          0.0005      0.000      1.399      0.162      -0.000       0.001
ret            1.1272      0.040     28.021      0.000       1.048       1.206
==============================================================================
Omnibus:                      210.950   Durbin-Watson:                   1.899
Prob(Omnibus):                  0.000   Jarque-Bera (JB):             3549.752
Skew:                           0.158   Prob(JB):                         0.00
Kurtosis:                      11.223   Cond. No.                         120.
==============================================================================
1
2
3
4
5
plt.plot(return_market['ret'], return_apple['ret'], '.')
x = np.linspace(-0.05, 0.05, 100)
y = results.slope * x + results.intercept
plt.plot(x, y)
plt.show()

output_20_0

可以看到,三种方法计算的Beta参数是一样的:1.1272。但是,需要注意的是,我们并没有用到公式(1)(1)中的无风险利率RfR_f,但是实际上如果RfR_f是常数的话,并不需要用到RfR_f(可以从公式(2)(2)看出)。当然如果以美国国债利率作为无风险利率的话,那么可能需要用到其他模型了(例如Fama-French三因子模型)。

滑窗Beta

有时,我们需要根据滑动窗口,例如三年的窗口,生成beta时间序列。 例如,我们可以估计苹果公司股票的年度Beta:

1
2
3
4
5
6
7
def ret_f(ticker, beg_date, end_date):
p = pddata.DataReader(name=ticker, data_source='yahoo',
start=beg_date, end=end_date,
retry_count=3, pause=0.1,
session=None, access_key=None)
ret = p / p.shift(1)
return ret.dropna()
1
2
3
4
5
6
7
8
9
10
years = return_apple.index.strftime('%Y').unique()
for year in years:
y = return_apple[year]
x = return_market[year]
(beta,alpha,r_value,p_value,std_err) = stats.linregress(x.ret, y.ret)
alpha = round(alpha, 6)
beta = round(beta, 4)
r_value = round(r_value, 4)
p_value = round(p_value, 4)
print(year, alpha, beta, r_value, p_value)
2014 0.001338 0.869 0.4437 0.0
2015 -1e-06 1.1458 0.6639 0.0
2016 0.000176 1.0086 0.5659 0.0
2017 0.00066 1.3641 0.518 0.0
2018 0.000193 1.2565 0.7454 0.0

Beta系数的修正

许多研究者发现,Beta系数存在“均值回归”的趋势,也就是说,如果当期的beta值小于1,则下一期的beta值可能会变大,反之亦然。对此,可以采用一种简单的方式对beta进行调整:

βadj=23β+131.0\beta_{adj}=\frac{2}{3}\beta + \frac{1}{3}1.0

Scholes-William修正Beta

股票交易不活络或过热时,单因子模型所估计的系统风险即beta值会产生偏误:当股票交易比市场交易平均水平活跃时,所求出之β估计值向上偏误(biased upward),反之,则β估计值向下偏误(biased downward)。因此,针对市场上一些交易较不活跃的股票,Dimson(1979)及Scholes and Williams (1977)各提出修正方法:
Scholes-Williams的修正β计算方法为利用个股报酬率分别对落后1期市场报酬率、当期市场报酬率及领先1期市场报酬率作简单回归分析求出各系数,将其相加,再除以(1+2ρm)(1+2\rho_m),其中ρm\rho_m为市场收益率的一阶序列相关系数:

β=β1+β0+β+11+2ρm(3)\beta=\frac{\beta^{-1}+\beta^{0}+\beta^{+1}}{1+2\rho_m}\tag{3}

其中,三个beta的计算如下:

Rt=α+β1Rm,t1+εtRt=α+β0Rm,t+εtRt=α+β+1Rm,t+1+εt\begin{aligned} R_t &= \alpha + \beta^{-1}R_{m,t-1}+\varepsilon_t\\ R_t &= \alpha + \beta^{0}R_{m,t}+\varepsilon_t\\ R_t &= \alpha + \beta^{+1}R_{m,t+1}+\varepsilon_t \end{aligned}

1
2
3
4
5
6
7
8
9
10
11
12
13
results = stats.linregress(return_market.ret, return_apple.ret)
beta_current = results.slope
results = stats.linregress(return_market.ret[:-1], return_apple.ret[1:])
beta_lag = results.slope
results = stats.linregress(return_market.ret[1:],return_apple.ret[:-1])
beta_forward = results.slope
print(beta_lag, beta_current, beta_forward)

rho_market = return_market.ret.autocorr(lag=1)
print(rho_market)

adjusted_beta = (beta_current + beta_lag + beta_forward) / (1+2*rho_market)
print(adjusted_beta)
-0.045779304294165386 1.1272452267207456 0.0448843577857814
-0.009859925937180204
1.1490085587941545
1
2
results = stats.linregress(return_apple.ret, return_market.ret)
print(results)
LinregressResult(slope=0.3412508659195776, intercept=1.0421565455574495e-05, rvalue=0.620220452518429, pvalue=1.311085503675735e-134, stderr=0.012178272855290141)

小结

本文简单介绍了金融中基础性的CAPM模型以及Beta系数的估计和调整方法,作为一种单因子模型,CAPM以其易用性得到了广泛认可,但是从Beta系数的调整我们也可以发现,单因子模型在许多情况下还有着相当的局限性,所以多因子模型也就自然而然地出现了,我们将在后面介绍。