🗄️ 歴史的文書(アーカイブ) — この文書は過去の研究フェーズの記録であり、現在の結論・手法を反映していません。現在の研究状況は解説セクションを参照してください。
Phase 1.5-v2 — mm_zero_fee_inside_spread は逆選択 100% が損失原因
本ページは Phase 1.5-v2 の診断レポート research_reports/phase1_5_v2_mm_zero_fee_adverse_selection.md を整理したもの。
Fix-1 で fill simulator を wire up した結果、mm_zero_fee_inside_spread が -¥22.6M(62 日)でリーダーボード最下位に転落した(leaderboard-v2.md rank 15)。本診断はその損失が (A) 純粋な逆選択に 100% 帰属 し、シミュレータ・バグ (B) も設計外の戦略ロジック・バグ (C) も存在しないことを示す。
- Date: 2026-04-21
- Scope:
src/atc/cli/backtest.py(resting-quote fill loop, lines 457-512),src/atc/strategies/baselines/mm_zero_fee_inside_spread.py,data/derived/reports/backtest_ETH_JPY_2026-02-17_2026-04-19_mm_zero_fee_inside_spread_phase1_5_s3_v2.json - Upstream:
research_reports/phase1_5_fix1_maker_quote_simulator.md(Fix-1),phase1_5_v2/leaderboard-v2.md
Verdict
(A) 真正な逆選択 — 100% 帰属。 (B) シミュレータ・バグ無し。(C) 設計外の戦略ロジック・バグ無し — ゼロ・オフセットで top-of-book にクォートするという設計選択が、各 fill 後に続く informed-flow ドリフトをカバーするに足る half-spread を捕捉できない、という構造的問題のみ。
Breakdown:
- Simulator は正しい(下記 §1 logical audit)
- 戦略はトレンド市場に re-quote し、resting quote は trade flow の informed leg に picked off される
- 実現 30 秒 forward PnL per ETH = -58 JPY、平均 half-spread = +98 JPY — 市場は 30 秒で 1.6 bps 逆行し、spread capture を完全に飲み込んで 58 JPY の損失を上乗せ
Evidence
1 日診断ラン
Input: mm_zero_fee_inside_spread を業務日 2026-03-18、--source s3-stream、initial_position_eth=10.0, max_abs_position_eth=10.0 で実行。
fills: 1,571 (62 日換算 ~97k — 実測 95,945 と整合)
strategy_total_pnl_jpy: -¥340,721 (62 日換算 -¥21M — 実測 -¥22M)
alpha_total_pnl_jpy: -¥135,616
turnover: 7,870 ETH
maker_quote_submissions: 124,302
maker_quote_cancellations:122,731
fill ratio: 7.48% of trades が自クォートを fill
Artifacts: /tmp/mm_diag/backtest.json, /tmp/mm_diag/markers.jsonl。
Per-fill forward-return 分布
Signed forward return = (trade_mid_at_+h − fill_px) × sign(delta_qty) — fresh fill 保有者が horizon h で支払う / 受け取る JPY-per-ETH MtM。
| horizon | BUY avg | SELL avg | combined per-ETH | combined bps |
|---|---|---|---|---|
| 5 s | -75 JPY | -62 JPY | -69 JPY | -1.90 bps |
| 30 s | -76 JPY | -41 JPY | -58 JPY | -1.61 bps |
| 60 s | -80 JPY | -30 JPY | -55 JPY | -1.52 bps |
| 300 s | -131 JPY | +39 JPY | -46 JPY | -1.28 bps |
- BUY fill: 買った後 30s で 2 bps、5 分で 3.6 bps 市場下落。2-3 bps エッジを持つ seller に picked off される
- SELL fill: 売った後 30s で ~1 bps 市場上昇(5 分前後で mean-reversion が効くが 30s ダメージは既に確定)
- 1s-60s の全 horizon で両サイド逆方向に negative — unambiguous な逆選択シグネチャ
全 1571 fills での +30s MtM 推定: -¥456k。実現 1 日 PnL(hold-and-close 含): -¥340k。実現損失が 30s MtM より小さいのは一部 fill が EoD までに部分回復するため、方向性は明確。
Simulator バグを排除する sanity check
-
Fill 価格 ≈ 同時刻 trade 価格(± 1-2 bps, cross-side)。 per-fill
(trade_px_at_fill − fill_price) / fill_price: BUY 平均 -2.1 bps(aggressor が自 bid より安く払う — 正)、SELL 平均 +1.8 bps(aggressor が自 ask より高く払う — 正)。1571 fills 全体 max |delta| は 12 bps(小規模 market sweep が book 2 tick 分食い込み、物理的に妥当)。非現実的経済なし。 -
Fill size = quote size ちょうど(毎回 5.0 ETH)。 turnover 7,870 / fills 1,571 = exactly 5.0。これはシミュレータ簡略化(queue-position / partial fill なし)だが、maker に over-fill する bullish な単純化。現実なら fill は小さく遅くなるため、絶対損失は比例的に縮小する。符号・一件あたり量には影響しない。
-
Latency gate 機能中。 1531/1571 fills が直前 submit に matched。Quote-age-at-fill: p5 = 0.34s, median = 0.98s, max = 96s — 全て 0.3s
order_latency_secを十分超過。submit event 自体を食ってはいない(code line 474)。 -
Fill 時に quote drop。 line 512 で
resting_quotesはsurvivorsに置換(fill 済 quote は append されない)。実測: 11 distinct(side, price)ペアで > 1 fill、max は 1 ペアで 4 fills — これは simulator の二重 fill ではなく同(side, price)タプルの正統な re-submit(prior fill 除去後に新 submit 扱い)。 -
同サイド連続 fill は max 3。 一方向に叩かれ続けていれば BUY-BUY-BUY-... の long run が出るが、実測 run は mean 1.31, max 3。Fill 分布は ~均衡: 784 BUY vs 787 SELL。Imbalance-lean パラメータが book バランスに機能している → 方向性ある simulator リークは棄却。
-
Fill rate ≠ 1 fill/分。 1571 fills / 24h = 1 fill per 55s(1 fill/分ではない)。62 日スケーリングは適切に動作、trade-rate limited(全 trade の 7.5% が自 quote に hit、100% ではない)。
Spread capture 数学
Average spread at fill: 5.42 bps。Half-spread at mean fill price 361,146 = +97.9 JPY per ETH(理論 maker capture)。
Average 30s adverse drift = 1.61 bps = -58 JPY per ETH(実現)。
Net per-fill expected PnL = +98 − 98(closing half-spread) − 58(drift) ≈ -58 JPY per ETH per round trip(最終的に mid で close 前提)。1571 fills × 5 ETH = 7,855 ETH round-trip exposure → ~-¥456k/日 MtM 損失。実測損失と正確に一致。
Fix-1 simulator loop audit(src/atc/cli/backtest.py:457-512)
Line-by-line 再レビュー:
- L463-467:
TRADE + TradePayloadでの gate が正 - L471-476: per-quote latency gate は
quote.submitted_at_utc(event ts でなく strategy 自身のタイムスタンプ)を使用 — 正 - L477-482: cross 条件は L1321-1324
_should_fill_limit_orderと mirror。BUY(trade_price ≤ quote.price)/ SELL で符号正 - L483-499: booking は
apply_limit_fill(..., limit_price=quote.price, ...)、SimulatedFill.price = limit_priceを構築。Maker 慣例: resting quote の価格で fill。Ledger に単一apply_fill呼出し。ポジションは sharedExecutionManagerのexecution.current_position_eth経由で更新 → ledger / execution / strategy state が consistent - L512:
resting_quotes = survivorsで filled quote は除去され二重 fill 不可
Simulator バグなし。
What's NOT the problem
- TTL 経由の stale quote ではない: quote age at fill は p5 0.34s, median 0.98s, p95 3.94s — 5s TTL を大きく下回る。Fill の大半は quote が "current" な状態で発生 → 逆選択は stale quote 経由でなくリアルタイムに発生
- キャップでの在庫滞留ではない: 0.6% の fill のみ |pos|=10、fill 後の mean |pos| = 2.57。在庫軸の inventory skew + imbalance-lean は機能している
- 即時再発火ではない: fill → 次 same-side fill の median = 71s(sub-second でない)。Fill 間は quiescent、逆選択ダメージは各独立 fill で 1500+ trades/日 累積
min_order_interval_secthrottle ではない:src/atc/cli/backtest.py:795-811審査、この throttle は legacypending_orderpath のみに適用、maker-quote submit path(L664-689)には未適用 — maker strategy が fast requote cadence を必要とする設計意図に整合(戦略のmin_order_interval_sec = 0.5hint と consistent)。バグではない
What IS the problem — 戦略設計レベル
戦略は "inside spread" を名乗りながら、QUOTE_OFFSET_BPS_FROM_INSIDE = 0.0 でちょうど bid/ask に quote する。結果:
-
Zero offset では既存キューの上に積む。Simulator は trade cross 時に即時 fill 仮定だが、現実は queue position 依存で遅い。つまり simulator 結果は maker capture 率の 上限推定 — live では fill は減るが 30 秒逆行率は変わらない。Per-fill bps 損失は fill 数に比例縮小、fill 数も同様に縮小、bps-per-fill ダメージは不変。
-
Zero taker fees は逆選択議論を変えない。Counterparty のエッジは情報源、手数料構造ではない。30 秒後の下落を知る seller は喜んで自 bid を叩き、30s 2 bps をポケット、自分はそれを失う。
-
Adverse-selection guard(
ADVERSE_VOL_500MS_THRESH = 0.8 ETH/500ms)は緩すぎる。2026-03-18 の ETH_JPY tape 平均 0.24 trades/sec では、単発 0.9 ETH trade が 500ms に入れば pause 発動するが、大半の informed flow は<0.5ETH の小口 burst で、個々では guard を trip せず集合的に価格ドリフトを発生させる。
ミティゲーション案(追求する場合 — ただし Bottom line 参照)
- Quote 配置を広げる:
QUOTE_OFFSET_BPS_FROM_INSIDEを 1-2 bps に。Half-spread capture と fill-selection 品質のトレードオフ。3 bps でパラメータ sweep。 - Adverse-vol guard を締める:
ADVERSE_VOL_500MS_THRESHを 0.8 → 0.3 ETH/500ms、または multi-trade "N trades in T seconds" gate(純 count + 方向一致、cumulative size だけでない)。 - Orderbook imbalance でクォートを方向性スキュー:
ob_imbalance_top5 > 0のときのみ BUY quote、< 0 のときのみ SELL quote。現状は両サイド無条件、imbalance-lean は価格微調整のみ。 - 戦略を廃止: zero-fee 下の maker-capture 仮説は single-venue で informed flow に対して成立しない、という第一原理的結論(market-making edge = spread capture − adverse selection、single-venue small book で queue-information 無しの adverse selection は ~1.5 bps/fill でほとんどのレジームで half-spread を超過)。所見を文書化して次に進む方が高 EV の可能性。
Bottom line
-¥22.6M の損失は完全に real。 Fix-1 simulator は正常動作。mm_zero_fee_inside_spread は informed flow に対して 30s あたり per-ETH 1.6 bps で敗北、95,945 fills × 5 ETH/fill × 62 日で観測損失を正確に生成。
live では損失はむしろ拡大し得る:
- 実 fill 経済には partial fill / queue penalty があり fill 数は減る(損失も減る)が、queue 後方 maker は逆選択 trade で worse price で fill される(本シミュレータは未モデル)
- GMO Coin 実 queue-priority ルールは小口 quoter に不利
アーティファクト
/tmp/mm_diag/backtest.json— 1 日再ラン結果/tmp/mm_diag/markers.jsonl— 248,610 order markers(1,571 fills + timestamp / side / price / size / reason tag)/tmp/mm_diag/run_diagnostic.py,analyze_fills.py,analyze_lifecycle.py,post_fill_trajectory.py,check_extremes.py— reproducible 分析スクリプト
関連ページ
- Phase 1.5-v2 — Leaderboard — 本診断の起点となった -¥22.6M rank-15 結果
- Phase 1.5 — MM Zero-Fee Simulator Bug — Fix-1 で埋めた resting-quote fill simulator 欠落バグ(本診断の前提)
- Phase 2b — Alternative Alpha Candidates — MM 路線の構造的限界を受けた alpha-signal 転換の後続研究
- Source report — Line-by-line audit、fill 分布、spread capture 数学の全量