Runtime 与结果契约方案
本文档定义 zelqor 在完成 request 侧宿主契约之后,下一步应该补齐的另一半能力:让 runtime 和 metadata 真正成为宿主可消费的结果契约。
一句话总结:
当前仓库已经比较清楚地定义了“怎么发起一次回测请求”,下一步要解决的是“宿主拿回什么结果、怎么识别这次运行、怎么判断哪些数据被裁剪了”。
1. 为什么现在应该做这件事
当前仓库已经具备:
- 统一的
strategyrequest 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"
}
}
但当前结果仍然主要是:
summarytradesequityCurvedailyPoolsdailyDecisionspositionsByDatewarningsproviderstrategyType
也就是说:
runtime只影响裁剪动作metadata还没有正式进入结果- 结果没有显式声明这次运行应用了哪些 runtime 输出规则
3. 目标
这一阶段建议聚焦三个目标。
3.1 目标一:让 metadata 正式回传
建议 BacktestResult 明确保留宿主元数据,而不是让它只存在于 request 里。
最小可行目标:
- 把 request.metadata 原样透传到 result
- 保证 CLI 输出中也能看到这部分内容
推荐支持的典型字段包括:
requestIdcallerprofiletagsbatchId
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 增加宿主元信息
优先补:
metadataruntime
其中 runtime 先只记录:
outputAppliedomittedSections
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正式支持metadataBacktestResult正式支持 runtime 结果元信息- 宿主能区分“空结果”和“被裁剪结果”
- CLI 输出稳定包含宿主关心的结果字段
- 契约测试覆盖 metadata 与 runtime 结果行为
9. 结论
zelqor 当前已经把 request 侧宿主契约收得比较清楚,下一步最值得做的是把结果侧也收口:
- 让
metadata不只是请求字段,而是完整调用链的一部分 - 让
runtime.output不只是内部裁剪逻辑,而是宿主可理解的结果契约
完成这一阶段后,仓库会从:
- 宿主能发起请求
进一步进入:
- 宿主能稳定消费结果
- 宿主能追踪这次运行
- 宿主能理解哪些输出被保留、哪些被省略