运行回测中第二步:价格因子回测以及它的报告解读

枚举阶段找到了"哪些股票在哪些天有机会",但没算每笔交易赚多少。价格因子阶段就是补这一步——在真实历史 K 线上回放每笔交易,算出 ROI、持仓天数、胜率,告诉你"如果按策略的止盈止损规则买卖,实际能赚多少"。

案例:用 RSI v1 跑一次价格因子

前提

价格因子阶段依赖枚举阶段的产物。如果你还没跑枚举,系统会自动先跑枚举,再跑价格因子。

跑价格因子

python cli.py sp --strategy rsi_v1

sp 是 strategy_price_factor 的缩写。跑完后的报告在:

userspace/extensions/strategies/rsi_v1/results/simulations/{version_id}/price/
├── overall_report.json       # 总体报告
├── entity_list.json          # 逐股摘要
├── performance.json          # 运行性能
└── entities/
    └── 000001_investments.csv   # 每只股票的逐笔交易记录

 

读总体报告

打开 overall_report.json,summary 里关注以下几组数:

 

胜率和盈亏

total_investments: 342           # 总交易 342 笔
total_completed_investments: 310  # 已平仓 310 笔
total_open_investments: 32        # 未平仓 32 笔
win_rate: 58.3                    # 胜率 58.3%
avg_roi: 0.045                    # 平均 ROI 4.5%
total_profit: 154200.0            # 总盈利 15.4 万
avg_profit_per_investment: 451.2  # 平均每笔赚 451 元

win_rate 过了 50% 看着不错,但要结合 avg_roi 看——如果胜率 58% 但平均 ROI 只有 0.5%,那盈亏比可能很差,亏的笔次虽然少但金额大。关键看总盈利 total_profit。

 

持仓周期

avg_duration_in_days: 12.3                    # 平均持仓 12 天(日历日)
avg_duration_in_trading_days: 8.5             # 平均持仓 8 天(交易日)
annual_return: 0.134                          # 年化收益 13.4%
annual_return_in_trading_days: 0.132          # 按交易日算年化 13.2%

年化收益是用 avg_roi × 365 / avg_duration 算出来的。12 天赚 4.5%,年化下来 13.4%——这个数字好不好取决于你的预期。但注意:年化是假设你能反复找到同等质量的机会,实际中不可能完全复现。

 

ROI 分布

roi_p10: -0.082     # 10% 最差的交易亏 8.2%
roi_p25: -0.025     # 25% 分位亏 2.5%
roi_p50: 0.018      # 中位数赚 1.8%
roi_p75: 0.055      # 75% 分位赚 5.5%
roi_p90: 0.092      # 10% 最好的赚 9.2%
roi_iqr: 0.080      # 四分位距 8%
roi_std_pct: 0.065  # 标准差 6.5%
roi_conclusion: "偏正且集中"   # ROI 分布偏正且集中

roi_conclusion 有四档:偏正且集中(好策略)、中位偏正(还行)、偏负且集中(差策略)、分散(不稳定)。RSI v1 的"偏正且集中"说明大部分交易赚的不多但亏的也不多,胜在稳定。

 

跳过统计

skipped_buy_at_limit_up: 12       # 12 次涨停买不进
skipped_sell_at_limit_down: 3     # 3 次跌停卖不出
skipped_stock_status: 5           # 5 次因状态标签(ST/停牌)跳过

这些机会被枚举记录了但没法实际成交。涨停买不进的会从总交易里排除,跌停卖不出的会顺延到下一个交易日卖出。

读逐股报告

entity_list.json 每只股票一行:

entity_id: "000001"
stock_name: "平安银行"
win_rate: 66.7                   # 这只股票的胜率
avg_roi: 0.032                   # 这只股票的平均 ROI
avg_duration_in_days: 14.2       # 平均持仓 14 天
expiration_ratio: 0.33           # 33% 的交易是过期退出的
total_investments: 3             # 共 3 笔交易

expiration_ratio 很重要——如果很多交易是"到期自动平仓"而不是"触发止盈止损",说明你的止盈止损条件可能设置得不太对,大部分交易没走到目标就过期了。

读逐笔交易记录

entities/000001_investments.csv 每笔交易一行:

opportunity_id: 42
enter_date: 20220315
enter_price: 12.45             # 进场价(前复权)
exit_date: 20220328
exit_price: 13.10              # 出场价(前复权)
roi: 0.052                     # ROI 5.2%
holding_days: 13               # 持仓 13 天
holding_trading_days: 9        # 交易日 9 天
exit_reason: take_profit       # 止盈退出
skip_reason:                    # 没有跳过
lifecycle: complete            # 已平仓
result: win                   # 盈利

exit_reason 可能的值:take_profit(止盈)、stop_loss(止损)、expiration(过期)、force_exit(强平)。看这个字段可以快速判断每笔交易是"主动退出"还是"被动退出"。

其他可用配置

进场价格模型

"simulation": {
    "enter_price": "next_open",   # 用次日开盘价进场(更真实)
    # 或
    "enter_price": "close",       # 用触发日收盘价(更理想化)
}

实盘中你看到收盘信号后,最快也是明天开盘才能买。所以 next_open 更接近实际。但如果你想测试"策略逻辑本身"而不是"执行可行性",用 close 也行。

 

涨跌停限制

"simulation": {
    "assumption": {
        "tradability": {
            "edges": {
                "allow_enter_at_limit_up": False,    # 涨停不买
                "allow_exit_at_limit_down": False,   # 跌停不卖
            },
        },
    },
}

默认都是 False——涨停买不进、跌停卖不出,这符合 A 股规则。如果你在研究美股或者其他市场,可以关掉这个限制。

 

状态标签过滤

"simulation": {
    "risk_control": {
        "skip_enter_when": ["st", "star_st"],     # ST 股不买
        "force_exit_when": ["st"],                # 持仓中变 ST 强制平仓
    },
}

 

ROI 计算

价格因子的 ROI 用的是前复权价格。每笔交易可能分多腿出场(比如止损一半、止盈一半),ROI 是加权计算的:

ROI = (腿1利润 × 腿1比例 + 腿2利润 × 腿2比例 + ...) / 进场价

这比简单的"卖出价 - 买入价 / 买入价"更准确,因为部分平仓的情况被考虑进去了。