
本文详解如何使用 Pandas 的 resample 与 rolling.max() 协同操作,为每个日历月末生成覆盖未来1个月、2个月、3个月的滚动最大累计收益率,解决传统 resample('2M') 导致数据对齐错位与缺失的问题。
本文详解如何使用 pandas 的 `resample` 与 `rolling.max()` 协同操作,为每个日历月末生成覆盖未来1个月、2个月、3个月的滚动最大累计收益率,解决传统 `resample('2m')` 导致数据对齐错位与缺失的问题。
在量化分析中,常需基于月度频率计算“向前看”的滚动累计收益——例如:对2023-02-28这一月末时点,2M cummreturns 应反映 2023-02至2023-03(即连续2个自然月)内的最大累计收益;3M cummreturns 则对应 2023-02至2023-04 的最大值。但直接对原始日频数据调用 resample('2M').max() 会按固定2个月周期分组(如2023-01–2023-02、2023-03–2023-04),导致结果无法对齐到每个日历月末,且产生大量 NaN。
正确做法是 两步解耦:
- 先按日历月聚合:用 resample('1M').max() 将日频累计收益压缩为标准月末序列(索引为各月末日期,值为当月内最大累计收益);
- 再滚动扩展窗口:在此月度序列上应用 rolling(window=n).max(),实现“以当前月为起点、向后覆盖n个连续自然月”的滚动最大值计算。
以下是完整、可复现的实现代码:
import yfinance as yf
import numpy as np
import pandas as pd
# 获取SPY日线数据(2023年起)
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)
# 计算日频累计收益率(e^sum(log-returns) = 总复利因子)
cum_ret_series = df['d_returns'].cumsum().apply(np.exp)
# ✅ 关键步骤1:按日历月末重采样,获取每月最大累计收益
monthly_max = cum_ret_series.resample('1M').max()
# ✅ 关键步骤2:在月度序列上执行滚动最大值(1M/2M/3M窗口)
N = 3
result_df = pd.DataFrame({
f'{i+1}M cummreturns': monthly_max.rolling(i+1, min_periods=1).max()
for i in range(N)
})
print(result_df)输出示例(截取前几行):
1M cummreturns 2M cummreturns 3M cummreturns Date 2023-01-31 1.067381 1.067381 1.067381 2023-02-28 1.094428 1.094428 1.094428 2023-03-31 1.075022 1.094428 1.094428 2023-04-30 1.092196 1.092196 1.094428 2023-05-31 1.103356 1.103356 1.103356 ...
✅ 关键优势说明:
- 所有列均严格对齐同一月末索引(如 2023-02-28 行中,2M cummreturns = max(2023-02, 2023-03),3M = max(2023-02, 2023-03, 2023-04));
- min_periods=1 确保首期不返回 NaN,保证时间序列完整性;
- 完全避免了 resample('2M') 因周期起始点漂移造成的逻辑错位。
⚠️ 注意事项:
- rolling().max() 默认按索引顺序从前向后滚动(即包含当前及之前 n-1 期),此处正符合“以当月为起点、向前追溯n月”的业务语义;
- 若需严格“向前看”(即仅含未来月份),需先 shift(-1) 再滚动,但通常月末指标天然代表该月结束状态,故当前实现更符合行业惯例;
- 数据需确保索引为 DatetimeIndex 且已排序(yfinance 默认满足),否则 resample 和 rolling 行为不可靠。
此方法兼具逻辑严谨性与工程简洁性,是构建多周期绩效归因、滚动风险指标或择时信号系统的标准范式。










