如何基于含CumReturn的Pandas DataFrame计算月度收益率?
场景化月度收益率计算与组合累计收益构建(Pandas实现)
一、需求说明
需要基于分场景的累计收益数据,计算每个场景内的月度收益率,规则为:
- 同一Scenario下,第n行月度收益率 = (第n行CumReturn / 第n-1行CumReturn) - 1
- 每个Scenario的最后一行收益率设为NaN
处理多个指数数据后,将各指数月度收益率按权重线性组合,最终生成组合的累计收益。
示例数据
输入(示例A)
| Scenario | TimeStep | CumReturn |
|---|---|---|
| 1 | 0 | 1 |
| 1 | 1 | 1.05 |
| 1 | 2 | 1.07 |
| 2 | 0 | 1 |
| 2 | 1 | 1.04 |
| 2 | 2 | 1.02 |
预期输出(示例B,注:第三列应为MonthlyReturn)
| Scenario | TimeStep | MonthlyReturn |
|---|---|---|
| 1 | 0 | 0.05 |
| 1 | 1 | 0.019 |
| 1 | 2 | NaN |
| 2 | 0 | 0.04 |
| 2 | 1 | -0.019 |
| 2 | 2 | NaN |
二、月度收益率计算代码补全
针对你提供的循环代码,补全Monthly_Return的计算逻辑,利用Pandas的groupby和pct_change方法实现场景内独立计算:
import pandas as pd import numpy as np # 假设indeces、fundweights、file_names、filepath已定义 all_returns = pd.DataFrame() for idx, wght in zip(indeces, fundweights): df_temp = pd.read_csv(filepath + file_names[idx], header=None) df_temp = df_temp.stack() df_temp.index.names=['Scen','TIME'] df_temp = df_temp.to_frame(name='CumGrowth_idx') df_temp.reset_index(inplace=True) df_temp['Scen'] = df_temp['Scen'] + 1 df_temp = df_temp.rename(columns={'TIME':'Month'}) # 核心:按场景分组计算月度收益率 df_temp["Monthly_Return"] = df_temp.groupby('Scen')['CumGrowth_idx'].pct_change() # 将每个场景的最后一行收益率设为NaN df_temp.loc[df_temp.groupby('Scen').tail(1).index, 'Monthly_Return'] = np.nan # 整理数据,保留场景、月份、收益率,并重命名收益率列为对应指数标识 df_temp = df_temp[['Scen', 'Month', 'Monthly_Return']].rename(columns={'Monthly_Return': f'return_{idx}'}) # 合并到总收益率表 if all_returns.empty: all_returns = df_temp else: all_returns = pd.merge(all_returns, df_temp, on=['Scen', 'Month'], how='outer')
代码说明
groupby('Scen')确保每个场景内的收益率独立计算,不受其他场景影响pct_change()方法直接实现(当前值/前值)-1的收益率公式groupby('Scen').tail(1)定位每个场景的最后一行,赋值为NaN符合需求
三、线性组合计算组合收益率
基于合并后的多指数收益率表,按给定权重计算组合月度收益率:
# 计算加权组合收益率 all_returns['portfolio_return'] = all_returns.apply( lambda row: sum(row[f'return_{idx}'] * wght for idx, wght in zip(indeces, fundweights)), axis=1 )
四、重新计算组合累计收益
基于组合月度收益率,按场景生成累计收益序列(贴合示例逻辑,最后一行设为NaN):
def calc_cumulative(returns_series): # 初始化累计收益为1(对应TimeStep 0的初始值) cum_vals = [1.0] # 遍历除最后一个NaN外的所有收益率 for ret in returns_series.dropna(): cum_vals.append(cum_vals[-1] * (1 + ret)) # 最后一行补充NaN cum_vals.append(np.nan) return pd.Series(cum_vals, index=returns_series.index) # 按场景分组计算组合累计收益 all_returns['portfolio_cum_return'] = all_returns.groupby('Scen')['portfolio_return'].apply(calc_cumulative)
五、优化建议
- 批量读取提升效率:若所有指数文件结构一致,可通过
pd.concat一次性读取所有文件,减少循环开销 - 缺失值预处理:读取数据后先检查
CumGrowth_idx的缺失情况,避免收益率计算出现异常值 - 类型校验:确保
CumGrowth_idx为数值类型,可通过df_temp['CumGrowth_idx'] = pd.to_numeric(df_temp['CumGrowth_idx'], errors='coerce')强制转换
内容的提问来源于stack exchange,提问作者N27
相关产品推荐
相关产品推荐

