Reproducible Backtest sa Python Nang Walang API Key
Magpatakbo ng reproducible backtest sa Python nang walang API key gamit ang pinned install at deterministic sample data, at unawain ang limitasyon ng isang equity curve.
Ang reproducible backtest sa Python ay isang backtest na maaaring patakbuhin muli ng ibang tao sa malinis na machine at makuha ang eksaktong parehong numbers, nang walang account at API key na magiging hadlang. Karamihan sa mga tutorial ay hindi pumapasa sa test na ito sa unang linya pa lamang, kung saan nagbibigay ang live download sa susunod na reader ng bahagyang ibang price history kaysa sa ibinigay nito sa author. Itinatakda ng walkthrough na ito ang isang open-source engine, quantjourney-bt sa version 0.12.4, pinapatakbo ang bundled example nito nang walang anumang credentials, at pagkatapos ay gumagamit ng totoong market data upang ipakita kung ano ang hindi pa rin masasabi ng isang malinis na run lamang.
Ano ang nagpapanatiling reproducible sa isang backtest?
May tiyak at nasusubok na kahulugan ang reproducibility dito: may ibang taong nagpapatakbo ng isang command sa malinis na machine at nakukuha ang eksaktong mga numero mo hanggang sa decimal. Dalawang karaniwang bagay ang sumisira rito.
Una ang code. Walang compatibility promise ang library na nasa version 0.x sa pagitan ng mga minor release. Isang pagpapalit ng pangalan, pagbabago sa default, o pag-aayos muli ng column, at patuloy na tatakbo ang script mo habang tahimik na iba na ang iniuulat nito.
Ikalawa ang data. Ang tutorial na live download ang nasa unang linya ay hindi kailanman reproducible. Binabago ng mga vendor ang historical data, ina-adjust ito para sa stock splits, at pinupunan ang mga puwang. Dahil dito, maaaring maglabas ang parehong script ng ibang mga numero makalipas ang isang buwan. Pagkatapos ng pagbabago, hindi mo na matutukoy kung code change o data change ang sanhi.
Ang pattern na dapat tularan mula sa project na ito ay ang pagtambal ng fixed engine version sa isang maliit na bundled dataset na kasama mismo sa package.
I-pin ang install: pip install quantjourney-bt==0.12.4
Ang quantjourney-bt ang QuantJourney backtester, na inilabas sa ilalim ng Apache License 2.0 at nangangailangan ng Python 3.11 o mas bago. Na-publish ang Version 0.12.4 noong 21 July 2026, at ito ang bersyong tinutukoy ng bawat command sa ibaba, gaya ng dokumentado noong August 2026.
Magtrabaho sa loob ng isolated environment. Gumagawa nito ang python3 -m venv .venv, pumapasok dito ang source .venv/bin/activate, at ina-update ng python -m pip install -U pip ang installer sa loob nito. Pagkatapos, kunin ang eksaktong version: pip install quantjourney-bt==0.12.4.
Idinodokumento ng proyekto ang unpinned form na pip install quantjourney-bt. Ikaw ang responsable sa bahaging ==0.12.4, at sa 0.x mahalaga ito. Isulat ang pin kung saan ito makikita ng susunod na gagamit: kinukuha ng pip freeze > requirements.txt ang lahat ng resolved dependency, kabilang ang mga dependency na hindi mo tahasang pinangalanan.
May dalawang optional extra. Idinadagdag ng pip install "quantjourney-bt[wf]" ang Optuna para sa walk-forward at optimization examples. Idinadagdag ng pip install "quantjourney-bt[data]" ang yfinance fallback na ginagamit para sa mga benchmark.
Permissive ang Apache-2.0. Maaari mong gamitin at baguhin ang code para sa komersiyal na paggamit, kailangan mong panatilihin ang licence at notice files sa anumang redistribution, at tahasang nagbibigay ang mga contributor ng patent rights.
Paano patakbuhin ang bundled SMA example nang walang API key
May launcher script ang repository, kasama ang fifty runnable example strategy na hinati sa weight-based path at order-based path. Kabilang dito ang five walk-forward workflow. Ipinapakita ng ./strategy.sh --list ang catalog. Nag-i-import ang ./strategy.sh example_weights_01_sma_daily --check ng isang strategy at hindi kumokonekta sa anumang data. Ito ang pinakamabilis na paraan para makumpirmang maayos ang installation.
Isang linya lang ang demo run: ./strategy.sh example_weights_01_sma_daily --sample-data --output /tmp/qj-sample
--sample-data flag ang pinakamahalagang bahagi. Ganito inilalarawan ng project ang dataset na ginagamit nito:
Sadyang maliit at reproducible ang sample dataset. Kapaki-pakinabang ito para sa install check, paggawa ng report, at pag-unawa sa daloy ng engine nang hindi kinakailangang gumawa ng account.
Source: quantjourney-bt README, version 0.12.4, binasa noong 6 August 2026.
Gumagawa ang run ng directory sa halip na maglabas ng verdict sa console: summary.txt at summary.json, isang metrics.csv, isang equity_curve.csv katabi ng equity_curve.png nito, isang dashboard.html, isang plots/ folder, at isang run_metadata.json na nagtatala kung paano na-configure ang run. Ang huling file na ito ang karaniwang nilalaktawan ng mga tao. Ito rin ang nagpapahintulot na ma-audit ang resulta makalipas ang isang taon.
Suriin nang tapat ang mga nagawang metric. Maliit at illustrative ang bundled dataset, kaya ang Sharpe ratio at ang maximum drawdown na ipinapakita sa summary.txt ay naglalarawan lamang ng isang sample file. Hindi ang mga ito ebidensiya tungkol sa isang strategy. Ang pagturing sa mga ito bilang aktuwal na resulta ang unang pagkakamaling maaaring mangyari.
Mahalaga pa rin ang naitatatag ng run: gumagana ang installation, at nailalabas ng buong engine flow—mula signal hanggang target weights at reconstructed portfolio value—ang mga artifact nito sa machine mo nang walang kahit isang credential. May credentialed path papunta sa sariling data service ng project para sa mga backtest gamit ang aktuwal na historical data. Naka-document ang path na iyon, at dito nagtatapos ang walkthrough, sa bahaging walang kinakailangan mula sa iba.
Ano ang hindi sinasabi ng isang in-sample equity curve
Isang equity curve ang ipinapakita ng sample run. Narito ang hindi nito kayang ipakita, batay sa aktuwal na market data sa halip na demo file.
Ginagamit ng panel sa ibaba ang parehong ideya ng example strategy: isang 20-session moving average na tumatawid sa 50-session moving average. Inilapat ito sa SPY, at hiwalay na iniulat ang bawat calendar year mula 2017 hanggang 2025. Tinutukoy ang position sa bawat session batay sa close ng nakaraang session. Kaya hindi kailanman nagte-trade ang rule gamit ang numerong hindi pa nito alam.
Ang eksaktong SQL sa likod ng bawat numero
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 yearIsang hindi nagbabagong rule, sinukat nang 9 magkakahiwalay na beses. Basahin muna ang dalawang percent column bago ang iba pa. Sa 2017, nagtapos ang rule sa 16% kumpara sa 19.4% para sa paghawak ng SPY sa parehong period. Sa 2025, 10.4% at 16.4% ang nakasaad sa parehong dalawang column. Magkapareho ang code sa dalawang row. Ang window lamang ang nagbago.
Ipinapakita ng crossover column kung gaano kaliit ang aktuwal na ebidensiya sa underlying. 4 position changes sa loob ng 2025 ay nangangahulugang nakabatay ang isang buong taon ng equity curve sa iilang desisyon lamang. Napakaliit nitong sample para tawaging resulta.
Pareho ba ang kilos ng parehong rule sa ibang names?
Isang paraan para suriin ang iisang curve ang pagbabago sa date window. Ang isa pa ay ang pagbabago sa universe. Nakapirmi ang mga parameter sa panel sa ibaba, at pinapatakbo nito ang parehong rule sa limang liquid names sa loob ng limang calendar year mula 2021 hanggang 2025.
Ang eksaktong SQL sa likod ng bawat numero
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 DESCNasa itaas ng panel ang QQQ sa 40.2%, habang ang nasa ibabang row, KO, ay nasa 3.8%. Ipinapakita ng column na days_long_pct kung gaano kalaking bahagi ng window ang ginugol ng bawat bersyon na may hawak na kahit anong posisyon, na 67% para sa nasa itaas na row. Isang set ng parameter, limang universe, at napakalawak na spread kaya walang sinasabi tungkol sa susunod na run ang pagpili sa winner batay sa mga resulta pagkatapos mangyari ang lahat.
Hindi ito rekomendasyon na mag-trade gamit ang crossover. Panukat lamang ang crossover para sa backtest, at ang backtest ang sinusukat natin.
Saan pumapasok ang look-ahead bias sa backtest ng weights
Ginagawang target weights ng engine na nakabatay sa weights ang signal, ini-simulate ang fills batay sa mga weight na iyon, at muling kinakalkula ang halaga ng portfolio mula sa mga nabuong posisyon. Nakakubli ang problema sa pag-uugnay ng signal at weight. Kung ang weight para sa araw na ito ay mula sa closing price ngayon at kumikita ito mula sa return ngayon, nag-trade ang backtest gamit ang impormasyong wala pa noong dapat inilagay ang order. Iyan ang look-ahead bias, at hindi ito nagdudulot ng error. Pinapaganda lang nito ang hitsura ng lahat ng resulta.
Ipinapakita ng panel sa ibaba ang dalawang bersyon ng isang rule gamit ang parehong history ng SPY.
Ang eksaktong SQL sa likod ng bawat numero
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 yearSa 2017, nag-print ang bersyon na nakabatay sa nakaraang session ng 16%, habang ang bersyon na nakabatay sa parehong session ay nag-print ng 17.1%, na may pagitan na 1.1 percentage points. Sa 2025, umabot sa 1.6 percentage points ang agwat ng dalawang sukat. Isa lamang sa mga column na iyon ang maaaring gawin ng isang machine na hindi pa alam kung saan magsasara ang session. Ang pagkakaiba sa pagitan ng mga ito ay purong accounting, na walang ideya, kasanayan, o aktuwal na trade na pinagmulan.
Malinaw na ipinapahayag ng engine na ito ang posisyon nito sa usapin ng timing, at mas mahalaga ito kaysa sa isang pangako:
Para sa fills sa open, ang range-sensitive slippage ay gumagamit lamang ng nakaraang nakumpletong bar, at ang volume capacity ay hinuhulaan mula sa mga lagged observation. Hindi ginagamit ng engine ang high, low, close, o full-day volume ng araw na iyon.
Source: quantjourney-bt README, version 0.12.4, binasa noong 6 August 2026.
Maaaring suriin ang isang dokumentadong assumption gamit ang source na na-install mo na. Ang hindi dokumentadong assumption ay hula lamang.
Bakit kailangan ang walk-forward extra
Kasama sa walk-forward examples na WF01 hanggang WF05 ang [wf] extra at ang Optuna dependency nito. Nagfi-fit ang walk-forward ng mga parameter gamit ang isang bahagi ng historical data, sinusukat ang mga ito sa kasunod na bahagi, pagkatapos ay isinusulong ang dalawang window at inuulit ang proseso. Nagkakaiba ang rolling at expanding variants batay sa kung inaalis o hindi ng fitting window ang pinakamatandang data habang umuusad ito. Sa isa pang example, may purge at embargo sa bawat boundary. Inaalis nito ang mga observation na pinakamalapit sa seam upang hindi makapasok ang data mula sa fitted slice sa slice na sinusukat.
Wala sa mga ito ang makapagpapagana sa isang mahinang ideya. Pinapalitan lamang nito ang isang numero ng distribution ng mga numerong maaari mong suriin at kuwestiyunin. Iyan ang buong upgrade. Ang kasunod na hakbang ay hindi pa rin live money: sinusukat ng paper trading bago gumamit ng totoong pera ang mga bagay na hindi kayang makita nang maayos ng backtest, kabilang ang kung mapupunan ang order mo sa presyong malapit sa ipinagpalagay ng simulator, at tinutugunan ng circuit breakers para sa trading bots ang maaaring gawin ng code mo sa araw na hindi ito gumana gaya ng inaasahan. Para sa statistics na pinagbabatayan ng lahat ng ito, mas malalim ang talakay ng aming notes tungkol sa open-source quant trading book.
Mga Madalas Itanong
Maaari bang mag-backtest ng strategy nang walang API key?
Oo. May kasamang sample dataset ang quantjourney-bt na nakatago sa likod ng --sample-data flag, at maaaring patakbuhin rito ang mga example strategy nang walang account o credentials. Maliit at pang-ilustrasyon lamang ang dataset, kaya ituring ang run na iyon bilang pagsusuri sa installation at pipeline, hindi bilang ebidensiya na gumagana ang isang strategy.
Bakit kailangang i-pin ang version ng Python backtesting package?
Ang package na nasa 0.x ay walang garantiya ng compatibility sa pagitan ng minor release, at maaaring hindi ipaalam ng binagong default o pinalitang pangalan ng metric ang sarili nito. Sa pag-pin gamit ang pip install quantjourney-bt==0.12.4 at pagtatala ng environment sa isang requirements file, maibabalik ang resultang ginawa mo ngayon sa susunod na taon gamit ang engine na gumawa nito.
Sa ilalim ng anong licence inilabas ang quantjourney-bt?
Apache License 2.0. Pinapayagan nito ang commercial use at modification, hinihingi nitong panatilihin ang licence at notice files sa anumang redistribution, at may kasamang tahasang patent grant mula sa mga contributor. Inilathala ang Version 0.12.4 noong 21 July 2026 at nangangailangan ito ng Python 3.11 o mas bago.
Nangangahulugan ba ang magandang resulta ng backtest na gumagana ang strategy?
Hindi. Ang backtest ay isang measurement lamang, sa isang window at sa isang universe. Ipinapakita ng mga panel sa itaas kung paano makapagbibigay ang iisang hindi nagbagong rule ng magkakaibang yearly figures sa isang ticker at ibang-ibang figures sa limang pangalan. Ito ang pagkakaibang inilalantad ng walk-forward validation at out-of-sample testing.
Paano kinalkula ang mga panel sa itaas
Ang daily close ay ang huling regular-session minute print para sa bawat petsa, batay sa New York clock time mula 9:30 a.m. hanggang 4:00 p.m. Dahil dito, tama ang paghawak sa mga half day na maagang nagsasara nang hindi kailangang mag-hardcode ng haba ng session. Sumasaklaw ang fast average sa 20 session at ang slow average sa 50 session; parehong simple ang mga ito. Ang unang 49 session ng bawat series ay warm-up at walang hawak na position. Kino-compound ng yearly figures ang close-to-close move ng bawat session para sa mga session kung kailan long ang rule, habang kino-compound ng hold column ang bawat session sa parehong taon bilang paghahambing. Pinili ang limang pangalan sa cross-section batay sa tuloy-tuloy na history na walang split sa loob ng window, kaya hindi kailangang i-adjust ang close series. Nakatakda sa nakaraan ang mga window, kaya pareho ang mga numerong ibinabalik ng mga panel sa bawat regeneration.
Taglay ng bawat panel dito ang eksaktong SQL sa ibaba nito, kaya maaaring patakbuhin muli ang mga numero sa page na ito, gaya ng pinned install. Para sukatin ang isang rule sa sarili mong window bago ka magsulat ng anumang backtest code, ilahad ang tanong sa plain English sa Strasmore terminal.