
本文详解如何使用 Pandas 的 resample 与 rolling.max() 协同完成“每月末为起点、向前滚动覆盖 N 个月”的累计收益率计算,确保每行(如 2023-02-28)的 2M/3M 列均填充对应未来 2 或 3 个自然月内的最大累计收益值。
本文详解如何使用 pandas 的 `resample` 与 `rolling.max()` 协同完成“每月末为起点、向前滚动覆盖 n 个月”的累计收益率计算,确保每行(如 2023-02-28)的 2m/3m 列均填充对应未来 2 或 3 个自然月内的最大累计收益值。
在金融时间序列分析中,常需基于月末截面计算“未来 N 个月滚动最大累计收益率”——例如:2023-02-28 对应的 2M cummreturns 应表示从该日开始,覆盖 2023-02 至 2023-03 这两个完整自然月的累计收益率最大值;同理,3M cummreturns 应覆盖 2023-02 至 2023-04 三个月。原始代码中直接对不同频率('1M'/'2M'/'3M')分别重采样,本质是按固定日历周期分组聚合,无法实现“以当前月为起点、向后滚动 N 期”的语义,导致大量 NaN 和逻辑错位。
正确解法分为两步:先按月降频得到月度代表值,再在此基础上做滚动窗口聚合。核心思想是:
✅ 先用 resample('1M').max() 将日频累计收益率压缩为每个自然月末的最大值(即 tmp);
✅ 再对 tmp 应用 rolling(window=N).max(),实现“当前月及前 N−1 个月内的最大值”,从而自然满足“以当月为起点、覆盖未来 N 个月”的业务含义(因数据已对齐至月末,滚动方向即时间正向)。
以下是完整可运行示例(含关键注释):
import yfinance as yf
import numpy as np
import pandas as pd
# 1. 获取数据并计算日度对数收益率与累计收益率
df = yf.download('SPY', '2023-01-01')
df = df[['Close']]
df['d_returns'] = np.log(df['Close'] / df['Close'].shift(1)) # 更清晰的写法
df.dropna(inplace=True)
# 2. 计算日度累计收益率(exp(cumsum(log_return)) = 累计净值)
cum_ret_daily = (1 + df['d_returns']).cumprod() # 推荐:比 exp(cumsum(log)) 更直观且数值稳定
# 若坚持对数法:cum_ret_daily = np.exp(df['d_returns'].cumsum())
# 3. 关键步骤:先按月重采样取月末最大累计收益
tmp = cum_ret_daily.resample('1M').max() # 得到 Series,索引为月末日期
# 4. 基于月度序列,滚动计算 1M/2M/3M 最大值
N = 3
result_df = pd.DataFrame({
f'{i+1}M cummreturns': tmp.rolling(window=i+1, min_periods=1).max()
for i in range(N)
})
print(result_df)⚠️ 注意事项:
- min_periods=1 确保首行不为 NaN(首月仅自身参与滚动);
- 使用 (1 + r).cumprod() 计算累计收益率比 np.exp(np.cumsum(np.log(1+r))) 更鲁棒,避免对数零值问题;
- resample('1M') 默认以月末对齐,若需月初可用 '1MS'(Month Start);
- 滚动窗口默认为左闭右闭,rolling(2) 包含当前月和上月——这恰好对应“当前月起算的 2 个月范围”,无需额外偏移。
最终输出中,每一行(如 2023-03-31)的 2M cummreturns 值,均为 2023-02-28 与 2023-03-31 两个月度累计收益中的较大者;3M 则取前三月最大值。这种模式可轻松扩展至 6M、12M 等任意期限,只需调整 N 和字典推导式即可,兼具简洁性与工程可维护性。










