Ruang Parameter Dua Sumbu: Mengapa Sebagian Besar Sweep Anda Seharusnya Nyaris Gratis
Bagian dari seri "Backtest Tanpa Ilusi".
Kutukan dimensionalitas biasanya diceritakan sebagai peringatan: setiap parameter yang Anda tambahkan melipatgandakan ruang pencarian, sehingga strategi 18 dimensi mustahil untuk di-sweep. Framing itu diam-diam mengasumsikan setiap dimensi sama mahalnya untuk dievaluasi. Kenyataannya tidak. Di mesin dual/triple-timeframe kami, dua pertiga parameter nyaris gratis untuk di-sweep, dan sepertiganya memikul hampir seluruh tagihan komputasi. Begitu Anda melihat pembelahan itu, "ruang pencariannya terlalu besar" berhenti menjadi keluhan yang tepat. Pertanyaan yang tepat adalah: dimensi yang mana yang sedang Anda bayar?
Dimensi yang tidak perlu dihitung ulang
Benchmark pencarian parameter kami menjalankan strategi momentum multi-timeframe: Hull moving average dan varian triple-smoothed-nya (HMA3) pada dua atau tiga timeframe, dengan gerbang separasi arah yang memutuskan kapan sebuah crossover cukup "bersih" untuk ditindaklanjuti. Ruang pencarian penuh untuk varian triple-TF berjumlah delapan belas dimensi — period dan panjang HMA per timeframe, plus sekumpulan ambang separasi untuk entry dan exit di tiap timeframe.
Cara naif untuk menyisir ruang ini adalah satu loop tunggal: pilih sebuah vektor parameter, bangun semua indikator dari nol, jalankan simulasi, beri skor, ulangi. Kami memulai dari sana. Lambat, dan lambatnya karena alasan yang ternyata memalukan begitu kami menyebutnya dengan lantang: untuk mayoritas besar trial yang bersebelahan, kami menghitung ulang indikator yang sama sekali tidak berubah.
Geser sedikit ambang separasi entry dari 0.03 ke 0.035 lalu jalankan ulang. Hull MA atas setahun bar 1 menit identik bit demi bit dengan trial sebelumnya — ambang itu tidak muncul di mana pun dalam definisinya. Namun loop naif tetap membangunnya kembali, atas lebih dari setengah juta bar, tiga timeframe dalamnya, setiap kali tanpa kecuali. Kami menghabiskan hampir seluruh anggaran komputasi untuk menurunkan ulang besaran-besaran yang invarian terhadap parameter yang benar-benar sedang kami variasikan.
Observasi itu adalah inti seluruh artikel ini. Begitu Anda menyadari bahwa sebagian parameter mengubah indikator dan sebagian lainnya hanya mengubah aturan keputusan yang diterapkan pada indikator yang tetap, ruang parameter berhenti menjadi awan dimensi yang datar dan menjadi dua lapisan bersarang dengan label harga yang jauh berbeda.
Dua sumbu, bukan satu

Pilah parameter berdasarkan apa yang terpaksa Anda hitung ulang ketika mengevaluasinya kembali:
-
Sumbu mahal — parameter indikator. Period timeframe dan panjang HMA. Ubah salah satunya dan Anda harus membangun ulang indikator di sepanjang seluruh deret harga: me-resample timeframe yang lebih tinggi, menjalankan pembobotan Hull, menghitung ulang deret crossover dan separasi pada tiap persilangan. Ini adalah penyisiran O(n) per bar riwayat, dan Anda membayarnya penuh untuk setiap kombinasi yang berbeda. Pada strategi triple-TF sumbu ini terdiri dari enam parameter:
(period, hma_length)untuk masing-masing timeframe tinggi, menengah, dan rendah. -
Sumbu murah — ambang keputusan. Gerbang separasi arah yang memutuskan, berbekal deret crossover dan separasi yang sudah dihitung, apakah masuk atau keluar posisi. Ubah sebuah ambang dan tidak ada satu indikator pun yang bergeser. Anda hanya menjalankan ulang satu lintasan O(n) atas sinyal yang sudah dihitung sebelumnya, memeriksa gerbang dan membukukan fill. Pada strategi triple-TF sumbu ini terdiri dari dua belas parameter (empat ambang separasi — entry-buy, entry-sell, exit-buy, exit-sell — pada masing-masing dari tiga timeframe). Varian dual-TF punya delapan (empat ambang pada masing-masing dari dua timeframe).
Jadi bentuk ruang yang sebenarnya adalah: 6 mahal + 12 murah = 18 untuk triple-TF, dan 4 mahal + 8 murah = 12 untuk dual-TF. Dua pertiga dimensionalitas hidup di sumbu murah. Dan kedua sumbu itu tidak hanya berbeda jumlah — biaya satuannya berbeda lebih dari tiga orde besaran, dan angka itulah yang menjadi pokok sisa artikel ini.
Strukturnya bersarang secara ketat. Setiap titik di sumbu mahal — satu (period, hma_length) konkret per timeframe — mendefinisikan sekumpulan array sinyal yang tetap. Di atas fondasi tetap itu bertengger satu lembar penuh sumbu murah: ribuan vektor ambang, masing-masing berupa lintasan cepat atas array yang sama. Anda membayar fondasinya sekali dan mengamortisasinya atas seluruh lembar.
Mengapa indikator invarian terhadap ambang
Caching ini hanya berfungsi berkat sebuah fakta matematis, bukan detail implementasi yang kebetulan mujur, dan fakta itu layak dinyatakan secara presisi karena dialah asumsi penopang beban. Array indikator adalah fungsi dari (period, hma_length) semata. Ambang separasi sama sekali tidak muncul dalam definisinya.
Konkretnya, untuk tiap timeframe mesin menghitung terlebih dahulu empat array yang selaras atas indeks dasar: Hull MA hma, versi triple-smoothed-nya hma3, deret crossover cross (+1 buy / −1 sell / 0), dan persentase separation pada tiap persilangan. Setiap array itu diturunkan dari harga dan dua parameter indikator. Ambangnya — angka yang menjadi pembanding bagi separation — diterapkan belakangan, pada saat pengambilan keputusan. Ia tidak pernah menyentuh array-array itu.
Ini bukan penataan ulang yang otomatis aman. Ia aman hanya karena kami cermat soal kapan informasi menjadi tersedia — pokok bahasan kajian look-ahead bias di bagian lain seri ini. Indikator timeframe tinggi pada bar dasar i dihitung dari candle timeframe tinggi yang sudah tertutup plus satu candle yang sedang terbentuk, yang running close-nya sama dengan close dasar saat ini close[i] — nilai yang sudah diketahui pada bar i. Tidak ada apa pun dari masa depan yang bocor masuk. Maka sinyal yang dihitung di muka pada bar i persis sama dengan yang akan dilihat bot live pada bar i, dan ia tetap sahih tak peduli ambang mana yang belakangan Anda ujikan terhadapnya. Meng-cache sinyal yang bocor hanya akan meng-cache kebocorannya; meng-cache sinyal yang kausal berarti meng-cache sesuatu yang benar-benar bisa Anda tradingkan.
Invarians itu memberi kita sebuah faktorisasi. Tuliskan evaluasi satu vektor parameter penuh θ = (indicator_params, threshold_params) sebagai:
signals = build_indicators(indicator_params) # EXPENSIVE, depends only on indicator_params
score = simulate(signals, threshold_params) # CHEAP, reuses signals across all thresholds
build_indicators tidak membaca threshold_params. Satu fakta itulah yang melegitimasi cache: tahan indicator_params tetap, variasikan threshold_params sebebasnya, dan signals menjadi konstanta yang Anda hitung sekali saja.
Arsitekturnya: hitung sekali, sweep berkali-kali

Mesin ini (scripts/engine_multitf.py, commit bfc8aaa di backtester kami) mengimplementasikan faktorisasi tersebut dengan dua komponen yang namanya persis menggambarkan tugasnya.
SignalCache — sumbu mahal, di-memoisasi. Ia adalah dictionary yang berkunci (period_bars, hma_length). Mintakan sinyal sebuah timeframe kepadanya dan ia mengembalikan TFSignals dari cache jika kombinasi indikator itu pernah dibangun sebelumnya, atau membangunnya sekali lalu menyimpannya. Karena kuncinya hanya parameter indikator, setiap konfigurasi ambang yang berbagi kombinasi indikator yang sama — dan pada sweep ambang yang padat itu berarti ribuan konfigurasi — mengenai entri cache yang sama. Timeframe yang lebih tinggi paling diuntungkan di sini: grid kasar berisi, katakanlah, empat kandidat period dikali segelintir panjang HMA hanyalah sejumlah kecil pembangunan mahal yang berbeda, dan tiap-tiapnya dipakai ulang di sepanjang seluruh lembar ambang yang ditumpuk di atasnya.
sweep_separations — sumbu murah, di-batch. Ia menerima array sinyal dari cache dan sebuah matriks vektor ambang (sps, berdimensi [m, 12]) lalu menjalankan semuanya lewat satu kernel terkompilasi. Tiap baris adalah satu lintasan O(n): telusuri bar-bar, terapkan gerbang, bukukan fill bebas kebocoran pada open[i+1], jumlahkan PnL dan lama waktu dalam posisi. Tidak ada indikator yang dibangun ulang di dalam loop ini — ia membaca cross dan separation langsung dari cache. Simulasi bagian dalamnya dikompilasi JIT (Numba), sehingga setelah pemanasan biaya per konfigurasi didominasi oleh satu pemindaian linear atas bar-bar, bukan oleh overhead Python.
Kedua komponen itu tersusun menjadi pencarian bersarang yang alami: loop luar menelusuri sumbu mahal (tiap iterasi membangun dan meng-cache satu kombinasi indikator), dan loop dalam menyebarkan batch lebar melintasi sumbu murah di atas array ter-cache tersebut. Dalam kode, bentuknya persis begitu — loop mahal yang kecil membungkus batch murah yang lebar:
cache = SignalCache(base_close, base_ts) # keyed by (period, hma_length)
for htf_p, htf_h, mtf_p, mtf_h, ltf_p, ltf_h in indicator_grid: # EXPENSIVE axis (coarse)
htf = cache.get(htf_p, htf_h) # built once, then a cache hit forever
mtf = cache.get(mtf_p, mtf_h)
ltf = cache.get(ltf_p, ltf_h)
sps = sample_thresholds(m=4000) # CHEAP axis: [m, 12] threshold vectors
pnl, n_trades, bars_in_pos = sweep_separations( # one compiled batch, no indicator work
base_close, base_open, htf, ltf, sps, mtf=mtf)
Anda menyentuh pembangun mahal sesedikit yang diizinkan grid indikator, dan membiarkan sweep murah yang menanggung volumenya — ribuan baris sps terhadap array yang tidak pernah bergeser. Itulah keseluruhan optimasinya — tanpa aproksimasi, tanpa fidelitas yang hilang, sekadar menolak menghitung ulang apa yang tidak berubah.
Berapa sebenarnya harga "gratis": angka-angkanya

Kami mengukur kedua sumbu pada beban kerja demo: setahun penuh bar ETHUSDT 1 menit (~527k bar), triple-TF, dengan indikator sudah dipanaskan dan kompilasi JIT dikeluarkan dari pengukuran waktu.
Di sumbu murah, sweep_separations bertahan pada laju ~5,600 konfigurasi ambang per detik. Itu adalah simulasi lengkap — gerbang, fill, PnL, eksposur — untuk tiap konfigurasi, atas lebih dari setengah juta bar, dengan biaya kira-kira 180 mikrodetik per konfigurasi. Ia bisa secepat itu karena sama sekali tidak mengerjakan indikator: setiap konfigurasi membaca array cross dan separation ter-cache yang sama.
Sekarang taksir harga alternatifnya. Membangun satu set indikator triple-TF sekali memakan waktu berorde beberapa ratus milidetik (~0.3 s) — me-resample tiga timeframe, pembobotan Hull, ekstraksi crossover dan separasi atas satu tahun penuh. Jika Anda menghitung ulang indikator di dalam loop konfigurasi — desain loop tunggal yang naif — setiap satu dari 5,600 konfigurasi per detik itu justru membayar pembangunan indikator penuh. Biaya per konfigurasi membengkak dari ~180 mikrodetik menjadi ~0.3 detik:
| Biaya per konfigurasi | Konfigurasi/detik | |
|---|---|---|
| Sumbu murah (sinyal ter-cache) | ~180 µs | ~5,600 |
| Hitung ulang indikator per konfigurasi | ~0.3 s | ~3.4 |
Rasionya ~1,600x. Menyisir sumbu murah di atas sinyal ter-cache kira-kira tiga orde besaran lebih murah daripada desain naif yang membangun ulang indikator untuk setiap vektor ambang. Konkretnya: satu batch beberapa ribu konfigurasi ambang yang selesai dalam waktu kurang dari satu detik lewat jalur ter-cache akan memakan hampir satu jam jika tiap konfigurasi membangun ulang indikatornya. Hasil sama, fidelitas sama, tanpa jalan pintas dalam matematikanya — satu-satunya perbedaan adalah yang satu menghitung ulang sesuatu yang invarian dan yang lain tidak.
Ini bukan optimasi mikro yang Anda taburkan di akhir. Ia mengubah pencarian mana saja yang layak dikerjakan. Pada 5,600 cfg/s, sumbu ambang cukup padat untuk dieksplorasi dengan benar — Anda mampu membayar grid halus atau sampel random/QMC yang panjang per kombinasi indikator — sementara sumbu mahal tetap berupa segelintir pembangunan yang sengaja dibuat kasar. Anggaran komputasi mengalir ke tempat parameter benar-benar berharga mahal.
Kutukan dimensionalitas, ditaksir ulang harganya

Kembalilah ke framing pembuka artikel ini. Kutukan dimensionalitas mengatakan ruang pencarian tumbuh eksponensial terhadap jumlah parameter, sehingga lebih banyak dimensi berarti secara kategoris lebih buruk. Itu benar soal ukuran grid. Tapi menyesatkan soal biaya untuk mencakupnya, karena ia menghargai setiap dimensi secara identik.
Begitu Anda memisahkan sumbunya, hitungannya terbaca berbeda. Strategi triple-TF berdimensi 18, tetapi hanya 6 di antaranya yang mahal. Dua belas sisanya adalah dimensi sumbu murah yang memperluas grid tanpa memperbesar tagihan komputasi secara berarti — Anda bisa melempar ribuan konfigurasi ke sana dengan harga recehan. Strategi dual-TF berdimensi 12 dengan hanya 4 dimensi mahal dan 8 dimensi murah. Pada keduanya, mayoritas "kutukan" itu terkonsentrasi di sumbu yang nyaris tak berbiaya untuk di-sweep.
Maka cara jujur menalar biaya pencarian bukanlah "berapa banyak parameter" melainkan "berapa banyak parameter mahal, dan seberapa kasar grid-nya boleh dibuat". Sumbu mahal adalah tempat eksponensial benar-benar menyakitkan, dan di sanalah Anda ingin grid yang kecil dan dipilih cermat — beberapa kandidat period, rentang panjang HMA yang sedang-sedang saja — bila perlu dipertajam dari kasar ke halus, sejalan dengan semangat drill-down resolusi adaptif yang kami pakai di tempat lain. Sumbu murah adalah tempat Anda boleh berfoya-foya, karena tiap konfigurasi ambang tambahan hanya 180 mikrodetik.
Penaksiran ulang harga ini menggeneralisasi jauh melampaui strategi HMA kami. Polanya — sebagian parameter mengubah fitur, kebanyakan parameter mengubah aturan yang diterapkan pada fitur yang tetap — berulang di mana-mana dalam trading sistematis. Panjang indikator, frekuensi resampling, dan jendela lookback itu mahal; ambang entry/exit, jarak stop, pengali ukuran posisi, dan gerbang konfirmasi itu murah. Kapan pun sebuah parameter hanya membentuk ulang batas keputusan atas sinyal yang sudah dihitung, ia milik sumbu murah, dan di sanalah ia harus di-sweep. Kemenangan caching serumpun muncul di caching parquet multi-timeframe dan di tangga kecepatan mesin backtest yang lebih luas.
Di mana makan siang gratis tetap punya tagihan
Sumbu murah itu murah dalam komputasi. Ia tidak murah dalam statistika, dan mencampuradukkan keduanya adalah cara mengubah kemenangan performa menjadi mesin overfitting.
Setiap konfigurasi ambang yang Anda evaluasi adalah satu trial, dan ketika Anda menjalankan ribuan trial pada satu dataset, yang terbaik di antaranya tampak bagus sebagian karena keberuntungan. Membuat trial-trial itu 1,600x lebih murah tidak menghilangkan faktor keberuntungan — hanya mempermudah penumpukannya. Sweep ambang yang padat justru adalah situasi tempat inflasi multiple-testing menggigit paling keras: banyak kandidat, satu riwayat, dan aturan seleksi yang melaporkan nilai maksimum. Mesin tercepat di dunia akan dengan senang hati menyerahkan kepada Anda sebuah vektor ambang yang mencocoki noise jendela pengujian Anda dengan indah lalu gagal di luar sampel.
Maka disiplinnya harus tumbuh seiring kecepatan. Begitu biaya komputasi satu trial jatuh mendekati nol, pembukuan statistik menjadi kendala pengikat, dan Anda harus membayarnya secara eksplisit:
- Deflasikan terhadap jumlah trial. Nilai sang pemenang terhadap berapa banyak konfigurasi yang Anda coba, bukan terhadap nol. Deflated Sharpe Ratio dan probabilitas overfitting backtest ada persis untuk ini — keduanya mengubah "kami mencoba 4,000 vektor ambang" menjadi potongan atas edge yang dilaporkan.
- Validasi di luar sampel, per fold. Sweep murah tetap harus dijalankan di dalam pembagian walk-forward yang jujur; ambang yang hanya menang in-sample tidak ada harganya secepat apa pun Anda menemukannya. Mesin kami menjaga fill tetap bebas kebocoran (
open[i+1]) persis agar sumbu murah tidak bisa membeli performa dengan mengintip. - Utamakan plateau, bukan puncak. Karena sumbu ambang padat dan cepat, Anda bisa memetakan permukaan responsnya, bukan sekadar argmax-nya. Wilayah ambang yang luas dan semuanya bekerja adalah edge sungguhan; satu maksimum runcing tunggal adalah artefak hasil pencocokan — pembedaan plateau versus puncak, yang menjadi terjangkau justru berkat kecepatan yang sedang kami uraikan.
Bacalah struktur dua sumbu ini dengan cara yang benar: ia tidak membelikan Anda keyakinan lebih, ia membelikan Anda lebih banyak trial dengan biaya keyakinan per trial yang sama. Itu sungguh berharga — cakupan padat sumbu murah itulah yang memungkinkan Anda menemukan plateau dan mengkarakterisasi permukaannya — tetapi hanya jika Anda menjaga buku besar statistik tetap jujur. Kecepatan menghapus alasan komputasi untuk tidak mencari secara menyeluruh; ia tidak menghapus kewajiban mendiskon apa yang ditemukan pencarian menyeluruh itu.
Cara menyusun pencarian Anda sendiri
Untuk menerapkan ini pada strategi Anda sendiri, pekerjaannya sebagian besar adalah klasifikasi — memilah parameter Anda ke sumbu yang tepat — diikuti sebuah loop bersarang:
- Labeli setiap parameter berdasarkan apa yang terpaksa Anda hitung ulang. Jika mengubahnya mengubah array indikator/fitur, ia mahal. Jika ia hanya mengubah perbandingan, ambang, atau aturan penentuan ukuran yang diterapkan pada array tetap, ia murah. Kalau ragu, tanyakan: apakah parameter ini muncul di mana pun di dalam definisi indikatornya? Jika tidak, ia murah.
- Kunci cache hanya pada sumbu mahal. Memoisasi konstruksi fitur berdasarkan parameter indikatornya (seperti yang dilakukan
SignalCachedengan(period, hma_length)). Trial-trial bersebelahan yang berbagi kombinasi indikator lantas memakai ulang array yang sama secara gratis. - Batch sumbu murah di atas fitur ter-cache. Jalankan konfigurasi ambang sebagai loop terkompilasi yang rapat atas sinyal yang sudah dihitung, bukan sebagai evaluasi ulang penuh. Dari sinilah throughput Anda — dan dalam kasus kami ~5,600 cfg/s — berasal.
- Sarangkan loop-nya: mahal di luar, murah di dalam. Jaga grid mahal tetap kecil dan disengaja (kasar, atau kasar-ke-halus); biarkan sweep murah menjadi padat. Belanjakan anggaran di tempat parameter memang berharga mahal.
- Anggarkan trial terhadap overfitting, bukan terhadap jam. Kini setelah jam bukan lagi batasnya, biarkan deflated Sharpe / PBO yang menjadi batas. Putuskan berapa banyak trial sumbu murah yang mampu Anda tanggung secara statistik, dan validasi pemenangnya di luar sampel.
Imbal hasil rekayasa dan pagar pengaman statistik adalah dua sisi dari satu gagasan: memisahkan sumbu memungkinkan Anda menyisir dimensi murah secara tuntas, dan persis karena itulah Anda kemudian wajib mendiskon setuntas apa Anda menyisirnya.
Intisari
- Tidak semua dimensi sama biayanya. Parameter sebuah strategi terbelah menjadi sumbu mahal (indikator — dihitung ulang atas seluruh deret) dan sumbu murah (ambang — satu lintasan O(n) atas sinyal yang sudah dihitung). Di mesin kami itu berarti 6 mahal + 12 murah untuk triple-TF, 4 + 8 untuk dual-TF.
- Indikator invarian terhadap ambang, dan invarians itulah keseluruhan optimasinya. Konstruksi fitur hanya bergantung pada parameter indikator, jadi Anda membangun sekali, meng-cache berdasarkan
(period, hma_length), dan memakainya ulang di setiap konfigurasi ambang yang berbagi kombinasi itu. - Sumbu murah berjalan ~1,600x lebih murah. ~5,600 konfigurasi ambang/detik (~180 µs per konfigurasi) di atas sinyal ter-cache, versus ~0.3 s per konfigurasi jika Anda membangun ulang indikator setiap kali. Fidelitas sama — satu-satunya perbedaan adalah menolak menghitung ulang sesuatu yang invarian.
- Kutukan dimensionalitas sejatinya adalah kutukan dimensionalitas mahal. Sebagian besar jumlah parameter hidup di sumbu yang nyaris gratis untuk di-sweep. Jaga grid mahal tetap kasar; berfoya-foyalah di sumbu murah.
- Kecepatan menggeser kendala pengikat dari komputasi ke statistika. Sweep yang padat dan cepat adalah mesin multiple-testing. Deflasikan terhadap jumlah trial, validasi per fold, dan utamakan plateau ketimbang puncak — makan siang gratisnya nyata, tetapi tagihan statistiknya bukan pilihan.
Mesin lengkapnya — SignalCache, sweep_separations, simulasi multi-TF bebas kebocoran, dan uji paritas yang memakukannya pada semantika running-candle live — tinggal di scripts/engine_multitf.py (commit bfc8aaa) di backtester kami. Lain kali seseorang berkata kepada Anda bahwa strategi 18 parameter terlalu besar untuk dicari, tanyakan berapa banyak dari parameter itu yang benar-benar menggerakkan indikator. Biasanya sepertiganya, dan sisanya nyaris gratis.
Penulis
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.