03-定投回测方法论

基于本地数据库的历史行情与估值分位,对比"简单定投"与"估值分位定投"两种策略的长期收益。回测脚本:backtest_dca.py,最近一次回测结果档案:01-定投策略回测结果。

一、回测方法论

1.1 设计原则

回测遵循 仿真库总体设计 中"方法论在知识库,执行在仿真库"的边界:

  • 数据库只提供数据:从 index_daily 与 index_valuation 表读取行情和 PE 历史分位
  • 回测逻辑在 Python 中执行:扣款、累计份额、计算收益等步骤不进入 SQL
  • 不写入数据库:回测结果仅用于策略选型,不污染行为数据表(decision_log 等留给真实决策)

1.2 策略定义

策略代号扣款规则倍数映射
简单定投simple每月第一个交易日,固定金额始终 1.0x
估值分位定投valuation每月第一个交易日,按当时 PE 历史分位调整金额见下表

估值分位定投的倍数映射,与 06-定投决策查询 中的信号生成逻辑保持一致:

PE 历史分位估值区域定投倍数
[0, 0.2)极低1.5x
[0.2, 0.6)低 / 中1.0x
[0.6, 0.8)高0.5x
[0.8, 1.0]极高0x(暂停)

分位缺失的月份按 1.0x 正常定投处理,避免数据缺失导致策略中断。

1.3 回测参数

参数取值说明
扣款时点每月第一个交易日以 index_daily.trade_date 为准
基础月投金额1000 元估值定投的实际投入 = 基础金额 × 倍数
起始日期2016-07-18数据库中三只标的共同的最早日期
结束日期2026-07-14数据库最新交易日
数据质量过滤data_quality IN ('ok', 'manual_fixed')排除可疑与错误数据
年化口径(累计收益 + 1) ^ (1 / 年数) - 1按自然年化,非 IRR

1.4 回测标的

只选取同时具备完整日线与估值数据、历史深度达 10 年的标的:

指数代码指数名称日线条数估值条数覆盖区间
000016上证50242524252016-07-18 ~ 2026-07-14
000852中证1000242524252016-07-18 ~ 2026-07-14
000905中证500242524252016-07-18 ~ 2026-07-14

未纳入回测的标的及原因:

指数原因
000300 沪深300日线数据仅 10 条(fetcher 异常),需重跑 update_all.py 修复
000688 科创50估值数据缺失(2020 年上市,估值 fetcher 未覆盖)
399006 创业板指估值数据缺失(同上)

二、对实盘的启示

  1. 估值定投的纪律性比单次择时更重要。回测中估值定投的优势来自系统性地"高估不买",而不是某一次精准逃顶。实盘中应让倍数映射固化在扣款流程里,避免人工干预。

  2. 暂停月不是"错失机会"而是"等更好的价格"。上证50 的 34 个暂停月看似很多,但最终年化反而更高。实盘中遇到暂停信号时,应按纪律执行,而不是因为"已经涨了"就追进去。

  3. 策略验证需要持续复盘。历史样本的市场结构可能变化。建议每季度用 06-定投决策查询 的查询接口重新跑一次回测,对比"策略预期 vs 实际表现",写入 05-季度复盘。

  4. 实盘决策应写入 decision_log。回测结果支撑策略选型,实盘开始后,每次扣款都应通过 07-行为数据查询 写入 decision_log,记录决策时的 PE 分位快照,后续用同一套查询接口做"决策命中率"复盘。

三、如何复现

3.1 前置条件

  1. 数据库已建库并灌入历史数据:
    python 03-仿真库/01-数据库/02-采集脚本/db.py
    python 03-仿真库/01-数据库/02-采集脚本/update_all.py --mode full
  2. 依赖已安装:pandas(已在 03-仿真库/01-数据库/02-采集脚本/requirements.txt 中声明)

3.2 运行回测

# 默认回测 000016/000852/000905,基础月投 1000
python 03-仿真库/03-回测/backtest_dca.py

# 单标的
python 03-仿真库/03-回测/backtest_dca.py --code 000905

# 指定起始日期与月投金额
python 03-仿真库/03-回测/backtest_dca.py --start 2018-01-01 --monthly 2000

3.3 扩展回测

如需增加标的或调整策略参数,修改 backtest_dca.py:

  • 增加标的:在 main() 的 default_targets 列表中添加,前提是该指数在数据库中同时有完整日线与估值数据
  • 调整倍数映射:修改 valuation_multiplier() 函数的阈值与返回值
  • 增加止盈逻辑:在 backtest_valuation() 的循环中加入"累计收益超阈值则止盈"分支

延伸阅读