取引ボットのバックテストで先読みバイアスを検出する方法
ローソク足の時刻や上位時間足から、Pythonの特徴量、学習・テストの分割、注文約定まで、取引ボットのバックテストへの未来データ混入を実践的に監査します。
先読みバイアスは、シミュレーション上の取引判断を下した時点では利用できなかった情報をバックテストが使うと生じます。検出するには、各入力が利用可能になった時刻、シグナルを計算した時刻、注文が最初に約定できた時刻を書き出します。次に、ローソク足のインデックス、上位時間足の取得、特徴量の作成、モデルの学習、執行シミュレーターを調べます。戦略のコードは一見正しくても、翌日のラベル、当日の未確定の足、すでに過ぎた価格を使って取引している場合があります。
押さえておきたいポイント
- ローソク足のタイムスタンプと、その確定値が分かる時刻を区別します。
- 通常の足確定時に判断するモデルでは、確定した足を使うシグナルを同じ足のそれ以前の時点で約定させることはできません。
- 上位時間足の値、中央寄せの窓、未来の値をシフトした特徴量は、よくある混入経路です。
- 時系列の各分割の中で、学習データを使ってスケーラーを学習し、特徴量を選択します。
- 現実的なウォークフォワードの結果でも、コストや過剰適合によって失敗することがあります。混入の除去は必要ですが、収益性を確立するものではありません。
情報の時系列から始める
各取引について、基となるデータの公開時刻、戦略が読めた時刻、判断を下した時刻、注文が約定できた時刻の四つを記録します。ローソク足のタイムスタンプは、確定した高値、安値、終値が分かった時刻ではなく、始まりを示すことがよくあります。取引所のデータ配信、市場カレンダー、セッションの境界によって、正確に利用可能になる時刻は変わります。
10:00と記された5分足が、10:00から10:05の直前までを含むとします。その確定終値に基づくルールは、10:00には終値を知りません。同じ足が最終的にしきい値を上回って引けたことを理由に、バックテストが10:00の始値で買いを記録するなら、情報の境界を越えています。10:05の終値で約定させる場合も、そのタイミングを実際にサポートするシミュレーターが必要です。標準の足確定時戦略は、成行注文を次に利用可能なティック、通常は次の足の始値で約定させることがよくあります。
これは仮想のタイミング例であり、TensorParadigmの実際の取引や成績ではありません。
監査を行う理由になる兆候
滑らかな資産曲線、ほぼ完璧な転換点でのエントリー、シグナルを一足遅らせると崩れる収益は、詳しく調べるべきです。どれもそれだけでは混入を証明しません。マーケットメイクや一部の短期システムには別の理由で特異な形があり、逆に混入のある戦略でも損失が出ることがあります。
- その足の確定終値が必要なルールなのに、同じ足の高値や安値で取引が記録されている。
- 過去の検証で、1時間足の指標がそれ以前の5分足内で変化する。
- 特徴量が未来の収益率やラベルと驚くほど強く相関している。
- 前処理を学習データだけで別途学習すると、学習・テストの結果が大きく変わる。
- 関連する価格情報や観測が利用可能になる前に、バックテストが約定を記録している。
Pine Scriptの上位時間足の取得を確認する
TradingViewは、上位時間足のrequest.security()にbarmerge.lookahead_onを使い、取得する式にオフセットを設定していない場合、過去の足で上位時間足の確定値が早すぎる時点に見えると説明しています。たとえば、その時間の最初の5分足へ1時間足の確定終値を渡すと、5分足のルールは1時間が終わる前にその終わりを知ってしまいます。
直前に確定した上位時間足だけが必要な戦略に対して、TradingViewが示すリペイントしないパターンは、lookahead_onを使いながら、取得する式を上位時間足一足分ずらすものです。この用途では過去とリアルタイムの利用可能時点がそろいます。このパターンを、下位時間足の取得や形成中の値で意図的に取引するルールへ無差別にコピーしてはいけません。
すべてのrequest.security()の式、時間足、lookahead設定、判断に使う足のインデックスを調べます。計算がチャートの時間足で確定しているかも確認してください。上位時間足の入力を修正しても、非現実的に早い約定を与えるシミュレーターは直りません。
学習前にPythonの特徴量作成を監査する
中央寄せの移動平均は、現在の時刻の前後両側から観測を借ります。特徴量の列で負のシフトを使うと、翌日の値が当日の行へ入る場合があります。どちらも後で予測を採点するためのラベルには使えますが、今日の実運用判断で利用する特徴量には含められません。
decision_time、各特徴量のsource_timeとavailable_time、target_window_end、signal_time、earliest_fill_timeの列を持つ小さな表を作ります。すべての特徴量について、available_timeがsignal_timeより後ではないことを検査します。次の足の収益率などのラベルを作る場合、モデル入力、結合したメタデータ、間接的に答えを明かす前処理には含めないでください。
イベントデータには、実際の公開時刻や取り込み時刻が必要です。市場のタイムスタンプを割り当てた資金調達率、改定済みマクロ経済系列、ニュースでも、その時刻より後に届くことがあります。これらの入力が取引を動かす場合、各時点のスナップショットとデータ改定履歴が重要です。
時系列で分割し、各分割内で前処理を学習する
ランダム分割では、後の市場局面でモデルを学習し、前の観測でテストできます。scikit-learnのTimeSeriesSplitは学習・テストの時系列順を保ち、両者の間に空白期間を設定できます。ラベルの予測期間と観測窓の重複に基づいて空白を選んでください。共通の固定値ではありません。
各分割では、学習データだけを使ってスケーリング、欠損値補完、特徴量選択、ハイパーパラメータ選択を行います。scikit-learnのPipelineは、変換を交差検証の内部に保つのに役立ちます。教師なしのスケーラーでも、サンプル全体で学習すると将来のテスト期間の性質を学びます。最後に未使用の評価期間を残すと、開発中に行った選択の評価に役立ちます。
テストセットを見てから戦略を繰り返し変えることは、各特徴量に未来のデータがなくても、選択バイアスや過剰適合です。実験を記録し、直接的な先読み情報の混入とこの問題を区別してください。
シグナルとシミュレーションの注文約定を照合する
シグナルの時刻と約定の時刻は別です。TradingViewのブローカーエミュレーターは通常、計算ティックで注文を作り、その後の利用可能なティックより前には約定できません。process_orders_on_closeでは足の確定ティックで約定を試みられます。他の足内設定や注文種類も執行モデルを変えるため、一足の差をバグと断定する前に実際の戦略設定を読んでください。
疑わしい各取引について、使用した指標値、足の確定確認、注文作成時刻、注文種類、報告された約定時刻と価格をエクスポートするかログに記録します。その価格に判断後に到達できたかを問います。スプレッド、手数料、スリッページの前提は別途含めてください。これらは混入を直しませんが、修正したバックテストに経済的な価値があるかに影響します。
三つの反証テストを実行する
第一に、すべてのシグナルをチャートの確定足一つ分遅らせ、結果がどれだけ変わるかを測定します。これは診断であり、自動的な修正ではありません。有効な速いシグナルも遅延で弱まることがあります。第二に、時系列の再生で指標を一足ずつ再計算し、各判断時刻で全履歴からの計算と比較します。第三に、各学習分割を独立に作り直し、戦略を固定してから後の期間を確認し、検証を再実行します。
結果が食い違う場合、目立つ利益だけを比較せず、最初に一致しない取引を追跡します。示された時刻には存在し得なかった具体的なデータ項目や注文約定を特定してください。修正後は執行コストと合理的なパラメータの変動を加え、検証を繰り返します。なお成立する結果はより強い証拠ですが、実運用成績の保証ではありません。
疑わしい一取引の具体的な監査
5分足が直前の高値を上回って引けたことを理由に、ボットが10:00の始値で買うとします。予定の約定を10:00の始値、確定終値が最初に分かる時刻を10:05の完了時点とし、戦略の実際の判断時刻と注文時刻を書き出します。条件は予定の約定より後の情報に依存するため、このルールでは記録された取引は無効です。
コードを修正して10:05に判断するようにしたら、執行エンジンが次に実際に何をするかを調べます。次のティックでの成行約定は、次の足の始値近くで起こるかもしれません。確定ティックを使う選択肢は別の前提であり、市場が動いた後に届く実際のアラートとは一致しない場合があります。シグナル時刻だけを移して元のエントリー価格を残してはいけません。
次に、同じ取引の1時間足フィルターを確認します。その時間が終わる前に1時間足の確定終値を読んでいれば、執行時刻を修正しても5分足のエントリーは汚染されたままです。入力の利用可能時点と約定の両方が基準を満たす必要があります。
混入のないバックテストで分かることと分からないこと
未来データの混入を除くことで、信頼できる検証の基本要件が回復します。それでも、市場局面の変化、障害、スプレッドの拡大、資金調達料の支払い、不十分な流動性、リスク制限を戦略が乗り切ることは確立できません。有効なバックテストでも、取引システムとしては悪い場合があります。
検証を再現できるよう、データのバージョン、戦略のコード、コスト、パラメータの選択、分割の境界を保全してください。不具合を見つけたら、変更を説明したうえで、疑わしかった結果と修正後の結果の両方を報告します。透明な修正は、見栄えよく整えた資産曲線より有用です。
確認チェックリスト
- 各特徴量の判断時刻と利用可能時刻を記録します。
- ローソク足のタイムスタンプが始値か終値のどちらを表すか、確定値がいつ届くかを確認します。
- 足確定時のシグナルに、それ以前の足の始値での約定を与えないようにします。
- すべての上位時間足の取得で、過去の検証に未来の値が入っていないか調べます。
- 特徴量のコードで、中央寄せの移動窓や未来へのシフトを探します。
- 未来の目的変数とその代替指標を、実運用の特徴量から除外します。
- 時系列の分割を使い、重複する目的変数には十分な間隔を設けます。
- 学習を伴う前処理のすべてを、各学習分割内で学習します。
- 注文の作成、種類、約定時刻を、利用可能な価格と照合します。
- 順番に再生し、取引コストを含め、後の未使用期間でのテストを保全します。
よくある質問
取引ボットの先読みバイアスとは何ですか?
実際の取引ではまだ利用できなかったデータを、シミュレーションの判断で使うことです。始値の時点で足の確定終値を使う、特徴量に翌日の収益率を入れる、後の上位時間足の終値を早く見せる、といった例があります。
現在の足の終値を使うと必ず先読みバイアスになりますか?
いいえ。足が確定した後に判断するシステムは、確定終値を使えます。問題は、検証が判断や約定をそれより前に行ったと主張する場合や、執行に利用できない終値を仮定する場合に生じます。
request.security()は常にリペイントしますか?
いいえ。リスクは時間足、式、lookahead設定によって変わります。TradingViewは、オフセットを設定していない上位時間足の式でlookahead_onを使うと、過去の検証に未来の値が混入すると警告しています。具体的な取得内容とリアルタイムの挙動を確認してください。
TimeSeriesSplitですべての混入を除去できますか?
いいえ。学習とテストの時系列順は保ちますが、特徴量、公開時刻、重複するラベル、学習した変換、執行は別途確認する必要があります。重複する窓には適切な空白期間が必要な場合があります。
先読みバイアスと過剰適合はどう違いますか?
先読みは、過去の判断に当時知り得なかった情報を与えます。過剰適合は、過去のノイズや繰り返したテストに偶然合うルールを選ぶことです。どちらも結果を膨らませ、同時に起こることもあります。
混入を除いても戦略が利益を出し続けることはありますか?
可能性はありますが、有効な時系列だけでは優位性は示せません。実運用の判断を行う前に、後の未使用期間、妥当な約定とコスト、ドローダウン、取引数、頑健性を評価してください。
参考資料
過去の上位時間足の挙動とシミュレーションの注文タイミングに関するプラットフォームの文書、および時系列検証とデータ混入の防止に関するscikit-learnのガイドを参照しています。取引の時系列と監査手順は、TensorParadigmによる説明用の分析です。
- TradingView Pine Script:他の時間足とデータ
- TradingView Pine Script:戦略と注文執行
- scikit-learn:TimeSeriesSplit
- scikit-learn:よくある落とし穴とデータの混入
教育目的の研究に限ります。この記事は投資助言を提供せず、TensorParadigmの実運用結果を表すものでもありません。