Backtest Python Reproducible Tanpa API Key
Jalankan backtest Python yang reproducible tanpa API key dengan instalasi terkunci dan data sampel deterministik, lalu pahami batasan satu equity curve.
Backtest yang dapat direproduksi di Python adalah backtest yang dapat dijalankan ulang oleh orang lain di mesin yang bersih dan menghasilkan angka yang persis sama, tanpa terhalang kebutuhan akan akun atau API key. Sebagian besar tutorial gagal memenuhi pengujian ini sejak baris pertama, ketika unduhan live memberikan riwayat harga yang sedikit berbeda kepada pembaca berikutnya dibandingkan dengan yang diterima penulis. Panduan ini menetapkan satu engine open-source, quantjourney-bt pada versi 0.12.4, menjalankan contoh bawaannya tanpa kredensial, lalu menggunakan data pasar aktual untuk menunjukkan hal-hal yang tetap tidak dapat disampaikan oleh satu kali eksekusi yang bersih.
Apa yang membuat backtest dapat direproduksi?
Reproduksibilitas memiliki arti yang sempit dan dapat diuji di sini: orang kedua menjalankan satu perintah pada mesin yang bersih dan memperoleh kembali angka Anda hingga desimalnya sama. Dua hal umum dapat mengganggunya.
Yang pertama adalah kode. Library pada versi 0.x tidak menjamin kompatibilitas antar-rilis minor. Perubahan nama, default yang diubah, atau urutan kolom yang berbeda dapat membuat skrip tetap berjalan, tetapi diam-diam melaporkan hasil yang lain.
Yang kedua adalah data. Tutorial yang baris pertamanya mengunduh data secara langsung tidak pernah benar-benar dapat direproduksi. Vendor merevisi data historis, menyesuaikannya dengan stock split, dan mengisi kembali data yang sebelumnya kosong. Akibatnya, skrip yang sama dapat menghasilkan angka berbeda sebulan kemudian. Setelah itu, Anda tidak dapat memisahkan perubahan kode dari perubahan data.
Pola yang layak ditiru dari proyek ini adalah memasangkan versi engine yang tetap dengan dataset kecil yang disertakan langsung di dalam package.
Sematkan versi instalasi: pip install quantjourney-bt==0.12.4
quantjourney-bt adalah backtester QuantJourney yang dirilis berdasarkan Apache License 2.0 dan memerlukan Python 3.11 atau versi lebih baru. Versi 0.12.4 diterbitkan pada 21 Juli 2026 dan merupakan versi yang dirujuk oleh setiap perintah di bawah ini, sebagaimana didokumentasikan pada Agustus 2026.
Gunakan isolated environment. python3 -m venv .venv membuatnya, source .venv/bin/activate mengaktifkannya, dan python -m pip install -U pip memperbarui installer di dalamnya. Selanjutnya, gunakan versi yang tepat: pip install quantjourney-bt==0.12.4.
Dokumentasi proyek mencantumkan bentuk tanpa pin versi, pip install quantjourney-bt. Bagian ==0.12.4 menjadi tanggung jawab Anda, dan pada seri 0.x bagian ini sangat penting. Simpan pin versi di tempat yang mudah ditemukan oleh pengguna berikutnya: pip freeze > requirements.txt mencatat setiap dependency yang berhasil di-resolve, termasuk dependency yang tidak pernah Anda sebutkan secara eksplisit.
Tersedia dua optional extras. pip install "quantjourney-bt[wf]" menambahkan Optuna untuk contoh walk-forward dan optimization. pip install "quantjourney-bt[data]" menambahkan fallback yfinance yang digunakan untuk benchmark.
Apache-2.0 bersifat permisif. Anda dapat menggunakan dan memodifikasi kode tersebut untuk keperluan komersial. Anda harus menyertakan file lisensi dan notice dalam setiap redistribusi, sementara para kontributor memberikan hak paten secara eksplisit.
Cara menjalankan contoh SMA bawaan tanpa API key
Repository ini menyertakan skrip launcher bersama lima puluh contoh strategi yang dapat dijalankan. Contoh-contoh tersebut terbagi ke dalam jalur berbasis bobot dan jalur berbasis order. Lima di antaranya merupakan workflow walk-forward. ./strategy.sh --list menampilkan katalognya. ./strategy.sh example_weights_01_sma_daily --check mengimpor satu strategi tanpa menyentuh data sama sekali. Ini merupakan pemeriksaan tercepat untuk memastikan instalasi berjalan baik.
Demo dijalankan dalam satu baris: ./strategy.sh example_weights_01_sma_daily --sample-data --output /tmp/qj-sample
Flag --sample-data memuat inti dari contoh ini. Proyek tersebut menjelaskan dataset di baliknya sebagai berikut:
Dataset sampel sengaja dibuat kecil dan dapat direproduksi. Dataset ini berguna untuk memeriksa instalasi, menghasilkan laporan, dan memahami alur engine tanpa membuat akun.
Sumber: quantjourney-bt README, versi 0.12.4, dibaca 6 Agustus 2026.
Run tersebut menghasilkan direktori, bukan kesimpulan di console: summary.txt dan summary.json, sebuah metrics.csv, sebuah equity_curve.csv di samping equity_curve.png, sebuah dashboard.html, folder plots/, serta run_metadata.json yang mencatat konfigurasi run. File terakhir ini paling sering dilewati, padahal file inilah yang membuat hasil dapat diaudit setahun kemudian.
Baca metrik yang dihasilkan secara objektif. Dataset bawaan berukuran kecil dan hanya bersifat ilustratif. Karena itu, Sharpe ratio dan maximum drawdown yang dicetak dalam summary.txt hanya menggambarkan file sampel. Keduanya bukan bukti mengenai suatu strategi. Menganggapnya sebagai hasil strategi merupakan kesalahan pertama yang dapat terjadi.
Hal yang benar-benar dibuktikan oleh run ini tetap penting: instalasi berfungsi, dan seluruh alur engine—mulai dari sinyal, target weights, hingga nilai portofolio yang direkonstruksi—menghasilkan artefaknya di mesin Anda tanpa satu pun credential. Tersedia jalur dengan credential ke data service milik proyek untuk backtest menggunakan histori riil. Jalur tersebut telah didokumentasikan. Panduan ini berhenti di sini, pada bagian yang tidak memerlukan apa pun dari pihak lain.
Hal yang tidak dapat ditunjukkan oleh satu equity curve in-sample
Satu simulasi hanya menghasilkan satu equity curve. Berikut hal-hal yang tidak dapat ditunjukkan oleh equity curve tersebut, berdasarkan data pasar riil, bukan file demo.
Panel di bawah ini menerapkan gagasan yang sama dengan strategi contoh: moving average 20 sesi yang melintasi moving average 50 sesi. Aturan tersebut diterapkan pada SPY, lalu hasilnya dilaporkan untuk setiap tahun kalender secara terpisah dari 2017 hingga 2025. Posisi pada setiap sesi ditentukan berdasarkan penutupan sesi sebelumnya. Dengan demikian, aturan tersebut tidak pernah bertransaksi berdasarkan angka yang belum tersedia.
SQL tepat di balik setiap angka
WITH daily AS
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toFloat64(argMax(close, window_start)) AS px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= '2016-01-01 00:00:00'
AND window_start < '2026-01-01 05:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY d
),
averaged AS
(
SELECT
d,
px,
avg(px) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
avg(px) OVER (ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
row_number() OVER (ORDER BY d) AS session_no
FROM daily
),
positioned AS
(
SELECT
d,
px,
if(session_no >= 50 AND fast_ma > slow_ma, 1, 0) AS long_today,
lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
lagInFrame(px, 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
FROM averaged
)
SELECT
toYear(d) AS year,
round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS rule_pct,
round((exp(sum(log(px / px_prior))) - 1) * 100, 1) AS hold_pct,
countIf(long_today != long_prior) AS crossover_count
FROM positioned
WHERE px_prior > 0
AND toYear(d) >= 2017
GROUP BY year
ORDER BY yearSatu aturan yang tidak berubah, diukur sebanyak 9 kali secara terpisah. Baca dua kolom persentase terlebih dahulu. Pada 2017, aturan tersebut mengakhiri tahun dengan hasil 16%, dibandingkan dengan 19.4% untuk strategi buy-and-hold SPY selama periode yang sama. Pada 2025, kedua kolom tersebut masing-masing menunjukkan 10.4% dan 16.4%. Kode yang digunakan pada kedua baris identik. Hanya periode moving average yang berubah.
Kolom crossover menunjukkan betapa terbatasnya bukti yang mendasari hasil tersebut. Sebanyak 4 perubahan posisi selama 2025 berarti equity curve sepanjang satu tahun hanya bertumpu pada sejumlah kecil keputusan. Itu merupakan sampel yang sangat kecil untuk disebut sebagai hasil yang meyakinkan.
Apakah aturan yang sama berperilaku sama pada instrumen lain?
Mengubah jendela tanggal adalah salah satu cara untuk menguji satu kurva. Mengubah universe adalah cara lainnya. Panel di bawah mempertahankan parameter yang sama dan menjalankan aturan identik pada lima instrumen likuid selama lima tahun kalender, dari 2021 hingga 2025.
SQL tepat di balik setiap angka
WITH daily AS
(
SELECT
ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toFloat64(argMax(close, window_start)) AS px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY', 'QQQ', 'AAPL', 'MSFT', 'KO')
AND window_start >= '2020-07-01 00:00:00'
AND window_start < '2026-01-01 05:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY ticker, d
),
averaged AS
(
SELECT
ticker,
d,
px,
avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
row_number() OVER (PARTITION BY ticker ORDER BY d) AS session_no
FROM daily
),
positioned AS
(
SELECT
ticker,
d,
px,
lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
lagInFrame(px, 1)
OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
FROM averaged
)
SELECT
ticker AS symbol,
round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS rule_pct,
round((exp(sum(log(px / px_prior))) - 1) * 100, 1) AS hold_pct,
round(avg(long_prior) * 100, 0) AS days_long_pct
FROM positioned
WHERE px_prior > 0
AND d >= toDate('2021-01-01')
GROUP BY symbol
ORDER BY rule_pct DESCQQQ berada di posisi teratas panel pada 40.2%, sedangkan baris terbawah, KO, berada pada 3.8%. Kolom days_long_pct menunjukkan berapa besar bagian dari jendela tersebut ketika setiap versi memiliki posisi terbuka, 67% untuk baris teratas. Satu set parameter, lima universe, dan spread yang cukup lebar sehingga memilih pemenang setelah melihat hasilnya tidak memberi informasi tentang pengujian yang belum Anda lakukan.
Semua ini bukan rekomendasi untuk memperdagangkan crossover. Crossover adalah tolok ukur untuk backtest, dan backtest itulah yang sedang kita ukur.
Di mana look-ahead bias muncul dalam backtest berbasis bobot
Engine berbasis bobot mengubah sinyal menjadi bobot target, menyimulasikan eksekusi berdasarkan bobot tersebut, lalu menghitung ulang nilai portofolio dari posisi yang dihasilkan. Kegagalannya tersembunyi pada penggabungan sinyal dan bobot. Jika bobot hari ini berasal dari harga penutupan hari ini lalu menghasilkan return hari ini, backtest telah melakukan transaksi berdasarkan informasi yang belum tersedia saat order seharusnya dimasukkan. Itulah look-ahead bias, dan kondisi ini tidak memicu error. Backtest hanya membuat seluruh hasil terlihat lebih baik.
Panel di bawah ini menjalankan dua versi dari satu rule atas histori SPY yang sama.
SQL tepat di balik setiap angka
WITH daily AS
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toFloat64(argMax(close, window_start)) AS px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= '2016-01-01 00:00:00'
AND window_start < '2026-01-01 05:00:00'
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
GROUP BY d
),
averaged AS
(
SELECT
d,
px,
avg(px) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
avg(px) OVER (ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
row_number() OVER (ORDER BY d) AS session_no
FROM daily
),
positioned AS
(
SELECT
d,
px,
if(session_no >= 50 AND fast_ma > slow_ma, 1, 0) AS long_today,
lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
lagInFrame(px, 1)
OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
FROM averaged
)
SELECT
toYear(d) AS year,
round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS next_bar_pct,
round((exp(sum(log(if(long_today = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS same_bar_pct,
round(abs(exp(sum(log(if(long_today = 1, px / px_prior, 1.0))))
- exp(sum(log(if(long_prior = 1, px / px_prior, 1.0))))) * 100, 1) AS gap_pp
FROM positioned
WHERE px_prior > 0
AND toYear(d) >= 2017
GROUP BY year
ORDER BY yearDalam 2017, versi yang menggunakan sesi sebelumnya menghasilkan 16%, sedangkan versi yang menggunakan sesi yang sama menghasilkan 17.1%. Selisihnya sebesar 1.1 percentage points. Dalam 2025, jarak antara keduanya tercatat sebesar 1.6 percentage points. Hanya satu dari kedua kolom tersebut yang dapat dihasilkan oleh mesin yang belum mengetahui posisi penutupan sesi. Selisih di antara keduanya murni merupakan persoalan akuntansi, tanpa gagasan, keahlian, atau trade yang mendasarinya.
Engine ini menyatakan sendiri posisinya mengenai persoalan timing. Pernyataan tersebut lebih bernilai daripada sekadar janji:
Untuk eksekusi pada pembukaan, slippage yang sensitif terhadap range hanya menggunakan bar terakhir yang telah selesai, sedangkan kapasitas volume diproyeksikan dari observasi yang di-lag. Engine tidak menggunakan high, low, close, atau volume penuh hari tersebut yang baru tersedia kemudian.
Sumber: README quantjourney-bt, versi 0.12.4, dibaca pada 6 Agustus 2026.
Asumsi yang terdokumentasi dapat diperiksa terhadap source yang sudah Anda instal. Asumsi yang tidak terdokumentasi hanyalah dugaan.
Mengapa tambahan walk-forward diperlukan
Contoh walk-forward, WF01 hingga WF05, tersedia bersama tambahan [wf] dan dependensi Optuna. Walk-forward menyesuaikan parameter pada satu bagian riwayat, mengukurnya pada bagian berikutnya, lalu menggeser kedua bagian tersebut ke depan dan mengulangi prosesnya. Varian rolling dan expanding berbeda dalam hal apakah jendela fitting membuang data tertuanya saat bergerak maju. Contoh lain menambahkan purge dan embargo pada setiap batas, dengan membuang observasi yang paling dekat dengan titik sambungan. Dengan demikian, data dari bagian yang digunakan untuk fitting tidak bocor ke bagian yang sedang diukur.
Semua itu tidak mengubah ide yang lemah menjadi strategi yang berhasil. Proses tersebut mengganti satu angka dengan distribusi angka yang dapat Anda evaluasi dan perdebatkan. Itulah peningkatan utamanya. Tahap berikutnya tetap bukan penggunaan uang sungguhan: paper trading sebelum uang sungguhan mengukur hal-hal yang secara struktural tidak dapat dilihat oleh backtest, termasuk apakah order Anda benar-benar terisi pada harga yang mendekati asumsi simulator. Sementara itu, circuit breaker untuk trading bot menangani kondisi ketika kode Anda gagal menjalankan tugasnya. Untuk statistik yang mendasari seluruh proses ini, catatan kami tentang buku trading kuantitatif open-source membahasnya secara lebih mendalam.
FAQ
Bisakah Anda melakukan backtest strategi tanpa API key?
Bisa. quantjourney-bt menyediakan sample dataset bawaan di balik flag --sample-data, dan contoh strateginya dapat dijalankan tanpa akun maupun kredensial. Dataset ini kecil dan bersifat ilustratif. Karena itu, anggap hasil tersebut sebagai pemeriksaan instalasi dan pipeline, bukan sebagai bukti mengenai kinerja suatu strategi.
Mengapa versi package Python untuk backtesting perlu dikunci?
Package pada versi 0.x tidak memberikan jaminan kompatibilitas antarrilis minor. Perubahan default atau penggantian nama metrik juga tidak selalu terlihat. Mengunci versi dengan pip install quantjourney-bt==0.12.4 dan mencatat environment dalam requirements file berarti hasil yang Anda peroleh hari ini dapat dibangun ulang tahun depan menggunakan engine yang sama.
Lisensi apa yang digunakan untuk quantjourney-bt?
Apache License 2.0. Lisensi ini mengizinkan penggunaan komersial dan modifikasi. Anda harus menyertakan file lisensi dan notice dalam setiap redistribusi. Lisensi ini juga memuat pemberian hak paten secara eksplisit dari para kontributor. Versi 0.12.4 diterbitkan pada 21 Juli 2026 dan memerlukan Python 3.11 atau yang lebih baru.
Apakah hasil backtest yang kuat berarti strategi tersebut berhasil?
Tidak. Backtest adalah satu pengukuran pada satu periode dan satu universe. Panel di atas menunjukkan satu aturan yang tidak berubah dapat menghasilkan angka tahunan yang sangat berbeda pada satu ticker, serta angka yang sangat berbeda di antara lima nama. Perbedaan inilah yang dirancang untuk diungkap oleh validasi walk-forward dan pengujian out-of-sample.
Cara menghitung panel di atas
Harga penutupan harian adalah print menit terakhir pada regular session untuk setiap tanggal. Data tersebut menggunakan waktu New York antara pukul 9.30 hingga 16.00. Dengan demikian, half-day dengan penutupan lebih awal tetap dihitung dengan benar tanpa menetapkan durasi session secara hardcode. Fast average mencakup 20 session dan slow average mencakup 50 session; keduanya merupakan simple average. Sebanyak 49 session pertama dari setiap seri digunakan sebagai warm-up dan tidak memiliki posisi. Angka tahunan mengompound pergerakan close-to-close setiap session ketika aturan berada dalam posisi long. Kolom hold mengompound setiap session dalam tahun yang sama sebagai pembanding. Lima nama dalam cross-section dipilih berdasarkan histori kontinu tanpa split selama periode tersebut, sehingga seri penutupan tidak memerlukan penyesuaian. Window ditetapkan pada periode lampau. Karena itu, panel ini menghasilkan angka yang sama setiap kali dibuat ulang.
Setiap panel di sini memuat SQL lengkap tepat di bawahnya. Dengan demikian, angka pada halaman ini dapat dijalankan ulang sebagaimana instalasi yang versinya telah dikunci. Untuk mengukur suatu aturan dalam window Anda sendiri sebelum menulis kode backtest, ajukan pertanyaannya dalam bahasa sehari-hari di terminal Strasmore.