LabHub
学习 学习路径 课程

资本市场与清结算

订单不是状态,是一串事件

在 LabHub 中继续学习

一句话总结

订单的最终状态不是存储在某个地方的字段,而是通过按顺序折叠多个事件而形成的结果,如果没有设定折叠规则和折叠顺序,在同一日志中会产生不同的答案。

概念图: 相信订单状态是一个值 · 我们规定的规则 · 每一行包含什么样的判断 · cancelreq不改变状态是核心。

为什么需要这个?

进入证券公司支持系统,几乎没有例外,都会以这句话开始:“我收到了结算通知,但屏幕上显示为取消。”

第一次听到这句话时,会认为画面出了问题或者DB更新晚了。所以orders打开桌子status看体温。那里有cancelled上面写着。如果不知道接下来要做什么,调查就到此为止。

停止的原因是相信订单状态是一个值。实际上是这样的。

09:31:04.118  new           수량 300, 지정가 71,500
09:31:04.180  ack           거래소가 접수를 확인
09:44:12.007  partial_fill  150주 체결, 누적 150
09:47:55.310  cancel_req    잔량 취소 요청
09:47:55.402  fill          150주 체결, 누적 300
09:47:55.408  cancelled     잔량 취소 확정

在这六行中,必须找出“这个命令是什么”。只看最后一个活动,就是取消。只看结算数量,就是全部结算。两者都是这个日志中的事实,哪一个叫状态是我们规定的规则。没有明确规定的规则的系统,代码很多地方都写着不同的规则,所以屏幕、通知邮件和结算布局会给出不同的答案。

怎么行动

处理事件的函数大致是这样的。这里重要的是不是代码,而是每一行包含什么样的判断

new           주문 수량이 정해진다. 상태는 '접수'
ack           거래소가 받았다. 상태는 '유효'
reject        거절. 여기서 끝난다
replace       주문 수량이 바뀐다. 상태는 그대로
cancel_req    아무것도 바꾸지 않는다   ← 여기가 첫 번째 함정
cancelled     상태는 '취소'
fill 계열     누적 체결에 더하고, 주문 수량에 닿으면 '체결'

**cancel_req不改变状态是核心。**请求取消和取消是不同的事实。请求是我们发送的,确认是交易所给的。请求时状态cancelled实际上,将改为换成这种实现方式是很常见的,这样一来,在请求之后附有结算的订单就会被取消。顾客收到了股票,但我们的账簿上却没有。

第二个陷阱是部分结算会多次发生。300周订单以150+100+50分成三次结算,这不是奇怪的事情,而是基本情况。每次结算只包含自己份额的数量,累积我们需要计算。交易所cum_qty虽然也会一起给,但如果那个值和我们测到的值不一样,那本身就是购买信号。

第三个陷阱是相同的签约会两次到来。如果会话中断或重新连接或请求重新发送,已经收到的签约会再次到来。所以每次签约都是唯一的exec_id附着着,用它过滤的是接收方的责任。如果不过滤的话,累积数量会膨胀,超过订单数量的是明显的,但没有超过的是没有任何警报的情况下留下错误。

在现场相遇的样子

一家证券公司每天凌晨都失败地安排夜间结算。日志中有六条消息说“成交数量超过订单数量”,负责人在每天早上用手把六条消息改过来。已经六个月了。

原因是没有经过重新发送的结算。而且真正的问题不是这六件。由于同样的原因,还有二十几件订单数量膨胀,但它们没有超过订单数量,所以没有留下任何信息。每天早上纠正的六件是冰山一角,下面六个月没有人计数。

另一个经常遇到的画面是状态字段和事件日志分开数年的样子。状态字段由实时处理器更新,事件日志保持原样堆积,如果处理器错过一次事件,该订单的状态字段将永远错误。事件不会再次流出,通常没有重新计算状态字段的步骤。因此,调查时不要相信状态字段,而是从事件中重新创建是第一步。

接下来在实习中要做的事情

首先在下面的理论中观察时钟和顺序,然后进行实践。

亲自制作900件订单的活动日志,只用那个日志重新构建每个订单的最终状态。然后与OMS持有的状态字段进行对比,寻找不一致的订单。只比较状态字符串时和比较成交数量时产生的数字都不一样,这个差异是这次事故的本体。