
kevin9327andClaude Opus 4.8
d92ef17851
fix(backtest): max_hold_days 强制退出信号真正生效 (#198)
问题: /api/backtest/run 设置 max_hold_days 时, 强制平仓完全不生效, 持仓不会
按最大持有天数退出, 导致交易记录、持仓周期和收益统计失真。
根因(services/backtest.py 的 max_hold 内联逻辑, 两处缺陷叠加):
1. `exits_idx.iloc[end_i][col] = True` 是链式索引: iloc[end_i] 先取出一行副本,
再对副本赋值, 在 pandas Copy-on-Write 语义下写入不会落到原矩阵(pandas 3.x
直接抛 ChainedAssignmentError)。强制退出单元格始终为 False。
2. `exits_idx = entries.copy()` 以入场矩阵起步, 使强制退出矩阵天然带上所有入场位;
即便修好第 1 点, 也会在入场当日就强制平仓, 而非 max_hold_days 之后。
修复: 抽出纯函数 _build_max_hold_exits(entries, max_hold_days), 从全 False 起步,
用单步定位 iloc[row, col_loc] 写入入场后第 max_hold_days 个交易日的强制退出位;
run() 中改为与用户 exits 做 OR, 保留原有信号退出。抽出后该逻辑不依赖 vectorbt,
可独立回归(run() 整体仍需 vectorbt 可选依赖)。
边界: end_i 越界 clamp 到最后一根 K; 入场即最后一根 K 时 end_i==i 不产生退出。
验证: 新增 tests/backtest/test_max_hold_exits.py 覆盖强制退出落位、不误标入场位、
越界 clamp、末根不退出、多入场、多列独立 6 例, 修复后全过; 并以复刻旧内联逻辑的
脚本在 pandas 3.0.5 上确认修复前目标退出单元格恒为 False(ChainedAssignmentError)。
ruff 通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 18:47:08 +09:00
..
2026-07-26 12:19:55 +08:00
2026-06-18 17:18:23 +08:00
2026-08-21 17:41:35 +08:00
2026-08-24 17:14:39 +08:00
2026-07-08 18:10:19 +08:00
2026-07-03 14:11:27 +08:00
2026-08-05 17:56:27 +08:00
2026-08-30 18:47:08 +09:00
2026-08-17 17:47:46 +08:00
2026-08-23 19:12:57 +08:00
2026-08-17 12:18:17 +08:00
2026-08-16 23:39:07 +08:00
2026-07-16 14:34:07 +08:00
2026-08-10 17:53:14 +08:00
2026-07-05 22:57:46 +08:00
2026-08-17 17:47:46 +08:00
2026-08-16 23:39:07 +08:00
2026-08-16 23:39:07 +08:00
2026-07-05 22:57:46 +08:00
2026-07-06 17:00:56 +08:00
2026-07-08 18:10:19 +08:00
2026-08-16 23:39:07 +08:00
2026-08-24 17:14:48 +08:00
2026-07-16 14:34:07 +08:00
2026-08-16 23:39:07 +08:00
2026-07-08 18:10:19 +08:00
2026-08-17 17:47:46 +08:00
2026-08-16 23:39:07 +08:00
2026-08-16 23:39:07 +08:00
2026-08-16 23:39:07 +08:00
2026-08-16 23:39:07 +08:00
2026-08-16 23:39:07 +08:00
2026-08-16 23:39:07 +08:00
2026-08-16 23:39:07 +08:00
2026-06-23 21:47:29 +08:00
2026-08-23 14:57:12 +08:00
2026-08-17 16:49:28 +08:00
2026-08-21 17:41:35 +08:00
2026-08-17 16:49:28 +08:00
2026-08-23 19:12:57 +08:00
2026-08-24 17:14:48 +08:00
2026-07-18 23:37:54 +08:00
2026-08-06 19:28:39 +08:00
2026-08-17 17:47:46 +08:00
2026-07-08 18:10:19 +08:00
2026-07-20 13:01:03 +08:00
2026-08-22 20:26:34 +08:00
2026-07-09 13:33:18 +08:00
2026-07-09 16:35:21 +08:00