桁を一つ多く打った注文はどこで止まるのか
目標
注文ストリームに、単一注文の上限・価格バンド・制限銘柄・重複・累積エクスポージャー・キルスイッチを1層ずつ重ねてみて、累積を更新する時点とチェックの順序が結果をどう変えるかを、数字で確認します。
なぜ重要なのか
約定したあとで見つけることと、出ていく前に止めることは、別の仕事です。出ていった注文はすでに市場にあり、元に戻すコストは、そのまま損失です。そのため、チェックは注文経路の上に置かれます。ところが、チェックはオンにしておくだけでは足りません。拒否された注文まで累積限度に入れると、実際のエクスポージャーが限度の半分の人が止められ、基準価格のない銘柄を通過にしておくと、チェックがある理由がなくなります。保留を拒否として数えると、キルスイッチが見当違いの場所でオンになり、遮断状態をメモリにだけ置くと、1回の再起動で解けます。このラボは、そうした落とし穴を1つずつ自分で作ってみて、数字で確認します。
ステップ
python3で/root/risk/dataに、orders.jsonl・limits.json・limits_tight.json・refprice.csv・restricted.txt・accounts.csvを作成してください。- 単一注文の上限(数量・金額)だけをかけて、
/root/risk/single.csvを作ってください。 - 価格バンドのチェックを加えて、
/root/risk/band.csvを作ってください。基準価格のない銘柄は保留です。 - 制限銘柄と重複注文のチェックを加えて、
/root/risk/screen.csvを作ってください。 - 口座・デスクの累積エクスポージャー限度を加えて
/root/risk/exposure.csvを作り、拒否された注文まで累積に入れる誤った実装が止めたはずの注文を、/root/risk/naive_blocked.csvに書いてください。 - キルスイッチを加えて
/root/risk/final.csvを作り、遮断状態を/root/risk/killswitch.jsonに残してください。 - 価格バンドのチェックを前に繰り上げた順序でもう一度回して、
/root/risk/order_compare.csvと/root/risk/order_compare.jsonに差を書いてください。 - 理由別の統計を
/root/risk/stats.jsonに、限度額を厳しくした設定との比較を/root/risk/tuning.jsonに書いてください。
参考
- 判定値は4つです。
ACCEPT(通過)・REJECT(拒否)・HOLD(保留)・BLOCK(キルスイッチによる全体遮断)。理由は、OK・MAX_QTY・MAX_NOTIONAL・PRICE_BAND・NO_REF_PRICE・RESTRICTED・DUPLICATE・ACCT_EXPOSURE・DESK_EXPOSURE・KILL_SWITCHの10個です。 - 標準のチェック順序は、
RESTRICTEDの次にMAX_QTY、MAX_NOTIONAL、PRICE_BAND、DUPLICATE、ACCT_EXPOSURE、DESK_EXPOSUREです。最初に引っかかったチェックの理由を書き、そこで止めます。 - 各ステップは、そこまでにオンにしたチェックだけをかけます。ステップ2は数量・金額の上限だけ、ステップ3はそれに価格バンドまでです。注文はすべて1行ずつ書きます(通過したものも書きます)。
- 金額は
decimalで計算し、小数第2位まで書きます。価格バンドは、基準価格に対する百分率で比べますが、割り算の代わりに両辺に掛けて比較すれば、丸めの問題が生じません。 - 累積エクスポージャーは、通過した注文だけを足します。拒否と保留は、累積を消費しません。
- 重複判定の基準は、口座・銘柄・売買方向・数量・指値がすべて同じで、先に通過した注文との時間差が
duplicate_window_sec以下であるものです。 - キルスイッチは、拒否が
window_secの間にrejects件以上たまった瞬間にオンになります。保留と遮断は、拒否として数えません。オンになった注文自身は元の判定を維持し、その次の注文から、すべてBLOCKです。 - データファイルは直さないでください。採点ツールが、データのフィンガープリントを毎ステップ確認します。
- よくある間違い1: 基準価格のない銘柄を通過にしてしまいます。
- よくある間違い2: 累積エクスポージャーを注文ログからそのまま合算して、拒否された注文まで入れてしまいます。
- 公開ドキュメント: eCFR 240.15c3-5、連邦官報の採択告示、FIX Standards、decimal。限度の数値と注文47件は、このラボの合成データです。
注文ストリームと限度の定義を作る
python3で/root/risk/dataに、orders.jsonl・limits.json・limits_tight.json・refprice.csv・restricted.txt・accounts.csvを作成してください。乱数を使わない生成スクリプトを、そのまま使ってください。
顧客の注文ログをそのまま持ってこられないので、同じ形の合成データを作ります。乱数を使わなければ、誰が何回回しても同じデータが出て、お互いの判定を突き合わせられます。時刻もデータに固定します。今日の日付で作ると、明日回したときに判定が変わります。採点ツールは、データを標準形に変換してフィンガープリントを突き合わせるので、手で直すと、あとのステップがすべて行き詰まります。
単一注文の上限をかける
/root/risk/single.csvに、1行目にorder_id,decision,reason,notionalを置いて、数量の上限と金額の上限だけをかけて、注文47件をすべて1行ずつ書いてください。
金額は数量掛ける指値です。数量の上限だけを置くと、高い銘柄で漏れるので、両方を見ます。標準の順序では、数量が金額より前なので、両方を超える注文の理由は、前のほうです。通過した注文の理由はOKで、金額は小数第2位まで書きます。
価格バンドと基準価格のない銘柄
/root/risk/band.csvに、1行目にorder_id,decision,reasonを置いて、前のステップの2つのチェックに価格バンドのチェックを加えて、注文47件をすべて書いてください。基準価格のない銘柄は、HOLDとNO_REF_PRICEです。
基準価格から許容百分率以上離れた指値を止めます。比較するときに割り算を使うと丸めが入り込むので、差に100を掛けて、許容百分率掛ける基準価格と比べてください。基準価格のない銘柄を通過にしておくと、チェックがある理由がなくなります。通過でも拒否でもない場所を、1つ作ってください。
制限銘柄と重複注文を除く
/root/risk/screen.csvに、1行目にorder_id,decision,reasonを置いて、前のステップに制限銘柄のチェックと重複注文のチェックを加えて、注文47件をすべて書いてください。
制限銘柄は名簿との突合なので最も安く、標準の順序で一番前です。重複は、口座・銘柄・売買方向・数量・指値がすべて同じで、ウィンドウの中に入ってきた注文ですが、比べる対象は先に通過した注文だけです。拒否された注文を基準にすると、1回間違えた人が、2回目の正常な注文まで止められます。
累積エクスポージャー限度と、誤った実装の比較
/root/risk/exposure.csvに、口座・デスクの累積限度までかけた判定をorder_id,decision,reasonで書き、/root/risk/naive_blocked.csvに、1行目にorder_id,account,desk,reason_naiveを置いて、拒否された注文まで累積に入れる実装が止めたはずの注文を書いてください。
累積は、口座別・デスク別に、別々に数えます。通過した注文の金額だけを足し、拒否と保留は累積を消費しません。そのあと、わざと誤った実装をもう1つ作ってください。注文ログをそのまま走査して、すべての注文の金額を足すものです。2つの結果で、正しいほうは通過なのに誤ったほうは通過ではない注文が、不当に止められた注文です。
キルスイッチをオンにして、状態をファイルに残す
/root/risk/final.csvに、キルスイッチまでかけた判定をorder_id,decision,reasonで書き、/root/risk/killswitch.jsonにengaged・engaged_at・trigger_order_id・rejects_in_window・threshold・window_sec・blocked_ordersを書いてください。
個別の拒否と全体の遮断は、別の動作です。ウィンドウの中に拒否が基準の数だけたまったら、そのときから、経路をまるごと閉じます。保留と遮断は、拒否として数えません。オンにさせた注文自身は、元の判定のままにし、その次の注文から、すべて遮断です。そして、遮断状態は必ずファイルに残してください。メモリにだけ置くと、1回の再起動で解けます。
チェックの順序を1つ変えてもう一度回す
価格バンドのチェックを数量の上限の前に繰り上げた順序でもう一度回して、/root/risk/order_compare.csvにorder_id,decision_canonical,reason_canonical,decision_band_first,reason_band_firstで、変わった注文だけを書き、/root/risk/order_compare.jsonに、2つの順序の要約を書いてください。
理由だけが変わるように思えますが、そうではありません。基準価格のない銘柄は保留になりますが、保留は拒否として数えないので、バンドのチェックが前に来ると、拒否が1件、保留に変わって、キルスイッチがオンになる場所が遅れます。その間に入ってきた注文は、止められずに出ていきます。要約には、accept_canonical・accept_band_first・reject_canonical・reject_band_first・hold_canonical・hold_band_first・blocked_canonical・blocked_band_first・kill_trigger_canonical・kill_trigger_band_first・diff_ordersを入れてください。
理由別の統計と、限度額を厳しくした設定の比較
/root/risk/stats.jsonにorders・accept・reject・hold・blocked・by_reasonを書き、/root/risk/tuning.jsonにbase_accept・tight_accept・newly_rejected_count・newly_rejected・tight_by_reasonを書いてください。newly_rejectedは、今の設定では通過だったのに、厳しくした設定では通過ではない注文の一覧です。
統計は、ステップ6の判定、つまりキルスイッチまでオンにした結果から出します。そのあと、同じ1日をlimits_tight.jsonでもう一度回します。2つの設定の差は、理由別の数だけでなく、通過件数自体にも表れます。newly_rejectedは、注文番号の昇順で書き、限度額を厳しくすると、なぜキルスイッチまで影響を受けるのかを、出力で確認してください。