第 33 篇开始从历史角度看模拟盘是否稳定。第 34 篇回到每日运行前的输入检查如果目标股票里有价格缺失系统应该明确报出缺口而不是让策略在None、0或旧价格上继续跑。缺价格不是小事模拟盘里最危险的 bug 往往不是程序直接崩溃而是数据缺了但程序继续运行。如果某只股票没有最新价格调仓金额、风险敞口、权益快照都会跟着失真。早期系统宁可保守一点把这类问题标成 blocker。缺口对象第 34 章新增app/data_gaps.py。dataclass(frozenTrue) class DataGap: symbol: str trade_date: date field: str severity: str dataclass(frozenTrue) class DataGapPlan: gaps: tuple[DataGap, ...] severity: str第一版只检查last_price后面可以继续扩展到成交量、复权因子、停牌状态等字段。构造缺口计划函数输入是价格快照和必需 symbol 列表def build_price_gap_plan( price_snapshot: PriceSnapshot, *, required_symbols: list[str], ) - DataGapPlan:实现时先去重再比较必需列表和快照里已有价格。required list(dict.fromkeys(required_symbols)) missing sorted(set(required) - set(price_snapshot.prices))每个缺失 symbol 都变成一个 blockerDataGap( symbolsymbol, trade_dateprice_snapshot.trade_date, fieldlast_price, severityblocker, )这个设计很朴素但它让缺口从“日志里可能有一行”变成了可测试、可汇总、可进入运维检查清单的数据结构。常见的数据缺口不只有价格缺失。日频策略里还可能遇到成交量缺失、复权因子缺失、停牌状态缺失、股票池成分更新延迟等问题。第一版只检查last_price是因为价格缺口会直接影响权益、仓位和调仓金额是模拟盘最该先挡住的一类问题。当前联动运行结果paper-ops-check会故意构造一个缺价格场景必需 symbol 包含000001.SZ和600519.SH但价格源只返回000001.SZ。uv run python -m scripts.chapter_examples paper-ops-check因此缺口计划输出severityblockergap_symbols[600519.SH]。这类结果会直接进入第 35 篇的运维检查清单阻止系统在行情不完整时继续执行。测试缺口输出测试场景里价格快照只有000001.SZ必需列表还有600000.SH和300001.SZ。uv run pytest tests/test_data_gaps.py断言结果assert plan.severity blocker assert gap_symbols(plan) [300001.SZ, 600000.SH] assert plan.gaps[0].field last_price如果所有必需价格都存在则返回severity ok且gaps ()。本章更新与代码仓库本章更新内容新增app/data_gaps.py。实现DataGap和DataGapPlan。对必需 symbol 和价格快照做缺口检查。缺失价格统一标记为 blocker。新增gap_symbols()便于测试和展示。增加paper-ops-check联动示例展示缺价格如何变成 blocker 级别数据缺口。补充价格、成交量、复权因子、停牌状态等常见数据缺口背景。新增tests/test_data_gaps.py覆盖缺价格和数据完整两种场景。代码仓库https://github.com/ax2/zi-quant-platform本章代码git clone https://github.com/ax2/zi-quant-platform.git cd zi-quant-platform git checkout chapter-34 uv sync --extra dev uv run pytest tests/test_data_gaps.py第 34 章提交为5c94461tag 为chapter-34。本篇小结行情缺口要尽早变成结构化结果。第 34 篇把缺失价格从隐性风险变成DataGapPlan。下一篇会把运行时间窗、历史摘要、数据缺口和健康报告组合成一张最终的运维检查清单。