TT Lab
开始
学习 学习路径 课程

资本市场与清结算

屏幕上的盘口一直悄悄错着

在 TT Lab 中继续学习

目标

用一张快照和增量更新重新搭起订单簿,并把序号断开的位置和迟到的行所造成的差别,用价位数数出来。最后,确认加入了重新同步规则的实现,与基准快照没有一个价位的出入。

为什么重要

行情画面即使错了,也不会发出任何声音。不是值变空或者抛出异常,而是略有不同的值一直在运转,所以直到几天之后订单出现异常,才会往回追溯。快照里标记的序号与增量的序号怎样衔接,序号断开时是停下还是继续叠加,这两条只有两行的规则,决定了这种差别。而且本实验的计算全部处理价格。价格用二进制浮点数一减,判断是否相差一个最小变动价位的结果就会悄悄变成 0,所以也一并看一看,哪些情况是在值之前,先在数据类型上输掉的。

步骤

  1. 用 python3 在 /root/cap/book/data 中生成四个数据文件。请原样使用生成脚本。
  2. 数出增量数据流的形态,用九行写入 /root/cap/book/shape.txt。
  3. 用起始快照搭起订单簿,数出重叠的增量,写入 /root/cap/book/init.json。
  4. 把增量按序号顺序叠加,把订单簿和按动作统计的笔数写入 /root/cap/book/naive.json。
  5. 找出回放过程中不变式被破坏的位置,用七行写入 /root/cap/book/invariant.txt。
  6. 把断开的区间与重新搭起的结果之间的差别,写入 /root/cap/book/gap.json。
  7. 把迟到的行和它所造成的差别,写入 /root/cap/book/late.json。
  8. 把加入了重新同步规则的最终订单簿留在 /root/cap/book/final/book.json 中,把质量报告留在 /root/cap/book/final/report.json 中。

参考

生成快照和增量数据流

用 python3 在 /root/cap/book/data 中生成 snapshots.jsonl、updates.jsonl、session.json 和 feed_spec.txt 四个文件。请原样使用不使用随机数的生成脚本。

隔离网络里没有可以下载的行情抓包,所以要先自己做出数据。不使用随机数,是为了无论谁运行多少次都得到同样的数据,才能互相对照重构的结果。评分器会把数据转换成标准形式来核对指纹,所以如果手工修改数据,后面的步骤就会全部被堵住。

数出增量数据流的形态

在 /root/cap/book/shape.txt 中写入 updates=、snapshots=、symbols=、seq_min=、seq_max=、seq_missing=、delete_ops=、upsert_ops= 和 out_of_order= 九行。

seq_missing 是从 seq_min 到 seq_max 的整数中,文件里没有的个数。delete_ops 是数量为 0 的行,upsert_ops 是非 0 的行。不要在这里区分新增和变更——这两者必须持有订单簿才能区分。out_of_order 是序号比前面的行小的行数。

用快照搭起订单簿并丢弃重叠的增量

用 session.json 的 start_checkpoint 检查点快照,为每个标的搭起订单簿,并在 /root/cap/book/init.json 中,按标的写入 snap_id、last_seq、bid_levels、ask_levels、best_bid、best_ask、discarded、applicable 和 first_applied_seq 九个值。

快照在被拍下的期间,增量也仍在不断发出,所以要一并带上 last_seq。序号小于等于这个值的增量已经包含在快照里,必须丢弃。discarded 是这样丢弃的行数,applicable 是剩下的行数,两者都只统计该标的的。best_bid 是最高的买价,best_ask 是最低的卖价。

叠加增量并重构订单簿

在第 3 步的订单簿上,把序号大于 start_checkpoint 的增量按序号升序全部叠加,并在 /root/cap/book/naive.json 中写入 book 和 ops 两个键。ops 中放入 add、change、delete、delete_missing 和 applied 五个数。

动作由当前持有的订单簿决定,而不是由消息决定。数量为 0 就是删除,但要删除的价格也可能不在订单簿里。不要把它当作错误处理,而要用 delete_missing 统计下来——这个数字本身,就是有没有收到的增量的信号。断开的序号,这一步只是直接略过。

找出出现交叉订单簿的那一刻

按与第 4 步相同的方式回放,每处理一条更新,就检查该标的的不变式,并在 /root/cap/book/invariant.txt 中写入 first_cross_seq=、first_cross_symbol=、first_cross_bid=、first_cross_ask=、crossed_states=、tick_spread_states= 和 zero_qty_levels= 七行。

交叉是最优买价大于等于最优卖价的状态。crossed_states 是应用之后该标的处于交叉状态的次数,tick_spread_states 是没有交叉、而价差恰好是一个最小变动价位的次数。如果第二个值是 0,请怀疑的不是计算,而是数据类型——把十进制小数用二进制浮点数相减,是得不出恰好一个最小变动价位的。

比较在断开处继续叠加的结果与重新搭起的结果

在 /root/cap/book/gap.json 中写入 gaps(断开区间的列表)、resync(重新搭起的位置的列表)、level_mismatch(按标的统计的出现偏差的价位数)和 level_mismatch_total 四个键。gaps 的各项是 from_seq、to_seq 和 count,resync 的各项是 detected_after_seq、next_seq 和 checkpoint。

序号断开,意味着有没收到的变更,而那是什么,没有办法知道。所以不要再叠加增量,而要在下一张快照处重新搭起。重新搭起所用的检查点,是序号大于等于断开之前最后应用的序号的检查点中最早的那个,序号小于等于该快照的 last_seq 的增量,也还要再次丢弃。两个列表都从序号小的一方开始写。

把迟到的行原样叠加会怎样

在 /root/cap/book/late.json 中写入 late_seqs(迟到的行的序号列表)、harmful_seqs(其中会改变结果的)、level_mismatch(按标的统计的出现偏差的价位数)和 level_mismatch_total 四个键。比较的对象,是第 4 步的按序号顺序的回放,与在同样条件下按文件中的顺序原样叠加的回放。

并不是所有迟到的行都有害。只有当同一个标的、方向、价格,被更大的序号先覆盖了的时候,旧值才会复活。其他的行,即使顺序变了,结果也相同。两次回放都直接略过断开的位置,并同样丢弃小于等于起始检查点的行——这样,差别才只来自到达顺序这一点。

用加入了重新同步规则的最终实现与基准对照

用加入了两条规则(按序号顺序叠加、断开就从下一张快照重新搭起)的实现运行整个数据流,在 /root/cap/book/final/book.json 中写入最终订单簿,并在 /root/cap/book/final/report.json 中写入 applied、discarded、resync_count、resync_checkpoints、gap_count、lost_seqs、late_seqs、naive_vs_reference、final_vs_reference、crossed_symbols_final 和 reference_checkpoint 十一个值。

基准订单簿是 session.json 的 reference_checkpoint 检查点快照。naive_vs_reference 是第 4 步的订单簿与基准之间、final_vs_reference 是这次的订单簿与基准之间出现偏差的价位数,后者必须是 0。discarded 是因为序号已经过去而被丢弃的全部行——要把一开始重叠的区间,与重新搭起之后重叠的区间一并统计。前面步骤的产出物不要删,请留在新的目录里。