Commit sama, file parquet sama, mesin sama, dua kali dijalankan dengan jeda satu jam. Sharpe 1.34 dan Sharpe 1.19. Imbal hasil total 41.2% dan 37.8%. Jumlah trade 1,418 dan 1,421. Kurva ekuitas keduanya cocok hingga setiap angka desimal yang ditampilkan selama 11,205 bar berturut-turut sebelum mulai berbeda.
Tiga angka pertama itu mengganggu. Angka terakhir yang menarik, karena menunjukkan bahwa masalahnya bukan galat floating point yang tersebar di sepanjang proses. Ada sesuatu yang diskret terjadi pada bar tertentu, lalu dampaknya berlipat setelah itu. Tulisan ini membahas cara menemukan bar tersebut, serta dampak selisih 0.15 itu terhadap setiap sweep parameter yang pernah Anda jalankan.
Strateginya: momentum lintas-seksi pada 30 perpetual USDⓈ-M dengan volume tertinggi, rebalance setiap 4 jam, long pada lima teratas berdasarkan imbal hasil 12 jam, short pada lima terbawah, riwayat 18 bulan, dengan biaya dan funding dibebankan per leg.
Menemukan bar saat kedua proses mulai berbeda
Jika Anda mencatat ekuitas per bar dengan presisi penuh, ini hanya memakan waktu sekitar empat menit. Ekspor kedua proses ke CSV berisi (bar_index, equity, open_positions_hash), muat keduanya, lalu cari indeks pertama yang nilainya berbeda. Skrip itu sudah kami tulis untuk bug lain, dan hanya karena itulah saya tidak menghabiskan pagi untuk mengerjakannya.
Bar 11,206, 2025-03-14T08:00 UTC. Ekuitas pada bar sebelumnya identik hingga 13 angka signifikan. Pada bar 11,206, hash posisi berbeda: proses A long SOL, proses B long AVAX. Sisinya sama, nilai nosionalnya sama, simbolnya berbeda. Semua yang terjadi setelah itu merupakan dampak lanjutan.
Jadi saya mencetak input pemeringkatan untuk bar itu dari kedua proses. Semuanya identik. Sama persis hingga tingkat byte, dengan 30 simbol yang sama dan 30 skor yang sama. Dua skor bernilai 0.0. Tepat nol, keduanya, karena kedua aset mencatat bar 4 jam tanpa trade dalam jendela lookback, sehingga rasio close terhadap close menjadi satu dan logaritmanya menjadi nol. Bukan nol akibat pembulatan. Benar-benar nol.
Dua simbol seri di peringkat lima. Pemilihan mengambil lima teratas. Mana dari keduanya yang terpilih bergantung pada urutan yang tersisa setelah pengurutan, dan pengurutan itu ternyata tidak bekerja seperti asumsi saya.
Nilai seri, pengurutan tidak stabil, dan hal yang saya abaikan selama dua tahun
Pemeringkatan dilakukan melalui agregasi berkelompok, dan frame yang menjadi inputnya berasal dari dict comprehension atas sekumpulan simbol yang dibangun ulang setiap bar dari pengambilan data asinkron. Urutan iterasi set berubah mengikuti hash seed, dan Python mengacak hash seed string untuk setiap proses kecuali Anda menetapkan PYTHONHASHSEED. Jadi urutan baris sebelum pengurutan berbeda antarpemrosesan. Karena pengurutan pada kunci yang seri tidak stabil, hasil penentuan urutannya pun berbeda.
Nilai seri seperti ini bukan kejadian langka. Ini sifat yang melekat pada struktur sistem. Setiap kali suatu fitur mencapai batas atau dipangkas, Anda menciptakan nilai yang benar-benar sama: bar tanpa volume menghasilkan imbal hasil tepat nol, z-score yang dipangkas terpaku pada ±3.0, transformasi peringkat dengan sedikit nilai berbeda menghasilkan banyak nilai seri, filter boolean memberi skor 1.0 untuk semua yang lolos. Selama 18 bulan dengan bar 4 jam, proses ini memiliki 47 bar dengan nilai seri di batas pemilihan. Tiga di antaranya mengubah aset yang terpilih dalam portofolio. Selebihnya seri antara dua aset yang keduanya sudah masuk atau keduanya sudah tidak masuk.
Selama sekitar sehari saya yakin pemuat data bersifat nondeterministik, karena itulah jawaban yang paling menarik. Ternyata bukan. Memang tak pernah begitu. Hanya set, nilai seri, dan asumsi tentang kestabilan pengurutan yang tak pernah dituliskan siapa pun.
Mengapa tiga trade bisa menghasilkan selisih Sharpe 0.15
Bagian inilah yang sering diperdebatkan orang. Jawabannya: backtest dengan ukuran posisi berdasarkan fraksi ekuitas adalah sistem yang bergantung pada lintasan. Jika ukuran posisi 8% dari ekuitas saat ini per leg, selisih ekuitas pada bar n berarti selisih pada setiap nilai nosional mulai bar n dan seterusnya.
Divergensi pertama dampaknya kecil jika dilihat sendiri. Leg AVAX proses B merugi 2.1% selama sembilan jam; leg SOL proses A untung 0.4%. Selisih ekuitas setelah trade itu: 0.21%. Sepele. Namun sejak saat itu, kedua proses bukan lagi strategi yang sama. Ukuran posisi yang mereka pegang sedikit berbeda, sehingga akrual funding yang dikenakan juga sedikit berbeda, dan dua nilai seri lain di batas pemilihan kemudian kembali menghasilkan urutan berbeda karena skor inputnya kini berasal dari posisi yang dipegang dengan selisih kecil. Salah satunya terjadi pada 2025-03-27, sehari sebelum tren enam hari yang menghasilkan sekitar sepertiga dari total PnL proses. Proses A mengikuti seluruh pergerakan; proses B masuk satu rebalance terlambat.
Selisih imbal hasil: 3.4 poin. Selisih Sharpe lebih besar daripada yang tersirat dari selisih imbal hasil karena trade proses B yang bergeser waktunya bertepatan dengan periode yang lebih volatil. Akibatnya penyebut naik sementara pembilang turun. Penyebab kecil, dua faktor penguat.
Jika ukuran posisi Anda berupa nosional tetap dan entry tidak bergantung pada posisi yang sedang dipegang, dampaknya jauh lebih kecil. Kebanyakan strategi menarik tidak memenuhi keduanya.
Lima sumber masalah ini biasanya muncul
| Sumber | Gejala | Perbaikan |
|---|---|---|
PYTHONHASHSEED yang seed-nya tidak ditetapkan, dengan urutan iterasi set/dict sebagai input pengurutan | Penentuan urutan saat seri berubah antarpemrosesan; divergensi pertama terjadi pada bar tertentu | Tetapkan seed; urutkan dengan kunci sekunder eksplisit (simbol) agar hasil saat seri selalu sama |
Pengurutan tidak stabil pada kunci seri (quicksort bawaan di NumPy/pandas) | Sama seperti di atas, tetap terjadi meski seed sudah ditetapkan | kind="stable", atau jadikan kuncinya urutan total |
| RNG tanpa seed dalam bootstrap, pengacakan train/test, atau jitter isian sintetis | Perbedaan tersebar di seluruh proses, tanpa titik divergensi yang jelas | Gunakan satu seed eksplisit untuk setiap komponen dan catat dalam manifest proses |
| Reduksi float paralel (urutan penjumlahan bergantung pada jumlah thread) | Perbedaan pada beberapa bit terakhir, biasanya tidak berbahaya sampai melewati ambang perbandingan | Tetapkan jumlah thread untuk proses riset; jangan pernah membandingkan float dengan == di batas pengambilan keputusan |
| Versi pustaka yang tidak ditetapkan | Dapat direproduksi hari ini, belum tentu pada bulan November | Sertakan hash lockfile dalam manifest bersama hash snapshot data |
Baris keempat tidak sepenting yang dikhawatirkan orang, sedangkan baris pertama terus-menerus menjadi sumber masalah.
Reproduksibilitas hingga tingkat bit adalah alat, bukan keutamaan
Anda membutuhkan determinisme agar ketika mengubah satu baris, perubahan pada kurva ekuitas bisa dikaitkan dengan baris itu. Itu saja alasannya. Kini setiap proses agen di Stratmill mencatat manifest berisi hash snapshot data, hash lockfile, dan setiap seed. Jika proses dijalankan ulang tetapi kurva sebelumnya tidak bisa direproduksi hingga tingkat bit, build itu gagal, bukan sekadar hal yang menarik.
Namun setelah hasilnya bisa direproduksi, sengaja buat variasi. Jalankan proses itu 64 kali dengan 64 seed dan amati sebarannya:
Rentang jitter. Strategi sama, data sama, dengan 64 permutasi tie-break dan urutan pengisian yang menggunakan seed. Sharpe p5 1.12, median 1.27, p95 1.41. Lebar rentang 0.29.
Sekarang lihat kembali sweep parameter. Konfigurasi terbaik mendapat skor 1.46. Konfigurasi di peringkat 40 dari 96 mendapat skor 1.31. Selisihnya 0.15, yaitu setengah dari rentang jitter. Sweep itu tidak mengurutkan kedua konfigurasi tersebut. Sweep hanya mengambil satu sampel dari distribusi masing-masing, lalu mengurutkan hasil sampelnya.
Cara pandang itu mengubah cara kami memilih. Hasil sweep hanya bisa menjadi pemeringkatan jika selisih antar-konfigurasi melampaui jitter satu konfigurasi. Pada strategi yang bergantung pada lintasan dengan 1,400 trade, jitter biasanya cukup besar untuk membuat sepertiga teratas papan peringkat seri. Jika itu terjadi, pilih berdasarkan hal yang tidak bisa disamarkan oleh rentang tersebut: turnover lebih rendah, parameter lebih sedikit, asumsi biaya yang bisa Anda pertahankan di hadapan orang skeptis, atau perilaku yang lebih baik pada fold walk-forward yang paling tidak Anda sukai. Itulah pembeda yang nyata. Keunggulan Sharpe 0.15 bukan.
Satu hal lagi yang sebaiknya dilakukan sebelum memercayai semua hasilnya. Jalankan backtest Anda dua kali sekarang, bandingkan ekuitas per bar, dan cari tahu apakah hasil Anda identik hingga tingkat bit atau termasuk kategori selisih 0.15. Eksperimen ini hanya memakan waktu lima belas menit dan menunjukkan seberapa banyak riwayat riset Anda yang mengukur strategi, dibandingkan yang mengukur hash seed.
← Semua artikel


