バックテストを実行してから6週間後、同じコード、設定、期間でもう一度実行しました。最終時点の資産額が異なっていました。大きな差ではありませんが、Sharpe比は1.31から1.08に変わり、最良に見えるパラメーターセットも変わるほどでした。
戦略は変わっていません。過去データが変わっていたのです。ベンダーが数日分の約定データとバーを改訂していましたが、最初の実行時に、正確な入力スナップショットを保存していませんでした。その後数日かけて、新しい結果がデータの修正によるものなのか、パイプラインの不具合なのか、それとも再現できない研究プロセスのせいなのかを調べました。
月曜日:再実行で結果が食い違う
最初の手がかりは、流動性の高い現物ペアに見つかった小さな差でした。元のバーでは高値が62,418でした。再実行では62,431でした。戦略のエントリー条件を調べるまでは、無害に見える差でした。閾値がその2つの価格の間にあったのです。バーがいくつか変わるだけでシグナルが変わり、数日後のポジションまで変わっていました。
日ごとの約定件数を比較すると、食い違いは特定の1日から始まっていました。次に、その日のデータを構成する生データの行を比較しました。ベンダーから現在取得できるデータには、修正済みの約定データが数件と、改訂された集計バーが含まれていました。当時使ったデータには、変更履歴が付いていませんでした。
これはデータによくあることです。取引所は記録を修正し、ベンダーは欠損を補完し、異常な約定を訂正し、集計ロジックを調整します。「1月のデータ」を問い合わせても、3月には違う結果が返ってくることがあります。
火曜日:残っていない受領記録
実行記録には、ベンダー名、シンボル、期間を保存していました。しかし、応答そのものも、入力パーティションごとのコンテンツハッシュも保存していませんでした。つまり、何をリクエストしたかは証明できても、ベンダーから返されたバイト列までは証明できませんでした。
見つかった中で最も古いキャッシュファイルを取り出し、現在の応答と比較しました。差異は分割調整でもタイムゾーン変換でもありませんでした。バーの生成処理に流れ込む、少数の修正済み約定データが原因でした。これで1つの仮説は除外できましたが、キャッシュが不完全だったため、影響を受けたすべてのバーを正確に再構成することはできませんでした。
| 記録していた情報 | そこから分かったこと | 欠けていた情報 |
|---|---|---|
| ベンダー、シンボル、期間 | 実行時に行ったリクエスト | 応答の内容 |
| パイプラインのバージョン | バーを生成したコード | 正確な入力行 |
| 実行時刻 | ジョブを実行した時刻 | その時点で利用できたベンダーの改訂版 |
私たちは「同じクエリ」を「同じデータ」だと扱っていました。その前提のせいで、実験の再現性を実際以上に高く見積もっていました。
水曜日:修正で結果の見え方が変わる
影響を受けた日付について、利用可能なデータの両バージョンで再生しました。改訂版のバーではエントリーが2回なくなり、1回の決済が1本分ずれました。この3件の取引が、Sharpe比の変化の大半を占めていました。チャート上では価格系列全体がほぼ同じに見えても、戦略はトリガー付近の少数の観測値に敏感でした。
だからといって、元の結果が不正だったわけでも、改訂後の結果が自動的に優れているわけでもありません。必要なのは、主張の範囲を明確にすることでした。以前の実行結果は当時のデータスナップショットを示し、再実行の結果はベンダーが現在提供する過去データを示しています。一方を他方の再現と正直に呼ぶことはできませんでした。
木曜日:研究記録を変更する
新しい研究実行では、変更できない入力スナップショットを保存するようにしました。各パーティションにコンテンツハッシュを付け、実行マニフェストにはデータソース、取得時刻、スキーマのバージョン、調整ポリシー、変換コードのバージョンを記録します。大規模データセットでは、コンテンツアドレス方式のストレージに実ファイルを保管します。同じパーティションを共有しながら、過去のバージョンを上書きせずに済みます。
ストレージやライセンスの規則によって生データを保持できない場合は、契約条件の範囲内で最も確かな記録を残します。ハッシュ、リクエストのパラメーター、ソースの改訂識別子、診断用にパースした行の小さなサンプルです。ハッシュからデータを復元することはできませんが、2つの実行で同一のバイト列が使われなかったことは判別できます。
再現可能なバックテストには、データURLだけでなく入力を特定できる情報が必要です。何を、いつ受け取り、それがどのように戦略の入力行になったのかを記録しましょう。
金曜日:次回は省くこと
主要な指標だけを比較するのはやめます。Sharpe比が変わったことは、何かに変化があったと教えてくれますが、原因までは分かりません。まず行数とパーティションのハッシュを確認し、最初に差が出た入力日を探します。次に、変換後の行、シグナル、注文、約定の順に比較します。
古いデータを修正済みの過去データで黙って置き換えることもやめます。修正データは役立ちますが、過去の実行は元の入力に結び付けたままにすべきです。新しいデータセットを使う場合は新しい実行を作り、古い実行との関係を明示します。
今の実務ルールはシンプルです。実験の再現が目的なら、データスナップショットを固定します。修正済みの過去データを反映するのが目的なら、新しい実験として記録します。この区別を学ぶのに数日かかりましたが、今では実行マニフェストの1行に記されています。
← 記事一覧


