One number for market risk
Most stocks rise when the market rises and fall when it falls. The beta of an asset measures by how much: a beta of 1.5 means that, on average, a 1% market move comes with a 1.5% move in the asset. It is the slope of the asset's returns plotted against the market's, covariance(asset, market) / variance(market). Beta is the part of the risk you cannot diversify away, because every asset in the market carries it.
import numpy as np
market = np.array([0.01, -0.02, 0.015, 0.005, -0.01, 0.02])
asset = 1.5 * market + np.array([0.002, -0.001, 0.003, 0.0, -0.002, 0.001]) # moves 1.5x the market, plus noise
market_dev = market - market.mean()
covariance = np.sum((asset - asset.mean()) * market_dev) / (len(market) - 1)
variance = np.sum(market_dev ** 2) / (len(market) - 1)
print(round(float(covariance / variance), 4)) # 1.5972: about 1.5, the noise moves it a little
print(round(float(np.polyfit(market, asset, 1)[0]), 4)) # the regression slope gives the same number# Beta does not care about a constant, and scales with the asset
import numpy as np
def beta(asset, market):
market_dev = market - market.mean()
return float(np.sum((asset - asset.mean()) * market_dev) / np.sum(market_dev ** 2))
rng = np.random.default_rng(4)
market = rng.normal(0, 0.01, size=100)
print(round(beta(2.5 * market, market), 4)) # 2.5 an asset that moves 2.5x the market
print(round(beta(market + 0.01, market), 4)) # 1.0 adding a constant changes nothing
print(round(beta(-market, market), 4)) # -1.0 a perfect hedgeThe Capital Asset Pricing Model
CAPM turns that into a price of risk. Investors are paid for bearing market risk, and for nothing else, so an asset's expected return is the risk-free rate plus its beta times the market risk premium (market return minus the risk-free rate): E[R] = rf + beta * (E[R_m] - rf). A beta of 0 earns the risk-free rate, a beta of 1 earns the market return, and a beta of 2 earns the risk-free rate plus twice the premium.
def capm_expected_return(rf, beta_value, market_return):
return rf + beta_value * (market_return - rf)
rf, market_return = 0.01, 0.06
for b in (0.0, 0.5, 1.0, 1.5, 2.0):
print(b, round(capm_expected_return(rf, b, market_return), 4)) # 0.01, 0.035, 0.06, 0.085, 0.11Alpha: what is left over
If CAPM is right, an asset earns exactly the return its beta justifies. The difference between what it earned and what CAPM expected is Jensen's alpha: mean(asset) - (rf + beta * (mean(market) - rf)). A positive alpha means it did better than its market risk explains; that is what active managers sell, and what is hardest to find. The part of the asset's movement the market does not explain is the residual risk, measured by the standard deviation of the regression residuals, which can be diversified away.
# Alpha and residual risk of an asset with a little skill in it
import numpy as np
rng = np.random.default_rng(2)
market = rng.normal(0.0004, 0.01, size=500)
asset = 0.0003 + 1.2 * market + rng.normal(0, 0.005, size=500) # true alpha 3 bps a day, beta 1.2, residual 0.5%
rf = 0.0001
market_dev = market - market.mean()
b = np.sum((asset - asset.mean()) * market_dev) / np.sum(market_dev ** 2)
alpha = asset.mean() - (rf + b * (market.mean() - rf))
a = asset.mean() - b * market.mean()
residual = asset - (a + b * market)
print(round(float(b), 3), round(float(alpha), 5), round(float(np.sqrt(np.sum(residual ** 2) / (len(asset) - 2))), 4))- Beta =
covariance(asset, market) / variance(market), the slope against the market; it is the undiversifiable part of risk. - CAPM:
rf + beta * (market - rf); Jensen's alpha is the average return minus that, the skill (or luck) beyond market risk. - Residual risk is the standard deviation of
asset - (a + b * market)withn - 2; it can be diversified; beta itself is noisy.
Next up: multi-factor models (Fama–French), explaining returns with more than the market.
Lab · what's graded
- Measure the market sensitivity with
beta25 pts - Price the risk with
capm_expected_return25 pts - Find the skill, if any, with
jensen_alpha25 pts - Size the leftover risk with
residual_vol25 pts