← Kembali ke artikel
July 8, 2026
Bacaan 5 minit

Membuktikan Tiada Look-Ahead dalam Ujian Balik Multi-Timeframe: Ganggu Masa Depan, Buktikan Masa Lalu Tidak Dapat Melihatnya

Membuktikan Tiada Look-Ahead dalam Ujian Balik Multi-Timeframe: Ganggu Masa Depan, Buktikan Masa Lalu Tidak Dapat Melihatnya
#algotrading
#backtest
#bias look-ahead
#multi-timeframe
#kebocoran data
#pengesahan
🎯
Part 2 of 9 · Collection
Backtesting Without Fooling Yourself

Sebahagian daripada siri "Backtests Without Illusions".

Artikel sebelumnya berakhir dengan satu diagnostik: ujian anjakan satu-bar. Alihkan setiap pengisian (fill) satu bar lebih lewat; jika prestasi runtuh, bermakna anda telah berdagang pada masa lalu. Ujian itu memadai untuk strategi timeframe tunggal, di mana look-ahead tersembunyi dalam pelaksanaan — anda membuat keputusan pada bar i dan mengisi pada bar i.

Strategi multi-timeframe mempunyai satu lagi tempat kebocoran yang lebih senyap, dan ujian anjakan tidak dapat mengesannya. Apabila keputusan pada bar 1 minit merujuk kepada trend 1 jam, kebocoran itu bukan lagi pada pengisian — ia berada pada penunjuk (indicator), yang dikira daripada bar timeframe lebih tinggi yang belum selesai terbentuk. Artikel ini membincangkan kebocoran tersebut, dan cara membuktikan — secara mekanikal, bukan sekadar meneliti kod — bahawa enjin anda tidak mengalaminya.

Dakwaan yang kami buat pada akhirnya adalah kukuh: enjin multi-TF kami lulus 25 daripada 25 semakan pariti dan kebocoran ke atas 86,400 bar data ETHUSDT 1 minit sebenar, termasuk satu ujian anjakan-masa-depan yang mengganggu setiap bar masa depan dan mengesahkan tiada satu pun keputusan masa lalu bergerak. Dan kerana enjin ini sama tepat bit demi bit dengan teras kanonik bot langsung, bukti ini adalah sifat sistem langsung, bukan sekadar backtest.

Medan ranjau multi-timeframe

Satu keputusan 1 minit yang merujuk ke tiga timeframe serentak, dengan lilin timeframe tertinggi masih terbentuk dan harga penutupan akhirnya tersembunyi di sebalik sempadan masa

Strategi multi-timeframe (multi-TF) membuat keputusan pada jam pantas sambil merujuk kepada jam perlahan. Enjin kami adalah contoh konkrit: satu peraturan momentum tiga-TF di mana trend timeframe lebih tinggi (HTF) dan trend timeframe pertengahan (MTF) mengawal (gate) kemasukan, dan pertindihan (cross) timeframe lebih rendah (LTF) mencetuskan kemasukan tersebut. Pada jam asas 1 minit, kemasukan hanya tercetus apabila pertindihan HMA/HMA3 1 minit sepadan dengan arah trend 1 jam dan trend 15 minit; keluar tercetus apabila trend timeframe lebih tinggi berbalik menentang kedudukan, atau LTF bertindih semula.

Bahayanya bersifat struktural. Setiap satu nilai timeframe lebih tinggi itu perlu dijawab pada setiap bar 1 minit — 60 kali sejam — dan setiap jawapan mesti hanya menggunakan apa yang bot langsung ketahui pada saat itu. Sebaik sahaja satu daripada kira-kira 180 bacaan sebar (tiga timeframe, trend kemasukan dan keluar, sekatan pemisahan) menyentuh bar yang belum tercetak, anda mempunyai look-ahead. Ruang permukaan untuk kesilapan off-by-one adalah amat besar, dan tidak seperti kebocoran pelaksanaan, ia tidak menzahirkan dirinya melalui anjakan pengisian.

Bar yang sedang terbentuk belum mempunyai harga penutupan akhir

Inilah perangkap sebenar. Andaikan sekarang 10:37 dan anda membuat keputusan pada bar 1 minit yang baru sahaja ditutup. Peraturan anda memerlukan trend 1 jam. Lilin 1 jam yang meliputi 10:00–11:00 masih terbentuk — harga penutupan akhirnya tidak akan diketahui sehingga 10:59:59. Apa yang sebenarnya anda ketahui pada 10:37? Hanya harga penutupan berjalan lilin tersebut setakat itu, iaitu harga penutupan bar 10:37. Harga penutupan akhir lilin itu adalah 23 minit ke hadapan pada masa depan.

Backtest multi-TF yang naif melakukan sesuatu yang kelihatan sangat tidak berbahaya: ia mengsampel semula (resample) keseluruhan siri 1 minit kepada 1 jam sekali sahaja, terlebih dahulu, kemudian, untuk setiap bar 1 minit, membaca "harga penutupan 1 jam." Tetapi nilai yang dibacanya untuk setiap bar 1 minit di antara 10:00 dan 11:00 ialah harga penutupan akhir 10:00–11:00 — satu nombor yang, dalam masa nyata, tidak wujud sehingga jam itu berakhir. Setiap keputusan dalam jam tersebut secara senyap diserahkan sehingga 59 minit ke masa depan. Dan ini bukan kebocoran kecil — harga penutupan timeframe lebih tinggi adalah kira-kira peramal terkuat yang mungkin bagi pulangan 1 minit masa depan terdekat yang bakal anda dagangkan, jadi membocorkannya hampir sama dengan membaca kunci jawapan. Ini ialah saluran kebocoran-penunjuk daripada taksonomi, diperbesarkan: intipan itu bukan satu bar, tetapi boleh mencapai sepenuh tempoh HTF.

Ujian anjakan satu-bar daripada artikel sebelumnya tidak dapat mengesan ini. Anda boleh mengalihkan pengisian satu bar lebih lewat dan siri 1 jam yang disampel semula masih tercemar — kebocoran itu berada pada cara penunjuk dibina, bukan pada bila anda melakukan transaksi.

Peraturan bot langsung, diulang dengan tepat: semantik bar-tertutup

Buffer timeframe lebih tinggi pada bar i: satu barisan harga penutupan lilin tertutup yang selesai diikuti satu lilin yang sedang terbentuk yang nilainya adalah harga penutupan asas semasa, dengan harga penutupan akhir yang masih tersembunyi dicoret

Peraturan yang betul ialah peraturan yang telah dijalankan oleh bot langsung. Dalam pangkalan kod kami ia adalah satu kelas kecil, RunningCandleBuffer, dipindahkan sebaris demi sebaris daripada simulator tik langsung. Ia menstrimkan bar asas ke dalam lilin bertempoh tetap dan, pada setiap bar asas, mengira penunjuk HTF daripada satu array yang sangat khusus:

all_closes = np.array(self.closes + [self.current_close], dtype=np.float64)

Bacalah itu secara literal. self.closes adalah harga penutupan akhir bagi lilin yang telah sedia tertutup — satu lilin hanya ditambah apabila sempadan tempoh baharu dilintasi, dan nilai yang disimpannya ialah harga penutupan bar asas terakhir dalam tempoh tersebut (candle_buffer.py:39–44). Lilin yang sedang terbentuk menyumbang tepat satu nombor, self.current_close, iaitu harga penutupan berjalan — harga penutupan bar asas paling terkini, base_close[i]. Itu adalah kuantiti yang diketahui pada bar i mengikut takrifan. Harga penutupan akhir lilin yang sedang terbentuk itu tidak pernah digunakan, kerana ia belum wujud lagi.

Jadi penunjuk HTF pada 10:37 melihat [..., close(lilin 9:00), close(10:37 setakat ini)]. Apabila 10:38 tercetak, slot terakhir dikemas kini kepada close(10:38). Apabila 11:00 dilintasi, close(10:59) menjadi nilai akhir lilin yang baru tertutup dan satu slot pembentukan baharu dibuka. Pada bila-bila masa pun, satu keputusan dalam jam tersebut tidak menyentuh harga penutupan akhir 10:00–11:00. Itulah semantik bar-tertutup: lilin tertutup menyumbang harga penutupan yang selesai, lilin yang sedang terbentuk hanya menyumbang harga penutupan berjalannya.

Enjin pantas kami (engine_multitf.py) ialah pelaksanaan semula yang divektorkan dan dikompil dengan numba bagi ini. Berbanding gelung Python dengan senarai yang membesar, ia mengira terlebih dahulu, untuk setiap bar asas i, berapa banyak lilin telah sepenuhnya tertutup (n_closed[i]) dan menyusun tetingkap HMA/HMA3 sebagai [harga penutupan lilin tertutup…, base_close[i]] — harga penutupan berjalan dipakukan ke slot akhir (engine_multitf.py:168–169). Ia adalah matematik yang sama, dibuka gulungannya demi kelajuan merentasi tiga timeframe dengan sekatan pemisahan arah. Kontraknya jelas: nilai pada bar i hanya bergantung pada base_close[0..i].

Itulah dakwaannya. Selebihnya artikel ini adalah bagaimana kami membuktikannya, kerana dakwaan dalam docstring tidak bernilai apa-apa.

Pariti adalah perlu tetapi tidak mencukupi

Satu enjin numba yang divektorkan yang membuka gulungan kelas streaming menjadi gelung eksplisit adalah tempat sebenar di mana kesilapan off-by-one berkembang biak. Jadi gerbang pertama ialah ujian pariti bit-demi-bit terhadap rujukan kanonik, ke atas satu potongan data sebenar — ETHUSDT 1 minit, Januari–Februari 2024, 86,400 bar.

Kami menyemak dua perkara bebas terhadap dua rujukan bebas:

  • Penunjuk dan pertindihan berbanding RunningCandleBuffer yang dijalankan bar demi bar. Bagi setiap timeframe kami memainkan semula kelas langsung tersebut merentasi kesemua 86,400 bar dan membandingkan peristiwa pertindihan — bar, arah, pemisahan — untuk padanan yang tepat, ditambah nilai HMA/HMA3 kepada toleransi titik apungan (rujukan menggunakan np.dot, enjin menggunakan gelung eksplisit, jadi susunan penjumlahan berbeza pada ~1e-15). Pertindihan sepadan dengan tepat: 408 pertindihan pada HTF (1 jam, panjang HMA 21), 2,792 pada MTF (15m/14), 3,691 pada LTF (1m/50). Tiada satu pun peristiwa pertindihan berbeza dari segi bar atau arah.
  • Dagangan berbanding simulasi Python tulen yang bebas bagi peraturan dagangan, dipacu oleh pertindihan rujukan itu sendiri. Ini mengulangi semula gelung backtest langsung — trend kemasukan mengawal kemasukan, satu keterbalikan trend-keluar atau pertindihan LTF bertentangan menutup, pengisian pada open[i+1], yuran pusing lengkap 0.09%, penutupan paksa pada bar terakhir — tanpa mana-mana jentera numba enjin tersebut. Kami kemudian membandingkan dagangan demi dagangan: bar kemasukan/keluar, sisi, harga kemasukan/keluar, PnL, sebab keluar, dan jumlah masa aktif dalam kedudukan.

Ambang pemisahan dalam ujian ini bukan lalai yang tidak berbahaya. Ia dipilih untuk mengenai satu penjuru yang janggal — ambang keluar MTF ditetapkan lebih tinggi daripada ambang kemasukan yang sepadan — yang memaksa cabang "takrifan pertama trend keluar semasa kedudukan terbuka" yang dianggap rujukan sebagai satu keterbalikan. Pariti mesti bertahan pada kes-kes penjuru, bukan hanya laluan yang mudah.

Medan demi medan, dagangan adalah sama: 466 dagangan untuk konfigurasi berkembar (dual), 211 dagangan untuk konfigurasi berkembar tiga (triple), dengan jumlah PnL sepadan sehingga 1e-12 dan setiap medan dagangan sama sehingga toleransi. Dua pelaksanaan yang tidak berkongsi kod — satu enjin divektorkan yang dikompil dan satu gelung Python naif ke atas pertindihan pelaksanaan ketiga — menghasilkan dagangan yang sama sehingga angka perpuluhan terakhir.

Itu adalah keputusan yang kukuh, dan ia bukan bukti tiada look-ahead. Pariti menyatakan enjin pantas mengulangi semula rujukan dengan setia. Jika rujukan itu sendiri bocor — jika RunningCandleBuffer mengintip — pariti akan mengulangi kebocoran itu dengan setia dan lulus. Persetujuan antara pelaksanaan memberitahu anda bahawa mereka sama, bukan bahawa mereka bersebab. Untuk kausaliti anda memerlukan jenis ujian yang berbeza, satu yang bertanya secara langsung kepada enjin sama ada masa lalu dapat melihat masa depan.

Ujian anjakan-masa-depan: bukti sebenar

Satu potongan menegak pada 60 peratus siri: segala-galanya di sebelah kanan didarab dan digegar, segala-galanya di sebelah kiri dibekukan sama tepat bit demi bit

Takrifan look-ahead adalah operasional, jadi ujilah secara operasional. Look-ahead bermakna keputusan masa lalu bergantung pada data masa depan. Kontrapositifnya adalah satu ujian yang boleh anda jalankan: jika anda mengubah masa depan dan mana-mana keputusan masa lalu bergerak, masa lalu itu telah membaca masa depan. Jadi ubahlah masa depan — secara ganas — dan perhatikan masa lalu.

Pilih satu titik potongan j pada 60% siri (bar 51,840 daripada 86,400). Ganggu setiap bar dari j dan seterusnya: darabkan semua harga penutupan dan pembukaan masa depan dengan 1.05. Kira semula keseluruhan tindanan isyarat bagi ketiga-tiga timeframe ke atas data yang diganggu. Kemudian sahkan bahawa segala-galanya sebelum j secara ketat adalah sama tepat bit demi bit dengan larian yang tidak diganggu:

j = int(n * 0.6)                       # bar 51,840
cl2 = cl.copy(); cl2[j:] *= 1.05       # shove the future up 5%
op2 = op.copy(); op2[j:] *= 1.05

base = [precompute_tf_signals(cl,  ts, p, L) for (p, L) in tf_params]
pert = [precompute_tf_signals(cl2, ts, p, L) for (p, L) in tf_params]

for s0, s1 in zip(base, pert):
    assert eq_nan(s0.hma[:j],  s1.hma[:j])      # HMA identical, NaNs included
    assert eq_nan(s0.hma3[:j], s1.hma3[:j])
    assert np.array_equal(s0.cross[:j], s1.cross[:j])   # every cross event
    assert np.array_equal(s0.sep[:j],   s1.sep[:j])     # every separation

Bukan "hampir sama." Bukan "dalam toleransi." np.array_equal, dengan NaN dikehendaki sepadan dengan NaN — setiap nilai HMA, setiap nilai HMA3, setiap bendera pertindihan, dan setiap pemisahan pada 51,840 bar masa lalu mesti menjadi nombor apungan yang sama. Kemudian pengesahan yang sama ke atas dagangan: setiap dagangan yang keluarnya jatuh sebelum j mesti tidak berubah medan demi medan. Jika tolakan 5% kepada masa depan menggerakkan satu HMA masa lalu pada tempat perpuluhan kedua belas, satu keputusan masa lalu telah merujuk kepada masa depan, dan ujian tersebut gagal.

Enjin kami lulus ujian ini — bagi ketiga-tiga timeframe, dan bagi kedua-dua simulasi dagangan berkembar dan berkembar tiga. Mengganggu 34,560 bar masa depan meninggalkan 51,840 bar masa lalu dan setiap dagangan yang ditutup dalam kalangan mereka betul-betul seperti sediakala. Itu bukan persetujuan antara pelaksanaan; itu adalah demonstrasi langsung bahawa sempadan maklumat dalam masa itu terpelihara.

Ujian yang tidak boleh gagal tidak membuktikan apa-apa

Terdapat satu cara untuk lulus ujian di atas yang tidak membuktikan apa-apa langsung: jika gangguan itu tidak memberi kesan — jika mendarab masa depan dengan 1.05 tidak mengubah apa-apa di mana-mana — maka "masa lalu tidak berubah" adalah benar secara remeh dan sama sekali tidak bermaklumat. Tanda hijau pada ujian yang tidak boleh gagal adalah lebih buruk daripada tiada ujian langsung, kerana ia mencipta keyakinan palsu. Jadi ujian ini membawa dua lagi pengesahan yang memberinya keberkesanan.

Masa depan mesti benar-benar berubah. Kami mengesahkan bahawa gangguan itu benar-benar mengubah pertindihan di suatu tempat dalam [j, n):

assert not np.array_equal(s0.cross[j:], s1.cross[j:])   # probe has teeth

Kini hasilnya bermakna: tolakan 5% yang sama yang menulis semula masa depan meninggalkan masa lalu sama tepat bit demi bit. Gangguan itu benar, ia merambat ke hadapan, dan ia berhenti mati di titik potongan tersebut. Kebocoran sehala — masa lalu membaca masa depan — akan mengalir ke belakang merentasi j; ia tidak berlaku.

Sempadan itu tepat pada bar semasa — bukan satu awal, bukan satu lewat. Kegagalan yang lebih halus ialah satu enjin yang bersebab tetapi lapuk (stale): ia mengabaikan harga penutupan berjalan bar semasa dan bertindak balas satu bar lewat (tiada kebocoran, tetapi lengah yang tidak akan wujud dalam dagangan langsung), atau ia bertindak balas satu bar awal (satu kebocoran satu-bar). Jadi kami mengganggu satu bar tunggal j (sebanyak 1.02) dan mengesahkan dua perkara serentak: masa lalu [0, j) tidak tersentuh, dan hma[j] bertindak balas serta-merta.

cl3 = cl.copy(); cl3[j] *= 1.02        # nudge exactly one bar
s3 = precompute_tf_signals(cl3, ts, p_ltf, L_ltf)
assert eq_nan(s0.hma[:j], s3.hma[:j])  # nothing before j moves
assert s0.hma[j] != s3.hma[j]          # bar j reacts on the same bar

Ini menetapkan sempadan itu dengan tepat. Harga penutupan berjalan lilin yang sedang terbentuk memasuki penunjuk pada bar j dengan kelewatan sifar dan jangkaan sifar: bar j melihat harga penutupannya sendiri secara serta-merta, dan tiada bar lebih awal melihatnya langsung. Itulah mata pisau tepat di mana semantik bar-tertutup sepatutnya berada, dan ujian ini mengesahkan enjin itu berada di situ.

Berikut adalah gerbang penuh — kesemua 25 semakan yang berdiri antara enjin ini dan satu backtest yang direka-reka:

Kumpulan Apa yang setiap semakan sahkan Bilangan
Penunjuk & pertindihan (×3 timeframe) peristiwa pertindihan tepat berbanding RunningCandleBuffer; pemisahan pada pertindihan; nilai HMA/HMA3 (rtol 1e-9) 9
Dagangan (berkembar + berkembar tiga) bilangan dagangan; sama medan demi medan; jumlah PnL sehingga 1e-12; masa aktif dalam kedudukan 8
Ujian anjakan-masa-depan (berkembar + berkembar tiga) isyarat masa lalu tidak berubah bit demi bit; ujian mempunyai keberkesanan (masa depan benar-benar berubah); dagangan sebelum j tidak berubah; gangguan bar tunggal disetempatkan 8
Jumlah 25

Dua kumpulan pertama mewujudkan bahawa enjin pantas itu adalah rujukan langsung. Kumpulan ketiga mewujudkan bahawa rujukan itu bersebab. Anda memerlukan kesemua tiga: satu enjin pantas yang bocor, satu enjin bocor yang bersebab, dan satu enjin bersebab yang lengah adalah tiga kegagalan berbeza, dan gerbang ini menyingkirkan setiap satunya.

Mengapa ujian ini tidak bergantung kepada timeframe

Keanggunan ujian anjakan-masa-depan ialah ia tidak tahu atau mengambil peduli di mana sesuatu kebocoran mungkin berada. Ia tidak pernah menyebut timeframe, persampelan semula, atau sempadan lilin. Ia hanya bertanya: adakah mengganggu masa depan menggerakkan masa lalu? Itulah yang menjadikannya alat yang tepat khusus untuk kebocoran multi-TF, yang terlepas daripada ujian anjak-pengisian.

Pertimbangkan pepijat naif sampel-semula-seluruh-siri secara langsung. Jika strim 1 jam dibina dengan menyampel semula keseluruhan siri terlebih dahulu, "harga penutupan 1 jam" yang dibaca pada bar j-1000 (jauh dalam jam yang ditutup selepas j) akan menjadi harga penutupan akhir bagi satu lilin yang harga penutupan akhirnya bergantung pada bar pada dan selepas j. Darabkan masa depan dengan 1.05 dan harga penutupan akhir itu berubah — jadi penunjuk HTF pada j-1000 berubah, sekatan pada j-1000 berubah, dan satu keputusan masa lalu bergerak. Ujian tersebut akan menyala pada strim HTF serta-merta, pada bar seribu langkah sebelum titik potongan.

Strim HTF enjin kami tidak bergerak, kerana pada j-1000 lilin yang sedang terbentuk hanya menyumbang base_close[j-1000] — satu harga penutupan masa lalu — dan harga penutupan akhir lilin itu tidak pernah dirujuk langsung sehingga sempadan itu berlalu. Ujian ini buta kepada mekanismenya dan masih menangkap pepijat tersebut, yang merupakan tepat apa yang anda inginkan daripada satu bukti: ia mengekang tingkah laku (tiada keputusan masa lalu bergantung pada data masa depan) dan bukannya mengaudit pelaksanaan (adakah kita mengindeks persampelan semula dengan betul?). Tingkah laku adalah apa yang berdagang; pelaksanaan adalah apa yang anda harap sepadan dengannya.

Backtest dan bot langsung berkongsi satu kebenaran

Dua panel berkongsi satu tulang belakang tunggal berlabel RunningCandleBuffer: satu sapuan backtest di satu pihak, satu bot langsung di pihak lain, kedua-duanya disambungkan kepada teras yang sama

Terdapat satu lagi sebab ini penting lebih daripada audit backtest yang biasa. Rujukan yang menjadi bukti enjin ini — RunningCandleBuffer — bukan satu perlengkapan ujian yang ditulis untuk menjadikan backtest kelihatan baik. Ia adalah logik lilin bot langsung itu sendiri, diangkat sebaris demi sebaris daripada simulator tik yang berjalan dalam pengeluaran. Peraturan bar-tertutup yang disahkan oleh ujian ini adalah peraturan yang dilaksanakan oleh bot langsung, bar demi bar.

Jadi gerbang pariti itu melaksanakan dua tugas. Ia membuktikan enjin pantas itu sepadan dengan rujukan, dan kerana rujukan itu adalah teras langsung, ia membuktikan enjin pantas itu sepadan dengan sistem langsung. Artikel sebelumnya mengingatkan bahawa satu kebocoran adalah penjelasan paling bersih bagi jurang pariti backtest-langsung — bot langsung adalah satu-satunya tempat anda secara mekanikal tidak boleh mengintip, jadi satu backtest yang mengintip akan menyimpang sebaik sahaja ia berjalan langsung. Di sini jurang tersebut ditutup secara struktur: backtest dan bot berkongsi satu buffer lilin, satu peraturan pertindihan, satu takrifan "diketahui pada bar i." Angka yang dioptimumkan oleh carian itu adalah angka yang benar-benar boleh didagangkan oleh bot.

Itulah keseluruhan tujuan membuktikan tiada look-ahead berbanding mengandaikannya sahaja. Satu carian multi-TF menjalankan beribu-ribu konfigurasi; jika enjin itu bocor, carian tersebut akan menemui konfigurasi yang mengeksploitasi kebocoran itu paling agresif dan menyerahkan kepada anda pemenang yang direka-reka — mod kegagalan yang diukur oleh taksonomi pada Sharpe 15 daripada bunyi bising tulen. Ujian ini adalah apa yang membolehkan anda mempercayai bahawa pemenang itu sebenar sebelum anda menyambungkannya kepada modal.

Apa yang ujian ini buktikan dan tidak buktikan

Ketelitian tentang ujian ini berlaku dalam kedua-dua arah, jadi bertepatlah tentang skopnya. Ujian anjakan-masa-depan membuktikan satu sifat tunggal yang khusus: pada data ini, tiada keputusan isyarat atau dagangan pada atau sebelum bar j bergantung pada mana-mana bar selepas j — sempadan maklumat dalam masa terpelihara sepanjang laluan penunjuk, pertindihan, dan dagangan. Itu tepat kebocoran multi-TF yang cuba kami hapuskan, dan itu adalah sifat yang tidak dapat diwujudkan oleh semakan kod.

Ia tidak membuktikan strategi itu mempunyai kelebihan (edge). Satu enjin yang bersebab sepenuhnya boleh kehilangan wang secara jujur, dan ujian ini senyap tentang itu — sebagaimana sepatutnya; membuktikan tiada kebocoran dan menemui kelebihan sebenar adalah dua tugas berasingan, dan mencampuraduknya adalah bagaimana backtest yang bocor akhirnya digunakan. Ia tidak merangkumi bias bukan-temporal: survivorship dalam instrumen, bias pemilihan daripada menjalankan ujian hanya selepas enjin kelihatan baik, atau model yuran yang terlalu murah hati. Dan ia tidak, dengan sendirinya, membuktikan bahawa pengisian langsung sepadan dengan pengisian backtest — gelinciran (slippage) dan kependaman (latency) adalah jurang sebenar yang tidak dapat dilihat oleh ujian ini, kerana ia beroperasi pada laluan keputusan, bukan tempat pelaksanaan. Apa yang menutup jurang itu adalah fakta berasingan bahawa teras lilin enjin itu sama tepat bit demi bit dengan bot langsung.

Satu amaran jujur tentang reka bentuk ujian ini sendiri: ia memotong pada satu j tunggal (60% siri). Sifat yang disahkannya adalah seragam mengikut j — tiada apa-apa yang istimewa tentang bar 51,840 — jadi satu potongan adalah ujian yang adil bagi sifat struktural, tetapi versi yang lebih berhati-hati akan menyapu j merentasi seluruh siri. Kami menganggap satu potongan yang dipilih dengan baik ditambah semakan penyetempatan satu-bar sudah mencukupi, kerana satu kebocoran yang tersembunyi daripada gangguan masa depan 34,560-bar tetapi muncul pada potongan lain akan menjadi pepijat yang sangat pelik. Intinya adalah untuk mengetahui had bukti anda, bukan berpura-pura satu ujian adalah kuantifayer sejagat.

Rumusan

  1. Strategi multi-timeframe bocor melalui bar yang sedang terbentuk, bukan pengisian. Satu keputusan dalam tempoh HTF yang belum selesai mesti hanya menggunakan harga penutupan berjalan lilin itu (harga penutupan asas paling terkini), tidak sekali-kali harga penutupan akhirnya. Backtest sampel-semula-seluruh-siri menyerahkan setiap keputusan dalam-tempoh sehingga sepenuh tempoh HTF masa depan.
  2. Ujian anjakan satu-bar tidak dapat mencapai kebocoran ini. Ia menangkap look-ahead pelaksanaan; kebocoran multi-TF berada pada cara penunjuk dibina. Anda memerlukan ujian yang berbeza.
  3. Ulang semula peraturan langsung dengan tepat, kemudian buktikannya. Kami membina semula semantik bar-tertutup RunningCandleBuffer bot itu sebagai satu enjin divektorkan dan menghalangnya di sebalik 25 semakan: pertindihan tepat berbanding rujukan (408 / 2,792 / 3,691), dagangan sama medan demi medan (466 berkembar, 211 berkembar tiga), PnL sehingga 1e-12.
  4. Pariti adalah perlu tetapi tidak mencukupi. Sepadan dengan rujukan membuktikan anda sama, bukan bahawa anda bersebab. Satu rujukan yang bocor diulang semula dengan setia masih bocor.
  5. Ujian anjakan-masa-depan adalah bukti sebenar. Ganggu setiap bar pada atau selepas j; sahkan setiap isyarat dan dagangan sebelum j tidak berubah bit demi bit. Jika masa depan boleh menggerakkan masa lalu, anda mempunyai look-ahead.
  6. Berikan ujian itu keberkesanan. Sahkan bahawa masa depan benar-benar berubah (gangguan itu bukan tidak memberi kesan) dan bahawa satu tolakan bar tunggal bertindak balas pada bar yang sama itu (tiada kebocoran, tiada lengah). Ujian yang tidak boleh gagal tidak membuktikan apa-apa.
  7. Apabila backtest dan langsung berkongsi satu teras, bukti itu berpindah. Kerana enjin itu sama tepat bit demi bit dengan logik lilin bot langsung, tiada-look-ahead adalah sifat sistem langsung juga — dan jurang pariti backtest-langsung ditutup secara struktur.

Artikel sebelumnya menunjukkan bagaimana satu kebocoran satu-baris mereka-reka Sharpe 15. Artikel ini menunjukkan disiplin yang bertentangan: bukan "bagaimana kebocoran menipu anda," tetapi bagaimana untuk membuktikan, secara mekanikal dan pada data sebenar, bahawa satu enjin tertentu tidak bocor. Ganggu masa depan. Jika masa lalu tidak berkelip, anda sedang berdagang pada masa kini.

Penafian: Maklumat yang disediakan dalam artikel ini adalah untuk tujuan pendidikan dan maklumat sahaja dan bukan merupakan nasihat kewangan, pelaburan, atau dagangan. Dagangan mata wang kripto melibatkan risiko kerugian yang ketara.

Pengarang

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Kekal Mendahului Pasaran

Langgan surat berita kami untuk pandangan dagangan AI eksklusif, analisis pasaran, dan kemas kini platform.

Kami menghormati privasi anda. Berhenti melanggan pada bila-bila masa.