自動売買から買い注文を送った直後、通信が途切れた。画面にはエラーが出たので同じ注文をもう一度送ったところ、予定の二倍の建玉ができていた。ここでの問題は、予測が外れたことではない。「返事が来なかった」を「注文が届かなかった」と読み替えたことにある。
タイムアウトとは、決めた時間内に応答を確認できなかった状態だ。注文が失敗した証明ではなく、受付済み・約定済みという可能性も残る。トレーダーが理解しておきたいのは、再送の速さより、分からない状態を勝手に確定させない仕組みである。
注文送信と受付確認は別の出来事
注文は、自分のシステムから通信網を通って証券会社や取引所へ届き、受付や照合が行われ、その結果が返る。往路は成功したのに復路だけ途切れることもある。注文を出す処理と返答を読む処理は、一つの不可分な出来事ではない。
例えば100株の買い注文がサーバーで受け付けられ、40株まで約定した後に通信が切れたとする。手元では応答待ちでも、サーバー側には残り60株の注文が存在する。ここで新しい100株を送れば、最大200株の買いになり得る。架空例だが、通信障害時の数量管理を考える基本形になる。

状態に「不明」を用意する
注文状態を未送信と完了だけで管理すると、中間の出来事を表現できない。少なくとも、送信予定、送信済み・受付未確認、受付済み、部分約定、全量約定、取消し要求中、取消し確認済み、拒否を分ける。APIの状態名はサービスごとに違うが、論理的な区別は共通する。
通信断が起きたら「受付未確認」へ置き、勝手に未送信へ戻さない。未送信なら新規発注できるが、受付未確認なら照合が必要だ。この違いを消すと、再起動後に同じシグナルを新規注文として処理しやすくなる。
状態はメモリーだけでなく、再起動しても残る記録へ保存する。注文の意図、数量、価格、識別子、送信時刻を、実際に送る前に記録しておけば、途中でプログラムが落ちても調査できる。成功した応答だけを保存する方式では、最も重要な不明注文が履歴から抜け落ちる。
注文IDと売買の意図を結び付ける
サービスがクライアント側の注文識別子を受け付ける場合は、一つの売買意図へ一つの識別子を割り当てる。ただし、同じ識別子で再送すれば必ず重複を防げるとは限らない。重複IDの扱い、検索可能期間、受付と約定の照会方法を、そのAPIの公式仕様で確認する必要がある。
Coinbase Exchangeの公式資料では、取引所が付けたIDに加え、クライアントが付けた識別子を使って注文を照会する方法が示されている。一方、未約定のまま取消しになった注文では404が返る場合も説明されている。したがって、照会で見つからないという応答だけで、過去に注文が存在しなかったとは決められない。
口座、銘柄、方向、数量、価格、戦略IDも併せて保存する。時刻と銘柄が同じというだけで別の注文を一つへまとめると、意図して分割した注文まで消してしまう。識別子の役割は似た注文を雑にまとめることではなく、同じ論理注文を追跡することだ。
復旧時は注文・約定・建玉を三方向から照合する
まず未約定注文を取得し、不明注文が残っているかを確認する。次に直近の約定履歴を取得し、部分約定や全量約定を確認する。最後に現在建玉を取得し、記録から計算した数量と合うかを確かめる。いずれか一つだけでは、完了して注文一覧から消えた取引を見逃す場合がある。
現在建玉だけを見ても十分ではない。元から100株を持っていた口座で、新たに100株買い、別の手動操作で100株売ったなら、建玉は元の100株に見える。総量が一致しても、想定外の取引がなかった証明にはならない。約定履歴まで照合して初めて経路が分かる。
取消しボタンを押した瞬間に注文が消えるわけではない
取消し要求が処理される前に、残注文が約定することがある。100株注文のうち40株約定した状態で取消しを送り、その途中でさらに20株約定したなら、最終約定数量は60株だ。取消し要求時点の40株を確定値として次の数量を決めると、20株分の誤差が残る。
目標が100株で、残り40株の取消しが確認できたなら、追加を検討する数量は40株になる。最初の注文数量100から、取消し要求時点の40を引いて60株追加すると、合計120株を買うことになる。取消し確認と最終累計約定数量を一組で読むことが重要だ。

イベントの重複受信と重複約定を区別する
通信の再接続で、同じ状態通知や約定通知をもう一度受け取る場合がある。一つの約定を二回受信したからといって、実際に二回売買されたとは限らない。取引所や証券会社が付ける約定識別子を基準に記録し、再配信を二重加算しない処理が必要になる。
Interactive BrokersのAPI資料も、同じ情報を持つ注文状態通知が重複することや、全ての状態変更が通知されるとは限らないことに触れている。状態通知だけでなく約定情報も確認する設計が重要だ。これは通知の回数と経済的な取引回数を同一視しないという問題である。
再発注する前にシグナルの有効期限を見る
元の注文が存在しないと確認できても、そのまま再送してよいとは限らない。注文を決めてから30秒の間に価格が大きく動き、許容したスプレッドや損切り幅を超えている可能性がある。通信の復旧と投資判断の有効性は別に確認する。
シグナルには、有効期限、許容価格、最大数量、最大スプレッドなどの条件を保存する。復旧後にその範囲を外れていれば、取引を見送ることも正常な結果である。「取り逃した分を必ず取り返す」という処理にすると、技術障害の後に価格リスクまで増やしてしまう。
不明状態を資金余力から除外しない
受付未確認の注文がある間、その注文が全量約定した場合の資金とリスクを仮押さえする考え方がある。100株の存在が不明なのに、ゼロ株として別の100株注文を許可すると、複数の不明注文が重なって上限を超える。発注中数量を建玉とは別に管理したい。
売り注文でも同じだ。既存の買い建玉を閉じるつもりで売りを送った後、応答がないためもう一度売れば、空売りへ反転する可能性がある。商品の仕様に応じた決済専用注文や数量制約は補助になるが、その機能の有無や保証範囲を確認し、照合の代用品にはしない。
手動注文との競合を防ぐ
障害時に人が画面から注文を取り消したり売買したりすると、自動売買側の記録と差が生まれる。復旧したプログラムが「予定数量が足りない」と考えて買い直すと、人の緊急対応を打ち消してしまうこともある。
手動介入したら戦略を停止状態へ置き、口座全体の照合が終わるまで新規注文を再開しない。手動取引も記録へ取り込み、どの建玉をどの戦略へ帰属させるかを決め直す。全てを自動で補正しようとするより、確認不能な状態を止める境界が必要である。
本番前に試す障害シナリオ
発注前に切断、受付後に応答消失、部分約定中に再起動、取消し要求中に追加約定、同じイベントの再配信というケースを、模擬環境で試す。狙いは速く再接続することではなく、どの場合も数量が二重に増えず、記録と口座が一致することを確認する点にある。
テストでは、送信回数、論理注文数、実際の約定数量を別に数える。一つの注文について照会を何度行っても、実注文が増えていなければ正常だ。反対にエラーメッセージが消えていても、建玉が予定より増えていれば復旧成功ではない。
停止条件も決めておく。注文の存在を確認できない、口座数量が一致しない、履歴取得範囲に欠落がある場合は、新規売買を止めて調査する。既存建玉の管理方法は別途必要であり、システム停止が価格リスクをゼロにするわけではない。
再接続後の履歴取得には重なりを持たせる
最後に受信した時刻の直後から履歴を取り直すだけでは、同じタイムスタンプを持つ約定や、遅れて記録されたイベントを取りこぼす可能性がある。一定の過去区間を重ねて取得し、約定IDで重複を除く方法が考えられる。重なりを持たせることと、重複を損益へ加算することは別である。
取得件数の上限やページ分割にも注意する。直近100件が取得できたとしても、通信断の間に200件のイベントがあれば全履歴ではない。どこまで取得したかを記録し、欠けた区間がないと確認できてから復旧を完了させる。大量の通知が来たからといって、必要な記録が全てそろったとは限らない。
監視の通知と売買の再開を分ける
異常を検知して通知する処理は、注文を再開する処理とは別にする。通信が一度つながっただけで自動再開すると、まだ照合中の注文と新しい注文が混ざる。接続復旧、履歴取得、数量照合、条件再評価、再開という順番を記録し、どこで止まっているかを人が確認できる形にする。
通知には認証情報を含めず、不明な論理注文ID、銘柄、予定数量、最終確認状態、発生時刻を示す。エラーの文字列だけでは対応者が危険度を判断しにくい。価格予測の精度とは別に、何が不明で何を確認すべきかを説明できる運用記録が必要である。
安定した運用は「分からない」を残せる
自動売買の障害対策は、成功か失敗かを急いで二択にする作業ではない。受付未確認という中間状態を保存し、注文、約定、建玉を照合してから次の行動を決めることだ。結果を確認できない間に再送しないという仕組みが、通信の小さな不具合を大きな建玉へ変えないための土台になる。
参考:Coinbase Exchangeの注文照会仕様、Interactive Brokersの注文状態・約定通知資料。APIの利用可否や注文仕様は各サービスで異なる。


コメント