屏幕上的盘口一直悄悄错着
目标
用一张快照和增量更新重新搭起订单簿,并把序号断开的位置和迟到的行所造成的差别,用价位数数出来。最后,确认加入了重新同步规则的实现,与基准快照没有一个价位的出入。
为什么重要
行情画面即使错了,也不会发出任何声音。不是值变空或者抛出异常,而是略有不同的值一直在运转,所以直到几天之后订单出现异常,才会往回追溯。快照里标记的序号与增量的序号怎样衔接,序号断开时是停下还是继续叠加,这两条只有两行的规则,决定了这种差别。而且本实验的计算全部处理价格。价格用二进制浮点数一减,判断是否相差一个最小变动价位的结果就会悄悄变成 0,所以也一并看一看,哪些情况是在值之前,先在数据类型上输掉的。
步骤
- 用
python3在/root/cap/book/data中生成四个数据文件。请原样使用生成脚本。 - 数出增量数据流的形态,用九行写入
/root/cap/book/shape.txt。 - 用起始快照搭起订单簿,数出重叠的增量,写入
/root/cap/book/init.json。 - 把增量按序号顺序叠加,把订单簿和按动作统计的笔数写入
/root/cap/book/naive.json。 - 找出回放过程中不变式被破坏的位置,用七行写入
/root/cap/book/invariant.txt。 - 把断开的区间与重新搭起的结果之间的差别,写入
/root/cap/book/gap.json。 - 把迟到的行和它所造成的差别,写入
/root/cap/book/late.json。 - 把加入了重新同步规则的最终订单簿留在
/root/cap/book/final/book.json中,把质量报告留在/root/cap/book/final/report.json中。
参考
- 推送格式写在
/root/cap/book/data/feed_spec.txt里。它不是真实的交易所规范,而是为本实验准备的合成格式。 - 会话常量在
/root/cap/book/data/session.json里。最小变动价位、快照被拍下时的序号、起始快照和基准快照都在那里,所以不要把数字写死在代码里。 - 价格用
decimal.Decimal来处理。如果用float相减,就得不出恰好一个最小变动价位,第 5 步的价差判定就会落到 0。 - 出现偏差的价位数,是把两个订单簿各自做成
(종목, 방향, 가격, 수량)(占位符依次为标的、方向、价格、数量)的集合,再用对称差集的大小来数。如果只有某一个价格的数量不同,两边各会出现一个,所以是 2。 updates.jsonl的行顺序就是到达顺序。它可能与序号顺序不同。- 常见错误 1:把数量 0 当作值存起来。0 不是值,而是要求删除这个价位的动作。
- 常见错误 2:在第 6 步选最后一张快照来重新搭建。要使用能填补断开位置的检查点中最早的那一个。
- 标准文档:FIX Trading Community、FIXimate、decimal 模块。
生成快照和增量数据流
用 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 是因为序号已经过去而被丢弃的全部行——要把一开始重叠的区间,与重新搭起之后重叠的区间一并统计。前面步骤的产出物不要删,请留在新的目录里。