Salah satu pengujian backtest yang berguna tidak ada hubungannya dengan mencari parameter yang lebih baik: hentikan proses di tengah jalan, pulihkan, lalu selesaikan replay yang sama. Dengan input identik dan simulator eksekusi yang terkendali, keputusan, order, dan ekuitasnya semestinya sama dengan proses yang berjalan tanpa jeda.
Jika hasilnya berbeda, berarti Anda menemukan masalah pengelolaan state. Strategi bergantung pada sesuatu yang tidak Anda simpan atau tidak bisa Anda rekonstruksi. Ketergantungan itu penting setiap kali proses riset dilanjutkan, worker diganti, atau layanan paper trading menerapkan kode baru.
Saya suka pengujian ini karena jawaban yang diharapkan sangat jelas. Tidak ada perdebatan soal apakah pasar berubah. Kedua proses menerima data pasar yang sama.
Berikut tiga cara memulai ulang yang keliru. Angkanya hanya ilustrasi; setiap kegagalan ini bisa terjadi dalam sistem yang selain itu deterministik.
1. Muat beberapa bar lalu anggap indikator sudah siap
Misalkan strategi menggunakan exponential moving average 100 periode. Pembaruannya adalah:
alpha = 2 / 101
ema_next = alpha * close + (1 - alpha) * ema_previous
Proses yang berjalan tanpa jeda meneruskan EMA yang sudah terakumulasi. Proses yang dimulai ulang mengambil 100 bar, menginisialisasi EMA dengan harga penutupan pertama, lalu menganggap indikator 100 periode membutuhkan 100 observasi.
Anggapan itu mencampuradukkan parameter penghalusan indikator dengan jendela memori terbatas. EMA mempertahankan kontribusi yang kian mengecil dari state awalnya. Jika dua versi memulai dengan selisih EMA sebesar 10 unit harga, harga-harga berikutnya yang identik akan mengurangi selisih tersebut seperti berikut:
| Pembaruan sejak inisialisasi | Selisih yang tersisa | Porsi dari galat awal |
|---|---|---|
| 100 | 1.353 | 13.53% |
| 250 | 0.0674 | 0.674% |
| 500 | 0.000454 | 0.00454% |
Perhitungannya adalah 10 * (99 / 101)^k. Mengambil 100 bar hanya memberi Anda 99 pembaruan jika observasi pertama digunakan sebagai nilai awal.
Dampaknya biasanya muncul di dekat ambang keputusan. Satu proses melihat harga di atas EMA, sementara proses lainnya melihat harga di bawahnya. Selisih numerik yang kecil memicu satu transaksi tambahan. Setelah itu terjadi, cooldown, kas yang tersedia, dan keputusan berikutnya juga bisa menyimpang.
Simpan state indikator rekursif, status inisialisasinya, dan event terakhir yang diproses. Sebagai alternatif, putar ulang dari state awal yang diketahui. Pemanasan yang lebih lama bisa menghasilkan pendekatan yang memadai, tetapi tentukan durasinya berdasarkan toleransi galat yang jelas dan periksa apakah toleransi itu dapat mengubah keputusan. “Lima kali periodenya” adalah kebiasaan, bukan bukti.
Dan indikator bukan satu-satunya yang membutuhkan riwayat. Persentil bergulir membutuhkan jendelanya. Model online mungkin membutuhkan state pengoptimalnya. Aturan yang menunggu tiga bar setelah rugi perlu mengingat kerugian dan penghitungnya.
2. Simpan posisi, tetapi lupakan order yang sedang diproses
Posisi target Anda adalah 10 unit. Order beli 10 unit sudah terisi 4, sehingga 6 masih tertunda. Anda menyimpan checkpoint posisi sebesar 4, memulai ulang, lalu mengirim order beli lagi untuk sisa 6.
Jika sisa order awal dan penggantinya sama-sama terisi, Anda memiliki 16 unit.
Versi backtest dari bug ini sering tidak terlihat karena memulai ulang mesin fill diam-diam menghapus order yang masih aktif. Dalam paper trading, simulator atau layanan eksternal mungkin tetap menyimpannya. Kode pemulihan yang sama kemudian menghasilkan eksposur berbeda, bergantung pada komponen mana yang bertahan.
| Saat mulai ulang | State sebenarnya | Yang terlihat dalam pemulihan posisi saja |
|---|---|---|
| Posisi target | 10 | 10 |
| Posisi yang sudah terisi | 4 | 4 |
| Kuantitas beli yang masih tertunda | 6 | 0 |
| Kuantitas tambahan yang dibutuhkan | 0 | 6 |
Dampaknya berupa lonjakan order yang tidak bisa dijelaskan segera setelah pemulihan. Kadang eksposur menjadi dua kali lipat. Kadang posisi ditutup meski order pelindungnya masih aktif, sehingga order itu dapat membuka posisi baru kemudian.
Checkpoint perlu menyimpan identitas dan status siklus hidup order bersama posisi. Sebelum membuat tindakan baru, proses pemulihan harus mencocokkan catatan tersebut dengan sistem eksekusi. Jika hasil sebuah order belum diketahui, hasilnya perlu diselidiki; menganggap “tidak ada konfirmasi yang tersimpan” berarti “belum pernah dikirim” adalah cara order duplikat tercipta.
ID order klien yang stabil membantu Anda mencari tahu apa yang terjadi. ID itu hanya mencegah duplikasi jika sistem penerima benar-benar menerapkan aturan keunikan atau idempotensi yang diperlukan. Simpan juga ID eksekusi yang sudah diproses agar fill yang diputar ulang tidak menambah posisi dua kali.
Saya punya ketertarikan khusus pada layar status order yang membosankan. Saat tiba hari untuk memulai ulang, baris-baris kecilnya tiba-tiba menjadi antarmuka paling menarik di gedung ini.
3. Pulihkan posisi, lalu mulai pembukuan P&L dari awal
Perhatikan contoh spot tanpa leverage dan tanpa biaya. Mulai dengan kas $10,000, beli 10 unit pada harga $100, lalu simpan checkpoint saat harga mark mencapai $110.
State yang benar adalah kas $9,000 ditambah posisi senilai $1,100: ekuitas sebesar $10,100. Jika pemulihan mengembalikan 10 unit tetapi mengatur ulang kas ke nilai awal $10,000, sistem melaporkan $11,100. Anda menciptakan $1,000 hanya dengan memulai ulang sebuah proses.
Kasus lain dampaknya tidak sedramatis itu. Pemulihan mempertahankan ekuitas tetapi mengatur ulang harga masuk ke $110. Total ekuitas tetap benar, sementara atribusi terealisasi versus belum terealisasi berubah. Jika stop atau kondisi keluar mengacu pada harga masuk, jalan pintas pembukuan itu kini mengubah perilaku trading.
Atau sistem melupakan puncak ekuitas sebelumnya. Misalkan ekuitas sempat mencapai $10,600 sebelum turun ke $10,100. Drawdown-nya sekitar 4.72%. Jika nilai tertinggi itu diatur ulang saat pemulihan, strategi tiba-tiba menganggap drawdown-nya nol. Kontrol risiko apa pun yang berbasis drawdown baru saja diatur ulang tanpa izin.
Dampaknya bisa berupa lonjakan ekuitas, drawdown yang tampak membaik secara mencurigakan, atau aturan risiko yang berhenti aktif setelah penerapan kode. Pertahankan pembukuan dan state strategi yang bergantung pada akuntansi: arus kas, posisi, dasar biaya yang berlaku, biaya yang terakumulasi, dan memori kontrol risiko. Cocokkan ekuitas yang dipulihkan dengan pembukuan pada timestamp valuasi yang sama.
Checkpoint membutuhkan batas yang konsisten. Menyimpan kas setelah fill tetapi kuantitas posisi sebelum fill itu menghasilkan state yang tidak pernah ada. Simpan state terkait secara bersamaan, atau catat urutan event yang persisten agar state dapat dibangun ulang. Simpan kursor event bersama state itu agar pemulihan tidak melewatkan fill atau menerapkannya dua kali.
Pengujian yang akan saya pertahankan dalam harness riset menjalankan satu replay acuan tanpa jeda, lalu memulai ulang proses kedua pada titik-titik yang sengaja dibuat rumit: saat inisialisasi indikator, setelah partial fill, dan ketika batas risiko sedang aktif. Gunakan urutan event yang sama dan pertahankan state acak simulator. Bandingkan keputusan pertama setelah pemulihan, catatan order dan fill, serta lintasan ekuitas. Saldo akhir yang cocok saja bisa menyembunyikan kesalahan yang saling meniadakan.
Untuk crash setelah order dikirim tetapi sebelum konfirmasi diterima, harness juga perlu mempertahankan state layanan eksekusi secara terpisah dari proses strategi. Jika tidak, harness menghapus ketidakpastian yang justru ingin Anda uji.
Spesifikasi strategi mencakup apa yang diingatnya. Buatlah memori itu cukup jelas sehingga Anda bisa mematikan proses di tengah replay dan menunjukkan dengan tepat bagaimana proses itu kembali bekerja.
← Semua artikel


