Look-Ahead Bias: Bagaimana Kesilapan Satu-Bar Menghasilkan Sharpe 15 daripada Bunyi Hingar Tulen
Sebahagian daripada siri "Backtest Tanpa Ilusi".
📄 Artikel ini berkembang menjadi kertas penyelidikan. Tiga kebocoran look-ahead yang halus diuji secara terkawal berbanding ground truth yang diketahui (4,000 sejarah simulasi). Baca kertas kerja dalam talian (versi interaktif + PDF) di lookahead.marketmaker.cc, kod dan data di github.com/suenot/lookahead-inflation.
Beberapa minggu lalu, penanda aras carian parameter kami sedang menipu kami, dan kami hampir tidak menyedarinya.
Enjin itu kelihatan bersih. Logik closed-bar, pembahagian rolling walk-forward yang jujur, carian Sobol/QMC merentasi ruang parameter, tetingkap ujian yang disisihkan. Carian itu menemui konfigurasi yang kelihatan baik in-sample. Satu-satunya masalah: out-of-sample, hampir semuanya negatif. Kami menganggap strategi itu semata-mata lemah.
Kemudian kami menemui satu baris. Isyarat itu ditentukan pada penutupan bar i, tetapi fill dicatat pada bar i yang sama dan bukannya pembukaan bar seterusnya. Satu kesilapan off-by-one dalam indeks pelaksanaan. Kami mengalihkan fill ke open[i+1] — satu-satunya harga yang boleh anda benar-benar urus niaga selepas melihat penutupan bar-i — dan keputusan out-of-sample bertukar tanda. Carian Sobol beralih daripada kerugian kepada keuntungan. Tiada apa-apa tentang strategi itu berubah. Kami hanya berhenti berdagang pada masa lampau.
Itulah look-ahead bias, dan bahagian yang meresahkan ialah betapa kecilnya kesilapan itu dan betapa besarnya akibatnya. Artikel ini adalah audit-kendiri terkawal: kami membina simulator di mana ground truth diketahui secara reka bentuk, menyuntik kebocoran halus itu satu demi satu, dan mengukur dengan tepat berapa banyak setiap satu menggembungkan backtest. Berita utama: dengan tiada kelebihan sebenar langsung, satu fill same-bar menghasilkan Sharpe tahunan +14.8 daripada bunyi hingar tulen.
Apa Sebenarnya Look-Ahead Bias

Look-ahead bias ialah mana-mana titik dalam pipeline anda di mana satu keputusan atau pengukuran menggunakan maklumat yang tidak akan tersedia, secara masa nyata, pada saat ia digunakan. Contoh buku teks adalah kasar — menggunakan pendapatan tahun penuh sesuatu saham pada bulan Januari, atau penyata semula yang belum diterbitkan lagi. Itu mudah dikesan. Yang terlepas daripada semakan kod adalah halus, dan ia bersembunyi di tiga tempat:
- Pelaksanaan — anda membuat keputusan pada bar
idan fill pada bari(atau menggunakan high/low bariuntuk stop pada bar yang sama yang menghasilkan isyarat itu). Anda bertransaksi pada harga yang berkorelasi dengan perkara yang mencetuskan anda. - Normalisasi — anda men-z-score, min-max, atau menskalakan ciri dengan cara lain menggunakan statistik yang dikira merentasi keseluruhan siri, termasuk masa hadapan. Scaler itu "mengetahui" set ujian.
- Penunjuk / ciri — anda melicinkan atau menapis dengan tetingkap yang berpusat (atau dengan cara lain mengintip ke hadapan), jadi nilai pada bar
isudah mengandungi serpihan bari+1.
Ketiga-tiganya adalah bentuk apa yang dipanggil literatur pembelajaran mesin sebagai leakage (kebocoran): pencemaran latihan/penilaian dengan maklumat daripada masa hadapan sasaran (Kaufman et al., 2012; Kapoor & Narayanan, 2023). Dalam kewangan, rawatan kanonik ialah Advances in Financial Machine Learning (2018) oleh López de Prado — purged cross-validation, embargo, bahaya backtesting. Disiplin point-in-time boleh dikesan sekurang-kurangnya hingga Fama & French (1992), yang sengaja melengahkan data perakaunan selama enam bulan supaya pemboleh ubah itu diketahui sebelum pulangan yang diterangkannya.
Soalan yang dijawab oleh artikel ini adalah kuantitatif: bukan "adakah leakage itu buruk" (semua orang bersetuju) tetapi "berapa banyak mata Sharpe yang diperoleh oleh setiap bentuk, dan yang mana satu berbahaya?" Tanpa satu nombor, anda tidak boleh menaakul tentangnya. Anda tidak boleh membezakan sama ada penggembungan +0.3 itu bunyi hingar atau penggembungan +14 itu bukti jelas.
Simulator dengan Ground Truth yang Diketahui

Untuk mengukur penggembungan, anda perlu mengetahui kebenarannya. Data sebenar tidak pernah memberitahu anda kebenaran — ia memberi anda satu realisasi dan tiada oracle. Jadi kami membina pasaran sintetik di mana kami menetapkan kelebihan itu.
Proses penjanaan data adalah kausal secara ketat dan tidak eksplosif:
Di sini ialah drift laten berterusan yang eksogen (satu AR(1) dengan ), dan pulangan bar mempunyai drift kecil yang diketahui satu bar lebih awal. Oleh kerana tidak bergantung pada pulangan lalu, tiada maklum balas dan tiada apa yang meletup. Parameter ialah penala untuk berapa banyak kelebihan sebenar yang wujud:
- — null: tiada kelebihan langsung. Sebarang Sharpe backtest positif adalah 100% artifak.
- — kelebihan sebenar yang boleh didagangkan: peraturan momentum yang jujur benar-benar menghasilkan wang.
Strategi itu sengaja dibuat ringkas — peraturan tanda momentum. Ciri itu ialah jumlah pulangan trailing- ( bar), dan kedudukan itu ialah tandanya:
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
Ciri momentum ini adalah wahana yang sempurna untuk mengkaji kebocoran same-bar, kerana ia mempunyai satu sifat yang dikongsi oleh penunjuk sebenar: ia secara mekanikal mengandungi bar semasa. mom[t] merangkumi r[t]. Jadi jika anda mencatat r[t] sebagai dagangan anda, anda sebahagiannya bertaruh pada kuantiti yang sudah berada di dalam isyarat anda sendiri. Itulah kebocoran itu, dibuat konkrit.
Persediaan: (volatiliti 1% setiap bar), yuran sehala 0.00045 (round-trip 0.09%, sepadan dengan enjin kami), Sharpe ditahunkan dengan (bar setiap jam), 4,000 sejarah bebas dengan 4,000 bar setiap satu. Semuanya diseed dan deterministik.
Pipeline yang Jujur (Satu-Satunya yang Boleh Didagangkan)
Buat keputusan pada penutupan bar t, perolehi pulangan bar seterusnya, bayar yuran pada perubahan kedudukan:
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
Tiga Kebocoran, Setiap Satu Perubahan Bedah Tunggal
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])
Setiap kebocoran hanya satu baris jauh daripada pipeline yang jujur. Itulah keseluruhan intinya: ini bukan kesilapan eksotik, ia adalah jenis perkara yang lulus semakan.
Keputusan: Magnitud Setiap Kebocoran

Dijalankan merentasi 4,000 seed, berikut adalah Sharpe tahunan yang dilaporkan oleh setiap pipeline, di bawah null (tiada kelebihan) dan di bawah kelebihan sebenar (, ditala supaya Sharpe yang jujur adalah +1.57 yang boleh dipercayai):
| Pipeline | Null (tiada kelebihan) | Kelebihan sebenar |
|---|---|---|
| Jujur (kebenaran) | −0.74 | +1.57 |
| Fill same-bar | +14.79 | +15.85 |
| Tinjauan penunjuk (1 bar) | +4.76 | +6.62 |
| Normalisasi keseluruhan-siri | −0.84 | +1.46 |
Selang keyakinan 95% merentasi seed adalah ±0.05 atau lebih ketat pada setiap sel; ujian-t berpasangan pada penggembungan adalah signifikan secara astronomik apabila kesannya nyata (t > 400, p ≈ 0).
Baca lajur null dahulu, kerana ia adalah eksperimen paling bersih yang mungkin: tiada kelebihan, jadi pipeline yang jujur betul-betul rugi wang (−0.74, seretan akibat membayar yuran untuk berdagang bunyi hingar). Sekarang lihat apa yang dilakukan oleh kebocoran itu kepada ketiadaan yang sama itu:
- Fill same-bar: −0.74 → +14.79. Strategi dengan kuasa ramalan sifar, berdagang bunyi hingar rawak, melaporkan Sharpe tahunan hampir 15. Ini bukan bias yang halus; ini adalah rekaan. Mekanismenya tepat seperti yang kami bina: ciri momentum mengandungi
r[t], jadi mencatatr[t]adalah bertaruh pada isyarat anda sendiri. - Tinjauan penunjuk: −0.74 → +4.76. Membenarkan pelicin melihat satu bar ke hadapan menghasilkan Sharpe hampir 5 daripada bunyi hingar, kerana nilai yang dilicinkan pada
tkini berkorelasi denganr[t+1]yang akan anda perolehi sebentar lagi. - Normalisasi keseluruhan-siri: −0.74 → −0.84. Pada dasarnya tiada penggembungan. Ini adalah dapatan yang jujur dan tidak jelas (lebih lanjut di bawah).
Lajur kelebihan menyampaikan mesej yang lebih berbahaya. Apabila kelebihan sebenar memang wujud (jujur +1.57), kebocoran itu bukan sekadar menambah pemalar — ia menolak Sharpe yang diukur kepada +15.85 dan +6.62, jauh melebihi +1.57 yang boleh anda benar-benar dagangkan. Jadi nombor yang diukur itu tidak dapat membezakan kemahiran daripada kebocoran. +6 yang bocor dan +6 yang jujur kelihatan sama pada laporan. Anda hanya akan mengetahui yang mana satu anda ada selepas anda melancarkan modal.
Kebocoran itu Kecerunan, Bukan Suis

Satu bantahan yang wajar: "mencatat keseluruhan bar isyarat adalah kesilapan yang ekstrem dan tidak realistik." Jadi kami mengimbas dos itu — pecahan daripada bar isyarat yang ditangkap oleh kebocoran, daripada 0 (jujur) hingga 1 (same-bar penuh):
| Pecahan tangkapan | Sharpe Null | Sharpe Kelebihan |
|---|---|---|
| 0.00 (jujur) | −0.74 | +1.57 |
| 0.25 | +3.90 | +6.41 |
| 0.50 | +9.86 | +12.20 |
| 1.00 (kebocoran penuh) | +14.79 | +15.85 |
Menangkap hanya satu perempat daripada bar isyarat membawa strategi tanpa kelebihan daripada −0.74 kepada +3.90. Anda tidak memerlukan off-by-one penuh untuk tertipu; satu fill yang sedikit terlalu menguntungkan — sedikit slippage optimistik pada bar isyarat, satu stop intrabar yang diperiksa berbanding bar yang mencetuskannya — sudah cukup untuk melepasi kebanyakan ambang "boleh-dilancarkan". Penggembungan itu licin dan monoton mengikut berapa banyak masa kini yang anda benarkan diri anda dagangkan.
Berapa Kerap Ini Meletakkan Strategi Rugi ke dalam Pengeluaran?
Nombor yang sepatutnya merisaukan pengamal ialah kadar pelancaran palsu: berapa kerap satu kebocoran menyebabkan konfigurasi yang benar-benar rugi wang melepasi bar yang akan anda gunakan untuk memberi lampu hijau kepadanya. Menggunakan "Sharpe tahunan ≥ 1.0" sebagai kriteria pelancaran, di bawah null:
- Fill same-bar: 68% strategi tanpa kelebihan kelihatan boleh-dilancarkan dan benar-benar rugi wang. Dua daripada tiga konfigurasi bunyi hingar tulen akan lulus pintu pagar Sharpe-≥-1 dan rugi wang secara langsung. (Kadar ini ditakrifkan dengan bersih di sini kerana kebocoran itu semata-mata dalam pelaksanaan — rakan jujurnya ialah isyarat yang sama dengan fill yang jujur.)
- Tinjauan penunjuk: ia menolak pada dasarnya setiap konfigurasi tanpa kelebihan melepasi bar pelancaran juga (99.9% melepasi Sharpe ≥ 1) — ia akan melambaikan bunyi hingar terus ke dalam pengeluaran.
- Normalisasi keseluruhan-siri: 12% melepasi bar itu — pada dasarnya kadar asas bunyi hingar, tiada premium kebocoran.
Taksonomi, dan Cara Mengesan Setiap Satu
Ketiga-tiga kebocoran itu tidak sama bahayanya, dan perbezaannya memberi pengajaran.
1. Kebocoran Pelaksanaan (yang Mahal)

Gejala: harga fill berkorelasi dengan isyarat kerana kedua-duanya datang daripada bar yang sama. Magnitud: amat besar (+15 daripada bunyi hingar pada dos penuh, +3.9 pada dos suku). Mengapa ia paling teruk: isyarat anda, hampir mengikut definisi, dibina daripada pergerakan harga terkini, jadi pulangan bar isyarat itu adalah tepat perkara yang paling berkorelasi dengan ciri anda. Mencatatnya hampir sama seperti melihat jawapan terus.
Pengesanan — ujian anjakan satu-bar. Ini adalah diagnostik paling bernilai dalam artikel ini. Ambil backtest anda dan anjakkan setiap fill satu bar kemudian (buat keputusan pada i, fill pada open[i+1]). Jika keputusan hampir tidak berganjak, pelaksanaan anda jujur. Jika keputusan itu runtuh atau bertukar tanda, anda sedang berdagang pada masa lampau. Inilah tepatnya yang berlaku pada carian Sobol kami: anjakkan fill itu, dan OOS yang "menguntungkan" ternyata rugi — atau lebih tepat, hubungan yang sebenar terserlah sebaik sahaja kebocoran itu dibuang.
entry_price = open_[i + 1] # NOT close[i], NOT open[i]
2. Kebocoran Penunjuk / Ciri (yang Senyap)
Gejala: penunjuk pada bar i bergantung pada data daripada i+1 atau lebih lewat — purata bergerak yang berpusat, penapis tanpa lengah kausal, label puncak/lembah yang memerlukan bar masa hadapan untuk mengesahkannya, transformasi gaya Heikin-Ashi yang disuap dengan lilin masa hadapan. Magnitud: besar (+4.8 daripada bunyi hingar). Mengapa ia bersembunyi: kebocoran itu terkubur di dalam panggilan pustaka. scipy.signal.filtfilt adalah zero-phase — dan zero-phase bermaksud tidak kausal. Ciri "bar ini adalah maksimum tempatan" tidak dapat diketahui sehingga bar seterusnya tercetak.
Pengesanan: untuk setiap penunjuk, tanya apakah indeks tertinggi yang ia baca? Jika pengiraan nilai pada t pernah menyentuh t+1, ia tidak kausal. Kira penunjuk pada tetingkap kausal yang berkembang/bergerak dan sahkan bahawa nilai pada bar t adalah sama sama ada bar selepas t wujud dalam tatasusunan itu atau tidak. (Pelaksanaan HMA/ADX kami lulus ujian ini: setiap output pada t hanya membaca input pada ≤ t.)
3. Kebocoran Normalisasi (yang Khusus-Saluran)
Gejala: satu scaler (StandardScaler, min-max, z-score global) di-fit pada keseluruhan dataset, termasuk set ujian. Amaran ML kanonik adalah jelas tentang ini — Elements of Statistical Learning §7.10.2 oleh Hastie, Tibshirani & Friedman ("the wrong and right way to do cross-validation"), dan panduan common-pitfalls scikit-learn sendiri: "the average should be the average of the train subset, not the average of all the data."
Magnitud dalam ujian kami: ≈ sifar (−0.74 → −0.84). Ini adalah dapatan yang mengejutkan dan jujur, dan ia berbaloi untuk difahami dan bukan sekadar dihafal.
Mengapa ia tidak menggembung? Kerana strategi kami hanya menggunakan ciri itu melalui tandanya (ambang sifar). Penskalaan sisihan piawai tidak pernah mengubah tanda, dan pemusatan-min global hanya menganjak sedikit titik lintasan sifar itu. Jadi penyeragaman keseluruhan-siri bagi peraturan tanda tulen adalah hampir tidak berbahaya.
Jangan terlalu menggeneralisasikan ini. Kebocoran normalisasi adalah khusus-saluran. Sebaik sahaja strategi anda menggunakan magnitud ciri itu — saiz kedudukan berkadar dengan z-score, ambang kemasukan bukan-sifar yang dipilih dengan melihat taburan yang diskalakan, rangkaian neural yang menggunakan input yang diseragamkan — scaler yang sedar-masa-hadapan itu mula memberi kesan, dan ia memberi kesan lebih besar semakin statistik global berbeza daripada statistik kausal. Dapatan kami bukanlah "kebocoran normalisasi itu selamat." Ia adalah "magnitud kebocoran bergantung pada saluran yang dilalui oleh kuantiti bocor itu untuk memasuki keputusan, dan anda perlu mengukurnya dan bukan menganggapnya." Peraturan tanda adalah satu-satunya kes di mana kebocoran khusus ini murah.
Di Mana Ini Berkait
Look-ahead bias adalah rangkaian pertama dalam satu rantai yang telah didokumenkan oleh siri ini:
- Ia mencemari input kepada pengesahan. Backtest yang bocor akan melalui pembahagian walk-forward dengan mudah dan kelihatan seperti dataran luas dan bukannya puncak overfit — kebocoran itu konsisten merentasi fold, jadi cross-validation tidak dapat menangkapnya. Kebocoran adalah mod kegagalan huluan daripada overfitting, dan tiada berapa banyak pun pengesahan jujur di hilir yang dapat menyelamatkan anda.
- Ia berinteraksi dengan carian parameter: satu carian merentasi beribu-ribu percubaan pada data yang bocor akan menemui konfigurasi yang mengeksploitasi kebocoran itu paling agresif. "Pemenang" itu adalah pesalah yang paling teruk.
- Inilah sebabnya parity backtest-langsung menyimpang. Kebocoran adalah penjelasan paling bersih untuk jurang 30–50% antara backtest dan bot, kerana dagangan langsung, secara mekanikal, adalah satu-satunya tempat di mana anda tidak boleh mengintip.
Disiplin yang menangkap kesemua ini adalah sama seperti yang telah lama didesak oleh literatur akademik: layan backtest sebagai satu eksperimen statistik dengan sempadan maklumat yang ketat. Bailey, Borwein, López de Prado & Zhu menunjukkan betapa mudahnya overfitting menghasilkan prestasi palsu (2014); protokol backtesting oleh Arnott, Harvey & Markowitz (2019) mengkodkan kebersihan ini. Look-ahead bias adalah sempadan paling asas daripada semuanya — sempadan dalam masa — dan paling murah untuk dilanggar secara tidak sengaja.
Kesimpulan Utama

- Look-ahead bias secara kuantitatif adalah besar dan secara kualitatif tidak kelihatan. Satu kesilapan pelaksanaan satu-bar tunggal menukar Sharpe daripada −0.74 (bunyi hingar tulen, betul-betul rugi) kepada +14.79. Kesilapan itu satu baris; akibatnya adalah rekod prestasi rekaan.
- Ia adalah kecerunan. Menangkap walaupun 25% daripada bar isyarat menghasilkan +3.90 daripada tiada apa-apa. Anda tidak memerlukan pepijat yang jelas — sedikit terlalu banyak optimisme dalam fill anda sudah cukup.
- Nombor yang diukur tidak dapat membezakan kemahiran daripada kebocoran. Apabila kelebihan sebenar wujud, kebocoran menggembungkan laporan jauh melepasi kebenaran yang boleh didagangkan. Satu-satunya pertahanan ialah proses, bukan metrik.
- Ujian anjakan satu-bar adalah diagnostik terpantas anda. Anjakkan setiap fill satu bar kemudian. Jika prestasi runtuh, anda sedang berdagang pada masa lampau.
- Magnitud kebocoran adalah khusus-saluran. Pelaksanaan dan tinjauan penunjuk adalah membinasakan; normalisasi keseluruhan-siri bagi peraturan tanda adalah hampir percuma. Ukur kebocoran melalui saluran yang sebenarnya dimasukinya — jangan menganggap.
Kajian terkawal penuh — kesemua tiga kebocoran, imbasan dos, analisis pelancaran palsu, kaedah formal, dan setiap nombor yang boleh dihasilkan semula daripada satu skrip deterministik tunggal — terdapat dalam kertas kerja pengiring di lookahead.marketmaker.cc, dengan kod dan data di github.com/suenot/lookahead-inflation.
Strategi dalam eksperimen null kami tidak mempunyai kelebihan langsung. Ia masih menunjukkan Sharpe 15. Jika backtest anda kelihatan terlalu bagus, perkara pertama yang perlu disyaki bukanlah kejeniusan anda — tetapi jam anda.
Pengarang
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.