← 記事一覧に戻る
July 16, 2026
読了時間: 5分

約定シミュレーション: 終値幻想からキュー認識の現実へ至るはしご

約定シミュレーション: 終値幻想からキュー認識の現実へ至るはしご
#約定シミュレーション
#バックテスト
#指値注文
#キューポジション
#部分約定
#マーケットメイキング
#執行
#マーケットマイクロストラクチャー

あなたのバックテストには2つのモデルが含まれている。アルファのモデルと約定のモデルだ。ほとんどの人は労力の95%を前者に注ぎ、後者はたまたま使っているフレームワークからそのまま受け継ぐ。これは順序が逆だ。誠実な約定モデルを備えた平凡なシグナルは、平凡だが現実的なPnL推定値を生み出す。一方、fill_price = candle.close を使った優れたシグナルは、何かを推定した数値にすらならない — それは一度も検証されなかった仮定の出力に過ぎない。

流動性の高い商品に対するテイカー戦略では、約定モデルは補正項に過ぎない。だが指値の待機注文を伴うもの — マーケットメイキング、パッシブなエントリー、ポストオンリーのリベート狙い — においては、約定モデルこそが戦略そのものだ。約定するかどうか、いつ、どれだけの数量で、その後どんな値動きが起きた場合に約定するかが、PnLの大きさだけでなく符号までも決定する。

Backtest-live parity では、バックテストと実運用の乖離の全体的な分類法をマッピングし、執行の乖離を深刻度5/5 — 最悪のクラス — と評価した。あの記事では約定シミュレーションを3段階の粗い精度レベルに分けて先へ進んだ。本稿はその深掘り版だ。完全なはしごを一段ずつたどり、注文のライフサイクルを状態機械として扱い、実際に計算可能な約定確率の上下限を示し、「利益の出る」マーケットメイク戦略がはしごのどこで死ぬのかを実測実験で明らかにする。

はしご

約定シミュレーション忠実度の5段階はしご

各段はより多くのデータとより多くのコードを要求し、それぞれが特定の系統的バイアスを取り除く。段の順序は、コストではなく「何を間違えているか」によって決まる。

第0段: 終値約定

fill_price = bar.close

シグナルを生成したバーの終値で、注文が即座に、全量、約定する。シグナルはその同じ終値から計算されているため、価格が印字された時点ではまだ存在しなかったデータで取引していることになる — 執行の衣をまとったルックアヘッドバイアスだ。回転率のある戦略はここでは何でも良く見える。

第1段: 次バーの始値

fill_price = next_bar.open

バー単位のテイカーロジックにおける最低限誠実なモデル。シグナルはバー tt で計算され、約定はバー t+1t+1 の最初の観測可能な価格で発生する。これによりルックアヘッドは排除されるが、それでもスプレッドゼロ、インパクトゼロ、始値での無限の流動性、そして100%の約定確実性を前提としている。指値注文についてはタッチ約定ロジックに退化する(これがなぜ毒になるかは後述する)。

第2段: スプレッド + 固定スリッページ

half_spread = mid * spread_bps / 2e4
slip        = mid * slippage_bps / 1e4
fill_price  = mid + side * (half_spread + slip)   # side: +1 buy, -1 sell

これで、すべてのテイカー取引はハーフスプレッドとキャリブレーションされた定数を支払うようになる。ここが、高回転率戦略がバックテスト上で死に得る最初の段だ — それこそがポイントである。残る誤差はスリッページが一定ではないという点だ。スリッページは表示されている板の厚みに対する注文サイズに応じてスケールし、まさにあなたの戦略が最も取引したい局面で爆発的に拡大する。固定5bpsはレジームをまたいだ平均値であり、あなたの戦略は平均的なレジームでは取引しない。

第3段: 成行注文に対するL2板の歩行(depth-walk)

オーダーブックのスナップショットがあれば、スリッページを推測するのではなく計算できるようになる。サイズ QQ の成行買い注文は売り気配側をレベルごとに歩き、約定価格は消費した各レベルにわたる出来高加重平均になる。

Pfill(Q)=1Qipimin ⁣(qi,  Qj<iqj)P_{\text{fill}}(Q) = \frac{1}{Q}\sum_{i} p_i \cdot \min\!\left(q_i,\; Q - \textstyle\sum_{j<i} q_j\right)

def depth_walk(levels: list[tuple[float, float]], qty: float) -> tuple[float, float]:
    """levels: [(price, size), ...] sorted best-first. Returns (vwap, filled_qty)."""
    remaining, cost = qty, 0.0
    for price, size in levels:
        take = min(size, remaining)
        cost += take * price
        remaining -= take
        if remaining <= 0:
            break
    filled = qty - remaining
    return (cost / filled if filled > 0 else float("nan"), filled)

これを実質的により誠実にする補正が2つある。1つ目はレイテンシーだ。意思決定タイムスタンプの Δt\Delta t 後 — Δt\Delta t はあなたが実測したシグナルから取引所までのレイテンシー — の板の状態を歩くこと。あなたが見た板は、あなたが実際にヒットした板ではない。2つ目は部分的な成行約定だ。あなたの指値通過価格内の表示板厚が QQ に満たない場合、モデルは部分約定を返し、残りを待機注文として残さなければならない。これは以下の状態機械に問題を引き渡すことになる。

第3段に残る誤差はインパクトとリフィルだ。板を静的なオブジェクトとして消費しているが、実際の板は部分的に補充される(そして実際の取引相手は反応する)。板上値のごく数パーセント以下のクリップサイズであればこの誤差は小さいが、より大きなクリップにはインパクトモデル(Almgren-Chriss 2001)を上乗せする必要がある。

第4段: 指値注文に対する確率的キューポジション約定

第0段から第3段は「積極的な注文がどの価格で約定するか」に答える。第4段はより難しい問い、すなわち受動的な注文がそもそも約定するのかに答える — そしてこれこそがマーケットメイク戦略の価格付けができる唯一の段だ。価格 pp で待機している指値注文は、pp での累積出来高が、その注文の前にあったキュー内出来高を上回ったときに約定する。これには、直接観測できないFIFOキュー内での自分の位置を追跡する必要がある。

キューポジション推定の仕組み — 初期位置、トレード対キャンセルに対する更新ルール、観測されないキャンセルを割り当てるための f(x)f(x) 確率族 — は、Queue inside the wall で構築したプリミティブだ。ここでは再導出せず、シミュレーターはそれを消費する。第4段が付け加えるのは、その推定値の上に構築される約定判定ルールであり、これは以下の約定確率のセクションで扱う。

この段がなぜ重要かは文献によって定量化されている。Moallemi and Yuan (2016)、「A Model for Queue Position Valuation in a Limit Order Book」は、大ティック銘柄について、キューの先頭ポジションと末尾ポジションの経済的価値の差がハーフスプレッドに匹敵する — つまりマーケットメイク戦略の理論的エッジ全体と同じオーダーの大きさである — ことを示している。キューポジションを無視した約定モデルは、マーケットメイカーのPnLを誤推定しているのではない。全く別の戦略のPnLを推定しているのだ。

第5段も存在する。市場があなたの注文に反応する完全なエージェントベースシミュレーション(Huang, Lehalle and Rosenbaum 2015の意味でのキュー反応モデル、ABIDES (Byrd et al. 2020)のようなマルチエージェントフレームワーク)だ。ヒストリカルリプレイは、キューを認識していても、あなたの注文が他の全員の行動を何も変えないと仮定している。この仮定はリテールサイズでは問題ないが、あなたの気配が板レベルの目に見える割合を占めるようになるにつれて誤りが大きくなる。第5段は本稿の範囲外だが、はしごが第4段で終わらないことは知っておいてほしい。

状態機械としての部分約定

第0段から第2段までは、注文を関数呼び出しであるかのように扱える。送信、価格取得、完了だ。第3段以上になると、注文はライフサイクルを持つプロセスとなり、シミュレーターはそれを状態機械としてモデル化しなければならない。さもなければ最も重要なケースを黙って誤処理することになる。

キューポジション注釈付きの注文ライフサイクル状態機械

enum OrderState {
    PendingNew,                                  // sent, not yet acked (latency window)
    Resting     { remaining: f64, q_ahead: f64 }, // in book, queue position estimated
    PartialFill { remaining: f64, q_ahead: f64 }, // some qty done, rest still queued
    PendingAmend,                                 // amend in flight
    PendingCancel,                                // cancel in flight
    Filled,
    Canceled    { filled_qty: f64 },              // may be partially filled at cancel time
    Rejected,
}

この遷移こそが経済性を運んでいる。

  • PendingNewResting: 注文は意思決定時点ではなく、確認応答(ack)時点で存在していたすべての注文の後ろでキューに加わる。あなたのキューポジションは t+Δtackt + \Delta t_{\text{ack}} 時点でのレベル出来高でシードされる。意思決定時点でシードするシミュレーターは、キュー優先順位を系統的に過大評価する — その誤差の大きさは、レイテンシーウィンドウ中に到着した出来高そのものであり、これはバースト時にはとりわけ大きな出来高になる。
  • RestingPartialFill: あなたのレベルでキューの前方より大きなトレードが発生すると、部分的に約定する。残量は(今や先頭に近づいた)ポジションを維持したままキューに残る。部分約定はノイズではなく情報だ。1.0のうち0.3だけ約定して価格が逆に跳ねるのを見るのは、フル約定とは異なるPnLイベントであり、マーケットメイカーの在庫プロセスはこうした断片から構築される。
  • RestingPendingAmendResting: これが罠だ。事実上すべての暗号資産取引所では、amend(修正)はキャンセル/リプレイスとして扱われる。Binanceの cancelReplace は二重約定に対してはアトミックだが、キューの末尾に新しい注文IDを返す。ネイティブな修正セマンティクスを持つ取引所(CME Globex)でさえ、時間優先が保たれるのは数量の減少の場合だけであり、価格変更や数量増加は優先権を失う。したがってシミュレーターでは、価格のamendはすべて q_ahead を現在のレベル出来高全体にリセットするべきだ。500ms毎に再ペグするクオートエンジンは「気配を維持している」のではなく、常にキューの末尾に再参入し続けているのであり、その現実的な約定プロファイルはほぼ純粋な逆選択となる。Moallemi-Yuanの結果と組み合わせると、再クオートには価格がついており、その価格とはあなたのキューポジションのことだ。
  • PendingCancelPartialFillCanceled: キャンセルにもレイテンシーがかかる。気配を取り消す決定をしてからそのキャンセルがマッチングエンジンに到達するまでのウィンドウの間でも約定は起こり得る — そしてそれらの約定は、あなたが受け取る中で最悪の約定になる。なぜなら、気配を取り消していた理由は、まさに市場があなたを踏み潰そうとしていたからだ。キャンセルレイテンシーのないシミュレーターは、まさに最も毒性の高い約定を履歴から削除してしまう。

状態機械はまた、シミュレーターの会計を誠実にする土台でもある。手数料は約定イベントごとに発生し、在庫は約定イベントごとに更新され、状態滞在時間の統計(約定するまで、あるいはキャンセルされるまで注文がどれだけ待機したか)は実運用ログと直接比較可能になる — これが最後のキャリブレーションループの材料になる。

指値約定確率: 3つのモデルとブラケット

価格 pp で待機している注文(例えば買い気配)について、シミュレーターはいつ約定を宣言すべきか。誠実さが増す順に3つの判定ルールがある。

1. タッチ約定(素朴)。 価格が自分のレベルに触れたら約定とする: lowtp\text{low}_t \le p。これはファーストパッセージタイム(初到達時刻)ルールであり、その失敗は四半世紀前に実測されている。Lo, MacKinlay and Zhang (2002)、「Econometric Models of Limit-Order Executions」(Journal of Financial Economics 65)は、実際の指値注文データに生存モデルをフィットさせ、ファーストパッセージタイムから構築された仮想的な約定は「実際の指値約定の代理としては極めて不十分」と結論している。失敗のメカニズムは構造的だ。価格が自分のレベルに触れて跳ね返るとき、そのタッチはキューの先頭 — あなたより先に気配を出していたトレーダーたち — を消費している。タッチ約定は彼らの約定をあなたに与えてしまう。さらに悪いことに、それはまさに良い約定(タッチ・アンド・バウンスはマーケットメイカーにとって利益の出るシナリオ)を与える一方で、あなたの実際の約定セットはタッチ・アンド・スルー(逆選択された約定)に偏っている。

2. トレードスルー(保守的な下限)。 価格が自分のレベルを厳密に突き抜けて取引された場合にのみ約定とする: lowt<pϵ\text{low}_t < p - \epsilon、あるいはティックデータでは、pp での累積出来高がレベル全体を超えた場合。価格が突き抜けたなら、pp のキュー全体が消費されたことになるので、ポジションに関わらず約定していたはずだ。これは、実際には得られなかったはずの約定を与えることは決してない。そのバイアスはタッチ約定の鏡像だ。キューがあなたのポジションまで枯渇したが完全な突き抜けには至らなかったすべての約定を否定してしまい、それが与える約定は不釣り合いにラン・スルー(逆選択された)ものに偏る。トレードスルーの下でのマーケットメイクバックテストは、推定値ではなくストレステストだ。

3. キュー枯渇推定。 トレードテープから自分の価格での累積出来高 MtM_t を、L2の差分から推測されるキャンセル出来高 CtC_t を追跡する。推定されるキューの前方量は次の通り。

Q^ahead(t)=max ⁣(Q0MtϕCt,  0)\hat{Q}_{\text{ahead}}(t) = \max\!\Big(Q_0 - M_t - \phi \cdot C_t,\; 0\Big)

ここで Q0Q_0 は(ack時点での)注文エントリー時のレベル出来高、ϕ[0,1]\phi \in [0,1] はあなたより前方から発生したと仮定されるキャンセルの割合だ — このノブの原理的な形(f(x)f(x)族)はキューポジションの記事で導出している。約定は Q^ahead=0\hat{Q}_{\text{ahead}} = 0 になった時点で始まり、約定数量はそれを超過した出来高となるため、自然に部分約定が生じる。

filled(t)=min ⁣(S,  max(MtQ0+ϕCt,0))\text{filled}(t) = \min\!\Big(S,\; \max(M_t - Q_0 + \phi\, C_t,\, 0)\Big)

ϕ=1\phi = 1(すべてのキャンセルがあなたより前方)と設定するとこのモデルの楽観的な端が得られ、ϕ=0\phi = 0 で悲観的な端が得られる。疎なL2スナップショットしかなく、そのレベルでのトレードテープがない場合(100msスロットリングされた暗号資産フィードでよくあるケース)、モデルベースの事前分布にフォールバックできる。Cont, Stoikov and Talreja (2010)、「A Stochastic Model for Order Book Dynamics」(Operations Research 58)は各価格レベルを出生死滅過程のキューとしてモデル化し、ラプラス変換を用いて、現在のキューサイズを条件として、買い気配の注文がミッドが動く前に約定する確率を計算する。これは解析的な約定確率オラクルであり、テープリプレイに比べれば粗いが、タッチ約定よりはるかに優れており、ホットなバックテストループ内で評価するのに十分安価だ。

ブラケットの規律

この3つのルールは競合するものではなく、順序関係にある。

fillstrade-through    fillsqueue    fillstouch\text{fills}_{\text{trade-through}} \;\subseteq\; \text{fills}_{\text{queue}} \;\subseteq\; \text{fills}_{\text{touch}}

これはPnLブラケットを導く。すべてのマーケットメイクバックテストを3回実行し、区間として報告せよ。

PnLtrade-through    PnLlivePnLtouch\text{PnL}_{\text{trade-through}} \;\le\; \text{PnL}_{\text{live}} \lesssim \text{PnL}_{\text{touch}}

(上側の不等号は近似的なものだ。タッチ約定は戦略を単に水増しするだけでなく、順位を取り違えることさえあり得る。なぜなら反実仮想的な良い約定をあなたに与えてしまうからだ。)そこから導かれる判断ルールはこうだ。マーケットメイク戦略は、ブラケットの保守的な端で生き残った場合にのみデプロイ可能であり、かつブラケットが十分に狭くて点推定に意味がある場合に限る。 +9千ドル(タッチ)/ -3千ドル(トレードスルー)を示す戦略は1万2千ドル幅のブラケットを持ち、あなたはシミュレーターの仮定がアルファを支配しているという以外何も知らないことになる。+2,100ドル/ +400ドルを示す戦略は、何か本物のことを教えてくれている。

実験: 1つのマーケットメイク戦略をはしごに沿って歩かせる

約定モデルのはしごを段ごとに下るにつれて崩壊するマーケットメイク戦略のPnL

意図的に平凡なマーケットメイカーを1つ用意する。ベストビッド/アスクでの対称気配、固定0.05 BTCクリップ、キャップ到達時にテイカーでフラット化する±0.5 BTCの在庫上限。1ヶ月分のBTCUSDT無期限先物データを使う。低い段には1分足バー、高い段には100msのL2差分とトレードテープを使う。メイカー手数料1.0bps、テイカー4.0bps。すべての段で同一のシグナルコードを使う(共有コア)ため、唯一の変数は約定モデルだけだ。以下の数値は我々の実行の一例であり、取引所、月、サイズによって大きさは変わるが、形は変わらないはずだ。

約定モデル 気配約定率 約定数 月間PnL 判定
0 close-fill 98% 41,200 +$14,800 幻想
1 next-bar-open / 1分足でのタッチ 89% 37,400 +$9,600 レイテンシー込みの幻想
2 タッチ + テイカーフラット化時のスプレッドと固定スリッページ 89% 37,400 +$7,100 コストはモデル化、約定はまだ架空
3 + テイカーフラット化時のL2 depth-walk 89% 37,400 +$6,400 決済は誠実、エントリーはまだ架空
4a トレードスルー(保守的) 21% 8,900 -$3,900 ストレス下限
4b キュー枯渇、ϕ\phi キャリブレーション済み 37% 15,600 -$700 最良推定
4c キュー枯渇、ϕ=1\phi = 1(楽観的) 44% 18,700 +$1,900 楽観的上限
実運用シャドーラン、同月 35% 14,100 -$1,150 現実

この表を上から下へ読み、戦略がどこで死ぬかを見てほしい。それは第2段ではない — 手数料とスリッページで26%削られても、戦略はまだ堅牢に利益が出ているように見える。死ぬのは第3段から第4段の間であり、その理由はどんなコストモデルも捉えられないものだ。約定選択である。タッチ約定は37,400件の約定を与え、その大部分がタッチ・アンド・バウンス — 純粋なスプレッド獲得だった。キュー認識モデルはそれらの約定の58%を削除し、削除された約定は不釣り合いに利益の出るものだった。レベルが軽くタップされただけのとき、リテールレイテンシーで再クオートするマーケットメイカーより前方にあるキューがすべてを吸収してしまう。第4段まで生き残る約定は、レベルを一掃するようなスイープ — 価格がすでにあなたを突き抜けて動いている約定 — に偏っている。約定率は2.4倍に低下し、PnLの符号は反転した。良い約定を失い、悪い約定だけが残るというこの非対称性こそ、逆選択が機械的に可視化されたものであり、第4段より下のどの段でも見えない。

ブラケットが何をしたかにも注目してほしい。[-3,900ドル, +1,900ドル]はゼロをまたいでおり、キャリブレーション済みの推定値は-700ドル、実運用は-1,150ドルだ。シミュレーターは実運用PnLをドル単位で言い当てたわけではない — だが符号大きさ、そして約定率を2ポイント以内で捉えた。それこそが約定モデルの存在意義だ。第1段のバックテストは、月間の総エッジ全体がわずか数千ドルにすぎない戦略について、実運用PnLを10,750ドル外した。約定モデルの誤差はアルファそのものの大きさの約3倍だった。ゆえにこの記事の主張がある。あなたの約定モデルは、あなたのアルファよりも大きな仮定なのだ。

低い段について1つ注意点がある。バーデータ(第0段から第2段)に頼らざるを得ない場合、少なくともアダプティブドリルダウンでバー内の曖昧さを解消すべきだ — SL、TP、あるいは気配レベルがバーのレンジ内に収まる場合は、1分足から1秒/100ms/トレードへとドリルダウンする。ドリルダウンはシーケンシングの誤り(どのレベルが先にヒットしたか)を修正するが、キューの誤りは修正できない。本稿のデータ解像度における相棒ではあるが、第4段の代替にはならない。

キャリブレーションループ: 実約定に対して閉じる

第4段のシミュレーターには自由パラメータがある — ϕ\phi、レイテンシー Δt\Delta t、キャンセルレイテンシー、レベルリフィルの仮定だ。キャリブレーションされていなければ、それは単に形の違う推測にすぎない。それを計測器へと変えるループは以下の通り。

1. すべてを実運用でログに残す。 取引所タイムスタンプ付きのすべての注文イベント。送信、ack、各部分約定、amendのack、キャンセルのackだ。加えて送信時点のL2状態も。これはbacktest-live parityがDivergenceMonitorに求めているのと同じログ規律であり、約定モデルのキャリブレーションはそのモニターの最深層にあたる。

2. 同じ注文をシミュレーターに再生させる。 記録された市場データと記録された注文指示(約定結果ではなく)をシミュレーターに投入する。これで対になった結果が得られる — 各実注文に対して、シミュレートされた結末だ。

3. 平均ではなく、分布をバケットごとに比較する。 グローバルな約定率の一致だけでは、相殺し合う誤差を隠してしまう可能性がある(静かなレジームでは楽観的すぎ、バースト時には悲観的すぎて、正味では「キャリブレーション済み」に見えてしまう)。ドライバーごとにバケット分けする。

import numpy as np
from scipy.stats import ks_2samp

def calibration_report(pairs, bucket_key):
    """pairs: [{'bucket':…, 'live_filled':bool, 'sim_filled':bool,
                'live_ttf':float|None, 'sim_ttf':float|None}, …]"""
    out = {}
    for b in sorted({p['bucket'] for p in pairs}):
        grp = [p for p in pairs if p['bucket'] == b]
        live_fr = np.mean([p['live_filled'] for p in grp])
        sim_fr  = np.mean([p['sim_filled']  for p in grp])
        live_ttf = [p['live_ttf'] for p in grp if p['live_ttf'] is not None]
        sim_ttf  = [p['sim_ttf']  for p in grp if p['sim_ttf']  is not None]
        ks = ks_2samp(live_ttf, sim_ttf) if len(live_ttf) > 20 and len(sim_ttf) > 20 else None
        out[b] = {
            'n': len(grp),
            'fill_rate_live': live_fr,
            'fill_rate_sim':  sim_fr,
            'fill_rate_gap':  sim_fr - live_fr,      # signed: + means sim optimistic
            'ttf_ks_pvalue':  ks.pvalue if ks else None,
        }
    return out

バケットごとに2つの統計量がある。符号付きの約定率ギャップ(シミュレーター引く実運用)と、約定した注文間の約定までの時間(time-to-fill)分布に対するKS検定だ。この約定までの時間の比較こそが本質を突く指標である。シミュレーターは約定は一致させながら、系統的に間違ったタイミングで約定させてしまうことがあり、それは以降のすべての在庫統計・逆選択統計を汚染する。これはまさにLo-MacKinlay-Zhangの生存分析的な捉え方の教訓そのものだ。執行はイベントまでの時間の問題なので、そのように検証すべきである。

4. ノブを識別可能性の高い順にフィットさせる。 まずレイテンシー(fitするのではなく、ackタイムスタンプから直接測定する)。次に、キュー深度バケット全体で約定率ギャップを最小化することで ϕ\phi を求める。それからボラティリティレジームのバケットを確認する。バーストバケットに集中する持続的な楽観バイアスは、たいてい ϕ\phi の問題ではなく、シミュレーターがキャンセルレイテンシーによる毒性約定やレベルリフィルを過小評価していることを意味する。

5. ブラケットを再実行する。 キャリブレーション後、キュー枯渇推定は実運用に近いブラケット内に収まるはずであり — そして真の受け入れテストとして — シミュレーター上での戦略バリアントの順位がシャドーモードでの順位と一致するはずだ。それができたらパラメータを固定し、スケジュールに沿って再キャリブレーションする。約定のダイナミクスは取引所の手数料階層、ティックサイズの変更、HFT参加者の構成とともに変化していくため、3月にキャリブレーションされた ϕ\phi は7月には1つの仮説にすぎない。

我々の実行結果から得られた収束の目安はこうだ。キャリブレーションされていない第4段のシミュレーターは、典型的には実運用の約定率から±10〜15ポイント以内に収まる。1回のキャリブレーションパスの後は±3〜5ポイントになり、約定までの時間のKS検定のp値も一様に棄却されなくなる。ヒストリカルリプレイでこれ以上は望めない。残る誤差は市場があなたに対して反応する部分であり、それは第5段の問題だ。

まとめ

  1. 自分の段に名前を付けよ。 すべてのバックテストはこのはしごのどこかに座っている。あなたがその段を選んだのか、それともフレームワークがあなたの代わりに選んだのかに関わらずだ。自分の約定モデルの段とその既知のバイアスを名指しできないなら、あなたのPnLの数値には、あなたがまだ見ていない誤差幅がある。
  2. テイカーは第3段で止まってよい。 depth-walkと実測レイテンシーを組み合わせれば、リテールサイズでの積極的な執行は誠実に価格付けできる。浮いた労力はデータ品質に注ぎ込め。
  3. メイカーは第4段から始まる。 それより下では、指値約定ロジックは近似的なのではなく、逆選択の傾きが逆向きの反実仮想的な約定セットを選んでしまう。マーケットメイク戦略のタッチ約定バックテストは、本番投入と同時に死ぬ戦略を生み出す最も確実な装置だ。
  4. 約定ではなくライフサイクルをモデル化せよ。 部分約定、amendによるキューリセット、キャンセルレイテンシー — 状態機械こそが毒性のある約定が潜む場所であり、毒性のある約定こそがマーケットメイカーのPnLが死んでいく場所だ。
  5. ブラケットを報告せよ。 保守的な下限と楽観的な上限は追加2回分のバックテスト実行のコストで済み、「私のバックテストは+7千ドルと言っている」を「現実は[-3.9千ドル, +1.9千ドル]のどこかにある」に変える — これはより優れた、異なる意思決定だ。
  6. バケットごとに実約定と照らしてキャリブレーションせよ。 集計値だけで検証された約定モデルは、隠れた相殺誤差を持つ約定モデルだ。バケット化された約定率ギャップと約定までの時間のKS検定を、四半期ごとに再キャリブレーションせよ。

このはしごの各段は学術的な区分ではない。それぞれが、あなたのバックテストがあなたに語るのをやめる、ある特定の嘘なのだ。嘘があなたのエッジより小さくなるまで登り続けよ。

参考リンク

  1. Moallemi, C., Yuan, K. — A Model for Queue Position Valuation in a Limit Order Book (2016)
  2. Cont, R., Stoikov, S., Talreja, R. — A Stochastic Model for Order Book Dynamics, Operations Research 58(3), 549-563 (2010)
  3. Lo, A., MacKinlay, C., Zhang, J. — Econometric Models of Limit-Order Executions, Journal of Financial Economics 65(1), 31-71 (2002)
  4. Huang, W., Lehalle, C.-A., Rosenbaum, M. — Simulating and Analyzing Order Book Data: The Queue-Reactive Model, JASA 110(509), 107-122 (2015)
  5. Almgren, R., Chriss, N. — Optimal Execution of Portfolio Transactions (2001)
  6. Byrd, D., Hybinette, M., Balch, T. — ABIDES: Towards High-Fidelity Multi-Agent Market Simulation (2020)
  7. Binance API — Cancel-Replace order semantics
  8. CME Globex — Order modification and time priority rules

Citation

@article{soloviov2026fillsimulation,
  author = {Soloviov, Eugen},
  title = {Fill simulation: the ladder from close-price fantasy to queue-aware reality},
  year = {2026},
  url = {https://marketmaker.cc/blog/fill-simulation-partial-fills-backtest},
  description = {Five rungs of fill simulation fidelity — from close-price fills to probabilistic queue-position models. Partial fills as a state machine, limit-fill probability bounds as a PnL bracket, and a calibration loop against live fills.}
}
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

市場の先を行く

ニュースレターを購読して、独占的なAI取引の洞察、市場分析、プラットフォームの更新情報を受け取りましょう。

プライバシーを尊重します。いつでも配信停止可能です。