fix: 报告生成器数据对齐修复

修复内容:
1. 指数价格获取改用index_data而非index_close
   - 原问题:index_close对齐后N225最后几天的值为nan
   - 修复:从原始OHLCV获取close,用ffill填充缺失值

2. ETF净值数据对齐到回测日期
   - 原问题:etf_nav_data索引与backtest_result不对齐
   - 修复:用reindex(backtest_result.index, method='ffill')

3. ETF价格数据同样对齐到回测日期

修复后报告显示:
- 日经225指数最新价: 62713.65(原为nan)
- 创业板指溢价率: +3.70%⚠️
- 日经225溢价率: +0.85%
This commit is contained in:
2026-05-12 01:50:30 +08:00
parent 38a31357d1
commit 412177837f

View File

@@ -83,18 +83,33 @@ def run_with_legacy_report():
backtest_result['基准净值'] = benchmark_nav.values
backtest_result['基准日收益率'] = benchmark_close_aligned.pct_change().values
# 2. 各标的净值(指数价格)
index_close = data.get('index_close')
# 2. 各标的净值(指数价格)- 使用index_data而非index_close
# index_close可能对齐有问题直接从index_data获取
index_data = data.get('index_data')
valid_codes = data['valid_codes']
for code in valid_codes:
if index_close is not None and code in index_close.columns:
if index_data is not None and code in index_data:
# 从原始OHLCV数据获取close价格
price_df = index_data[code]
if 'close' in price_df.columns:
price_series = price_df['close']
else:
price_series = price_df.iloc[:, 0] # 取第一列
# 对齐到回测日期
price_aligned = price_series.reindex(backtest_result.index, method='ffill')
# 处理最后几天的NaN用最后一个有效值填充
price_aligned = price_aligned.ffill() # 前向填充剩余NaN
# 计算该标的的净值曲线
price_series = index_close[code].loc[backtest_result.index]
nav_series = (1 + price_series.pct_change()).cumprod()
nav_series = nav_series / nav_series.iloc[0] if nav_series.iloc[0] > 0 else nav_series
nav_series = (1 + price_aligned.pct_change()).cumprod()
first_valid = nav_series.dropna().iloc[0] if len(nav_series.dropna()) > 0 else 1
nav_series = nav_series / first_valid # 归一化起点为1
backtest_result[f'净值_{code}'] = nav_series.values
backtest_result[code] = price_series.values # 当前价格
backtest_result[code] = price_aligned.values # 当前价格
# 3. 得分列从factor_df获取
for code in valid_codes:
@@ -119,17 +134,24 @@ def run_with_legacy_report():
if etf_data is not None:
# 转换列名:指数代码 -> ETF代码通过etf_code_map
# 并对齐到回测日期
etf_code_map = data.get('etf_code_map', {})
etf_price_data = pd.DataFrame(index=etf_data.index)
etf_price_data = pd.DataFrame(index=backtest_result.index)
for idx_code, etf_code in etf_code_map.items():
if etf_code in etf_data.columns:
etf_price_data[idx_code] = etf_data[etf_code]
# 对齐ETF价格数据到回测日期
price_aligned = etf_data[etf_code].reindex(backtest_result.index, method='ffill')
etf_price_data[idx_code] = price_aligned.values
if etf_nav_data is not None:
etf_nav_data_raw = pd.DataFrame(index=etf_nav_data.index)
# ETF净值数据列名是ETF代码需要用etf_code_map映射
# 并对齐到回测日期
etf_nav_data_raw = pd.DataFrame(index=backtest_result.index)
for idx_code, etf_code in etf_code_map.items():
if etf_code in etf_nav_data.columns:
etf_nav_data_raw[idx_code] = etf_nav_data[etf_code]
# 对齐净值数据到回测日期使用ffill处理日期差异
nav_aligned = etf_nav_data[etf_code].reindex(backtest_result.index, method='ffill')
etf_nav_data_raw[idx_code] = nav_aligned.values
# 生成原引擎格式的报告
print("\n" + "=" * 60)
@@ -139,6 +161,9 @@ def run_with_legacy_report():
save_path = 'results/rotation_legacy'
os.makedirs('results', exist_ok=True)
# 获取index_close用于报告图表绘制
index_close = data.get('index_close')
metrics = generate_performance_report(
backtest_result=backtest_result,
code_list=valid_codes,