从一个资产到多个资产
到目前为止我们只看一条收益率序列。投资组合持有许多资产,它的风险不仅取决于每个资产本身的风险,还取决于它们如何*一起*变动。两个资产的协方差是它们各自偏离均值的偏差之积的平均:如果倾向于同涨同跌则为正,一个涨时另一个跌则为负。n 个资产有 n * n 个协方差,排成协方差矩阵,它是对称的,对角线上是方差。
import numpy as np
# Five days of returns for two assets: shape (days, assets)
returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])
centered = returns - returns.mean(axis=0) # subtract each column's mean
cov = centered.T @ centered / (len(returns) - 1) # (assets x days) @ (days x assets) -> (assets x assets)
print(np.round(cov, 6))
print(np.allclose(cov, np.cov(returns, rowvar=False))) # True样本协方差矩阵
对于形状为 (days, assets) 的收益率表,整个矩阵只需一行线性代数。先减去每列的均值(axis=0,按天求平均),再用中心化后的表的转置乘以它自己:centered.T @ centered 的 (i, j) 项是各天 x_i * x_j 之和。然后像样本方差那样除以 days - 1。np.cov(returns, rowvar=False) 和 DataFrame.cov() 计算的就是它;注意 rowvar=False,因为 np.cov 默认资产在行上。
相关系数:去掉单位的协方差
0.0004 的协方差说明不了什么:它取决于两个资产本身的波动有多大。除以两个标准差的乘积会去掉尺度,得到总是介于 -1 和 1 之间的相关系数:corr[i, j] = cov[i, j] / (std[i] * std[j]),其中 std 是对角线的平方根。代码中 np.outer(std, std) 构造乘积表,一次除法就重新缩放整个矩阵,对角线上是 1。相关系数说明两个资产联动得多紧;而保留尺度的协方差才是风险公式所需要的。
# From covariance to correlation and annualised volatilities
import numpy as np
returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])
cov = np.cov(returns, rowvar=False)
std = np.sqrt(np.diag(cov))
corr = cov / np.outer(std, std)
print(np.round(corr, 4))
print(np.round(np.sqrt(np.diag(cov) * 252), 4)) # daily variances scaled to a year: volatility grows with sqrt(252)这个矩阵可能成立吗?
随便一张数表并不是协方差矩阵。它必须是对称的和半正定的:对任意权重 w,w' C w 是一个投资组合的方差,不可能为负。等价地说,没有小于零的特征值。估计出来的矩阵能通过,但手工修改过的,或由不同来源的相关系数拼凑起来的(例如 A~B = 0.9、B~C = 0.9、A~C = -0.9)则通不过。用 np.linalg.eigvalsh 检查,它针对对称矩阵并返回实特征值,同时为舍入误差留一个很小的容差。
# A validity check: symmetry and no negative eigenvalue
import numpy as np
def is_valid_cov(matrix, tol=1e-10):
symmetric = bool(np.all(np.abs(matrix - matrix.T) <= tol))
return symmetric and bool(np.linalg.eigvalsh(matrix).min() >= -tol)
good = np.array([[1.0, 0.5, 0.2], [0.5, 1.0, 0.3], [0.2, 0.3, 1.0]])
bad = np.array([[1.0, 0.9, -0.9], [0.9, 1.0, 0.9], [-0.9, 0.9, 1.0]])
print(is_valid_cov(good), np.round(np.linalg.eigvalsh(good), 3))
print(is_valid_cov(bad), np.round(np.linalg.eigvalsh(bad), 3))# Few days, many assets: the sample covariance matrix is singular
import numpy as np
rng = np.random.default_rng(0)
returns = rng.normal(0, 0.01, size=(5, 8)) # 5 days, 8 assets
cov = np.cov(returns, rowvar=False)
print(np.linalg.matrix_rank(cov), "of", cov.shape[0]) # 4 of 8: some portfolios seem to have zero variance(days, assets)表的协方差矩阵是centered.T @ centered / (days - 1):对称,对角线为方差。- 相关系数是
cov / np.outer(std, std):无单位,介于 -1 和 1 之间,对角线为 1;方差乘以periods年化,波动率乘以其平方根。 - 有效的协方差矩阵是对称的且没有负特征值(
np.linalg.eigvalsh);天数太少的估计或手工修补的矩阵可能违反这一点。
下一节:投资组合的收益与方差,把权重和这个矩阵变成组合的风险。
实验:评分内容
- 用
cov_matrix构建协方差表25 分 - 用
corr_from_cov缩放为相关系数25 分 - 用
annualised_vols读出每个资产的波动率25 分 - 用
is_valid_cov检验它是否可能成立25 分