← Kembali ke artikel
July 2, 2026
5 menit baca

Pajak Framework: Saat Library Backtest Anda Lebih Lambat daripada Loop Pandas yang Naif

Pajak Framework: Saat Library Backtest Anda Lebih Lambat daripada Loop Pandas yang Naif
#algotrading
#backtest
#performa
#pencarian parameter
#vectorization
#benchmark
Part 2 of 10 · Collection
High-Performance Backtest Engines

Bagian dari seri "Backtests Without Illusions".

Ada asumsi yang nyaman di dasar sebagian besar proyek algo-trading: bahwa library backtesting yang matang dan berbintang banyak itu pasti cepat. Ia punya kontribusi bertahun-tahun, event loop yang sungguhan, model broker, skema komisi. Tentu saja ia mengalahkan loop pandas seadanya yang akan Anda tulis sendiri. Jadi Anda pakai library itu, hubungkan strategi Anda, lalu mulai parameter sweep — beberapa ribu konfigurasi, pekerjaan semalaman. Anda kembali di pagi hari dan itu masih berjalan.

Kami membandingkan delapan mesin backtest pada satu parameter sweep yang identik dan menemukan sesuatu yang seharusnya mengubah cara Anda memilih alat untuk pencarian. Dua framework event-driven open-source paling populer — backtrader dan bt — menjalankan sweep tersebut lebih lambat daripada loop pandas naif yang kami tulis sebagai baseline buangan. Bukan sedikit lebih lambat. backtrader butuh waktu kira-kira 2,5× lipat dibanding baseline pandas; bt, sekitar 4,7×. Sementara itu, mesin vectorized/compiled pada pekerjaan yang sama berjalan sekitar 13.000× lebih cepat daripada bt.

Inilah pajak framework. Backtester populer dibangun untuk satu run yang jujur — satu strategi, satu dataset, fill yang cermat, broker yang berperilaku seperti broker sungguhan. Itu tepat yang Anda inginkan untuk validasi akhir atau pengecekan paritas-live. Dan itu justru alat yang salah untuk hal yang benar-benar menghabiskan waktu riset algo: menjalankan strategi yang sama sepuluh ribu kali dengan knob yang sedikit berbeda-beda. Artikel ini mengukur pajak tersebut, menjelaskan mekanismenya, dan memberi aturan keputusan tentang kapan framework backtesting yang "sungguhan" adalah pilihan yang salah.

Setiap angka di sini berasal dari satu harness yang dapat direproduksi (benchmarks/bench_oss_engines.py, commit 250dbb5) pada beban kerja yang identik dan terkunci paritasnya. Jika kami tidak menjalankan sebuah mesin sendiri, kami katakan dengan jelas dan menaruhnya di bagian kejujuran terpisah, alih-alih mengarang angka.

Satu run versus sepuluh ribu

Komputasi strategi yang sama mengalir menyusuri tiga jalur yang berbeda: sebuah jalur event-driven berwarna merah-oranye yang dipaksa berhenti di setiap stasiun-bar, sebuah pohon weights/rebalance dari node-node algo berwarna kuning keemasan, dan satu berkas vectorized berwarna zamrud-sian yang menyelesaikan seluruh deret sekaligus — paradigma adalah takdir untuk kecepatan sweep

Fakta yang menentukan dari parameter search adalah bahwa mesin berjalan ribuan kali tetapi analisis terjadi sekali. Biaya tetap apa pun yang Anda bayar untuk menyiapkan sebuah backtest — membangun event loop, membuat instance broker, mengalokasikan satu objek per bar — Anda membayarnya pada setiap kombinasi. Biaya yang tak terlihat dalam satu run ("siapa yang peduli soal 6 detik?") menjadi keseluruhan tagihan di seluruh sweep ("6 detik × 10.000 = 16,6 jam").

Mesin backtest terbagi menjadi tiga paradigma, dan paradigma itulah yang menentukan takdir performa sweep:

  1. Event-driven — mesin berjalan bar demi bar, memancarkan event, memanggil callback next()/onBars() Anda, merutekan order lewat objek broker. Ini adalah arsitektur backtrader, backtesting.py, PyAlgoTrade, zipline, dan nautilus_trader. Ini mencerminkan cara kerja trading live yang sesungguhnya, itulah tepatnya sebabnya ia dipercaya untuk realisme — dan itulah tepatnya sebabnya ia lambat: overhead Python per-bar dibayar 150.000 kali per kombinasi.
  2. Weights/rebalancebt berada di sini. Anda memberinya matriks target-weight dan ia melakukan rebalance pada tanggal-tanggal yang Anda tentukan. Tidak ada callback per-bar, tapi tetap ada graf objek per-event (pohon algo, ledger transaksi) yang dievaluasi dalam Python.
  3. Vectorized / compiled — seluruh strategi diekspresikan sebagai operasi array (vectorbt), kernel yang dikompilasi JIT (numba), atau kode native (Rust, kernel GPU MLX). Tidak ada Python per-bar sama sekali. Loop, jika ada, berjalan pada kecepatan mesin.

Sisa artikel ini adalah konsekuensi empiris dari taksonomi tersebut. Kami membangun satu beban kerja, membuat setiap mesin melakukan pekerjaan yang terbukti sama, lalu mengukur waktunya.

Beban kerja: satu strategi, delapan puluh knob, identik untuk semua

Sebuah benchmark hanya jujur jika setiap mesin melakukan pekerjaan yang sama. Milik kami sengaja dibuat polos — sebuah beban kerja di mana satu-satunya yang berbeda antar mesin adalah mesinnya sendiri.

  • Data. Satu deret close sintetis geometric-Brownian-motion: 150.000 bar, seed=42, volatilitas per-bar sigma=0.0008, x0=30000. Deterministik, sehingga siapa pun bisa mereproduksinya bit demi bit. Close-only secara konstruksi — OHLC diset sama dengan close pada setiap kaki, karena strategi ini adalah close-cross.
  • Strategi. Sebuah Hull Moving Average cross: HMA dengan panjang L versus varian HMA-sepertiga yang lebih cepat (HMA vs HMA3). Selalu berada di pasar, membalik long/short pada setiap cross. Ini adalah indikator yang sungguhan dan tidak sepele — dua weighted moving average bersarang ditambah smoother window akar-kuadrat — bukan SMA mainan, sehingga pekerjaan per-bar-nya representatif.
  • Sweep. 80 panjang HMA yang membentang 6..200. Itulah "sepuluh ribu run" yang dibuat cukup kecil untuk diukur langsung: 80 kombinasi independen, masing-masing backtest penuh sepanjang 150 ribu bar.
  • Biaya. Fee round-trip 0,09%, dipecah per sisi untuk mesin yang memodelkan komisi per sisi. Fill pada bar yang sama di close[i] — sinyal pada bar i dieksekusi pada close bar tersebut, konvensi yang digunakan mesin produksi kami.

Timer per-kombinasi membungkus tepat dua hal: precompute HMA numpy dan run mesin. Setup yang benar-benar sekali saja (memuat data, membangun objek bar) berada di luar timer. Ada satu run pemanasan, lalu repeat best-of-N, dan — karena mesin event-driven cukup lambat sehingga 80 kombinasi penuh akan memakan waktu berpuluh-puluh menit hingga lebih dari satu jam — kami mengukur waktu sampel yang seragam dari grid dan mengekstrapolasi secara linier. Kombinasi bersifat independen, sehingga ekstrapolasi linier tepat secara ekspektasi; konvensi yang sama diterapkan pada baseline pandas, sehingga tidak ada mesin yang diuntungkan olehnya.

Paritas: membuktikan semua orang melakukan pekerjaan yang sama

Inilah jebakan yang dijatuhi perbandingan mesin yang naif: mesin yang "cepat" mungkin saja hanya melakukan lebih sedikit. Jika backtrader mencatat 2.700 trade dan mesin vectorized Anda mencatat 40, mesin vectorized itu bukannya lebih cepat — ia salah, dan perbandingannya jadi tidak bermakna.

Jadi kami mengunci perbandingan dengan pemeriksaan paritas jumlah trade. Pada L=104, referensi numpy menghasilkan tepat 2.707 trade yang ditutup. Setiap mesin harus mereproduksi angka itu dalam toleransi ±1, atau run dibatalkan dengan assertion work-parity FAILED. Toleransi ini ada semata-mata karena mesin-mesin berbeda konvensi pembukuannya — apakah posisi terbuka terakhir dipaksa-tutup dan dihitung, apakah entry awal dihitung sebagai "trade" — bukan pada trade itu sendiri:

Engine Trade yang dilaporkan @ L=104 Konvensi
referensi numpy 2707 round-trip yang ditutup
backtesting.py 2708 +1: posisi akhir dipaksa-tutup di ujung
backtrader 2707 posisi terbuka akhir tidak dihitung
bt 2708 +1: entry awal dihitung sebagai transaksi
PyAlgoTrade 2708 +1: entry awal dihitung sebagai fill

Setiap mesin mendarat di 2707 ± 1. Apa pun perbedaan kecepatan yang muncul nanti, itu bukan artefak dari satu mesin yang diam-diam melewatkan pekerjaan. Inilah disiplin yang memungkinkan kami menaruh framework event-driven dan kernel GPU dalam satu tabel yang sama dan benar-benar memaksudkannya.

Hasilnya

Berikut tabel lengkapnya, diurutkan dari tercepat ke terlambat. combos/s adalah throughput; kolom terakhir adalah berapa lama sweep 80-kombinasi penuh berjalan. Baris baseline adalah M0 — mesin pandas naif, sebuah loop for melintasi bar dengan pembukuan skalar, hal yang akan Anda tulis dalam semalam lalu dibuang. Semua yang lebih lambat dari baseline itu ditulis tebal.

Engine combos/s Paradigma Sweep 80-kombinasi penuh
kernel GPU MLX 779 vectorized (GPU Apple) 0,10 dtk
Rust native ~350 compiled 0,23 dtk
mp + numba 246 JIT compiled + multiprocess 0,33 dtk
vectorbt 56,9 vectorized (numpy/numba) 1,4 dtk
numba (single core) 39,7 JIT compiled 2,0 dtk
backtesting.py 1,42 event-driven 56 dtk
PyAlgoTrade 0,51 event-driven 2,6 mnt
M0 — pandas naif + loop 0,28 baseline skalar 4,8 mnt
backtrader 0,11 event-driven 12,7 mnt
bt 0,06 weights / rebalance 22,5 mnt

Baca tabel ini dari atas ke bawah dan paradigma itu mengurutkan dirinya sendiri: lima teratas semuanya vectorized atau compiled, lima terbawah semuanya event-driven atau berbasis graf objek — dengan loop pandas naif duduk di atas dua framework yang matang dan populer. Rentang dari atas ke bawah adalah empat orde besaran. Pada beban kerja 2.707-trade yang persis sama, kernel MLX menyelesaikan sweep dalam sepersepuluh detik; bt butuh dua puluh dua setengah menit. Itu faktor kira-kira 13.000×.

Skandal di tengah tabel

Paradoks di tengah tabel: sebuah runner naive for-loop berwarna abu-biru polos melintasi garis finis lebih dulu daripada dua mesin framework event-driven yang berhias dan penuh fitur berwarna merah-oranye yang masih berjuang di belakang, dibebani model broker dan tumpukan analyzer — baseline yang sederhana mengalahkan framework yang matang pada beban kerja 2.707-trade yang identik

Angka-angka yang mencolok ada di titik ekstrem, tetapi hasil yang paling mengajarkan ada di tengah: backtrader (0,11 combos/s) dan bt (0,06 combos/s) sama-sama lebih lambat daripada baseline pandas naif (0,28 combos/s).

Ini layak untuk benar-benar meresap. M0 bukan mesin yang canggih. Ia adalah loop for Python yang mengindeks ke dalam DataFrame, melacak posisi dan kas dalam skalar biasa, dan menambahkan trade ke sebuah list — "kontrol" yang sengaja tidak dioptimasi yang kami sertakan agar ada sesuatu yang jelas-jelas buruk untuk dikalahkan. Akses per-baris pandas terkenal lambat, dan kami sengaja memanfaatkannya. Namun dua library backtesting yang paling banyak direkomendasikan di ekosistemnya kalah darinya: backtrader kalah 2,5×, bt kalah 4,7×.

Nuansa yang membuat ini tetap jujur: tidak setiap mesin event-driven lebih lambat daripada pandas. backtesting.py (1,42 combos/s) mengalahkan baseline 5×, karena ia adalah event loop yang ramping dan didukung numpy yang menjaga pembuatan objek per-bar seminimal mungkin. PyAlgoTrade (0,51) juga sedikit mengungguli baseline. Jadi "event-driven" bukan otomatis vonis mati — tetapi semakin berat mesin per-bar-nya, semakin buruk keadaannya, dan backtrader serta bt membawa mesin paling berat di sini. Paradigma menentukan langit-langitnya; implementasi menentukan di mana di bawah langit-langit itu Anda mendarat.

Intinya bukan bahwa library-library ini buruk. Model broker backtrader dan desain tree-of-algos bt ada untuk membeli kebenaran dan ekspresivitas — penanganan order yang realistis, rebalancing portofolio, analyzer. Fitur-fitur itu punya biaya runtime, dan biaya itu tak terlihat saat Anda menjalankannya sekali. Di seluruh sweep, itulah keseluruhan ceritanya.

Mengapa mesin event-driven membayar pajak ini

Di mana pajak framework dipungut, per bar: satu iterasi bar diurai menjadi komponen-komponen mahalnya — sebuah objek bar yang dimaterialisasi, sebuah frame callback, sebuah broker yang merutekan order, sebuah ledger yang ditambahkan — masing-masing sebuah pos pungutan berwarna merah-oranye yang menyala mengambil satu koin, berulang menyusuri koridor yang menjauh sepanjang dua belas juta iterasi bar yang identik

Mekanismenya tidak misterius. Sebuah backtest event-driven, per bar, melakukan sesuatu seperti ini:

  1. Memajukan jam, memotong bar berikutnya dari data feed, dan memateriasilisasikannya sebagai objek (sebuah Bar, sebuah Line, sebuah dict).
  2. Memicu callback ke kode pengguna (next(), onBars()), yang merupakan pemanggilan fungsi Python dengan frame-nya sendiri.
  3. Di dalam callback, meng-query state broker/posisi, lagi-lagi lewat pemanggilan method dan lookup atribut.
  4. Jika sebuah order dibuat, merutekannya lewat broker: memvalidasinya, mengecek margin/kas, menjadwalkan fill, memutasi objek portofolio, menambahkan ke ledger transaksi.
  5. Memperbarui analyzer, observer, dan pembukuan apa pun yang dipertahankan framework.

Sekarang kalikan dengan 150.000 bar, lalu dengan 80 kombinasi: dua belas juta iterasi-bar per sweep, masing-masing satu genggam alokasi objek level-Python dan dispatch dinamis. Overhead per-operasi Python — puluhan hingga ratusan nanodetik untuk sebuah lookup atribut atau alokasi kecil — sepele sekali saja dan menghancurkan bila terjadi dua belas juta kali. Kasus bt adalah varian dari penyakit yang sama: meskipun ia hanya melakukan rebalance pada tanggal trade dan bukan setiap bar, setiap rebalance mengevaluasi pohon objek algo dan menyentuh ledger portofolio yang didukung pandas, dan ada 2.707 dari itu per kombinasi, dikali 80.

Loop pandas naif mengalahkan backtrader dan bt karena alasan yang sederhana: ia melakukan lebih sedikit per bar. Ia melompati broker, objek event, tumpukan analyzer, mesin state routing-order. Ia membayar pajak per-baris pandas yang jelek, tetapi satu pajak jelek itu tetap lebih murah daripada pajak framework yang rapi, lengkap fitur, satu-objek-per-event. Ketika Anda mengupas sebuah backtest sampai ke "posisi × return-berikutnya − fee", sebagian besar dari apa yang dilakukan sebuah framework per bar adalah overhead yang tidak Anda gunakan selama pencarian.

Dan inilah jebakannya: overhead itu adalah alasan Anda memilih framework tersebut. Anda menginginkan broker yang realistis. Anda menginginkan analyzer-nya. Selama validasi akhir, Anda menginginkan semuanya. Selama pencarian 10.000-kombinasi di mana Anda hanya membaca satu skalar objektif di ujung sana, Anda membayar limusin untuk berlari mengelilingi lintasan.

Ujung tabel yang lain: vectorized dan compiled

Bagian atas tabel adalah apa yang terjadi ketika Anda menghapus Python per-bar sepenuhnya.

  • vectorbt (56,9 combos/s) mengekspresikan seluruh strategi sebagai operasi array numpy/numba. Tidak ada loop bar dalam Python — sinyal, posisi, PnL semuanya level-array. Ia menjalankan sweep dalam 1,4 detik versus 22,5 menit milik bt: sekitar 950× lebih cepat pada pekerjaan yang identik. (Kami membahas desain vectorbt lebih dalam di ulasan vectorbt dan perbandingan pandas-vs-polars yang lebih luas.)
  • numba (39,7) adalah loop pandas, tidak berubah bentuknya, dikompilasi JIT ke kode native. Algoritma yang sama dengan M0, tetapi @njit mengubah 0,28 combos/s menjadi 39,7 — sebuah percepatan ~140× dari satu dekorator, karena overhead interpreter yang mendominasi loop skalar itu begitu saja menguap.
  • mp + numba (246) menjalankan kernel yang dikompilasi di beberapa core CPU. Kombinasi bersifat embarrassingly parallel — masing-masing independen — sehingga multiprocessing menskala hampir linier di atas JIT.
  • Rust native (~350) menghapus sisa terakhir dari lem Python: seluruh sweep bersifat native.
  • GPU MLX (779) memetakan sweep ke kernel GPU Apple-silicon. 80 kombinasi menjadi 80 jalur paralel aritmetika; sweep selesai sebelum Anda melepas tombol enter.

Ada dua hal yang layak disebut secara presisi. Pertama, numba membuktikan bahwa paradigma lebih penting daripada bahasa. M0 dan numba menjalankan algoritma yang sama — bedanya murni bahwa yang satu adalah Python per-bar yang diinterpretasi dan yang lain dikompilasi. Itulah keseluruhan pajak framework dalam sebuah A/B yang terkontrol: ~140× hanya karena menghapus interpreter dari inner loop. Kedua, lompatan dari numba (39,7) ke mp+numba (246) ke MLX (779) sudah tidak lagi soal mesin sama sekali — itu soal orkestrasi dan hardware. Begitu pajak per-bar hilang, kecepatan menjadi soal berapa banyak kombinasi yang Anda jalankan secara paralel dan di silikon apa. Kami menelusuri progresi penuh itu di tangga kecepatan mesin backtest, dan alasan mengapa mil terakhir didominasi oleh biaya process/serialisasi ada di artikel pajak-IPC.

Apa yang tidak kami ukur (dan mengapa kami memberitahunya)

Kredibilitas sebuah benchmark hidup dari apa yang ia tolak untuk dipalsukan. Kami menjalankan delapan mesin end-to-end di bawah paritas. Beberapa framework terkenal yang tidak kami beri angka, dan kami lebih memilih mendaftarnya secara jujur daripada mengekstrapolasi angka yang tidak kami ukur:

  • zipline / zipline-reloaded — event-driven, silsilah Quantopian. Setup yang berat (kalender trading lengkap dan bundle data), yang membuat pengukuran waktu per-kombinasi apples-to-apples jadi rumit. Secara arsitektural ia berada bersama backtrader di kubu event-driven; kami menduga ia akan berada dekat ujung tabel itu, tapi kami belum membuktikannya.
  • nautilus_trader — event-driven dengan inti Rust/Cython, dirancang secara eksplisit untuk paritas-live. Intinya dikompilasi, sehingga ia adalah mesin event-driven yang paling mungkin tidak membayar pajak Python penuh — sebuah pengukuran yang sungguh menarik yang belum kami jalankan.
  • QuantConnect Lean — berbasis C#, runtime yang sepenuhnya berbeda; tidak dapat dibandingkan secara langsung dalam harness Python.
  • Jesse — event-driven, berfokus kripto; kami mengulas desainnya di catatan terpisah tetapi tidak membenchmark-nya di sini.
  • QSTrader — event-driven, berorientasi portofolio; caveat paradigma yang sama.
  • fastquant — kami coba; instalasi/API-nya rusak di lingkungan kami, jadi tidak ada angka. Kami tidak akan menebak-nebak.

Dua catatan jujur tentang angka yang memang kami laporkan. Angka vectorbt, numba, mp+numba, Rust-native, dan MLX berasal dari tangga mesin kami sendiri pada beban kerja yang identik, bukan dari harness OSS yang menghasilkan empat baris event-driven — itu beban kerja yang sama tetapi rig pengukuran yang berbeda, dan angka Rust-native adalah perkiraan ~350, bukan pengukuran yang ketat. Dan combos/s absolut bersifat spesifik-hardware; yang bertahan adalah urutan dan rasionya, yang cukup besar (13.000× dari atas ke bawah, 2,5–4,7× untuk pembalikan pandas-vs-framework) sehingga tidak ada perbedaan hardware yang wajar bisa membaliknya.

Membela mesin event-driven

Mesin event-driven pada perannya yang sesuai: sebuah run validasi high-fidelity yang teliti dan tunggal dari satu strategi yang bertahan, dirender sebagai instrumen zamrud-dan-sian yang presisi yang memodelkan fill yang realistis, menghormati margin dan siklus hidup order — mesin per-bar yang sama yang murni sia-sia selama pencarian ditampilkan di sini sebagai keahlian yang bernilai, satu gladi resik yang cermat sebelum deployment

Akan mudah untuk membaca ini sebagai "backtester event-driven itu buruk." Itu pelajaran yang salah, dan tidak adil.

Mesin event-driven dibangun untuk pekerjaan yang berbeda, dan mereka hebat di dalamnya. Broker per-bar, siklus hidup order, logika fill, analyzer — mesin itu ada untuk membuat sebuah backtest semirip mungkin dengan trading live. Ketika tujuan Anda adalah satu gladi resik yang tepercaya dari sebuah strategi yang hendak Anda deploy, Anda menginginkan mesin itu berkeringat memikirkan setiap fill, memodelkan eksekusi parsial, menghormati margin, dan menolak membiarkan Anda trading pada harga yang tidak mungkin Anda dapatkan. Fidelitas itulah produknya. Biaya runtime-nya adalah harga dari realisme, dan untuk satu run harganya dapat diabaikan.

Kegagalannya bukan pada mesinnya; melainkan menggunakannya pada fase yang salah. Riset memiliki dua fase yang berbeda dengan kebutuhan yang berlawanan:

  • Search menginginkan throughput. Anda sedang menjelajahi sebuah landscape, sebagian besarnya sampah, dan Anda perlu mengevaluasi ribuan titik untuk menemukan segelintir yang layak dilirik ulang. Fidelitas per titik nyaris tidak penting — Anda sedang me-ranking, bukan men-deploy. Di sinilah pajak framework murni sia-sia.
  • Validasi menginginkan fidelitas. Anda punya segelintir kandidat dan perlu tahu, seakurat mungkin, apakah mereka bertahan dari eksekusi realistis, fee, slippage, dan jebakan look-ahead yang menggelembungkan return di atas kertas. Di sinilah mesin event-driven mendapatkan haknya atas biayanya.

Kesalahan yang dihukum oleh pajak framework adalah menjalankan search Anda pada mesin validasi Anda — membayar tarif limusin untuk menjelajahi sebuah landscape yang 99%-nya akan Anda buang.

Aturan keputusannya

Kesimpulan praktisnya mampat menjadi satu keputusan tunggal:

Lakukan search pada mesin vectorized/compiled. Validasi para penyintas pada mesin event-driven.

Secara konkret:

  1. Bangun atau pinjam kernel yang cepat untuk sweep. vectorbt jika Anda menginginkannya siap pakai; loop yang dikompilasi numba jika strategi Anda tidak tervektorisasi dengan bersih (dekorator @njit saja membeli ~140× di sini). Jalankan seluruh ruang parameter melaluinya.
  2. Jangan pernah menjalankan sweep besar pada backtrader, bt, zipline, atau framework event-driven berat apa pun. Jika sweep pada salah satunya adalah bottleneck Anda, perbaikannya bukan mesin yang lebih besar — melainkan mesin yang salah. Bahkan loop pandas naif akan mengalahkan dua di antaranya.
  3. Promosikan daftar pendek ke mesin event-driven untuk pemeriksaan fidelitas. Ambil segelintir penyintas dan jalankan ulang mereka pada mesin yang realistis, di mana model broker dan logika fill dapat membongkar masalah yang diabstraksi oleh kernel cepat tadi.
  4. Wajibkan paritas di antara keduanya. Mesin cepat dan mesin fidelitas harus sepakat soal jumlah trade dan PnL pada konfigurasi tetap (pemeriksaan ±1-trade kami pada L=104), atau search dan validasi sedang mengukur strategi yang berbeda dan seluruh pipeline-nya adalah kebohongan.

Ini adalah arsitektur dua-kecepatan yang sama yang muncul kapan pun search dan validasi memiliki profil biaya yang berlawanan, dan itulah mengapa stack kami sendiri mempertahankan jalur vectorized/compiled yang cepat untuk pencarian parameter dan menyimpan mesin berat untuk evaluasi objektif akhir dan pemeriksaan plateau.

Kesimpulan utama

  1. Populer ≠ cepat untuk sweep. Pada satu beban kerja yang identik dan terkunci paritasnya (150 ribu bar, 80 kombinasi HMA-cross, 2.707 trade), backtrader (0,11 combos/s) dan bt (0,06) sama-sama berjalan lebih lambat daripada loop pandas naif (0,28). Framework yang matang dan berbintang banyak bukan otomatis pilihan yang cepat.
  2. Pajak framework itu per-bar, dan sweep melipatgandakannya. Dua belas juta iterasi-bar per sweep, masing-masing membawa objek event, sebuah callback, dan sebuah round-trip broker. Biaya yang tak terlihat dalam satu run adalah keseluruhan tagihan di seluruh sepuluh ribu.
  3. Paradigma menentukan langit-langitnya. Mesin vectorized/compiled (vectorbt 56,9, numba 39,7, mp+numba 246, Rust native ~350, MLX 779) mengalahkan mesin event-driven dengan dua hingga empat orde besaran — hingga ~13.000× dari atas ke bawah. Algoritma yang sama, hanya saja dikompilasi JIT, menjadi 140× lebih cepat.
  4. Tidak semua mesin event-driven setara. backtesting.py (1,42) dan PyAlgoTrade (0,51) masih mengalahkan baseline naif; pajaknya berskala dengan seberapa berat mesin per-bar-nya. Implementasi menentukan di mana di bawah langit-langit itu Anda mendarat.
  5. Dua mesin, dua fase. Lakukan search pada kernel vectorized/compiled; validasi para penyintas pada mesin event-driven yang realistis. Wajibkan paritas jumlah-trade/PnL di antara keduanya sehingga keduanya mengukur strategi yang sama.
  6. Jujurlah tentang apa yang Anda ukur. Kami membenchmark delapan mesin di bawah paritas dan mendaftar yang tidak kami ukur (zipline, nautilus_trader, Lean, Jesse, QSTrader, dan instalasi fastquant yang tidak mau berjalan) alih-alih mengarang angka untuk mereka.

Ringkasan yang tidak nyaman: jika parameter sweep adalah bottleneck Anda, masalahnya kemungkinan besar bukan mesin Anda dan bukan strategi Anda. Masalahnya adalah Anda menjalankan search pada mesin yang dibangun untuk satu run yang jujur — dan Anda akan lebih cepat dengan loop pandas yang terlalu Anda malu-malukan untuk dipertahankan.

Harness lengkap, assertion paritas, dan hasil JSON mentah per-mesin ada di benchmarks/bench_oss_engines.py dan benchmarks/results_oss/ pada commit 250dbb5. Untuk ujung compiled/GPU dari tangga tersebut, lihat tangga kecepatan mesin backtest dan analisis pajak-IPC.

Penafian: Informasi yang disediakan dalam artikel ini hanya untuk tujuan edukasi dan informasi serta tidak merupakan nasihat keuangan, investasi, atau trading. Trading mata uang kripto mengandung risiko kerugian yang signifikan.

Penulis

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Selangkah Lebih Maju dari Pasar

Berlangganan newsletter kami untuk wawasan AI trading eksklusif, analisis pasar, dan pembaruan platform.

Kami menghormati privasi Anda. Berhenti berlangganan kapan saja.