スナップショットと差分のつなぎ目を誤ると、板は静かに狂う
一言でいうと
相場は、スナップショット1枚と、その後に続く増分更新で届きます。スナップショットが取られた順序番号と、増分の順序番号を重ねてつなぐ必要があり、順序番号が一度でも途切れたら、増分をそれ以上重ねず、新しいスナップショットを待つ必要があります。この規則が抜けた板は、止まったり空になったりしません。ただ少し間違ったまま、動き続けます。
なぜ必要なのか
板全体を毎回送ると、帯域が耐えられません。そのため、どの相場フィードでも同じ形を使います。今の状態を1枚撮って送り(スナップショット)、その後は、変わった価格レベルだけを1つずつ送ります(増分)。受け取る側は、スナップショットで板を立ち上げたあと、増分を順に重ねます。
問題はつなぎ目です。スナップショットは撮るのに時間がかかり、その間も増分は出続けています。そのため、スナップショットには「ここまではすでに反映されている」という順序番号が一緒に埋め込まれて出ていきます。受け取る側は、スナップショットより前の順序番号の増分を捨てて、その後から重ねる必要があります。この値を無視して、受け取った増分をすべて重ねると、すでに反映された変更がもう一度適用され、逆に、スナップショットが来る前の増分をすべて捨てると、その間の変更が消えます。FIX系の規格が、再開時点を扱うときに「最後に処理した順序番号」を常に一緒に運ぶ理由が、ここにあります(FIX Trading Communityの標準文書、フィールドの意味はFIXimateで探せます)。
どう動くのか
増分の動作は3つだけです。追加、変更、削除。ところが、大部分のフィードは動作の名前を送りません。数量0が削除で、0でない値は、その価格が板にあれば変更、なければ追加です。つまり、動作はメッセージではなく、受け取る側が持っている状態が決めます。そのため、同じ増分ファイルを受け取っても、開始状態が違えば、結果が変わります。数量0を値として保存してしまう実装は、数量0の価格レベルを残し、そのレベルが最良気配の位置を占めれば、画面には「0株待機」が最良として表示されます。
板には、守るべき不変条件があります。最良買い気配値は、最良売り気配値より低くなければなりません。両者が等しいか逆転していればクロスしており、クロスした板は現実にはありません。その状態が画面に出ているということは、自分の再構成が間違っているという意味です。そして、価格ごとの数量は正の数でなければならず、並び順は常に保たれていなければなりません。
不変条件を測る計算には、もう1つ落とし穴があります。スプレッドが1呼値かどうかを見ようと、2つの価格を引いた瞬間、2進浮動小数点数では答えが出ません。55.15から55.10を引くと0.04999999999999716になり、0.05と等しくありません。価格は10進の小数なので、10進で扱う必要があります(decimalモジュールのドキュメント)。この間違いは例外を出さないので、「1呼値のスプレッドが一度もなかった」という静かな0として現れます。
最後が再同期です。順序番号が途切れたということは、自分が受け取れなかった変更があるという意味で、その変更が何だったかを知る方法はありません。このとき増分を重ね続けると、板は途切れた箇所で分かれて、二度と戻ってきません。正しい処理は、増分の適用を止めて、次のスナップショットを待つことです。そのため、フィードはスナップショットを定期的に撒き直します。
現場での姿
あるチームに、「最良買い気配値が売り気配値より高く表示される」という報告が入りました。画面を更新すると問題なくなるので、画面の問題として受け付けられました。実際には、数分前に短いギャップがあり、そのときに消えるはずだった買いのレベルが、そのまま残っていました。更新が直したのは、画面ではなく板でした。その間の数分間に出た注文は、存在しない気配値を見て価格を決めたのです。
もう1つよく見るのが、遅延到着です。順序番号は過去なのに、あとから届いた行を到着順に重ねると、すでに上書きした値が、古い値に戻ります。これも、エラーなしで通り過ぎます。到着順ではなく順序番号の順に重ね、すでに過ぎた順序番号は捨てることが、唯一の防御です。
3つ目は、少し種類の違う事故です。再構成した板を1か所でしか持っていないときに生じます。画面と戦略エンジンがそれぞれ増分を重ねると、2つの板は、同じ入力を受けても互いに異なるものになります。一方だけがギャップを検知して再同期し、もう一方は重ね続けるからです。こうした場面では、「どちらが正しいか」を、人が目で見分けることができません。そのため、再構成は1か所で行って結果を配るか、最低限、2つの板がどの順序番号まで反映したかを一緒に持ち歩かせる必要があります。調査のときに最初に尋ねるのも、それです。この画面は、何番の順序番号まで見たのか。
受け取る側を直すときに、必ず一緒に決める必要があることが、もう1つあります。再同期をしている間、板を何で見せるかです。古い板をそのままにすると、人はそれが今の相場だと信じ、空にしてしまうと、注文画面が止まったように見えます。どちらにしても、その状態であるという事実が、画面とログに表れる必要があります。この選択を先送りにすると、ギャップを正しく検知するように直したあとで、かえって「画面がよく空になる」という報告が増えます。
次のラボですること
4銘柄が1つのチャネルで流れる合成フィードを作り、スナップショット1枚から板を立ち上げ、増分1,300行余りを重ねます。順序番号が重なる区間を捨てる規則を入れ、板がクロスする瞬間の順序番号を探し、途切れた箇所で重ね続けた結果と、新しいスナップショットから立て直した結果が、いくつの価格レベルで分かれるかを数えます。最後に、2つの規則をどちらも入れた実装で全ストリームを回し、基準のスナップショットと1レベルもずれないことを確認します。