第 2 章 · 01 均值历史收益 本节摘要:本节是「收益模型」三节里的第一节,讲最朴素也最常见的期望收益估计—— 。它的直觉一句话说清:用资产过去每期的收益率取平均,当作未来的期望收益。本节拆解这个函数在 里的实现细节:从价格算日度收益率、几何平均(CAGR)与算术平均两种模式、 参数如何把日度均值年化,以及 开关对结果的影响。最后指出它最大的局限——估计噪声极大,在均值-方差优化器里会被「放大成错误权重」,因此文档反复警告「给错收益不如不给收益」。 内容来源:原项目源码 (函数 、 )、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 说清 meanhistoricalreturn 的算法直觉与数学公式。 区分 几何平均(CAGR)与算术平均两种模式。
本节摘要:本节是「收益模型」三节里的第一节,讲最朴素也最常见的期望收益估计——
mean_historical_return。它的直觉一句话说清:用资产过去每期的收益率取平均,当作未来的期望收益。本节拆解这个函数在expected_returns.py里的实现细节:从价格算日度收益率、几何平均(CAGR)与算术平均两种模式、frequency参数如何把日度均值年化,以及compounding开关对结果的影响。最后指出它最大的局限——估计噪声极大,在均值-方差优化器里会被「放大成错误权重」,因此文档反复警告「给错收益不如不给收益」。
内容来源:原项目源码
pypfopt/expected_returns.py(函数mean_historical_return、returns_from_prices)、文档docs/ExpectedReturns.rst,汉化并套用体系化模板。
阅读完本节,你应当能够:
均值历史收益的核心假设:资产未来的期望收益等于它过去收益的平均。这是教科书里最朴素、最直观的估计法,把「预测未来」简化成「外推过去」。
三步:把价格序列转成日度收益率、对日度收益取平均、再年化。年化是因为优化器最终要比较的是「年化收益 vs 年化风险」,二者时间尺度必须一致。
打开 pypfopt/expected_returns.py,定位 mean_historical_return 函数(源码 129-171 行):
def mean_historical_return( prices, returns_data=False, compounding=True, frequency=252, log_returns=False ): if not isinstance(prices, pd.DataFrame): warnings.warn("prices are not in a dataframe", RuntimeWarning) prices = pd.DataFrame(prices) if returns_data: returns = prices else: returns = returns_from_prices(prices, log_returns) _check_returns(returns) if compounding: return (1 + returns).prod() ** (frequency / returns.count()) - 1 else: return returns.mean() * frequency
逐行解读:
returns_data 开关:默认 False,即输入是价格。若你的输入已经是日度收益序列,设 returns_data=True 跳过 returns_from_prices 一步。returns_from_prices:用 prices.pct_change() 算简单收益率,丢掉首行 NaN。若 log_returns=True,改用对数收益 np.log(1 + pct_change())。_check_returns:扫一遍 NaN(排除前导 NaN)与 Inf,有问题发 UserWarning。这是数据质量兜底。compounding=True,默认)(1 + returns).prod() ** (frequency / returns.count()) - 1
数学上,这是复合年化收益率(CAGR):
CAGR = ( ∏ (1 + r_t) ) ^ (252 / T) - 1
其中 T 是收益期数(对每只资产单独 count(),故能容忍不等长序列)。它考虑了复利效应,是更「真实」的累计收益度量。
compounding=False)returns.mean() * frequency
即日度收益简单平均后乘 252:
μ = (1/T) Σ r_t × 252
算术平均在数学期望意义下更「纯粹」(组合收益 wᵀ μ 用算术平均才严格成立),但低估了波动较大的资产的长期复合收益。
💡 该用哪种:文档默认
compounding=True(CAGR),因为更贴近「持有到期的真实年化」。但严格按马科维茨理论,组合期望收益wᵀ μ用的是算术平均。实务上两者数值差异不大,随你偏好;若要做严肃的均值-方差理论推导,用compounding=False。
| 参数 | 默认 | 含义 | 备注 |
|---|---|---|---|
prices |
— | 价格 DataFrame(或收益,看 returns_data) |
必填 |
returns_data |
False |
True 表示第一参数已是收益 | 收益不应是对数收益 |
compounding |
True |
True 几何(CAGR),False 算术 | 见上方讨论 |
frequency |
252 |
一年交易日数 | 月度数据改 12,周度改 52 |
log_returns |
False |
是否用对数收益 | 简单收益与对数收益数值接近但不等 |
⚠️ frequency 必须匹配数据频率:喂日度价格用 252(默认);若喂月度价格,必须改
frequency=12,否则年化结果差 21 倍。这是新手最常踩的坑。
最朴素的调用:
from pypfopt import expected_returns df = ... # 日度价格 DataFrame mu = expected_returns.mean_historical_return(df)
返回的是 pd.Series,索引为资产代码,值为年化期望收益:
GOOG 0.255 AAPL 0.302 FB 0.288 ... dtype: float64
切换到算术平均 + 月度数据:
mu = expected_returns.mean_historical_return( df_monthly, compounding=False, frequency=12, )
把它直接喂给优化器:
from pypfopt import EfficientFrontier, risk_models S = risk_models.sample_cov(df) ef = EfficientFrontier(mu, S) ef.max_sharpe()
这就是第 1 章三行代码背后的细节。
expected_returns.py 还提供 return_model(prices, method="mean_historical_return", **kwargs) 统一入口,根据字符串派发到三个实现:
expected_returns.return_model(df, method="mean_historical_return") expected_returns.return_model(df, method="ema_historical_return", span=180) expected_returns.return_model(df, method="capm_return")
便于在配置文件里切换模型而不改代码。
docs/ExpectedReturns.rst 开篇就发了一段刺眼的 caution:
供给期望收益可能弊大于利。如果预测股票收益像取历史均值那么简单,我们都发财了。对多数用例,我建议把精力放在选合适的风险模型上。
mean_historical_return 的具体局限:
σ/√T,即便 T=5 年(约 1260 个交易日),对于年化波动 30% 的股票,标准误仍约 0.3/√5 ≈ 13.4%——和收益本身一个量级。这意味着 95% 置信区间宽得离谱。💡 替代方案:若坚持要用历史收益,用下一节的 EMA(对近期更敏感)或第三节的 CAPM(理论更稳);更好的做法是放弃 μ 只跑
min_volatility,或第 7 章的 Black-Litterman。研究(Kritzman et al. 2010)表明,最小方差组合样本外常常跑赢最大夏普组合。
compounding=True 几何(CAGR,默认),compounding=False 算术。frequency=252(日度,默认);月度改 12,周度改 52——务必匹配数据频率。returns_data:True 表示输入已是收益序列;log_returns=True 用对数收益。return_model(df, method="mean_historical_return")。下一节,我们看指数加权 EMA——一个对近期更敏感的简单改进。