先読みバイアス:1本のバーのミスが純粋なノイズからSharpe 15を作り出す仕組み
「幻想なきバックテスト」シリーズの一篇。
📄 この記事は研究論文に発展しました。 3つの微妙な先読みリーケージを、既知の正解(4,000本のシミュレーション履歴)に対して制御実験にかけています。論文はオンライン版(インタラクティブ版+PDF)をlookahead.marketmaker.ccで、コードとデータはgithub.com/suenot/lookahead-inflationで公開しています。
数週間前、私たちのパラメータ探索ベンチマークは私たちに嘘をついていました。しかも、危うく気づかないところでした。
エンジンは一見クリーンに見えました。確定バーのロジック、誠実なローリング・ウォークフォワード分割、パラメータ空間に対するSobol/QMC探索、ホールドアウトのテストウィンドウ。探索はインサンプルで良好に見える設定を見つけました。唯一の問題は、アウトオブサンプルではほぼすべてがマイナスだったことです。私たちは単純に戦略が弱いのだと考えました。
そして、あるコード1行を見つけました。シグナルはバーiの終値で決定されているのに、約定は同じバーiで計上されていたのです。本来は次のバーの始値であるべきところが。実行インデックスの1つのオフバイワン。私たちは約定をopen[i+1]——バーiの終値を見た後に実際に取引できる唯一の価格——に移しました。すると、アウトオブサンプルの結果は符号が反転しました。Sobol探索は損失から利益へと変わったのです。戦略については何も変わっていません。私たちはただ、過去で取引するのをやめただけでした。
それが先読みバイアスであり、不安になるのはミスがいかに小さく、結果がいかに大きいかということです。この記事は制御された自己監査です。ground truth(正解)が構成によって既知であるシミュレーターを構築し、微妙なリーケージを1つずつ注入し、それぞれがバックテストをどれだけ水増しするかを正確に測定します。結論を先に言うと、本物のエッジが全くない状態でも、同一バー約定は純粋なノイズから年率Sharpe +14.8を作り出します。
先読みバイアスとは実際には何か

先読みバイアスとは、パイプライン中のどこかで、判断や測定が、その時点でリアルタイムには利用できなかったはずの情報を使っている状態を指します。教科書的な例は粗雑です——1月の時点で年間を通した通期決算を使う、まだ公表されていない修正後の数値を使う、といった類です。これらは見つけやすいものです。コードレビューを生き延びるものは微妙で、3つの場所に潜んでいます。
- 執行(Execution)——バー
iで判断し、バーiで約定する(あるいは、シグナルを生成したまさにそのバーの高値/安値をストップに使う)。トリガーとなった要因と相関する価格で取引してしまうということです。 - 正規化(Normalization)——特徴量をz-score化、min-max化、あるいはその他の方法でスケーリングする際に、系列全体、つまり未来を含む統計量を使ってしまう。スケーラーがテストセットを「知って」いる状態です。
- インジケーター/特徴量(Indicators / features)——中心化された(あるいは他の形で先を覗く)ウィンドウで平滑化やフィルタリングを行うため、バー
iの値にすでにバーi+1の断片が含まれてしまう。
これら3つはすべて、機械学習の文献が**リーケージ(leakage)**と呼ぶもの——学習/評価データが対象の未来の情報で汚染される現象——の一形態です(Kaufman et al., 2012; Kapoor & Narayanan, 2023)。金融分野での定番の教科書はLópez de PradoのAdvances in Financial Machine Learning(2018)——パージド交差検証、エンバーゴ、バックテストの危険性について扱っています。ポイントインタイムの規律は、少なくともFama & French(1992)まで遡ります。彼らは、その変数が説明対象のリターンより前に既知であるように、会計データを意図的に6ヶ月ラグさせました。
この記事が答える問いは定量的なものです。「リーケージは悪いか」(誰もが同意する)ではなく、「各形態のリーケージが何Sharpeポイントを買い取るのか、どれが危険なのか」です。数字がなければ、それについて推論することはできません。+0.3の水増しがノイズなのか、+14の水増しが決定的な証拠なのかを判別できないのです。
正解が既知のシミュレーター

水増しを測定するには真実を知る必要があります。実データは決して真実を教えてくれません——1つの実現値を与えるだけで、オラクルは存在しません。そこで私たちは自分たちでエッジを設定できる合成マーケットを構築しました。
データ生成プロセスは厳密に因果的(causal)で、爆発しません。
ここでは外生的な永続潜在ドリフト(のAR(1)過程)であり、バーのリターンは1バー前から既知である小さなドリフトを持ちます。は過去のリターンに依存しないため、フィードバックはなく、何も爆発しません。パラメータは、どれだけ本物のエッジが存在するかを決めるダイヤルです。
- ——ヌル:エッジは一切ない。プラスのバックテストSharpeがあれば、それは100%人工物です。
- ——本物の、取引可能なエッジ:誠実なモメンタムルールが実際に利益を生みます。
戦略は意図的にシンプルにしています——モメンタムのサイン則です。特徴量はトレイリング本のリターンの合計(本)で、ポジションはその符号です。
csum = np.concatenate(([0.0], np.cumsum(r))) # csum[k] = sum r[0..k-1]
mom = np.full(n, np.nan)
tt = np.arange(L - 1, n)
mom[tt] = csum[tt + 1] - csum[tt - L + 1]
signal = np.sign(mom) # position for the next bar
このモメンタム特徴量は、同一バー・リーケージを研究するのに最適な題材です。なぜなら、実際のインジケーターが共通して持つ性質を持っているからです。機械的に現在のバーを含んでいるのです。mom[t]はr[t]を含みます。つまり、r[t]を自分のトレードとして計上すれば、すでに自分自身のシグナルの中に含まれている量に部分的に賭けていることになります。これがリーケージの正体を具体化したものです。
セットアップ:(1バーあたり1%のボラティリティ)、片道手数料0.00045(往復0.09%、私たちのエンジンに合わせています)、Sharpeは(1時間バー)で年率化、4,000本の独立した履歴、各4,000バー。すべてシード固定・決定的です。
誠実なパイプライン(唯一の取引可能なもの)
バーtの終値で判断し、次のバーのリターンを稼ぎ、ポジション変更時に手数料を支払います。
def sharpe(sig, ret_booked):
dpos = np.abs(np.diff(np.concatenate(([0.0], sig))))
pnl = sig * ret_booked - FEE_ONEWAY * dpos
return pnl.mean() / pnl.std() * np.sqrt(8760)
honest = sharpe(signal[idx], r[idx + 1]) # earn r[t+1]: tradable
3つのリーケージ、それぞれ1箇所の外科的な変更
same_bar = sharpe(signal[idx], r[idx])
z_full = (mom - mom[valid].mean()) / mom[valid].std()
norm_full = sharpe(np.sign(z_full[idx]), r[idx + 1])
z_sm = (mom[:-2] + mom[1:-1] + mom[2:]) / 3.0 # uses t-1, t, t+1
indicator = sharpe(np.sign(z_sm[idx]), r[idx + 1])
それぞれのリーケージは、誠実なパイプラインから1行変えるだけで発生します。これこそが要点です。これらはエキゾチックなミスではなく、コードレビューを通過してしまう類のものなのです。
結果:各リーケージの規模

4,000個のシードにわたって実行した結果、各パイプラインが報告する年率Sharpeは、ヌル(エッジなし)と本物のエッジ(、誠実なSharpeが説得力のある+1.57になるよう調整)の下でそれぞれ以下の通りです。
| パイプライン | ヌル(エッジなし) | リアルエッジ |
|---|---|---|
| 誠実(正解) | −0.74 | +1.57 |
| 同一バー約定 | +14.79 | +15.85 |
| インジケーター先読み(1バー) | +4.76 | +6.62 |
| 全期間正規化 | −0.84 | +1.46 |
シード間の95%信頼区間はどのセルでも±0.05以内。効果が本物であるところでは、水増し量に対するペアt検定は天文学的に有意です(t > 400, p ≈ 0)。
まずヌル列を見てください。これは最もクリーンな実験です。エッジはないので、誠実なパイプラインは正しく損失を出します(−0.74、これはノイズを取引するために手数料を払う分の目減りです)。では、その同じ「何もない」状態にリーケージが何をするか見てみましょう。
- 同一バー約定:−0.74 → +14.79。 予測力ゼロで、ランダムなノイズを取引している戦略が、年率Sharpe約15を報告します。これは微妙なバイアスではなく、捏造です。メカニズムはまさに私たちが組み込んだ通りです。モメンタム特徴量が
r[t]を含んでいるため、r[t]を計上することは自分自身のシグナルに賭けているに等しいのです。 - インジケーター先読み:−0.74 → +4.76。 平滑化フィルターに1バー先を見せるだけで、ノイズから約5のSharpeが捏造されます。バー
tでの平滑化された値が、これから稼ぐr[t+1]と相関してしまうためです。 - 全期間正規化:−0.74 → −0.84。 実質的に水増しなし。 これが誠実で、非自明な発見です(詳しくは後述します)。
エッジ列はより厄介なメッセージを伝えます。本物のエッジが実在する場合(誠実な値は+1.57)、リーケージは単に定数を加えるだけでなく、測定されるSharpeを+15.85や+6.62まで押し上げます——実際に取引できる+1.57をはるかに超えて。つまり、測定された数値はスキルとリーケージを区別できないのです。リークした+6と誠実な+6はレポート上まったく同じに見えます。どちらだったのかがわかるのは、資本を投入した後です。
リーケージはスイッチではなく勾配である

自然な反論として、「シグナルバー全体を計上するのは極端で非現実的なミスだ」というものがあります。そこで私たちは用量——リーケージが捕捉するシグナルバーの割合——を0(誠実)から1(完全な同一バー)まで掃引しました。
| 捕捉割合 | ヌルSharpe | エッジSharpe |
|---|---|---|
| 0.00(誠実) | −0.74 | +1.57 |
| 0.25 | +3.90 | +6.41 |
| 0.50 | +9.86 | +12.20 |
| 1.00(完全なリーケージ) | +14.79 | +15.85 |
シグナルバーのわずか4分の1を捕捉するだけで、エッジなし戦略は−0.74から**+3.90**へと変化します。騙されるために完全なオフバイワンは必要ありません。少しだけ有利すぎる約定——シグナルバーに対する楽観的すぎるスリッページ、トリガーとなったバー自体でチェックされるイントラバーのストップ——だけで、大半の「実運用可能」閾値を超えるには十分なのです。水増しは、現在をどれだけ取引させてしまうかに対して滑らかで単調です。
損失を出す戦略が本番投入される頻度は?
実務家が懸念すべき数字は誤デプロイ率です。つまり、本当は損失を出す設定が、あなたがそれを本番投入するために使う基準をリーケージによってクリアしてしまう頻度です。デプロイ基準として「年率Sharpe ≥ 1.0」を用いると、ヌルの下では:
- 同一バー約定:エッジなし戦略の68%が実運用可能に見え、かつ実際には損失を出す。 純粋なノイズの設定の3つに2つが、Sharpe≥1のゲートを通過し、実運用で損失を出すことになります。(このリーケージは純粋に執行にあるため、誠実な対応物は同じシグナルに誠実な約定を組み合わせたものであり、この比率はここできれいに定義できます。)
- インジケーター先読み:エッジなしの設定のほぼすべてをデプロイ基準の上に押し上げます(99.9%がSharpe ≥ 1をクリア)——ノイズをそのまま本番に手招きしてしまうでしょう。
- 全期間正規化:12%が基準をクリア——これは実質的にノイズのベースレートであり、リーケージによる上乗せはありません。
分類法、そして各リーケージの検出方法
3つのリーケージは同じ程度に危険なわけではなく、その違いは示唆に富んでいます。
1. 執行リーケージ(高くつくもの)

症状: 約定価格がシグナルと相関している。両者が同じバー由来だからです。規模: 巨大(フルドーズでノイズから+15、4分の1ドーズで+3.9)。なぜ最悪なのか: あなたのシグナルは、ほぼ定義上、直近の値動きから構築されています。したがって、シグナルバーのリターンは、あなたの特徴量が最も相関する対象そのものです。それを計上することは、答えを見てしまうことにほぼ等しいのです。
検出——1バーシフトテスト。 これはこの記事で最も価値のある診断法です。バックテストを取り、すべての約定を1バー後ろにずらしてください(iで判断し、open[i+1]で約定する)。結果がほとんど動かないなら、あなたの執行は誠実です。結果が崩壊するか符号が反転するなら、あなたは過去で取引していたことになります。これはまさに私たちのSobol探索に起きたことです。約定をシフトすると、「利益の出る」OOSが実は損失だったことが判明しました——というより、リーケージが取り除かれたことで本物の関係が表に出てきたのです。
entry_price = open_[i + 1] # NOT close[i], NOT open[i]
2. インジケーター/特徴量リーケージ(静かなもの)
症状: バーiのインジケーターがi+1以降のデータに依存している——中心化された移動平均、因果的な遅延を持たないフィルター、確定に未来のバーを要するピーク/トラフのラベル、未来のローソク足を入力とするHeikin-Ashi風の変換など。規模: 大きい(ノイズから+4.8)。なぜ隠れやすいのか: リーケージはライブラリ呼び出しの内部に埋もれています。scipy.signal.filtfiltはゼロ位相であり——ゼロ位相ということは非因果的であることを意味します。「このバーは局所最大値である」という特徴量は、次のバーが確定するまで知りようがありません。
検出: すべてのインジケーターについて、読み込む最大のインデックスは何かを問うてください。tでの値を計算する際にt+1に触れることが一度でもあれば、それは非因果的です。インジケーターを拡張/ローリングの因果的ウィンドウで計算し、バーtでの値が、配列にtより後のバーが存在しようがしまいが同一であることを検証してください。(私たちのHMA/ADX実装はこのテストに合格します。tでのすべての出力は≤ tの入力のみを読みます。)
3. 正規化リーケージ(チャネル依存のもの)
症状: スケーラー(StandardScaler、min-max、全体z-score)がテストセットを含むデータセット全体でフィットされている。機械学習の定番の警告はこの点について明確です——Hastie, Tibshirani & FriedmanのElements of Statistical Learning §7.10.2(「交差検証の正しいやり方と間違ったやり方」)や、scikit-learn自身のcommon-pitfallsガイド:「平均は訓練サブセットの平均であるべきで、全データの平均であってはならない」。
私たちのテストでの規模: ほぼゼロ(−0.74 → −0.84)。これは意外で、誠実な結果であり、暗記するのではなく理解する価値があります。
なぜ水増しされなかったのでしょうか。私たちの戦略が特徴量を符号(ゼロ閾値)を通じてしか使わないからです。標準偏差によるスケーリングは符号を決して変えず、全体平均による中心化もゼロ交差をわずかにずらすだけです。したがって、純粋なサイン則に対する全期間標準化はほぼ無害です。
これを過度に一般化しないでください。 正規化リーケージはチャネル依存です。あなたの戦略が特徴量の大きさを使い始めた瞬間——z-scoreに比例したポジションサイジング、スケーリングされた分布を見て選んだゼロでないエントリー閾値、標準化された入力を受け取るニューラルネットなど——未来を知ったスケーラーが重要になり始め、全体統計量が因果的な統計量からかけ離れているほど、それはより重要になります。私たちの結果は「正規化リーケージは安全だ」ということではありません。「リーケージの規模は、リークした量が判断に入り込むチャネルに依存する。だから仮定するのではなく測定すべきだ」ということです。サイン則は、この特定のリーケージが安く済む1つのケースにすぎません。
この記事のつながり
先読みバイアスは、本シリーズがこれまで記録してきた連鎖の最初の環です。
- それは検証への入力を汚染します。リークしたバックテストはウォークフォワード分割を無傷で通過し、オーバーフィットしたピークではなく広いプラトーのように見えるでしょう——リーケージはフォールドをまたいで一貫しているため、交差検証ではそれを捕捉できません。リーケージはオーバーフィッティングよりも上流の失敗モードであり、下流でどれだけ誠実な検証を行っても救われません。
- それはパラメータ探索と相互作用します。リークしたデータに対して何千回もの試行で探索を行うと、そのリーケージを最も積極的に悪用する設定が見つかります。「勝者」は最悪の犯人なのです。
- それはバックテストとライブの乖離が生じる理由でもあります。リーケージは、バックテストとボットの間に30〜50%のギャップが生じる最もクリーンな説明です。なぜなら、ライブ取引は仕組み上、覗き見ができない唯一の場所だからです。
これらすべてを捕捉する規律は、学術文献が長年訴えてきたものと同じです。バックテストを、厳格な情報境界を持つ統計的実験として扱うことです。Bailey, Borwein, López de Prado & Zhuは、オーバーフィッティングがいかに容易に偽のパフォーマンスを生み出すかを示し(2014)、Arnott, Harvey & Markowitzのバックテストプロトコル(2019)はその衛生管理を成文化しています。先読みバイアスは、あらゆる境界の中で最も基本的なもの——時間における境界——であり、うっかり違反してしまうのに最も安上がりなものです。
要点

- 先読みバイアスは定量的には巨大で、質的には見えない。 たった1つの1バー執行エラーが、Sharpe −0.74(純粋なノイズ、正しく損失中)を+14.79に変えました。ミスはたった1行、結果は捏造されたトラックレコードです。
- それは勾配である。 シグナルバーのわずか25%を捕捉するだけで、何もないところから+3.90が得られます。露骨なバグは必要ありません——約定にちょっとした楽観が過ぎるだけで十分です。
- 測定された数値はスキルとリーケージを区別できない。 本物のエッジが存在するとき、リーケージは取引可能な真実をはるかに超えてレポートを水増しします。唯一の防御策は指標ではなくプロセスです。
- 1バーシフトテストが最速の診断法である。 すべての約定を1バー後ろにずらしてください。パフォーマンスが崩壊するなら、あなたは過去で取引していたのです。
- リーケージの規模はチャネル依存である。 執行とインジケーターの先読みは壊滅的ですが、サイン則に対する全期間正規化はほぼタダです。リーケージが実際に入り込むチャネルを通じて測定してください——仮定してはいけません。
3つのリーケージすべて、用量掃引、誤デプロイ分析、形式的な手法、そして単一の決定的なスクリプトから再現可能なすべての数値を含む完全な制御研究は、姉妹論文lookahead.marketmaker.ccに、コードとデータはgithub.com/suenot/lookahead-inflationにあります。
私たちのヌル実験の戦略には、エッジが全くありませんでした。それでもSharpe 15を示したのです。もしあなたのバックテストが出来すぎているように見えたら、最初に疑うべきはあなたの才能ではありません——あなたの時計です。
Authors
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.