第 2 章 · 01 均值历史收益


文档摘要

第 2 章 · 01 均值历史收益 本节摘要:本节是「收益模型」三节里的第一节,讲最朴素也最常见的期望收益估计—— 。它的直觉一句话说清:用资产过去每期的收益率取平均,当作未来的期望收益。本节拆解这个函数在 里的实现细节:从价格算日度收益率、几何平均(CAGR)与算术平均两种模式、 参数如何把日度均值年化,以及 开关对结果的影响。最后指出它最大的局限——估计噪声极大,在均值-方差优化器里会被「放大成错误权重」,因此文档反复警告「给错收益不如不给收益」。 内容来源:原项目源码 (函数 、 )、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 说清 meanhistoricalreturn 的算法直觉与数学公式。 区分 几何平均(CAGR)与算术平均两种模式。

第 2 章 · 01 均值历史收益

本节摘要:本节是「收益模型」三节里的第一节,讲最朴素也最常见的期望收益估计——mean_historical_return。它的直觉一句话说清:用资产过去每期的收益率取平均,当作未来的期望收益。本节拆解这个函数在 expected_returns.py 里的实现细节:从价格算日度收益率、几何平均(CAGR)与算术平均两种模式、frequency 参数如何把日度均值年化,以及 compounding 开关对结果的影响。最后指出它最大的局限——估计噪声极大,在均值-方差优化器里会被「放大成错误权重」,因此文档反复警告「给错收益不如不给收益」。

内容来源:原项目源码 pypfopt/expected_returns.py(函数 mean_historical_returnreturns_from_prices)、文档 docs/ExpectedReturns.rst,汉化并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 说清 mean_historical_return 的算法直觉与数学公式。
  2. 区分 几何平均(CAGR)与算术平均两种模式。
  3. frequency 参数把日度均值年化,知道 252 的来历。
  4. returns_data 开关切换「喂价格还是喂收益」。
  5. 列举它的三大局限,理解作者为何警告「不给 μ 可能更好」。

一、算法直觉

均值历史收益的核心假设:资产未来的期望收益等于它过去收益的平均。这是教科书里最朴素、最直观的估计法,把「预测未来」简化成「外推过去」。

三步:把价格序列转成日度收益率、对日度收益取平均、再年化。年化是因为优化器最终要比较的是「年化收益 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

逐行解读:

  1. 类型保护:如果不是 DataFrame,转成 DataFrame 并给警告。建议你从一开始就用 DataFrame,避免这个开销。
  2. returns_data 开关:默认 False,即输入是价格。若你的输入已经是日度收益序列,设 returns_data=True 跳过 returns_from_prices 一步。
  3. returns_from_prices:用 prices.pct_change() 算简单收益率,丢掉首行 NaN。若 log_returns=True,改用对数收益 np.log(1 + pct_change())
  4. _check_returns:扫一遍 NaN(排除前导 NaN)与 Inf,有问题发 UserWarning。这是数据质量兜底。
  5. 分两种模式(关键):

2.1 几何平均模式(compounding=True,默认)

(1 + returns).prod() ** (frequency / returns.count()) - 1

数学上,这是复合年化收益率(CAGR):

CAGR = ( ∏ (1 + r_t) ) ^ (252 / T) - 1

其中 T 是收益期数(对每只资产单独 count(),故能容忍不等长序列)。它考虑了复利效应,是更「真实」的累计收益度量。

2.2 算术平均模式(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 章三行代码背后的细节。

五、统一入口 return_model

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 的具体局限:

  1. 估计噪声极大:历史均值的标准误是 σ/√T,即便 T=5 年(约 1260 个交易日),对于年化波动 30% 的股票,标准误仍约 0.3/√5 ≈ 13.4%——和收益本身一个量级。这意味着 95% 置信区间宽得离谱。
  2. 对近期不敏感:把 10 年前与昨天的数据等权对待,无法捕捉近期趋势变化。
  3. 被优化器放大:均值-方差优化器会最大化输入 μ,因此错误的 μ 直接变成错误的极端权重——这是它「比不给更糟」的根本原因。

💡 替代方案:若坚持要用历史收益,用下一节的 EMA(对近期更敏感)或第三节的 CAPM(理论更稳);更好的做法是放弃 μ 只跑 min_volatility,或第 7 章的 Black-Litterman。研究(Kritzman et al. 2010)表明,最小方差组合样本外常常跑赢最大夏普组合。

本节要点回顾

  1. 直觉:用过去每期收益率的平均当作未来期望收益。
  2. 两种模式:compounding=True 几何(CAGR,默认),compounding=False 算术。
  3. 年化:frequency=252(日度,默认);月度改 12,周度改 52——务必匹配数据频率。
  4. returns_data:True 表示输入已是收益序列;log_returns=True 用对数收益。
  5. 统一入口:return_model(df, method="mean_historical_return")
  6. 局限:噪声大、对近期不敏感、被优化器放大成错误权重——作者警告「不给 μ 可能更好」。

下一节,我们看指数加权 EMA——一个对近期更敏感的简单改进。


发布者: 作者: 灏天文库 转发
评论区 (0)
U