どんなバックテストも避けて通る問いがあります。同じコードに、同じ相場をライブで流し込んだとして、本当に同じ振る舞いをしたのか? これはバックテストを積み増しても答えは出ません。答えを出す方法はただ一つ、実際の執行セマンティクスのもとでライブデータに対して戦略を走らせ、その上で2つの世界が一致しているかを1トレードずつ検証することです。私たちはこの検証を毎日、自動で回しています。そしてこれが、プラットフォーム全体を通じて最も多くのバグを掘り当ててきた仕組みになりました。
初日: ミラーを構築する
昇格した戦略はそれぞれ独立したライブノードで稼働し、実際の取引所ストリームを消費します。約定に使うのはライブ気配、スプレッドもセッション時間も本物です。戦略間で状態を共有することはありません(これは痛い目を見て学びました — 共有ノードでは、ある戦略のポジション照会が別の戦略に染み出してしまったのです)。すべての約定は、それを生んだシグナル値とセットで記録されます。
毎朝: リプレイして差分を取る
日次ジョブが各戦略のペーパー期間を取得し、同一のコードを同じ期間・カタログデータでバックテストエンジンに通し、約定を1対1で突き合わせます。売買方向が同じか、タイムスタンプが許容範囲内か、サイズが同じか。出力は相関係数でもなければ「なんとなくの感触」でもありません。matched(一致)、paper-only(ペーパーのみ)、replay-only(リプレイのみ)という表です。健全な戦略は55件中54件が一致という水準に落ち着きます。不健全な戦略は、どのトレードがいつ乖離したのかをそのまま教えてくれます。
壊れたもの、壊れた順に
- ウォームアップ中の約定。 ライブノードはインジケーターを温めるために過去データを流し込みますが、独自のバーハンドラを持つ戦略はそれを使って平然と発注してしまっていました。バックテストはこの幽霊エントリーを一度も見ていません。修正は戦略側ではなく発注レイヤーのガードとして入れました — そうしなければ、すべての戦略がこのバグを継承してしまうからです。
- マルチレッグの逐次ウォームアップ。 あるクロスセクショナル戦略は銘柄を1つずつ順番にウォームアップしていたため、どのレッグから見ても他のレッグは空っぽで、アンカーが初期化されることは永遠にありませんでした。ライブでは一切トレードせず、リプレイでは正常にトレードする。一致トレードはゼロ — しかし、それをはっきり言葉にしてくれたのは差分だけでした。
- ティック精度によるクラッシュループ。 ライブの aggTrade ティックは、サンドボックス側の銘柄が想定する精度とは異なる精度で届きます。ノードはバックテストでは決して発火し得ないアサーションでクラッシュループに陥りました。カタログのバーはすでに正規化済みだからです。
- 再起動による記憶喪失。 プロセス再起動が、すでに存在するポジションに対してエントリーロジックを再発火させていました。リバランス戦略にとって、再起動はそのまま静かなリバランスと化します。ライブ取引では、これはパリティの脚注などではありません。お金が絡んだ赤信号です。
パターンにお気づきでしょうか。この中に、シグナルのバグは一つもありません。すべて執行境界のバグです — ウォームアップ、状態、精度、再起動。まさにこれこそ、バックテストが構造的に見えない類の失敗です。バックテストにおいて、バックテストエンジンそのものが執行境界だからです。
意図的に残している非対称性
2つの世界は、あえて同一にはしていません。バックテストはモデル化した半スプレッドに平方根則のインパクトを加えてPnLに織り込みます。一方ペーパーは、約定価格に乗った本物のスプレッドを払い、インパクトモデルは持ちません。モデル化したスプレッドをペーパーに足す(あるいは実スプレッドをバックテストに足す)のは二重計上です。パリティが意味するのは同じ意思決定と、許容誤差内の同じ約定であって、ビット単位で一致するPnLではありません。どの差が筋の通ったもので、どの差がバグなのかを見分けること — それがこの規律のほとんどすべてです。
バックテストは仮説。ペーパートレードは実験。リプレイ・パリティは、自分が自分を騙している瞬間を捕まえる実験ノートです。
もしあなたが戦略を運用していて、ライブの挙動とリプレイの挙動を定期的に差分比較していないなら、まだ一度も目にしたことのないバグの一群を抱えていることになります。私たちはおよそ2週間に1件のペースでそれを見つけていますが、そのどれもがバックテストの内側からは見えないものでした。
← 記事一覧
