Cukai Rangka Kerja: Bila Pustaka Backtest Anda Lebih Perlahan Daripada Gelung Pandas Naif
Sebahagian daripada siri "Backtest Tanpa Ilusi".
Ada satu andaian yang selesa di dasar kebanyakan projek dagangan algo: bahawa pustaka backtest yang matang dan berbintang tinggi itu pantas. Ia mempunyai bertahun-tahun sumbangan, gelung peristiwa yang sebenar, model broker, skim komisen. Sudah tentu ia mengalahkan gelung pandas kasar yang mungkin anda tulis sendiri. Jadi anda pun mencapainya, menyambungkan strategi anda, dan memulakan sapuan parameter — beberapa ribu konfigurasi, kerja semalaman. Anda kembali pada waktu pagi dan ia masih lagi berjalan.
Kami menanda aras lapan enjin backtest pada satu sapuan parameter yang sama dan menemui sesuatu yang sepatutnya mengubah cara anda memilih alat untuk carian. Dua daripada rangka kerja dipacu peristiwa sumber terbuka yang paling popular — backtrader dan bt — menjalankan sapuan itu lebih perlahan daripada gelung pandas naif yang kami tulis sebagai garis dasar buangan. Bukan sedikit lebih perlahan. backtrader mengambil masa kira-kira 2.5× lebih lama daripada garis dasar pandas; bt, kira-kira 4.7×. Sementara itu, enjin tervektor/terkompil pada kerja yang sama berjalan kira-kira 13,000× lebih pantas daripada bt.
Inilah cukai rangka kerja. Enjin backtest yang popular dibina untuk satu larian yang jujur — satu strategi, satu set data, pengisian yang teliti, broker yang berkelakuan seperti broker. Itulah tepatnya yang anda mahu untuk pengesahan akhir atau semakan pariti langsung. Ia juga tepatnya alat yang salah untuk perkara yang sebenarnya menghabiskan masa penyelidikan algo: menjalankan strategi yang sama sepuluh ribu kali dengan tombol yang sedikit berbeza. Artikel ini mengukur cukai itu, menerangkan mekanismenya, dan memberikan satu peraturan keputusan tentang bila rangka kerja backtest yang "sebenar" adalah pilihan yang salah.
Setiap nombor di sini datang daripada satu rangka uji yang boleh dihasilkan semula (benchmarks/bench_oss_engines.py, commit 250dbb5) pada beban kerja yang sama dan dikunci pariti. Di mana kami tidak menjalankan sesuatu enjin sendiri, kami menyatakannya dan meletakkannya dalam bahagian kejujuran yang berasingan, bukannya mereka-reka angka.
Satu larian berbanding sepuluh ribu

Fakta yang menentukan carian parameter ialah enjin berjalan beribu-ribu kali tetapi analisis berlaku sekali sahaja. Apa jua kos tetap yang anda bayar untuk menyediakan backtest — membina gelung peristiwa, mengaktualkan broker, memperuntukkan satu objek per bar — anda bayar ia pada setiap kombinasi. Kos yang tidak kelihatan dalam satu larian ("siapa peduli tentang 6 saat?") menjadi keseluruhan bil merentas satu sapuan ("6 saat × 10,000 = 16.6 jam").
Enjin backtest terbahagi kepada tiga paradigma, dan paradigma itu ialah takdir bagi prestasi sapuan:
- Dipacu peristiwa — enjin berjalan bar demi bar, memancarkan peristiwa, memanggil panggil-balik
next()/onBars()anda, menghalakan pesanan melalui objek broker. Inilah seni bina backtrader, backtesting.py, PyAlgoTrade, zipline, dan nautilus_trader. Ia mencerminkan cara dagangan langsung sebenarnya berfungsi, yang tepatnya sebab ia dipercayai untuk realisme — dan tepatnya sebab ia perlahan: overhed Python per-bar dibayar 150,000 kali per kombinasi. - Pemberat/imbang-semula —
btberada di sini. Anda menyerahkan kepadanya matriks berat sasaran dan ia mengimbang semula pada tarikh yang anda tetapkan. Tiada panggil-balik per-bar, tetapi masih ada graf objek per-peristiwa (satu pokok algo, satu lejar transaksi) yang dinilai dalam Python. - Tervektor / terkompil — keseluruhan strategi diungkapkan sebagai operasi tatasusunan (vectorbt), kernel terkompil JIT (numba), atau kod asli (Rust, kernel GPU MLX). Tiada Python per-bar langsung. Gelung, jika ada, berjalan pada kelajuan mesin.
Selebihnya artikel ini ialah akibat empirikal daripada taksonomi itu. Kami membina satu beban kerja, memaksa setiap enjin melakukan kerja yang boleh dibuktikan sama, dan memasang masa larian.
Beban kerja: satu strategi, lapan puluh tombol, sama untuk semua orang
Sesuatu penanda aras hanya jujur jika setiap enjin melakukan kerja yang sama. Beban kerja kami sengaja dibuat biasa — beban kerja di mana satu-satunya perkara yang berbeza antara enjin ialah enjin itu sendiri.
- Data. Satu siri tutupan gerak Brownian geometri sintetik:
150,000bar,seed=42, kemeruapan per-barsigma=0.0008,x0=30000. Deterministik, jadi sesiapa pun boleh menghasilkannya semula bit demi bit. Tutupan-sahaja secara reka bentuk — OHLC ditetapkan kepada tutupan pada setiap kaki, kerana strategi ialah silang-tutupan. - Strategi. Silang Hull Moving Average: satu HMA berpanjang
Lmenentang varian HMA-satu-pertiga yang lebih pantas (HMAlawanHMA3). Sentiasa di dalam pasaran, tukar panjang/pendek pada setiap silangan. Ini ialah penunjuk sebenar yang tidak remeh — dua purata bergerak berwajaran bersarang tambah pelicin tetingkap punca-kuasa-dua — bukan SMA mainan, jadi kerja per-bar adalah mewakili. - Sapuan.
80panjang HMA merentangi6..200. Itulah "sepuluh ribu larian" yang dibuat cukup kecil untuk diukur secara langsung: 80 kombinasi bebas, setiap satu satu backtest penuh ke atas 150k bar. - Kos. Yuran pergi-balik
0.09%, dibahagi setiap sisi untuk enjin yang memodelkan komisen sisi. Pengisian bar-sama padaclose[i]— isyarat pada baridilaksanakan pada tutupan bar itu, konvensyen yang digunakan oleh enjin pengeluaran kami.
Pemasa per-kombinasi membalut tepat dua perkara: prakiraan HMA numpy dan larian enjin. Persediaan yang benar-benar sekali sahaja (memuatkan data, membina objek bar) berada di luar pemasa. Ada satu larian pemanasan, kemudian ulangan terbaik-daripada-N, dan — kerana enjin dipacu peristiwa cukup perlahan sehingga 80 kombinasi penuh akan mengambil masa berpuluh minit hingga lebih sejam — kami memasang masa satu sampel seragam grid dan mengekstrapolasikannya secara linear. Kombinasi adalah bebas, jadi ekstrapolasi linear adalah tepat dalam jangkaan; konvensyen yang sama digunakan pada garis dasar pandas, jadi tiada enjin yang mendapat kelebihan daripadanya.
Pariti: membuktikan semua orang melakukan kerja yang sama
Inilah perangkap yang perbandingan enjin naif terperangkap ke dalamnya: enjin "pantas" mungkin sebenarnya sekadar melakukan lebih sedikit. Jika backtrader membukukan 2,700 dagangan dan enjin tervektor anda membukukan 40, enjin tervektor itu bukanlah lebih pantas — ia salah, dan perbandingan itu tidak bermakna.
Jadi kami mengunci perbandingan itu dengan semakan pariti kiraan dagangan. Pada L=104, rujukan numpy menghasilkan tepat 2,707 dagangan tertutup. Setiap enjin mesti menghasilkan semula itu dalam toleransi ±1, atau larian dibatalkan dengan penegasan work-parity FAILED. Toleransi itu wujud hanya kerana enjin tidak sependapat tentang konvensyen simpan-kira — sama ada kedudukan terbuka akhir ditutup paksa dan dikira, sama ada kemasukan awal ialah "dagangan" — bukan tentang dagangan itu sendiri:
| Enjin | Dagangan dilaporkan @ L=104 | Konvensyen |
|---|---|---|
| rujukan numpy | 2707 | pusingan pergi-balik tertutup |
| backtesting.py | 2708 | +1: kedudukan akhir ditutup paksa pada penghujung |
| backtrader | 2707 | kedudukan terbuka akhir tidak dikira |
| bt | 2708 | +1: kemasukan awal dikira sebagai transaksi |
| PyAlgoTrade | 2708 | +1: kemasukan awal dikira sebagai pengisian |
Setiap enjin mendarat pada 2707 ± 1. Apa jua perbezaan kelajuan yang ternyata, ia bukanlah artifak daripada satu enjin yang diam-diam melangkau kerja. Inilah disiplin yang membolehkan kami meletakkan rangka kerja dipacu peristiwa dan kernel GPU dalam jadual yang sama dan bermaksud demikian.
Keputusan
Inilah keseluruhan jadual, disusun daripada terpantas ke paling perlahan. combos/s ialah daya pemprosesan; lajur terakhir ialah berapa lama sapuan penuh 80 kombinasi mengambil masa. Baris garis dasar ialah M0 — enjin pandas naif, gelung for ke atas bar dengan simpan-kira skalar, perkara yang anda akan tulis dalam satu petang dan buang. Segala yang lebih perlahan daripada garis dasar itu ditebalkan.
| Enjin | combos/s | Paradigma | Sapuan penuh 80-kombinasi |
|---|---|---|---|
| kernel GPU MLX | 779 | tervektor (GPU Apple) | 0.10 s |
| Rust asli | ~350 | terkompil | 0.23 s |
| mp + numba | 246 | JIT terkompil + multiproses | 0.33 s |
| vectorbt | 56.9 | tervektor (numpy/numba) | 1.4 s |
| numba (teras tunggal) | 39.7 | JIT terkompil | 2.0 s |
| backtesting.py | 1.42 | dipacu peristiwa | 56 s |
| PyAlgoTrade | 0.51 | dipacu peristiwa | 2.6 min |
| M0 — pandas naif + gelung | 0.28 | garis dasar skalar | 4.8 min |
| backtrader | 0.11 | dipacu peristiwa | 12.7 min |
| bt | 0.06 | pemberat / imbang-semula | 22.5 min |
Baca jadual dari atas ke bawah dan paradigma menyusun dirinya sendiri: lima teratas semuanya tervektor atau terkompil, lima terbawah semuanya dipacu peristiwa atau berasaskan graf objek — dengan gelung pandas naif duduk di atas dua rangka kerja yang matang dan popular. Julat dari atas ke bawah ialah empat susunan magnitud. Pada beban kerja 2,707 dagangan yang sama tepat, kernel MLX menyelesaikan sapuan dalam sepersepuluh saat; bt memerlukan dua puluh dua setengah minit. Itu ialah faktor kira-kira 13,000×.
Skandal di tengah-tengah jadual

Nombor yang menarik perhatian berada di kedua-dua hujung, tetapi keputusan yang memberi pengajaran berada di tengah: backtrader (0.11 combos/s) dan bt (0.06 combos/s) kedua-duanya lebih perlahan daripada garis dasar pandas naif (0.28 combos/s).
Ini patut diresapi. M0 bukanlah enjin yang bijak. Ia ialah gelung for Python yang mengindeks ke dalam DataFrame, menjejaki kedudukan dan tunai dalam skalar biasa, dan menambah dagangan ke dalam senarai — "kawalan" yang sengaja tidak dioptimumkan yang kami sertakan supaya ada sesuatu yang jelas-jelas buruk untuk dikalahkan. Capaian per-baris pandas terkenal perlahan, dan kami memanfaatkannya sepenuhnya. Namun begitu dua daripada pustaka backtest yang paling banyak disyorkan dalam ekosistem tewas kepadanya: backtrader sebanyak 2.5×, bt sebanyak 4.7×.
Nuansa yang mengekalkan kejujuran ini: bukan setiap enjin dipacu peristiwa lebih perlahan daripada pandas. backtesting.py (1.42 combos/s) mengalahkan garis dasar sebanyak 5×, kerana ia adalah gelung peristiwa yang langsing, disokong numpy, yang mengekalkan penciptaan objek per-bar pada minimum. PyAlgoTrade (0.51) juga sedikit mendahului garis dasar. Jadi "dipacu peristiwa" bukanlah secara automatik hukuman mati — tetapi semakin berat jentera per-bar, semakin teruk jadinya, dan backtrader serta bt membawa jentera yang paling berat di sini. Paradigma menetapkan siling; pelaksanaan menentukan di mana bawah siling itu anda mendarat.
Maksudnya bukanlah pustaka ini buruk. Model broker backtrader dan reka bentuk pokok-algo bt wujud untuk membeli anda ketepatan dan keupayaan ungkap — pengendalian pesanan yang realistik, imbang semula portfolio, penganalisis. Ciri-ciri itu ada kos masa larian, dan kos itu tidak kelihatan apabila anda berjalan sekali. Merentas satu sapuan, ia ialah keseluruhan cerita.
Mengapa enjin dipacu peristiwa membayar cukai

Mekanismenya tidak misteri. Satu backtest dipacu peristiwa, per bar, melakukan sesuatu seperti ini:
- Majukan jam, hiris bar seterusnya daripada suapan data, dan aktualkannya sebagai objek (satu
Bar, satuLine, satu dict). - Tembak satu panggil-balik ke dalam kod pengguna (
next(),onBars()), iaitu satu panggilan fungsi Python dengan bingkainya sendiri. - Di dalam panggil-balik, tanya keadaan broker/kedudukan, sekali lagi melalui panggilan kaedah dan carian atribut.
- Jika satu pesanan dicipta, halakannya melalui broker: sahkan ia, semak margin/tunai, jadualkan pengisian, ubah suai objek portfolio, tambah ke lejar transaksi.
- Kemas kini penganalisis, pemerhati, dan apa-apa simpan-kira yang dikekalkan rangka kerja.
Kini darabkan dengan 150,000 bar, kemudian dengan 80 kombinasi: dua belas juta lelaran-bar per sapuan, setiap satu segenggam peruntukan objek peringkat-Python dan penghantaran dinamik. Overhed per-operasi Python — berpuluh hingga beratus nanosaat bagi satu carian atribut atau peruntukan kecil — remeh sekali tetapi memusnahkan dua belas juta kali. Kes bt ialah varian penyakit yang sama: walaupun ia mengimbang semula hanya pada tarikh dagangan dan bukan setiap bar, setiap imbang semula menilai satu pokok objek algo dan menyentuh satu lejar portfolio yang disokong pandas, dan ada 2,707 daripada itu per kombinasi, didarab 80.
Gelung pandas naif mengalahkan backtrader dan bt atas sebab yang tumpul: ia melakukan lebih sedikit per bar. Ia melangkau broker, objek peristiwa, tindanan penganalisis, mesin keadaan penghalaan-pesanan. Ia membayar cukai per-baris pandas yang hodoh, tetapi satu cukai hodoh itu masih lebih murah daripada cukai objek-per-peristiwa rangka kerja yang kemas dan lengkap-ciri. Apabila anda melucutkan satu backtest kepada "kedudukan × pulangan-seterusnya − yuran", kebanyakan apa yang rangka kerja lakukan per bar ialah overhed yang anda tidak gunakan semasa satu carian.
Dan inilah perangkapnya: overhed itu ialah sebab anda memilih rangka kerja itu. Anda mahu broker yang realistik. Anda mahu penganalisis. Semasa pengesahan akhir, anda mahu kesemuanya. Semasa carian 10,000 kombinasi di mana anda hanya membaca satu objektif skalar keluar dari hujung yang satu lagi, anda sedang membayar untuk sebuah limosin untuk berpusing-pusing di litar.
Hujung jadual yang lain: tervektor dan terkompil
Bahagian atas jadual ialah apa yang berlaku apabila anda memadamkan Python per-bar sepenuhnya.
- vectorbt (56.9 combos/s) mengungkapkan keseluruhan strategi sebagai operasi tatasusunan numpy/numba. Tiada gelung bar dalam Python — isyarat, kedudukan, PnL semuanya di peringkat tatasusunan. Ia menjalankan sapuan dalam 1.4 saat berbanding 22.5 minit
bt: kira-kira 950× lebih pantas pada kerja yang sama. (Kami membincangkan reka bentuk vectorbt dengan lebih mendalam dalam gambaran keseluruhan vectorbt dan perbandingan pandas-lawan-polars yang lebih luas.) - numba (39.7) ialah gelung pandas, tidak berubah bentuknya, terkompil JIT kepada kod asli. Algoritma yang sama seperti M0, tetapi
@njitmengubah 0.28 combos/s menjadi 39.7 — satu percepatan ~140× daripada satu penghias, kerana overhed pentafsir yang mendominasi gelung skalar itu semudahnya lenyap. - mp + numba (246) menjalankan kernel terkompil merentas teras CPU. Kombinasi ialah selari yang memalukan — setiap satu bebas — jadi multiproses berskala hampir secara linear di atas JIT.
- Rust asli (~350) membuang yang terakhir daripada gam Python: keseluruhan sapuan adalah asli.
- GPU MLX (779) memetakan sapuan ke atas satu kernel GPU silikon-Apple. 80 kombinasi menjadi 80 lorong aritmetik selari; sapuan selesai sebelum anda melepaskan kekunci enter.
Dua perkara patut dinamakan dengan tepat. Pertama, numba membuktikan paradigma lebih penting daripada bahasa. M0 dan numba menjalankan algoritma yang sama — perbezaannya semata-mata bahawa satu ialah Python per-bar yang ditafsir dan satu lagi terkompil. Itulah keseluruhan cukai rangka kerja dalam A/B terkawal: ~140× kerana memadamkan pentafsir daripada gelung dalaman. Kedua, lonjakan daripada numba (39.7) kepada mp+numba (246) kepada MLX (779) tidak lagi berkenaan enjin langsung — ia berkenaan orkestrasi dan perkakasan. Setelah cukai per-bar hilang, kelajuan menjadi persoalan berapa banyak kombinasi yang anda jalankan secara selari dan pada silikon apa. Kami menyusuri kemajuan penuh itu dalam tangga kelajuan enjin backtest, dan sebab batu terakhir dikuasai oleh kos proses/pensirian dalam artikel cukai-IPC.
Apa yang kami tidak ukur (dan mengapa kami memberitahu anda)
Kredibiliti penanda aras terletak pada apa yang ia enggan palsukan. Kami menjalankan lapan enjin dari hujung ke hujung di bawah pariti. Beberapa rangka kerja terkenal yang kami tidak letakkan nombor padanya, dan kami lebih rela menyenaraikannya dengan jujur daripada mengekstrapolasikan angka yang kami tidak ukur:
- zipline / zipline-reloaded — dipacu peristiwa, keturunan Quantopian. Persediaan berat (satu kalendar dagangan penuh dan bungkusan data), yang menjadikan pemasaan per-kombinasi apel-dengan-apel merumitkan. Dari segi seni bina ia duduk bersama backtrader dalam kem dipacu peristiwa; kami jangka ia berhampiran hujung jadual itu, tetapi kami belum membuktikannya.
- nautilus_trader — dipacu peristiwa dengan teras Rust/Cython, secara eksplisit direka untuk pariti-langsung. Terasnya terkompil, jadi ia ialah enjin dipacu peristiwa yang paling berkemungkinan tidak membayar cukai Python penuh — satu pengukuran yang benar-benar menarik yang kami belum jalankan.
- QuantConnect Lean — berasaskan C#, satu masa jalanan yang berbeza sama sekali; tidak setanding secara langsung dalam rangka uji Python.
- Jesse — dipacu peristiwa, tertumpu kripto; kami menyemak reka bentuknya dalam nota berasingan tetapi tidak menanda arasnya di sini.
- QSTrader — dipacu peristiwa, berorientasikan portfolio; amaran paradigma yang sama.
- fastquant — kami cuba; pemasangan/API rosak dalam persekitaran kami, jadi tiada nombor. Kami tidak akan meneka satu.
Dua amaran jujur tentang nombor yang memang kami laporkan. Angka vectorbt, numba, mp+numba, Rust-asli dan MLX datang daripada tangga enjin kami sendiri pada beban kerja yang sama, bukan daripada rangka uji OSS yang menghasilkan empat baris dipacu peristiwa itu — ia beban kerja yang sama tetapi rig pengukuran yang berbeza, dan angka Rust-asli ialah anggaran ~350, bukan pengukuran yang ketat. Dan combos/s mutlak adalah khusus-perkakasan; apa yang merentas ialah susunan dan nisbah, yang cukup besar (13,000× atas-ke-bawah, 2.5–4.7× untuk pembalikan pandas-lawan-rangka kerja) sehingga tiada perbezaan perkakasan yang munasabah membalikkannya.
Membela enjin dipacu peristiwa

Mudah untuk membaca ini sebagai "enjin backtest dipacu peristiwa itu buruk." Itu ialah pengajaran yang salah, dan tidak adil.
Enjin dipacu peristiwa dibina untuk kerja yang berbeza, dan ia bagus dalam kerja itu. Broker per-bar, kitaran hayat pesanan, logik pengisian, penganalisis — jentera itu wujud untuk menjadikan satu backtest menyerupai dagangan langsung sedekat mungkin. Apabila matlamat anda ialah satu latihan pakaian tunggal yang boleh dipercayai bagi strategi yang anda hampir menggunakannya, anda mahu enjin itu berpeluh atas setiap pengisian, memodelkan pelaksanaan separa, menghormati margin, dan enggan membenarkan anda berdagang pada harga yang tidak mungkin anda perolehi. Kesetiaan itu ialah produknya. Kos masa lariannya ialah harga realisme, dan untuk satu larian harganya boleh diabaikan.
Kegagalan itu bukan enjin; ia menggunakannya untuk fasa yang salah. Penyelidikan mempunyai dua fasa berbeza dengan keperluan bertentangan:
- Carian mahukan daya pemprosesan. Anda meneroka satu landskap, kebanyakannya sampah, dan anda perlu menilai beribu-ribu titik untuk mencari beberapa yang berbaloi untuk pandangan kedua. Kesetiaan per titik hampir tidak penting — anda sedang menyusun, bukan menggunakan. Di sini cukai rangka kerja ialah pembaziran tulen.
- Pengesahan mahukan kesetiaan. Anda mempunyai segelintir calon dan anda perlu tahu, setepat mungkin, sama ada mereka terselamat daripada pelaksanaan realistik, yuran, gelinciran, dan perangkap pandang-hadapan yang menggelembungkan pulangan atas kertas. Di sini enjin dipacu peristiwa memperoleh kosnya.
Kesilapan yang dihukum oleh cukai rangka kerja ialah menjalankan carian anda pada enjin pengesahan anda — membayar kadar limosin untuk meneroka satu landskap yang 99% daripadanya akan anda buang.
Peraturan keputusan
Kesimpulan praktikalnya termampat kepada satu keputusan:
Cari pada enjin tervektor/terkompil. Sahkan yang terselamat pada enjin dipacu peristiwa.
Secara konkrit:
- Bina atau pinjam kernel pantas untuk sapuan. vectorbt jika anda mahukannya di luar rak; gelung terkompil numba jika strategi anda tidak tervektor dengan bersih (penghias
@njitsahaja membeli ~140× di sini). Jalankan keseluruhan ruang parameter melaluinya. - Jangan sekali-kali jalankan sapuan besar pada backtrader,
bt, zipline, atau apa-apa rangka kerja dipacu peristiwa yang berat. Jika satu sapuan pada salah satu daripadanya ialah kesesakan anda, penyelesaiannya bukan mesin yang lebih besar — ia enjin yang salah. Malah gelung pandas naif pun akan mengalahkan dua daripadanya. - Naikkan senarai pendek ke enjin dipacu peristiwa untuk semakan kesetiaan. Ambil segelintir yang terselamat dan jalankan semula pada enjin realistik, di mana model broker dan logik pengisian boleh mendedahkan masalah yang kernel pantas telah abstrakkan.
- Kuatkuasakan pariti antara kedua-duanya. Enjin pantas dan enjin kesetiaan mesti bersetuju tentang kiraan dagangan dan PnL pada konfigurasi tetap (semakan ±1-dagangan kami pada L=104), atau carian dan pengesahan sedang mengukur strategi yang berbeza dan keseluruhan saluran paip ialah satu pembohongan.
Inilah seni bina dua-kelajuan yang sama yang muncul apabila carian dan pengesahan mempunyai profil kos bertentangan, dan itulah sebabnya tindanan kami sendiri mengekalkan laluan tervektor/terkompil yang pantas untuk carian parameter dan mengekhaskan jentera berat untuk penilaian objektif akhir dan semakan dataran.
Intipati
- Popular ≠ pantas untuk sapuan. Pada satu beban kerja yang sama, dikunci pariti (150k bar, 80 kombinasi silang HMA, 2,707 dagangan), backtrader (0.11 combos/s) dan
bt(0.06) kedua-duanya berjalan lebih perlahan daripada gelung pandas naif (0.28). Rangka kerja yang matang dan berbintang tinggi bukanlah secara automatik pilihan yang pantas. - Cukai rangka kerja adalah per-bar, dan satu sapuan mendarabkannya. Dua belas juta lelaran-bar per sapuan, setiap satu membawa satu objek peristiwa, satu panggil-balik, dan satu perjalanan pergi-balik broker. Kos yang tidak kelihatan dalam satu larian ialah keseluruhan bil merentas sepuluh ribu.
- Paradigma menetapkan siling. Enjin tervektor/terkompil (vectorbt 56.9, numba 39.7, mp+numba 246, Rust asli ~350, MLX 779) mengalahkan enjin dipacu peristiwa sebanyak dua hingga empat susunan magnitud — sehingga ~13,000× atas-ke-bawah. Algoritma yang sama, sekadar terkompil JIT, berjalan 140× lebih pantas.
- Bukan semua enjin dipacu peristiwa adalah sama. backtesting.py (1.42) dan PyAlgoTrade (0.51) masih mengalahkan garis dasar naif; cukai berskala dengan betapa berat jentera per-bar. Pelaksanaan menentukan di mana bawah siling itu anda mendarat.
- Dua enjin, dua fasa. Cari pada kernel tervektor/terkompil; sahkan yang terselamat pada enjin dipacu peristiwa yang realistik. Kuatkuasakan pariti kiraan-dagangan/PnL antara mereka supaya kedua-duanya mengukur strategi yang sama.
- Jujur tentang apa yang anda ukur. Kami menanda aras lapan enjin di bawah pariti dan menyenaraikan yang kami tidak (zipline, nautilus_trader, Lean, Jesse, QSTrader, dan pemasangan fastquant yang tidak mahu berjalan) dan bukannya mereka-reka nombor untuk mereka.
Ringkasan yang tidak selesa: jika satu sapuan parameter ialah kesesakan anda, masalahnya mungkin bukan mesin anda dan bukan strategi anda. Ia ialah bahawa anda sedang menjalankan satu carian pada satu enjin yang dibina untuk satu larian jujur tunggal — dan anda akan lebih pantas dengan gelung pandas yang anda terlalu malu untuk kekalkan.
Rangka uji penuh, penegasan pariti, dan keputusan JSON per-enjin mentah hidup dalam benchmarks/bench_oss_engines.py dan benchmarks/results_oss/ pada commit 250dbb5. Untuk hujung terkompil/GPU tangga itu, lihat tangga kelajuan enjin backtest dan analisis cukai-IPC.
Pengarang
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.