Minggu lalu Anda mengirim notebook itu: sinyal sama, semesta sama, data perpetual BTCUSDT selama 14 bulan yang sama. Satu-satunya perubahan ada pada eksekusi. Anda berhenti menyapu spread dan mulai memasang bid satu tick di dalamnya, lalu Sharpe naik dari 0.42 menjadi 2.14. Anda bertanya apakah hasil itu nyata atau ada yang rusak.
Ada yang rusak. Saya ingin menunjukkan persis di mana letaknya, karena bug-nya hanya satu baris, sementara pelajaran di baliknya jauh lebih besar.
Ini aturan fill Anda, disalin dari engine:
if bar.low <= limit_price: fill(limit_price)
Artinya: jika pasar mencatat transaksi pada atau di bawah harga saya selama menit ini, order saya terisi pada harga saya. Yang sebenarnya dikodekan aturan itu adalah harga menyentuh level Anda. Tersentuh bukan berarti terisi. Di antara keduanya ada antrean, dan Anda memodelkan antrean itu seolah-olah kosong.
Apa yang mengantre di depan Anda
Saat Anda memasang bid di 84,120.0 pada perpetual BTCUSDT, Anda bergabung di belakang semua order yang sudah berada di sana. Di level teratas buku order kontrak itu, biasanya ada sekitar 4 hingga 30 BTC, bergantung pada waktu; mediannya dalam periode sampel Anda sekitar 12 BTC. Order Anda 0.4 BTC. Agar transaksi terjadi, penjual harus memukul level itu dengan ukuran agregat yang cukup untuk menghabiskan semua order yang tiba sebelum Anda, dan harus melakukannya sebelum level tersebut dibatalkan atau pasar bergerak naik menjauhi Anda.
Jadi pertanyaan yang seharusnya diajukan backtest Anda bukan "apakah harga mencapai 84,120.0", melainkan "apakah setidaknya 12.4 BTC penjualan pasar dieksekusi di 84,120.0 saat order saya berada di sana." Itu dua peristiwa yang sangat berbeda. Dalam data Anda, frekuensinya berbeda sekitar tiga kali lipat.
Tiga aturan fill, tiga strategi berbeda
Saya menjalankan ulang sinyal Anda dengan entry yang sama dan tiga model eksekusi. Alpha, biaya, dan funding tetap sama. Yang berubah hanya logika fill.
| Aturan fill | Fill | Rata-rata edge 60s setelah fill | Sharpe |
|---|---|---|---|
Sentuh: low <= limit | 4,180 | +2.6 bp | 2.14 |
Penetrasi ketat: low < limit - 1 tick | 1,712 | +0.4 bp | 0.61 |
| Simulasi antrean berdasarkan volume di level | 1,306 | +0.9 bp | 0.77 |
Baris tengah adalah perbaikan kasar yang pertama kali dicoba banyak orang: hitung fill hanya jika pasar bertransaksi benar-benar melewati harga Anda, dengan anggapan jika harga bergerak melampaui Anda, order Anda pasti sudah terserap. Arahnya benar dan sebagian besar khayalan pun hilang. Namun aturan ini juga punya bias yang merugikan, seperti yang akan saya jelaskan nanti.
Baris terbawah adalah model yang sebaiknya Anda bangun. Model ini tidak memerlukan feed L3 ataupun rekonstruksi per order. Sebagian besar komponennya sudah Anda punya.
Simulasi antrean yang bisa Anda bangun dari aggTrades
Ambil aliran transaksi agregat, bukan klines. Setiap catatan memuat harga, kuantitas, timestamp, dan penanda sisi maker yang memberi tahu apakah pihak agresor membeli atau menjual. Itu cukup untuk simulasi yang layak bagi order pasif Anda sendiri:
- Saat order Anda dipasang, ambil snapshot ukuran order yang sudah mengantre di harga Anda. Jika feed buku order Anda hanya 100ms, gunakan snapshot terakhir; galatnya kecil dibanding galat yang sedang Anda perbaiki.
- Tetapkan
queue_ahead = resting_size. Ambil asumsi pesimistis dan anggap posisi Anda paling belakang. Memang begitu, kecuali Anda yang membuat level tersebut. - Telusuri aliran transaksi. Setiap transaksi oleh penjual agresor pada atau di bawah harga Anda mengurangi
queue_aheadsebesar kuantitasnya. Saat nilainya menjadi negatif, order Anda terisi pada harga Anda, pada timestamp itu. - Jika harga bergerak menjauh satu tick, jangan setel ulang antrean menjadi nol. Kurangi bertahap. Sebagian order di depan Anda dibatalkan saat level itu tak lagi aktif, sebagian lagi tidak. Pengurangan 30-40% untuk setiap detik penuh saat harga menjauh dari level cocok dengan hasil rekonstruksi saya lebih baik daripada kedua asumsi ekstrem.
- Jika strategi Anda akan membatalkan lalu memasang ulang order, modelkan pemasangan ulang itu sebagai order baru di belakang antrean level baru. Langkah ini sering dilewatkan orang, dan di sinilah khayalan yang tersisa bersembunyi.
Langkah 2 mungkin akan Anda bantah. Ya, kadang posisi Anda dekat depan karena Anda memasang order tepat saat level terbentuk. Baik — ukur, jangan berasumsi. Catat ukuran order yang mengantre saat order dipasang, lalu biarkan data menunjukkan berapa proporsi order Anda yang benar-benar tiba lebih awal. Dalam strategi Anda angkanya 11%, karena sinyal Anda muncul setelah pergerakan, artinya level yang Anda masuki sudah ada dan sudah ramai.
Fill yang Anda dapat justru fill yang Anda harap tak terjadi
Sekarang bagian yang paling penting, sekaligus alasan aturan penetrasi ketat itu bias.
Bayangkan kapan bid Anda terserap sepenuhnya. Itu terjadi saat tekanan jual cukup besar untuk menghabiskan seluruh level. Artinya, berdasarkan konstruksi, pasar sedang bergerak turun melewati harga Anda. Fill yang paling pasti Anda dapatkan adalah fill yang langsung membuat posisi Anda merugi.
Pisahkan fill berdasarkan cara terjadinya, lalu ukur mark-out 60 detik:
| Jenis fill | Proporsi fill | Mark-out pada 60s |
|---|---|---|
| Level diperdagangkan, harga memantul naik | 38% | +3.1 bp |
| Level diperdagangkan, harga mendatar | 21% | +0.2 bp |
| Harga menembus 2+ tick | 41% | −2.4 bp |
Backtest naif Anda memasukkan ketiga kelompok itu dan menganggap semuanya gratis. Aturan penetrasi ketat hampir hanya menghasilkan kelompok ketiga, itulah sebabnya edge-nya merosot lebih jauh daripada simulasi antrean. Keduanya tidak tepat. Simulasi antrean memberi Anda campuran yang realistis, dan campuran itulah inti permainan: eksekusi pasif menghasilkan pendapatan dari spread, lalu membayarnya kembali lewat adverse selection; rasio di antara keduanya adalah strategi Anda yang sebenarnya.
Cara pandang lama dari desk ekuitas masih relevan: order maker adalah opsi gratis yang Anda tulis untuk pasar. Seseorang mengeksekusinya saat opsi itu menguntungkan. Backtest Anda memungut preminya, tetapi melupakan bahwa opsi itu juga punya sisi pembayaran.
Transaksi yang tak Anda dapatkan mengubah strategi, bukan sekadar biayanya
Inilah bagian yang paling ingin saya minta Anda renungkan. Saat model eksekusi taker Anda buruk, Anda mendapat transaksi yang tepat dengan harga yang salah, dan koreksi biaya memperbaiki sebagian besar masalahnya. Saat model eksekusi maker Anda buruk, Anda mendapat kumpulan transaksi yang sepenuhnya salah. Kira-kira 2,900 dari 4,180 entry Anda sebenarnya tidak pernah terjadi. Sebagian di antaranya adalah sinyal terbaik Anda, pada candle yang melonjak menembus level lalu berbalik — persis bentuk pergerakan saat pasar melesat pergi tanpa Anda.
Jadi cabang kondisi saat order tidak terisi perlu logika sungguhan. Apa yang dilakukan strategi jika entry belum terisi saat sinyal sudah kedaluwarsa? Mengejar dengan order taker dan membayar spread plus impact? Memasang ulang lebih rendah dan menerima basis entry yang berbeda? Melewatkan transaksi dan tetap tanpa posisi? Tiap pilihan menghasilkan kurva ekuitas yang sangat berbeda, dan tak satu pun berarti "anggap order terisi." Dalam pengujian kami, menambahkan aturan pengejaran yang jujur (menyeberangi spread setelah 20 detik tanpa fill, dibatasi pada slippage 3 bp) memulihkan sekitar sepertiga transaksi yang hilang dan kira-kira setengah selisih Sharpe antara model naif dan simulasi antrean. Hasilnya sangat menarik, dan baru terlihat setelah model fill cukup nyata untuk membuat pertanyaan itu bermakna.
Pemeriksaan kewajaran sederhana, 10 menit: ambil log paper trading langsung dan backtest Anda untuk periode yang sama. Bandingkan tingkat fill, bukan PnL. Jika backtest mengisi 100% order pasif sementara paper trading hanya 34%, Anda tidak sedang membandingkan strategi; Anda sedang melihat bug model fill. Perbaiki itu sebelum memeriksa satu angka imbal hasil pun.
Dua hal kecil lainnya selagi saya mengajak Anda
Penolakan post-only. Jika Anda menggunakan post-only untuk memastikan tingkat biaya maker dan buku order berubah antara keputusan Anda dan acknowledgment dari exchange, order akan ditolak alih-alih masuk antrean. Dalam log paper trading kami, ini terjadi pada 3-6% percobaan BTCUSDT pada jam normal dan lebih dari 15% pada menit setelah rilis CPI AS. Order yang ditolak bukan order terisi dan juga bukan order pasif yang belum terisi; itu transaksi yang tidak pernah ada. Jika backtest Anda tidak memiliki kondisi tersebut, jumlah transaksi Anda membengkak tepat pada kondisi pasar yang paling Anda pedulikan.
Pencegahan self-trade dan jejak order Anda sendiri. Dengan 0.4 BTC, Anda tidak menggerakkan BTCUSDT, jadi abaikan saja. Namun Anda menyebut ingin menjalankan ini pada perpetual altcoin mid-cap yang nilai notional top-of-book-nya sering di bawah $15k. Di sana, order Anda merupakan bagian antrean yang berarti, dan angka antrean di depan yang Anda ambil snapshot-nya mencakup dampak keberadaan order Anda sebelumnya. Begitu ukuran Anda melebihi sekitar 10% dari level pasif, simulasi perlu memperhitungkan reaksi peserta lain terhadap Anda. Terus terang, pada tahap itu saya lebih percaya paper trading daripada simulasi apa pun yang bisa kita tulis.
Jalankan ulang dengan simulasi antrean dan kirimkan tabel mark-out yang dipisahkan menurut jenis fill. Jika kelompok pantulan masih menyumbang sebagian besar edge dan kelompok penembusan tidak menghabiskan semuanya, mungkin ada sesuatu yang layak diuji di paper trading. Jika seluruh hasilnya berasal dari 2,900 fill yang sebenarnya tak akan Anda dapatkan, lebih baik mengetahuinya sekarang daripada setelah empat minggu menyaksikan akun paper trading tidak melakukan apa yang dijanjikan notebook.
← Semua artikel


