Ini dia satu bar. Binance USDⓈ-M perpetual, BTCUSDT, menit yang dimulai pukul 13:46:00 UTC. Bar ini keluar dari endpoint klines sebagai array polos berisi dua belas nilai, dan ini adalah unit input paling umum dalam riset kuant ritel dan yang berdekatan dengannya. Hampir setiap strategi yang kami hasilkan menyentuh sesuatu yang berbentuk seperti ini.
Jadi mari kita bedah, indeks demi indeks, dan lihat seberapa banyak bagiannya yang salah dibaca oleh backtest.
| Indeks | Nilai | Nama |
|---|---|---|
| 0 | 1773495960000 | waktu buka (ms) |
| 1 | "84120.50" | buka |
| 2 | "84177.90" | tertinggi |
| 3 | "84098.10" | terendah |
| 4 | "84163.40" | tutup |
| 5 | "38.417" | volume dasar (BTC) |
| 6 | 1773496019999 | waktu tutup (ms) |
| 7 | "3232108.94" | volume kuota (USDT) |
| 8 | 1204 | jumlah transaksi |
| 9 | "21.883" | volume beli taker (dasar) |
| 10 | "1841203.55" | volume beli taker (kuota) |
| 11 | "0" | abaikan |
[0] dan [6]: menit yang mana ini, dan jam siapa yang menentukannya
Waktu buka 1773495960000, waktu tutup 1773496019999. Perhatikan yang kedua: angkanya berakhir dengan 19999, kurang satu milidetik dari waktu buka bar berikutnya. Jendela waktunya bersifat half-open, dan exchange memberi tahu Anda hal ini secara eksplisit. Setengah dari bug data yang pernah saya lacak berawal dari seseorang yang memperlakukan kedua titik akhir sebagai inklusif dan menghitung ganda satu transaksi di batas waktu, atau menyelaraskan resample sehingga setiap bar 5 menit "meminjam" satu milidetik dari tetangganya.
Timestamp ini adalah waktu matching-engine exchange. Bukan jam Anda, bukan jam ingestion vendor Anda, dan bukan jam yang sama dengan yang dipakai snapshot funding. Ketika Anda menggabungkan (join) satu seri kline dengan seri funding rate atau seri open interest yang diambil dari endpoint berbeda, Anda sedang menggabungkan sumber-sumber yang, sebagian besar waktu, hanya sepakat dalam rentang kurang lebih satu detik. Untuk strategi 1 menit, itu berarti error waktu sebesar 1,7% pada setiap baris yang digabungkan. Untuk apa pun yang sub-menit, ini fatal.
Dan bar ini diberi stempel waktu pada saat buka-nya. Informasi di dalamnya baru bisa diketahui pada 13:46:59.999. Setiap pertanyaan tentang konvensi indexing — apakah saya harus menggeser satu langkah, apakah saya memakai timestamp bar-close atau bar-open sebagai event time saya — sebenarnya adalah pertanyaan lookahead yang sama, hanya berganti kostum.
[1] "84120.50": harga buka yang tidak bisa Anda tradingkan
Harga buka adalah harga dari transaksi pertama yang tercatat di dalam jendela waktu tersebut. Ini adalah transaksi yang sudah selesai antara dua pihak lain. Pada saat bar ini sudah muncul sebagai satu baris di dataframe Anda, harga tersebut sudah berumur enam puluh detik.
Bar sebelumnya ditutup di 84109.80, jadi ada pergerakan harga sebesar $10.70 yang bersembunyi di celah antara dua bar 1 menit yang berdekatan. Itu 1,3 basis poin, tak terlihat di chart, dan kira-kira seperempat dari taker fee. Baik-baik saja. Tapi coba lihat seri yang sama pada alt perp saat rilis data CPI AS, dan celah antar-bar itu bisa mencapai 15 hingga 40 bps. Backtest yang memberi sinyal saat bar close dan mengeksekusi order pada bar-open berikutnya diam-diam mengasumsikan celah itu nol — dan celah itu memang nol persis di saat hal itu tidak penting.
[2] dan [3]: "84177.90" tertinggi, "84098.10" terendah
Kedua field ini adalah tempat sebagian besar fill engine berakhir mati, jadi bagian ini yang paling panjang.
Harga tertinggi dan terendah adalah harga ekstrem yang tersentuh. Keduanya tidak membawa informasi size maupun durasi. Dari trade tape mentah pada menit yang sama, semua transaksi di harga 84100.00 atau di bawahnya berjumlah 0,62 BTC yang tersebar dalam sembilan print dalam jendela waktu 1,4 detik. Jadi stop backtest di 84100 "terisi" — dan jika posisi Anda 3 BTC, Anda menghabiskan seluruh order book yang terlihat dalam perjalanan turun, dan sisa size Anda terisi di suatu tempat dalam rentang walk-back 84105–84130. Bar hanya mengatakan bahwa harga terendah adalah 84098.10. Bar tidak mengatakan bahwa hanya $52.000 notional yang diperdagangkan di sana.
Arah sebaliknya justru lebih buruk, karena ia menyanjung Anda. Misalkan Anda punya limit sell yang mengendap di 84175. Harga tertinggi bar ini adalah 84177.90, jadi engine yang naif akan mengeksekusi Anda di 84175 dan mencatatnya sebagai maker rebate, bukan taker cost. Apakah fill itu benar-benar terjadi bergantung pada posisi antrean (queue position) di level harga tersebut, sesuatu yang tidak bisa diketahui oleh bar dan mungkin tidak pernah Anda catat. Tersentuh bukan berarti terisi.
Aturan yang akhirnya kami tetapkan dalam fill engine kami: limit yang mengendap hanya terisi jika bar trades through (tembus melewati) level tersebut, bukan sekadar menyentuhnya. Fill pada tick ekstrem yang persis membutuhkan bukti volume-at-price dari trade tape, jika tidak, fill tersebut ditolak. Aturan ini menghilangkan sekitar 6% transaksi dari book mean-reversion yang khas dan memangkas Sharpe backtest salah satu kandidat strategi dari 1,9 menjadi 1,1. Kandidat itu tidak pernah nyata; aturan fill itu hanyalah hal pertama yang cukup jujur untuk mengatakannya.
Jebakan terkait lainnya: jalur intrabar. Jika range satu bar mencakup baik stop maupun take-profit Anda, OHLCV tidak bisa memberi tahu mana yang terjadi lebih dulu. Setiap engine harus memilih satu konvensi. Konvensi kami selalu mengasumsikan stop-first, yang bersifat pesimistis dan kadang salah, tetapi tidak pernah menghasilkan kemenangan palsu. Jika engine Anda mengasumsikan take-profit-first, bar dengan range lebar akan menciptakan profit dari ambiguitas, dan bar dengan range lebar itu justru yang paling mendominasi distribusi PnL Anda.
[4] "84163.40": angka paling tidak robust dalam bar ini
Harga tutup adalah print terakhir dalam jendela waktu tersebut. Itu saja. Bisa jadi ini adalah odd-lot 0,002 BTC dari sebuah bot yang membulatkan posisinya pada 13:46:59.8. Satu tick yang secara struktural arbitrer inilah yang dipakai sebagian besar research pipeline untuk menghitung setiap sinyal, mark setiap posisi, dan mengevaluasi setiap exit.
Pada BTC perps, ini nyaris tidak berpengaruh; pada altcoin perp yang tipis likuiditasnya pukul 04:00 UTC, ini sangat berpengaruh, dan perbedaan harga tutup antara dua venue untuk menit yang sama bisa melebihi seluruh edge per-trade Anda. Ketika PnL sebuah strategi bergantung secara khusus pada harga tutup, kami menjalankannya ulang dengan mark price exchange sebagai gantinya, yang diturunkan dari index dan jauh lebih sulit "dicolek". Jika hasilnya berbeda jauh, berarti strategi itu sebenarnya sedang trading artefak data, bukan sinyal sungguhan.
[5] dan [7]: "38.417" dan "3232108.94", volume dalam satuan apa
Volume dasar adalah BTC; volume kuota adalah USDT. Keduanya tersedia di sini, sebuah kemudahan yang tidak semua venue berikan. Alasan mengapa ini penting adalah agregasi lintas venue. Kontrak coin-margined dikutip dalam kontrak senilai notional $100. Beberapa feed ekuitas melaporkan round lot. Venue prediction market melaporkan jumlah share, di mana satu share adalah klaim biner dalam denominasi dolar. Menjumlahkan "volume" lintas universe yang campur aduk tanpa menormalisasi ke satu satuan notional tunggal menghasilkan peringkat likuiditas yang sama sekali tidak masuk akal — dan ketidakmasukakalannya akan terlihat stabil sehingga lolos dari review.
Normalisasikan semuanya ke notional kuota pada saat ingest. Simpan juga field mentahnya, tapi jangan pernah biarkan strategi melihatnya.
[8] 1204: field yang seharusnya menentukan impact model Anda
Jumlah transaksi yang membagi volume dasar menghasilkan rata-rata print sebesar 0,032 BTC, sekitar $2.700. Jika strategi kandidat Anda ingin masuk $250.000 sekaligus, itu artinya meminta ukuran sekitar 92 kali lipat dari transaksi tipikal pada menit tersebut. Angka itulah, bukan konstanta slippage generik 5 bps, yang seharusnya menentukan term impact square-root Anda. Kami menghitung participation rate per bar sebagai kolom kelas satu dan menolak strategi yang median entry-nya melebihi beberapa persen dari notional bar, karena segala sesuatu setelah titik itu hanyalah fiksi.
Jumlah transaksi juga menandai menit-menit yang aneh dengan murah. Volume normal, tapi jumlah transaksi anjlok ke 11? Seseorang melakukan block trade. Volume normal, jumlah transaksi mencapai 9.000? Itu adalah cascade likuidasi yang sedang dikunyah habis dalam potongan-potongan kecil.
[9] dan [10]: "21.883", field yang selalu dibuang semua orang
Volume beli taker (dasar). 21,883 dari 38,417 BTC dalam bar ini diinisiasi oleh pembeli, sehingga signed volume delta-nya adalah +5,349 BTC dan pembagian aggressor-nya 57/43 berpihak ke pembeli. Exchange menyerahkan order flow imbalance kepada Anda secara cuma-cuma, dalam sebuah field yang hampir tidak pernah dibaca kebanyakan orang karena pandas tidak memberi nama kolom itu untuk mereka.
Saya tidak mengklaim bahwa field ini memprediksi return dengan sendirinya; strategi delta yang naif justru termasuk cara paling andal untuk menyumbangkan uang ke fee ledger. Tapi ini benar-benar pengukuran yang berbeda dari harga, tersedia dalam request yang sama yang sudah Anda lakukan, dan memungkinkan Anda membedakan rally yang terjadi karena dibeli dari rally yang terjadi karena penjual mundur. Kedua hal ini terlihat identik di OHLC tapi berperilaku berbeda sepuluh menit kemudian. Research agent kami menganggap hipotesis yang mengabaikan taker split pada venue yang mempublikasikannya sebagai membiarkan bukti tergeletak begitu saja di atas meja.
[11] "0": abaikan — dan segala sesuatu lain yang tidak ada di sini
Indeks 11 adalah field yang sudah deprecated, selalu bernilai nol. Yang lebih menarik adalah daftar hal-hal yang tidak dimuat bar ini: tidak ada bid, tidak ada ask, tidak ada spread, tidak ada book depth, tidak ada funding rate, tidak ada open interest, tidak ada likuidasi, tidak ada mark price, tidak ada index price. Dan yang krusial, tidak ada cara untuk mengetahui apakah order Anda akan menjadi maker atau taker, yang merupakan perbedaan antara membayar 0,045% dan mendapatkan 0,01% pada venue ini.
Jadi model fee apa pun yang dibangun hanya dari klines adalah sebuah asumsi yang berbalut pakaian angka. Kami menyelesaikannya dengan mewajibkan setiap strategi menyatakan gaya eksekusinya di awal, lalu mengenakan biaya taker pada apa pun yang tidak bisa membuktikan sebaliknya.
Bar yang tidak pernah muncul
Bagian terakhir, dan yang paling menggigit di luar pasangan major. Satu menit dengan nol transaksi tidak menghasilkan kline sama sekali. Vendor dan library umumnya melakukan forward-fill: open = high = low = close = harga tutup sebelumnya, volume 0. Indikator Anda menghitung dengan riang tanpa masalah. Strategi Anda melihat baris yang valid dan bisa menghasilkan sinyal pada menit di mana tidak ada satu pun orang di dunia yang memperdagangkan instrumen tersebut.
Pada satu perp mid-cap yang kami ingest, 4,1% dari bar 1 menit dalam jendela dua belas bulan tidak memiliki transaksi sama sekali. Sebuah kandidat strategi mean-reversion pada simbol tersebut menempatkan 38% dari entry-nya pada bar sintetis, karena harga sintetis yang datar bagaikan catnip bagi apa pun yang mengukur deviasi dari moving average. Backtest-nya terlihat sangat indah. Padahal ia sedang trading celah-celah dalam data.
Itulah sebabnya lapisan ingest kami sekarang membawa boolean synthetic per bar, yang diteruskan melalui setiap resample, dan gauntlet verifikasi akan menggagalkan strategi apa pun yang entry-nya berkerumun di sana. Kolom yang murah. Kolom ini telah menggugurkan lebih banyak kandidat strategi daripada indikator apa pun yang pernah kami tulis.
Dua belas nilai. Empat di antaranya rutin salah dibaca, dua di antaranya rutin dibuang, dan satu kategori penuh yang tidak ada dalam baris itu dan hanya dikhayalkan oleh engine. Sebelum backtest Anda berikutnya, coba ambil satu bar mentah dari penyimpanan data Anda sendiri dan baca setiap field-nya dengan lantang, bandingkan dengan apa yang diasumsikan oleh logika fill Anda tentangnya. Ini latihan dua puluh menit, dan saya belum pernah melihat ada orang melakukannya dan tidak menemukan apa-apa.
← Semua artikel