Skip to content

Runtime 与结果契约方案

本文档定义 zelqor 在完成 request 侧宿主契约之后,下一步应该补齐的另一半能力:让 runtimemetadata 真正成为宿主可消费的结果契约。

一句话总结:

当前仓库已经比较清楚地定义了“怎么发起一次回测请求”,下一步要解决的是“宿主拿回什么结果、怎么识别这次运行、怎么判断哪些数据被裁剪了”。

1. 为什么现在应该做这件事

当前仓库已经具备:

  • 统一的 strategy request schema
  • 正式的 marketData.source 配置来源
  • run_backtest(...) 真实执行
  • run-backtest --request-file ... 真实执行
  • runtime.output 对结果 section 的最小裁剪能力

这说明 request 侧已经有了比较稳定的形状。

但结果侧仍然有明显空档:

  • request 里的 metadata 还没有形成正式回传契约
  • runtime.output 虽然已经会裁剪结果,但结果本身没有说明“哪些 section 被裁掉了”
  • 宿主拿到空数组时,无法区分:
  • 本来就没有数据
  • 还是因为 runtime 配置被省略了
  • CLI 现在只是直接输出 BacktestResult,还没有把宿主关心的元信息稳定暴露出来

所以这一步的目标不是继续扩 request,而是把结果侧也产品化。

2. 当前边界

当前 request 已经支持:

{
  "runtime": {
    "output": {
      "includeDailyPools": true,
      "includeDailyDecisions": true,
      "includeTrades": true,
      "includePositionsByDate": true,
      "includeEquityCurve": true
    }
  },
  "metadata": {
    "requestId": "bt_20260326_001",
    "caller": "desktop",
    "profile": "research"
  }
}

但当前结果仍然主要是:

  • summary
  • trades
  • equityCurve
  • dailyPools
  • dailyDecisions
  • positionsByDate
  • warnings
  • provider
  • strategyType

也就是说:

  • runtime 只影响裁剪动作
  • metadata 还没有正式进入结果
  • 结果没有显式声明这次运行应用了哪些 runtime 输出规则

3. 目标

这一阶段建议聚焦三个目标。

3.1 目标一:让 metadata 正式回传

建议 BacktestResult 明确保留宿主元数据,而不是让它只存在于 request 里。

最小可行目标:

  • 把 request.metadata 原样透传到 result
  • 保证 CLI 输出中也能看到这部分内容

推荐支持的典型字段包括:

  • requestId
  • caller
  • profile
  • tags
  • batchId

3.2 目标二:让 runtime.output 成为显式结果契约

现在 runtime.output 已经能裁剪结果,但还缺少一层“结果自描述”。

建议结果里至少能回答这些问题:

  • 这次运行应用了哪些输出裁剪
  • 哪些字段被省略了
  • 结果中的空数组是“无数据”还是“已裁剪”

推荐形状例如:

{
  "runtime": {
    "outputApplied": {
      "includeDailyPools": false,
      "includeDailyDecisions": false,
      "includeTrades": true,
      "includePositionsByDate": false,
      "includeEquityCurve": true
    },
    "omittedSections": [
      "dailyPools",
      "dailyDecisions",
      "positionsByDate"
    ]
  }
}

3.3 目标三:让 CLI 和宿主输出更稳定

当前 CLI 能输出完整 JSON,但对宿主来说还不够“契约化”。

建议至少保证:

  • 结果中固定包含 provider
  • 结果中固定包含 strategyType
  • 结果中固定包含 metadata
  • 结果中固定包含 runtime 应用情况

这样桌面端、服务端或批量任务系统才能稳定消费。

4. 推荐 schema 演进方向

4.1 BacktestResult.metadata

建议在结果中直接增加:

{
  "metadata": {
    "requestId": "bt_20260326_001",
    "caller": "desktop",
    "profile": "research"
  }
}

这部分原则上应与 request.metadata 保持一致,不在回测过程中被策略逻辑污染。

4.2 BacktestResult.runtime

建议在结果中单独保留 runtime 执行元信息,例如:

{
  "runtime": {
    "outputApplied": {
      "includeDailyPools": false,
      "includeDailyDecisions": false,
      "includeTrades": true,
      "includePositionsByDate": false,
      "includeEquityCurve": true
    },
    "omittedSections": [
      "dailyPools",
      "dailyDecisions",
      "positionsByDate"
    ]
  }
}

这样宿主拿到结果后,不需要反推为什么某些字段为空。

4.3 是否保留被裁剪字段

建议继续保留现有做法:

  • 被裁剪字段仍然存在于 result 中
  • 但值为 []

同时新增显式的 runtime 说明字段。

原因是这样可以兼顾:

  • 结果 schema 稳定
  • 宿主容易反序列化
  • 又不会丢失“这部分被裁剪”的语义

5. 推荐实现顺序

5.1 第一步:给 BacktestResult 增加宿主元信息

优先补:

  • metadata
  • runtime

其中 runtime 先只记录:

  • outputApplied
  • omittedSections

5.2 第二步:在 run_backtest(...) 中统一填充结果侧元信息

统一由 API 层负责:

  • 把 request.metadata 透传到 result
  • 把 runtime.output 的最终生效值写进 result.runtime
  • 根据裁剪情况写出 omittedSections

5.3 第三步:补 CLI 契约测试

重点增加:

  • CLI 输出包含 metadata
  • CLI 输出包含 runtime.outputApplied
  • CLI 输出包含 runtime.omittedSections

5.4 第四步:补错误输出关联能力

如果 request 校验失败或运行失败,后面可以继续考虑:

  • 错误输出中带 requestId
  • 日志中带 requestId

这一步可以放在 metadata 正式回传之后再做。

6. 当前不建议优先做什么

这一阶段不建议优先做:

  • 增加新的回测指标字段
  • 增加新的策略类型
  • 扩展更复杂的 broker 输出
  • 把 result 设计成很重的任务报告对象

先把最小宿主结果契约收稳,比继续加功能更重要。

7. 风险与取舍

7.1 把 metadata 变成策略可写字段

建议避免。

metadata 应该是宿主字段,不应由策略逻辑直接修改,否则会混淆:

  • 宿主元信息
  • 策略调试信息

7.2 把 runtime 和 summary 混在一起

建议避免。

summary 更适合放:

  • 收益
  • 回撤
  • 成交统计

而 runtime 应该放:

  • 输出裁剪
  • 结果生成元信息

7.3 结果字段被裁掉后直接删除

当前不建议这么做。

直接删字段会让宿主做更多分支判断,也更容易造成反序列化不稳定。

8. 验收标准

这一阶段建议以这些结果作为完成标志:

  • BacktestResult 正式支持 metadata
  • BacktestResult 正式支持 runtime 结果元信息
  • 宿主能区分“空结果”和“被裁剪结果”
  • CLI 输出稳定包含宿主关心的结果字段
  • 契约测试覆盖 metadata 与 runtime 结果行为

9. 结论

zelqor 当前已经把 request 侧宿主契约收得比较清楚,下一步最值得做的是把结果侧也收口:

  • metadata 不只是请求字段,而是完整调用链的一部分
  • runtime.output 不只是内部裁剪逻辑,而是宿主可理解的结果契约

完成这一阶段后,仓库会从:

  • 宿主能发起请求

进一步进入:

  • 宿主能稳定消费结果
  • 宿主能追踪这次运行
  • 宿主能理解哪些输出被保留、哪些被省略