CAPM - Capital Asset Pricing Model

Watch the video for the full overview.

Portfolio Returns:

$r_p(t) = \sum\limits_{i}^{n}w_i r_i(t)$

Market Weights:

$ w_i = \frac{MarketCap_i}{\sum_{j}^{n}{MarketCap_j}} $

CAPM of a portfolio

$ r_p(t) = \beta_pr_m(t) + \sum\limits_{i}^{n}w_i \alpha_i(t)$


In [1]:
# Model CAPM as a simple linear regression

In [2]:
from scipy import stats

In [3]:
help(stats.linregress)


Help on function linregress in module scipy.stats._stats_mstats_common:

linregress(x, y=None)
    Calculate a linear least-squares regression for two sets of measurements.
    
    Parameters
    ----------
    x, y : array_like
        Two sets of measurements.  Both arrays should have the same length.
        If only x is given (and y=None), then it must be a two-dimensional
        array where one dimension has length 2.  The two sets of measurements
        are then found by splitting the array along the length-2 dimension.
    
    Returns
    -------
    slope : float
        slope of the regression line
    intercept : float
        intercept of the regression line
    rvalue : float
        correlation coefficient
    pvalue : float
        two-sided p-value for a hypothesis test whose null hypothesis is
        that the slope is zero.
    stderr : float
        Standard error of the estimated gradient.
    
    See also
    --------
    :func:`scipy.optimize.curve_fit` : Use non-linear
     least squares to fit a function to data.
    :func:`scipy.optimize.leastsq` : Minimize the sum of
     squares of a set of equations.
    
    Examples
    --------
    >>> import matplotlib.pyplot as plt
    >>> from scipy import stats
    >>> np.random.seed(12345678)
    >>> x = np.random.random(10)
    >>> y = np.random.random(10)
    >>> slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
    
    To get coefficient of determination (r_squared)
    
    >>> print("r-squared:", r_value**2)
    ('r-squared:', 0.080402268539028335)
    
    Plot the data along with the fitted line
    
    >>> plt.plot(x, y, 'o', label='original data')
    >>> plt.plot(x, intercept + slope*x, 'r', label='fitted line')
    >>> plt.legend()
    >>> plt.show()


In [4]:
import pandas as pd

In [5]:
import pandas_datareader as web

In [6]:
spy_etf = web.DataReader('SPY', 'google')

In [7]:
spy_etf.info()


<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 251 entries, 2016-09-19 to 2017-09-15
Data columns (total 5 columns):
Open      250 non-null float64
High      250 non-null float64
Low       250 non-null float64
Close     251 non-null float64
Volume    251 non-null int64
dtypes: float64(4), int64(1)
memory usage: 11.8 KB

In [8]:
spy_etf.head()


Out[8]:
Open High Low Close Volume
Date
2016-09-19 214.13 214.88 213.03 213.41 80250490
2016-09-20 214.41 214.59 213.38 213.42 69665279
2016-09-21 214.24 216.03 213.44 215.82 110284425
2016-09-22 217.00 217.53 216.71 217.18 76678713
2016-09-23 216.72 216.88 215.88 215.99 73630921

In [9]:
start = pd.to_datetime('2010-01-04')
end = pd.to_datetime('2017-07-18')

In [10]:
aapl = web.DataReader('AAPL', 'google', start, end)

In [11]:
aapl.head()


Out[11]:
Open High Low Close Volume
Date
2016-09-19 115.19 116.18 113.25 113.58 47023046
2016-09-20 113.05 114.12 112.51 113.57 34514269
2016-09-21 113.85 113.99 112.44 113.55 36003185
2016-09-22 114.35 114.94 114.00 114.62 31073984
2016-09-23 114.42 114.79 111.55 112.71 52481151

In [12]:
import matplotlib.pyplot as plt
%matplotlib inline

In [13]:
aapl['Close'].plot(label = 'AAPL',
                   figsize = (12, 8))
spy_etf['Close'].plot(label = 'SPY Index')
plt.legend()


Out[13]:
<matplotlib.legend.Legend at 0x1a7e3eeef98>

Compare Cumulative Return


In [14]:
aapl['Cumulative'] = aapl['Close'] / aapl['Close'].iloc[0]
spy_etf['Cumulative'] = spy_etf['Close'] / spy_etf['Close'].iloc[0]

In [15]:
aapl['Cumulative'].plot(label = 'AAPL',
                        figsize = (10,8))
spy_etf['Cumulative'].plot(label = 'SPY Index')
plt.legend()
plt.title('Cumulative Return')


Out[15]:
<matplotlib.text.Text at 0x1a7e506ea58>

Get Daily Return


In [16]:
aapl['Daily Return'] = aapl['Close'].pct_change(1)
spy_etf['Daily Return'] = spy_etf['Close'].pct_change(1)

In [17]:
fig = plt.figure(figsize = (12, 8))
plt.scatter(aapl['Daily Return'], spy_etf['Daily Return'],
            alpha = 0.3)


Out[17]:
<matplotlib.collections.PathCollection at 0x1a7e5535320>

In [18]:
aapl['Daily Return'].hist(bins = 100, figsize = (12, 8))


Out[18]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a7e5554c88>

In [19]:
spy_etf['Daily Return'].hist(bins = 100, figsize = (12, 8))


Out[19]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a7e55c99e8>

In [20]:
beta,alpha, r_value, p_value, std_err = stats.linregress(aapl['Daily Return'].iloc[1:],spy_etf['Daily Return'].iloc[1:])

In [21]:
beta


Out[21]:
0.228906597120866

In [22]:
alpha


Out[22]:
0.00030597213336046542

In [23]:
r_value


Out[23]:
0.49738282104700338

In [24]:
spy_etf['Daily Return'].head()


Out[24]:
Date
2016-09-19         NaN
2016-09-20    0.000047
2016-09-21    0.011245
2016-09-22    0.006302
2016-09-23   -0.005479
Name: Daily Return, dtype: float64

In [25]:
import numpy as np

In [26]:
noise = np.random.normal(0, 0.001, len(spy_etf['Daily Return'].iloc[1:]))

In [27]:
noise


Out[27]:
array([ -9.78836991e-05,   1.87674266e-04,  -1.99297712e-03,
        -2.61085893e-04,  -4.19135719e-04,  -1.12214385e-04,
        -1.54329803e-03,  -1.77263961e-04,  -1.03146392e-03,
        -2.17750196e-04,  -2.90126796e-05,   6.29873945e-06,
         6.72558435e-04,  -4.07485050e-04,   4.79680635e-04,
        -2.44509201e-03,  -2.88638120e-03,  -6.50990483e-04,
        -9.69376951e-04,   9.03761267e-04,   6.70812127e-04,
        -3.82394046e-04,   1.06799514e-03,  -5.72159450e-04,
         3.64957195e-04,   5.27049117e-04,   1.88525365e-04,
         1.25234304e-03,  -5.36604480e-04,   1.67152587e-03,
         5.99922284e-04,  -7.74024912e-04,  -1.78823452e-03,
         1.13993056e-03,   7.37033162e-05,  -8.26086282e-04,
         1.33928679e-04,   7.28833775e-04,   3.08134295e-04,
         1.74955758e-04,   8.49098409e-04,   2.23561400e-04,
        -4.13513006e-04,  -4.69609505e-04,  -1.60696522e-04,
        -4.75829596e-04,  -1.11199267e-03,   1.18648768e-03,
        -2.24434105e-04,  -1.24489545e-03,   7.39111150e-04,
         3.97920631e-04,   7.28116238e-04,   1.13454456e-03,
        -6.15049382e-04,   3.64130931e-06,  -1.15458332e-03,
        -1.08873382e-03,   2.25287894e-04,  -1.01310959e-03,
         1.35124000e-03,  -9.37938825e-04,  -1.01964139e-04,
         7.51558450e-04,  -3.10647464e-03,   1.31098770e-03,
         9.75975922e-04,   9.89967025e-04,  -1.20507332e-04,
         7.92035423e-04,   1.74323612e-04,   1.02090154e-03,
         9.66333648e-04,   4.78264733e-05,  -1.88167075e-03,
         8.81896188e-04,  -1.00693484e-03,  -6.41373242e-04,
        -5.68460385e-04,   1.35883041e-03,  -1.01098779e-03,
         1.04840565e-04,   3.83222028e-04,   8.74443012e-04,
         4.02020674e-04,   3.58094248e-04,   8.35183725e-05,
         2.65490747e-03,   2.96152055e-04,   1.26443412e-03,
         2.99764575e-04,   3.21734367e-04,   1.11541649e-03,
         6.25504370e-04,   2.58517875e-04,  -7.67378424e-04,
         8.81518909e-04,  -4.96383026e-04,   4.14465111e-05,
        -1.65921881e-03,   7.19688159e-04,  -3.86132843e-04,
         1.67818760e-03,  -1.24375745e-03,  -2.14065265e-04,
         9.26250507e-04,   1.53768922e-03,   2.10180415e-03,
         1.56418313e-03,  -9.22330144e-05,  -1.60214426e-03,
        -1.88275655e-03,   6.45843358e-04,  -1.90295185e-03,
         1.13518281e-03,   7.53813080e-04,   4.69908872e-05,
         8.69251483e-04,  -3.08642101e-04,   1.23632264e-03,
         4.97603488e-04,   4.87318563e-04,  -1.67597046e-03,
        -6.76839650e-04,   1.14499969e-03,  -1.08721167e-03,
         1.55591849e-03,  -4.75483239e-05,  -1.42619380e-03,
        -3.93300190e-04,   6.36793544e-04,   9.63368979e-04,
        -5.22360182e-04,   2.33731569e-04,   1.21789880e-03,
        -3.24107804e-04,   5.60125429e-05,   7.46734844e-04,
        -4.21482948e-04,  -1.31101103e-03,  -7.68776246e-04,
         1.43344815e-03,  -1.64052369e-04,   2.47101201e-04,
        -3.88118049e-04,  -4.75841107e-04,  -5.35722831e-04,
         4.76171918e-04,   4.81388686e-05,   2.31959705e-03,
        -2.11513283e-04,   2.62324270e-04,  -8.41700863e-04,
        -6.34648276e-04,   2.04855424e-04,   1.40417147e-03,
         6.11757064e-04,   3.25772280e-04,   1.58348977e-04,
        -1.23645992e-03,  -1.21841436e-03,   9.56325543e-05,
        -5.62812547e-04,  -2.19009052e-04,  -1.91745139e-05,
         1.74301628e-04,   6.69935912e-04,  -1.30117060e-03,
         6.42185235e-04,   3.38679263e-04,  -7.28872070e-04,
         5.63755262e-04,  -9.55183427e-04,   6.89841722e-04,
         1.64369963e-03,  -1.02096740e-03,   6.04072827e-04,
         1.19060657e-04,   1.54631714e-03,   3.51999601e-04,
         1.04183170e-03,  -2.53943401e-03,  -2.27056507e-04,
        -8.06542244e-04,  -1.93001776e-03,   4.45985512e-05,
        -1.67615379e-03,   2.27955257e-04,   5.85500273e-04,
         5.77673594e-05,  -3.37377777e-04,  -4.03359219e-04,
         6.26575191e-04,   8.58010168e-04,  -6.87613695e-04,
         1.33773470e-03,   5.64616220e-05,   1.81708956e-03,
        -1.69783206e-03,  -1.10183775e-03,   1.12015033e-03,
        -7.06568744e-04,  -9.03785116e-04,   1.36890135e-03,
        -7.53267501e-04,  -1.86091573e-04,  -6.12230930e-04,
        -1.44469134e-03,  -1.08452572e-03,  -6.00401812e-04,
         6.62531696e-04,   4.11298897e-04,  -1.02651255e-03,
        -2.45745879e-03,  -6.83438090e-04,  -1.08324804e-03,
        -9.35468161e-04,  -1.90800936e-03,  -5.78597392e-04,
        -4.42286438e-04,  -2.91768206e-04,   1.56221100e-03,
        -5.86975213e-04,  -2.16286343e-04,  -5.94256318e-04,
        -1.51156062e-03,   1.25804537e-03,  -7.13430528e-04,
         8.01161326e-04,   9.67652142e-05,  -3.40025475e-04,
         1.72311668e-04,   1.58368738e-03,  -6.71918934e-04,
        -3.57420115e-04,   1.78138419e-04,  -1.20128770e-04,
        -1.51037070e-03,   5.37265690e-05,  -1.47344769e-03,
         1.87012721e-04,   6.32707264e-05,   1.01490097e-03,
        -9.56390254e-04,  -1.29209591e-03,  -6.94242254e-04,
         7.36576023e-04,  -1.24140257e-03,  -6.06792854e-04,
         1.07059791e-03])

In [28]:
spy_etf['Daily Return'].iloc[1:] + noise


Out[28]:
Date
2016-09-20   -0.000051
2016-09-21    0.011433
2016-09-22    0.004309
2016-09-23   -0.005740
2016-09-26   -0.008521
2016-09-27    0.006096
2016-09-28    0.003420
2016-09-29   -0.009225
2016-09-30    0.006515
2016-10-03   -0.002622
2016-10-04   -0.005127
2016-10-05    0.004431
2016-10-06    0.001368
2016-10-07   -0.003837
2016-10-10    0.005688
2016-10-11   -0.015075
2016-10-12   -0.001574
2016-10-13   -0.003926
2016-10-14   -0.000453
2016-10-17   -0.002568
2016-10-18    0.006933
2016-10-19    0.002285
2016-10-20   -0.000799
2016-10-21   -0.000105
2016-10-24    0.004618
2016-10-25   -0.002824
2016-10-26   -0.001819
2016-10-27   -0.001414
2016-10-28   -0.003492
2016-10-31    0.001719
                ...   
2017-08-04    0.001530
2017-08-07    0.003421
2017-08-08   -0.003048
2017-08-09   -0.000257
2017-08-10   -0.014710
2017-08-11   -0.000035
2017-08-14    0.011171
2017-08-15   -0.000835
2017-08-16    0.002546
2017-08-17   -0.015494
2017-08-18   -0.001903
2017-08-21    0.000955
2017-08-22    0.012041
2017-08-23   -0.004257
2017-08-24   -0.002688
2017-08-25    0.002514
2017-08-28   -0.000079
2017-08-29   -0.000366
2017-08-30    0.004791
2017-08-31    0.004543
2017-09-01    0.001601
2017-09-05   -0.007119
2017-09-06    0.004429
2017-09-07   -0.001078
2017-09-08   -0.002467
2017-09-11    0.009972
2017-09-12    0.004107
2017-09-13   -0.000761
2017-09-14   -0.000927
2017-09-15   -0.002528
Name: Daily Return, Length: 250, dtype: float64

In [29]:
beta, alpha, r_value, p_value, std_err = stats.linregress(spy_etf['Daily Return'].iloc[1:]+noise,
                                                      spy_etf['Daily Return'].iloc[1:])

In [30]:
beta


Out[30]:
0.95682599662142187

In [31]:
alpha


Out[31]:
7.8117879583955419e-05

Looks like our understanding is correct!