第三章 资产收益率与风险:对数收益率计算、协方差矩阵、年化波动率

好,咱们进入第三章。说实话,这一章是整门课里最「数学」的部分,但也是最有用的部分。你想想看,做投资组合优化,核心就两件事:收益和风险。收益怎么算?风险怎么量化?这章就是解决这两个问题的。

我个人习惯,拿到一组价格数据后,第一件事就是算对数收益率。为什么?因为对数收益率在时间上可加,而且更符合正态分布的假设。嗯,这里要注意,普通收益率(简单收益率)在计算多期收益时会有偏差,而对数收益率没有这个问题。

3.1 对数收益率计算

对数收益率的公式很简单:

r_t = ln(P_t / P_{t-1})

其中 P_t 是 t 时刻的价格,P_{t-1} 是 t-1 时刻的价格。

用 Python 实现起来也就几行代码:

import numpy as np
import pandas as pd

# 假设 prices 是一个包含收盘价的 DataFrame
# 列名是股票代码,行是日期
prices = pd.read_csv('stock_prices.csv', index_col=0, parse_dates=True)

# 计算对数收益率
log_returns = np.log(prices / prices.shift(1))

# 去掉第一行的 NaN
log_returns = log_returns.dropna()

print(log_returns.head())

我在项目中遇到过一个问题:数据里偶尔会有价格为 0 的情况。你想想看,ln(0) 是什么?负无穷!所以一定要先做数据清洗。我曾经因为没检查这个,结果协方差矩阵里全是 NaN,折腾了半天才发现是数据源的问题。

避坑指南: 计算对数收益率前,务必检查价格数据中是否有 0 或负值。我曾经在回测时遇到过一个停牌股票,复牌后价格跳空,导致收益率异常大。建议用 prices[prices <= 0] = np.nan 先处理掉。

3.2 协方差矩阵

协方差矩阵是投资组合优化的核心。它衡量的是不同资产之间的联动关系。说白了,就是 A 涨的时候,B 是跟着涨还是跌?涨跌幅度有多大?

数学上,协方差矩阵的定义是:

Σ = E[(R - μ)(R - μ)^T]

其中 R 是收益率向量,μ 是期望收益率向量。

在 Python 里,计算协方差矩阵超级简单:

# 计算协方差矩阵
cov_matrix = log_returns.cov()

# 查看前几行
print(cov_matrix.head())

这里有个细节:cov() 默认是样本协方差,分母是 n-1。如果你想要总体协方差,可以用 cov(ddof=0)。我个人习惯用样本协方差,因为金融数据通常只是历史样本,不是全量数据。

小技巧: 协方差矩阵是对称矩阵,对角线是各资产的方差,非对角线是协方差。你可以用 np.linalg.eig(cov_matrix) 检查它是否正定——这在后续的优化中很重要。

3.3 年化波动率

波动率是风险的核心指标。日波动率怎么年化?很简单,乘以 sqrt(252)。为什么是 252?因为一年大约有 252 个交易日。

公式:

σ_annual = σ_daily * sqrt(252)

代码实现:

# 计算日波动率(标准差)
daily_vol = log_returns.std()

# 年化波动率
annual_vol = daily_vol * np.sqrt(252)

# 输出结果
print("年化波动率:")
print(annual_vol)

嗯,这里要注意:年化波动率假设收益率是独立同分布的。实际上,金融数据有波动率聚集效应——大涨之后往往跟着大跌。所以年化波动率只是一个近似值,别把它当真理。

核心要点: 年化波动率是投资组合优化的关键输入。在 Markowitz 均值-方差模型中,我们就是用年化波动率来度量风险的。波动率越高,风险越大,预期收益也应该越高。

3.4 知识体系总览

为了让你更直观地理解这一章的知识结构,我画了一张图:

第三章:资产收益率与风险 对数收益率 协方差矩阵 年化波动率 公式:ln(Pt/Pt-1) 时间可加性 对称矩阵 正定性检查 σ_annual = σ_daily * √252 波动率聚集效应 三者关系:对数收益率 → 协方差矩阵 → 年化波动率 共同构成投资组合优化的输入数据 图3-1:本章知识体系结构图

3.5 实战:完整流程

最后,我把整个流程串起来,给你一个完整的代码示例:

import numpy as np
import pandas as pd

# 1. 加载数据
prices = pd.read_csv('portfolio_prices.csv', index_col=0, parse_dates=True)

# 2. 数据清洗:检查是否有0或负值
if (prices <= 0).any().any():
    print("警告:发现非正价格,正在处理...")
    prices[prices <= 0] = np.nan
    prices = prices.fillna(method='ffill')

# 3. 计算对数收益率
log_returns = np.log(prices / prices.shift(1)).dropna()

# 4. 计算协方差矩阵(年化)
cov_matrix_annual = log_returns.cov() * 252

# 5. 计算年化波动率
annual_vol = np.sqrt(np.diag(cov_matrix_annual))

# 6. 输出结果
print("="*50)
print("资产年化波动率:")
for i, vol in enumerate(annual_vol):
    print(f"{prices.columns[i]}: {vol:.4f} ({vol*100:.2f}%)")
print("="*50)
print("\n年化协方差矩阵:")
print(cov_matrix_annual.round(6))
我的经验: 在实际项目中,我一般会用 3-5 年的历史数据来计算协方差矩阵。太短的数据噪声大,太长的数据可能包含市场结构变化。另外,记得检查协方差矩阵的条件数——如果条件数太大,说明矩阵接近奇异,优化结果可能不稳定。

好了,这一章的内容就这些。记住:对数收益率是基础,协方差矩阵是核心,年化波动率是输出。这三者构成了投资组合优化的数据基石。下一章我们会用这些数据来做真正的优化——找到那条漂亮的有效前沿。


专注资料整理