Python模拟上证指数定投:用IRR计算真实年化收益率
前言
“定投真的比一次性投资好吗?”——这是投资领域最经典的问题之一。
本文用Python模拟上证指数近5年的定投策略,通过IRR(内部收益率)方法计算真实年化收益率,用数据回答这个问题。
技术栈
| 工具 | 用途 |
|---|---|
| tushare | 获取A股指数数据 |
| pandas | 数据处理与重采样 |
| matplotlib | 可视化 |
| numpy_financial | IRR计算 |
Step 1:获取上证指数数据
import tushare as ts
import pandas as pd
import datetime
# 设置tushare token
ts.set_token('your_token_here')
pro = ts.pro_api()
# 获取近5年日期范围
end_date = datetime.datetime.today()
start_date = end_date - datetime.timedelta(days=365*5)
# 获取上证指数日线数据
sh_index = pro.index_daily(
ts_code='000001.SH',
start_date=start_date.strftime('%Y%m%d'),
end_date=end_date.strftime('%Y%m%d')
)
# 转换为月末收盘价
sh_index['trade_date'] = pd.to_datetime(sh_index['trade_date'])
sh_index = sh_index.sort_values('trade_date').set_index('trade_date')
df_month = sh_index.resample('ME').last().reset_index()
df_month = df_month[['trade_date', 'close']]
踩坑提醒:
resample('M')在新版pandas中已废弃,需改用resample('ME')(Month End)。
Step 2:可视化走势
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimSun'] # 中文字体
matplotlib.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(12, 6))
plt.plot(df_month['trade_date'], df_month['close'], marker='o')
plt.title('上证指数近5年月末收盘价')
plt.xlabel('日期')
plt.ylabel('收盘价')
plt.grid(True)
plt.show()
近5年上证指数在3000-3700点区间震荡,典型的”心电图”行情。
Step 3:模拟定投策略
假设每月月末定投1000元:
monthly_invest = 1000 # 每月定投金额
# 每月买入份额 = 投资金额 / 当月收盘价
df_month['share'] = monthly_invest / df_month['close']
# 累计份额和累计投入
df_month['cum_share'] = df_month['share'].cumsum()
df_month['cum_invest'] = (monthly_invest).cumsum() # 等价于 monthly_invest * 期数
# 当前总市值 = 累计份额 × 最新收盘价
current_value = df_month['cum_share'].iloc[-1] * df_month['close'].iloc[-1]
print(f"累计投入: {df_month['cum_invest'].iloc[-1]:.2f} 元")
print(f"当前总市值: {current_value:.2f} 元")
输出:
累计投入: 61000.00 元
当前总市值: 65098.80 元
总收益 = 65098.80 - 61000 = 4098.80 元(约6.7%总收益率)
Step 4:用IRR计算真实年化收益率
总收益率6.7%看起来不错,但这是5年累计的。真实年化收益率需要用IRR(内部收益率)计算。
为什么不能简单除以年数? 因为定投是分期投入的,每笔资金的持有时间不同。IRR考虑了每笔现金流的时间价值。
from numpy_financial import irr
# 现金流:每月投入-1000元,最后一期加上总市值
cash_flows = [-monthly_invest] * len(df_month)
cash_flows[-1] += current_value # 最后一期:-1000 + 总市值
# 计算月度IRR,转换为年化
ytm_monthly = irr(cash_flows)
ytm_annual = (1 + ytm_monthly) ** 12 - 1
print(f"定投年化收益率(YTM):{ytm_annual*100:.2f}%")
输出:
定投年化收益率(YTM):2.61%
Step 5:对比一次性投资
# 一次性投资:第一月投入全部资金
lump_sum_invest = df_month['cum_invest'].iloc[-1] # 61000元
lump_sum_cash_flows = [-lump_sum_invest] + [0] * (len(df_month) - 1)
lump_sum_cash_flows[-1] += current_value
ytm_lump_monthly = irr(lump_sum_cash_flows)
ytm_lump_annual = (1 + ytm_lump_monthly) ** 12 - 1
print(f"一次性投资年化收益率(YTM):{ytm_lump_annual*100:.2f}%")
输出:
定投年化收益率(YTM):2.61%
一次性投资年化收益率(YTM):1.31%
结果分析
| 策略 | 总投入 | 最终市值 | 总收益率 | 年化收益率(YTM) |
|---|---|---|---|---|
| 每月定投1000元 | 61,000元 | 65,098.80元 | 6.72% | 2.61% |
| 一次性投入61,000元 | 61,000元 | 65,098.80元 | 6.72% | 1.31% |
关键发现:
- 两种策略的总收益率相同(都是6.72%),因为最终都持有相同金额
- 定投的年化收益率更高(2.61% vs 1.31%),因为资金是分期投入的,后投入的资金持有时间更短
- 在震荡市中,定投通过”低点多买、高点少买”降低了平均成本
IRR方法详解
IRR是金融学中计算投资收益率的标准方法,考虑了现金流的时间价值。
核心思想:找到一个折现率r,使得所有现金流的净现值(NPV)等于0。
\[NPV = \sum_{t=0}^{n} \frac{CF_t}{(1+r)^t} = 0\]对于定投:
- 每月现金流:
CF = -1000(投入) - 最后一期:
CF = -1000 + 总市值(投入 + 赎回)
# IRR的计算过程
# cash_flows = [-1000, -1000, -1000, ..., -1000 + 65098.80]
# irr(cash_flows) = 月度收益率
# 年化 = (1 + 月度收益率)^12 - 1
完整代码
import tushare as ts
import pandas as pd
import datetime
import matplotlib.pyplot as plt
from numpy_financial import irr
# 1. 获取数据
ts.set_token('your_token_here')
pro = ts.pro_api()
end_date = datetime.datetime.today()
start_date = end_date - datetime.timedelta(days=365*5)
sh_index = pro.index_daily(ts_code='000001.SH',
start_date=start_date.strftime('%Y%m%d'),
end_date=end_date.strftime('%Y%m%d'))
sh_index['trade_date'] = pd.to_datetime(sh_index['trade_date'])
sh_index = sh_index.sort_values('trade_date').set_index('trade_date')
df_month = sh_index.resample('ME').last().reset_index()[['trade_date', 'close']]
# 2. 模拟定投
monthly_invest = 1000
df_month['share'] = monthly_invest / df_month['close']
df_month['cum_share'] = df_month['share'].cumsum()
current_value = df_month['cum_share'].iloc[-1] * df_month['close'].iloc[-1]
# 3. 计算定投YTM
cash_flows = [-monthly_invest] * len(df_month)
cash_flows[-1] += current_value
ytm_annual = (1 + irr(cash_flows)) ** 12 - 1
# 4. 计算一次性投资YTM
lump_sum = monthly_invest * len(df_month)
lump_flows = [-lump_sum] + [0] * (len(df_month) - 1)
lump_flows[-1] += current_value
ytm_lump_annual = (1 + irr(lump_flows)) ** 12 - 1
print(f"定投年化收益率:{ytm_annual*100:.2f}%")
print(f"一次性投资年化收益率:{ytm_lump_annual*100:.2f}%")
总结
| 指标 | 定投 | 一次性投资 |
|---|---|---|
| 年化收益率 | 2.61% | 1.31% |
| 资金利用率 | 分期投入 | 全部投入 |
| 风险分散 | ✅ 自动平滑 | ❌ 集中暴露 |
| 适合场景 | 震荡市 | 牛市初期 |
投资建议:在A股这种长期震荡的市场中,定投是更稳健的策略。虽然牛市中一次性投资可能收益更高,但定投的”纪律性”和”平滑成本”特性更适合普通投资者。
Notebook文件:
F:\202507\sh_index_investment_analysis.ipynb,216KB完整分析。
Stay tuned! 🚀