# 清算步骤核对改造方案 本文是 post_clear_check 按清算步骤补核对的目标设计。窗口 1 落地之前,现行代码口径仍以 `doc/业务链路核对设计.md` 第 5 节为准;本文是改造目标,两份文档不同时自称现行。 实现按第 9 节的五个窗口串行做。每个窗口开工时把该窗口状态从「未做」改成「本窗口」,做完再改成「已完成」。本文不改核对代码,也不改 `.cursor/skills/post-clear-check/SKILL.md`。 ## 1. 结论 - 继续用现有 11 条业务链路收卡。不为 23 个清算步骤各建一条链路,飞书标题仍不写系统和步骤名。 - 以前不能按步骤核对,是因为步骤中途改实时余额,做完就分不开「第 N 步的输入」和「第 N 步的结果」。现在输入、分步工作快照和 T 日结果都留在业务库里,清算完成后只读就能复核。能复核的步骤,作为节点挂进已经经过它的那条链路,并写进该链路的「涉及」。 - 清算前把输入再拷到 `POST_CLEAR_CHECK_OWN_HOST` 上的自有库:不做。见第 7 节。 - skill 要改,改动短,放在窗口 1,不在本文落地时改。见第 8 节。 数字公式仍以 `docs/tmp/product_prototype/共赢票据重要字段、计算公式等说明V2.xlsx` 和产品原型为准。架构文档只决定读哪一份快照、哪一列是输入、哪一列是产出。 ## 2. 权威文档 三端清算口径只认下面这些: | 文档 | 用途 | |---|---| | `docs/架构设计/清算/00_清算系统架构重构总纲.md` | 两阶段模型、六项准则、发布与门禁 | | `docs/架构设计/清算/01_TRS端清算重构详细方案.md` | TRS 十步、事实与数据集 | | `docs/架构设计/清算/02_发行端清算重构详细方案.md` | 发行六步、固定归档 | | `docs/架构设计/清算/03_销售端清算重构详细方案.md` | 销售七步、钱包与上游版本 | | `docs/架构设计/清算/实施计划/` 下三份实施计划 | 代码落点。与方案正文冲突时,以实施计划里「代码与本计划以此为准」的补记为准 | | `docs/清算/测试证据/` 下的 md | 只证明重构已按上述口径落地,不当核对公式 | 不读、本文也不引用这四份旧文: - `docs/清算/三端清算最终实施方案.md` - `docs/清算/发行端A股清算与营业日日切实施细节V1.md` - `docs/清算/连续漏清与跨交易日补清修复.md` - `docs/清算/清算与最近批次重算技术实施方案V1.md` 事后能读到哪张表,以当前代码和 DDL 为准,不以方案正文里「拟新增」的句子为准。 工具自身的链路合同、飞书版式、依据三种写法,仍以 `doc/业务链路核对设计.md` 为准。改某一条链路的节点时,先改该文档第 4 节的该条,再改代码。步骤挂靠以本文第 5 节为准;窗口 1 会把业务链路核对设计的第 5 节改成指向本文。 ## 3. 为什么现在能按步骤核对 三端是同一个模型: ```text Cleared_T = 上一日已发布结果 + 本轮冻结的 T 日事实与参数 Live = 把 T 日结果按后续已记账事实投影出去 ``` T 日候选在发布前就封存。后续事实不回写进候选。 ```mermaid flowchart LR baseline[上一日已发布结果] frozen[本轮冻结输入] stepSnap[分步工作快照] cleared[T日候选] live[实时投影] baseline --> stepSnap frozen --> stepSnap stepSnap --> cleared cleared --> live ``` 核对必须绑已发布的那一份数据集,不能按 `batch_id` 把一个批次里所有轮次的归档行加在一起。发行重算复用 `batch_id`,换 `dataset_revision`,对外只认 `published_flag=1` 的那条。销售、TRS 重算是新批次,仍只读该批次已发布的那份数据集。 ## 4. 事后能读到什么 ### 4.1 发行 库是 `db_issue_clear`。一轮清算一个 `clearing_dataset`。`clr_*` 用 `archive_stage` 加 `archive_step_no` 区分范围: | 范围 | 含义 | |---|---| | `input/0` | 第 2 步冻结的事实和参数 | | `work/2`、`work/3`、`work/4` | 第 2、3、4 步完成时的不可变快照 | | `cleared/0` | 第 5 步从 `work/4` 复制的 T 日结果 | 没有 `work/5`。当前投影不归档,只在实时表和 `clearing_writeback_log`。发行这份回写日志没有 `source_cursors`。报表读 `cleared`。运行期动态表 `clearing_table_backup` 已退役。 因此发行第 2、3、4 步可以按留下的快照分别核对。第 5 步的 T 日结果读 `cleared/0`,它应与 `work/4` 一致;当前投影另算,不把实时票据当成 T 日结果。 ### 4.2 销售 `clearing_dataset.kind` 三份职责不同: | kind | 能否当留存快照 | |---|---| | `input` | 能。只读事实,第 2 步封存后不改 | | `pre_clearing` | 不能。第 3、4、6 步会改写它,终态既不是第 2 步也不是第 3 步 | | `cleared` | 能。第 5 步封存的 T 日候选,第 6 步不回写它 | `note_view` 是第 6 步投影。第 7 步才发布,并用候选生成日终和报表。 因此销售第 3 步和第 4 步没有各自的快照,只能用 `input` 重算,再和第 5 步的 `cleared` 比。钱包只核这个 T 日结果,不读实时钱包,也不把营业日晚于 T 的流水加进 T 日结果。 ### 4.3 TRS 事实和参数在本批 `kind=pre_clearing`、`copy_no=1` 的只读分区,以及 `clearing_snapshot`。第 5、6 步把计算结果写进同一工作集。第 7 步才把 T 日结果复制为 `kind=cleared`。批次完成前这份候选的状态是 `backing_up`,第 10 步才切到 `available`。工具只在批次 `completed` 之后开跑,此时应能读到 `available`。 T 日汇率调整在 `clearing_snapshot.entity_type='clearing_fund_ledger'`。TRS006 合并本批原始流水快照和这份调整,不回查实时 `fund_ledger`。 TRS 以第 7 步的 `cleared` 为 T 日终态。第 5、6 步写同一工作集(`kind=pre_clearing`、`copy_no=1`)。`replace_mirror_rows` 只替换本步传入的实体,按数据集整表删除再插入,没有各自留存的快照。第 7 步把该工作集克隆为 `kind=cleared`(完成前 `backing_up`,第 10 步才 `available`)。 窗口 1 对写入点的只读确认:`clr_fund_account` 只在第 5 步的 `strong_rows` 里替换(`result["funds"]`)。第 6 步的 `strong_rows` 只有 `trade_order` 和 `user_position`,不替换资金账户,第 5 步各列保留到 `cleared`。`clr_user_position` 第 6 步整行替换:`build_step6` 先复制第 5 步的持仓行,再把 `available_qty` 写成 `position_qty`、`frozen_qty` 写成 0,并重写 `market_value`、`float_pnl`、`book_float_pnl`。`position_qty`、`cost_amount`、`cost_px`、`book_cost_amount` 和身份列原样带过。被覆盖的可用、冻结和估值没有独立留存。 窗口 5 不把第 5 步和第 6 步拆成两份快照节点。`cleared` 上数量和成本仍是第 5 步的值,可用、冻结和估值是第 6 步写在同一行上的结果。 ## 5. 23 步怎么挂到现有链路 仍只有两种处理:挂到已有链路,或明确不支持。批次级的是/否检查挂「核对门禁」,不挂到主订单、资金账户或用户账号上。 不支持的步骤不写进卡片「涉及」,也不发卡。已经挂上节点的步骤要写进「涉及」。涉及行用下表「名称」列的原文,这就是三端代码里的步骤名,不另起简称。名称还写着「备份」「票据激活」的,核对内容以「挂到哪里」为准,不按旧名称去找物理副本或激活消息。 ### 5.1 发行端(6 步) | 步骤 | 名称 | 结论 | 挂到哪里 | |---|---|---|---| | 1 | 开启清算 | 挂靠 | 核对门禁。查已发布数据集的 `baseline_kind`、`base_dataset_id`,以及本批固定的 TRS 版本仍是该日当前发布版本。没有金额公式 | | 2 | 备份清算前数据 | 挂靠 | 买入执行、卖出执行、票据存续、增额、展期、资金交收。见下文 | | 3 | 订单及票据处理 | 挂靠 | 已在买入执行、卖出执行、票据存续、增额、展期。补 `work/3` 节点 | | 4 | 交易交收处理 | 挂靠 | 已在资金交收。补 `work/4` 节点 | | 5 | 备份清算完成的数据、生成营业日日终数据与核心日常报表、更新T+1实时数据 | 挂靠 | 报表和 T 日订单结算读 `cleared/0`;当前投影另挂票据存续和买卖执行 | | 6 | 确认交易日切换、推进营业日、票据激活、清算完成 | 挂靠 | 核对门禁。营业日已推进,且该日只有一个 `published_flag=1`。不写金额式。激活已前移到第 5 步,本步不再核激活消息 | 第 2 步节点: - `input/0` 的客户执行事实列(成交名义本金、均价)与源表一致。 - 结算口径列、买入 `order_qty`、`fx_settle` 是清算产出,不得拿实时表当输入。 - 非交易按冻结主键集合核对,不把实时余额当起点。 - `work/2` 的期初等于上一已发布 `cleared` 的结转。 第 3 步补节点:`work/3` 的结算口径等于用 `input` 和封存费率、汇率算出的结果。现有「报表对不对」保留,报表对 `cleared`,不对实时票据。 第 4 步补节点:`work/4` 从上一日未交收加本日订单推演。本批交收不重复。历史单不因首次写成 T 日交收而在重算中消失。 第 5 步:`cleared/0` 应与 `work/4` 一致。实时值等于 `cleared` 加后续事实,客户终态订单不被改写。`yesterday_*` 是第 5 步从本轮 T 日结果滚入的物化值,挂票据存续;清算公式本身不读这几列当输入。 ### 5.2 销售端(7 步) | 步骤 | 名称 | 结论 | 挂到哪里 | |---|---|---|---| | 1 | 开启清算 | 挂靠 | 核对门禁。manifest 里的发行 `batch_id:cleared_dataset_id` 必须等于发行该日当前已发布轮次 | | 2 | 备份清算前数据 | 挂靠 | 账户资产、资金变动、资金交收。只读 `kind=input` | | 3 | 订单及票据处理 | 挂靠 | 与第 4 步合成核对,见下文。不读 `pre_clearing` 的终态当第 3 步结果 | | 4 | 交易交收处理 | 挂靠 | 与第 3 步合成核对 | | 5 | 备份清算完成的数据 | 挂靠 | 账户资产。`cleared` 不含 `business_date>T` 的流水,也不含后续当日买卖 | | 6 | 更新T+1交易日的票据详情实时数据 | 挂靠 | 票据存续。本步写入的实时钱包不进入钱包核对 | | 7 | 营业日日切 | 部分挂靠 | 报表节点保持原链路,改为对候选。延后流水挂资金变动。发布成功挂核对门禁 | 第 2 步:当日原始流水排除 `clearing_step_no IN (3,4)`。上一批仍未交收的第 3 步买卖流水会原样封进下一日 `input`,步骤号仍是 3,这是历史未交收来源,不是当日原始流水。期初钱包等于上一日已发布 `cleared`,不是实时余额减去后续发生额。 第 3、4 步:没有独立快照。在账户资产、资金变动、资金交收上,用 `input` 加第 1 步固定的发行结果重算,与 `cleared` 比。第 3 步之后 T 日 `buy_today/sell_today` 必须为 0。 第 6 步:实时钱包不进入核对。`note_view` 按发行当前状态版本写入,低版本不覆盖。这个当前值不得写进 `clr_note_view_cleared`。 第 7 步报表归属不变:OTC001 账户资产,OTC002 资金变动,OTC003 买入执行与卖出执行,OTC004 票据存续,OTC007、OTC008、OTC010 资金交收。这些报表对候选,不对实时钱包。延后入站和 relay 资金回调的流水营业日是切日之后的营业日。 ### 5.3 TRS(10 步) | 步骤 | 名称 | 结论 | 挂到哪里 | |---|---|---|---| | 1 | 开启清算与清算前备份 | 挂靠 | 委托执行、用户资产。封存集合与按交易日、营业日规则重查源表一致;账面汇率、融资额度用本批封存值。不把实时资金、持仓当 T 日起点 | | 2 | 检查订单终态 | 不支持 | 批次已经完成才开跑,不再单独发卡。委托执行里「执行根应不应该出现」已经覆盖 | | 3 | 计算用户汇率 | 部分挂靠 | 汇率数值没有权威公式,不计算「应该等于多少」。用户资产只加一条一致性:第 5 步和第 8 步用的是本批封存汇率,不是事后的实时 `fx_book` | | 4 | 确认应付利息 | 挂靠 | 已在用户资产。补「T 日债务不因 T+1 已收取而被冲掉」,输入读本批封存的计息证据 | | 5 | 订单与资金清算 | 挂靠 | 已在委托执行、用户资产。T 日结果以第 7 步 `cleared` 为准 | | 6 | 持仓清算 | 挂靠 | 同上。与第 5 步没有各自留存的快照,不拆节点。`cleared` 上数量和成本仍是第 5 步的值;`available_qty`、`frozen_qty` 和估值是第 6 步写在同一行上的结果。`clr_fund_account` 第 6 步不替换,第 5 步各列保留 | | 7 | 生成已清算数据集 | 挂靠 | 委托执行、用户资产。`cleared` 不含 `trade_date>T` 的成交和后续非交易 | | 8 | 生成清算报表 | 挂靠 | 保持现有挂靠,报表对 `cleared` | | 9 | 确认交易日切换并推进营业日 | 挂靠 | 核对门禁。只查日期推进 | | 10 | 完成清算 | 挂靠 | 核对门禁。只查版本已切换到本批 `cleared`。开跑条件里已有的「批次 completed」不重复成一张卡 | 第 5 步的汇率调整按「T 日清算成交现金 − T 日原始买卖凭证」核对,读本批快照里的 `clearing_fund_ledger`,不读实时流水。后续成交保持原发生额,不按 T 日汇率重算。 ### 5.4 仍然不进范围 询价报价、票据风控、授信与预警平仓线、市场汇率、港美股、B 类、基金、两端 OTC009 与 OTC012 的落库表、TRS011、TRS015。这些保持 `doc/业务链路核对设计.md` 第 2.2 节的原因,不因为本次改造加进来。 ## 6. 现有工具会读错的三处 这三处必须在加任何新节点之前改掉。新节点若建在这三处读数上,会把投影差额当成公式错误,或把整段核对跳过。 1. 发行订单和票据仍查 `db_issue_clear.clearing_table_backup`。`reader.choose_locations` 在登记表没有行时退回 `db_issue` 实时表。该表已不存在,退回是静默的。实时票据和对冲持仓是第 5 步投影,不是 T 日结果。有后续买卖时,OTC004 对实时票据会误报。对冲仓位「没有副本就不读实时」则会整段跳过。 2. TRS006 仍读 `db_trs.fund_ledger`。新口径是本批原始快照加 `clearing_fund_ledger` 调整。实时流水含 `CLR:%`,也含后续营业日。 3. 销售钱包不拿 `daycut_fund_eod` 或 `kind=cleared` 去比实时钱包。T 日结果只在本批 `input` 与 `kind=cleared` 之间重算。 窗口 1 不重写 11 条链路的 Excel 公式。无后续业务的营业日,换读 `cleared` 后,结论应与现在对实时表的结论一致。有后续业务的日子,票据的「当前」与「T 日」分开。钱包只核本批归档里的 T 日结果,不另核实时钱包。报表和 T 日结果读已发布候选。 ## 7. 清算前备份:不做 不在三端清算之前,把清算输入备份到自有库 MySQL。 - 冻结分区已经是清算用过的那一份,步骤失败重试不换源。核对工具在清算完成后只读它,就能复核冻结有没有冻对。 - 清算前拷实时库,拷到的是混合余额。新架构不用混合余额当计算起点。用它当期望值,会把正确的正向结果判成错误。 - 备份时点与冻结时点之间仍有入账。TRS 网关回报、发行对冲回报不受清算门禁。对不上就是误报。业务库上的冻结水位才是实际截点。 - 自有库只存 `check_run`、`check_result`、`check_line`。再镜像三端输入,就要跟着 DDL 改表,和这套工具运行时不读 leopard 源码和 `ddl/` 的隔离相反。现有进程是清算完成后跑一轮就退出,没有清算前钩子。 - 被回写改掉的列,发行和 TRS 已经规定先冻进 `input` 或快照,再写派生列。若实施时发现某列既被改写又没进冻结分区,那是清算封存缺口,应补业务侧封存,而不是在核对工具侧平行备份。 不给自有库加业务镜像表,也不新增清算前任务。 ## 8. skill 在窗口 1 改什么 窗口 1 改 `.cursor/skills/post-clear-check/SKILL.md`,只动这几处: - 「先读哪份」增加本文。改步骤核对或改业务链路核对设计第 5 节时,先读本文,再改该文档对应小节。 - 写明不要读第 2 节列出的四份旧清算文档。业务链路核对设计第 2.1 节末句和第 5 节现在指向 `docs/清算/三端清算最终实施方案.md`,窗口 1 把这两处改成第 2 节的架构设计目录和本文。 - 「不支持的步骤不写进涉及」改成:本文判定为不支持的才不写;已经挂上节点的步骤要写进「涉及」。前半句「不把 23 步各建一条链路」保留。 - 增加一句:不新增清算前输入备份,不给自有库加业务镜像表。 - 业务链路核对设计第 8 节仍然不读、不改。飞书版式、只读业务连接保持不变。窗口 2 至 5 对「一次一条链路」的放宽只写在第 9 节,不写进 skill 的普遍规则。 ## 9. 实现窗口 不要一个 agent 窗口做完。`reader.py` 约 1600 行,业务链路核对设计约 600 行,11 条链路的公式和测试叠在一起无法复核。`chain.py`、`reader.py` 是共享文件,多窗口并行会互相覆盖,只串行。 不跑工具全量用例,不跑 leopard 全仓测试。窗口 2 至 5 都依赖窗口 1 的取数。不在窗口 1 之前加节点。 每个窗口的验收是该窗口点名的定向测试,加上一个有后续业务的营业日、一个无后续业务的营业日。有后续业务的日子用来确认 T 日候选和当前投影被拆开;无后续业务的日子用来确认换读候选后,原有公式结论不漂移。 | 窗口 | 状态 | 范围 | 不做 | |---|---|---|---| | 1 | 已完成 | 取数契约、核对门禁、第 8 节的 skill 与业务链路核对设计第 5 节 | 不新增 Excel 节点 | | 2 | 已完成 | 票据存续、增额、展期 | 不改买卖执行公式,不改销售钱包三条 | | 3 | 已完成 | 账户资产、资金变动、资金交收 | 不改票据三条,不改 TRS 两条 | | 4 | 已完成 | 买入执行,然后卖出执行 | 两条不交错改 `order_rules.py` | | 5 | 已完成 | 委托执行、用户资产 | 不加用户汇率的金额公式 | ### 9.1 窗口 1:取数契约和核对门禁 状态:已完成。 发行改读已发布数据集的 `input`、`work/2`、`work/3`、`work/4`、`cleared/0`,不再读 `clearing_table_backup`,也不再在登记表缺失时退回实时表充当 T 日结果。实时表只留给第 5 步当前投影节点,那些节点在窗口 2、4 才加。 TRS 资金改读本批快照和 `clearing_fund_ledger`。同时确认第 6 步有没有覆盖第 5 步写在 `clr_fund_account`、`clr_user_position` 上的列,把结论补进本文 4.3 和 5.3,再决定窗口 5 要不要拆开第 5 步和第 6 步。 销售钱包只核本批 `input` 与 `kind=cleared` 的 T 日结果,不读实时钱包。日终 `daycut_fund_eod` 只与候选比。 核对门禁加上第 5 节里挂到门禁的那些是/否检查:发行基线与上游版本、发行发布唯一、销售锁定的发行版本、TRS 日期推进与版本切换。开跑条件里已有的「批次 completed」不再做一张卡。 本窗口同时改 skill(第 8 节)和业务链路核对设计第 5 节的引用。不重写 11 条链路的节点公式。 ### 9.2 窗口 2:票据存续、增额、展期 状态:已完成。 三条都归资金账户,读同一批发行 `input` 和 `cleared`。增额、展期当前没有结论行。同一窗口按票据存续、增额、展期的顺序做完。这是对 skill「一次一条链路」的有意识放宽:三条共用取数,增额和展期又没有现成结论行,拆开会三次改同一处取数。若要严格一条链路一个窗口,就拆成三个,顺序不变。 节点以本文 5.1 的第 2、3、5 步和 5.2 的第 6 步为准。先改业务链路核对设计第 4.7、4.8、4.9 节,再改代码。 ### 9.3 窗口 3:账户资产、资金变动、资金交收 状态:已完成。 销售 `input` 与 `cleared`,发行 `work/4`。同一窗口按账户资产、资金变动、资金交收的顺序做完。放宽理由与窗口 2 相同:三条都读销售数据集,分开会三次改同一处取数。严格遵守时拆成三个窗口,顺序不变。 先改业务链路核对设计第 4.6 节(账户资产)、第 4.4 节(资金变动)、第 4.5 节(资金交收),顺序与本窗口一致,再改代码。第 3 步和第 4 步用重算对比 `cleared`,不读 `pre_clearing` 终态。 ### 9.4 窗口 4:买入执行、卖出执行 状态:已完成。 执行事实读 `input`,结算口径读 `work/3` 或 `cleared`。两条公式不同。同一窗口内先做完买入执行,再做卖出执行,不交错改 `order_rules.py`。 先改业务链路核对设计第 4.2、4.3 节,再改代码。 ### 9.5 窗口 5:委托执行、用户资产 状态:已完成。 T 日回放、封存汇率一致性、利息不被 T+1 收取冲掉、`cleared` 不含后续成交。汇率调整读 `clearing_fund_ledger`。不计算用户汇率「应该等于多少」。 先改业务链路核对设计第 4.10、4.11 节,再改代码。第 5 步和第 6 步是否拆节点,以窗口 1 补进本文 4.3 的结论为准。