Python-native backtesting engine for reproducible portfolio and order-based strategy research
Project description
QuantJourney Backtester
A Python-native backtesting engine for reproducible portfolio research.
QuantJourney Backtester turns strategy ideas into auditable research packets: signals become target weights or explicit orders, orders become simulated fills, fills update cash and positions, and NAV is reconstructed from portfolio state.
It is designed for researchers who need more than an equity curve: execution assumptions, costs, slippage, rebalancing rules, crisis behavior, walk-forward validation, optimization diagnostics, metrics, plots, and run metadata from one repeatable run.
Installation
pip install quantjourney-bt
Current PyPI release: 0.12.2. The public package supports Python 3.11 and newer.
Why It Exists
Most backtests stop at signal x returns. That is fast, but it hides the
questions that matter before a strategy can be trusted:
- Was there look-ahead?
- What happened to missing bars?
- How were weights converted into trades?
- Did costs and turnover destroy the edge?
- Did parameters generalize out of sample?
- Which crisis regimes broke the strategy?
- Can the run be reproduced and reviewed later?
QuantJourney Backtester makes these assumptions explicit.
Two Research Modes
Weight mode is for portfolio research: factor portfolios, rotation models, long/cash strategies, long/short books, risk overlays, volatility targeting, and scheduled rebalancing.
Order mode is for execution-aware research: market, limit, stop, stop-limit, trailing stop, bracket, and OCO orders with commissions, slippage, volume participation, fills, positions, cash, NAV, and trade blotters.
Fast weight execution solves transaction costs recursively on one post-cost
capital path: NAV, implied quantities, trade notionals, booked costs and
reported positions reconcile to the same self-financing ledger. Use
weight_execution="orders" when discrete fills and cash movements matter.
For fills at the open, range-sensitive slippage sees only the previous
completed bar and volume capacity is forecast from lagged observations; the
engine does not use that day's later high, low, close or full-day volume.
Engine Contract
Data -> Features -> Signals -> Target Weights / Orders -> Fills -> Positions -> NAV -> Metrics -> Report Packet
Each stage is explicit. Data is transformed into features, features drive signals, signals become either target weights or orders, execution assumptions turn those decisions into fills, and portfolio state is used to reconstruct NAV, metrics, plots, and run metadata.
What you want to do -> what to use
| I want to... | Use |
|---|---|
| Generate long / flat / short or ranking intent | _compute_signals() |
| Convert intent into target portfolio exposure | _compute_weights() |
| Apply caps, vol targeting, inverse vol, or risk parity | risk_model=... |
| Trade only on calendar, drift, signal, or turnover triggers | RebalancePolicy(...) |
| Submit market / limit / stop / trailing / bracket / OCO orders | execution_mode="orders" + _compute_orders(...) |
| Model spread, impact, and commission assumptions | slippage & commission models |
| Validate parameters out of sample | walk-forward / Optuna |
The repository examples and tests demonstrate these engine semantics locally.
What You Get From One Run
Each local run can produce metrics, plots, equity curves, drawdowns, rolling risk, optimization evidence, walk-forward results, CSV/JSON artifacts, a static HTML dashboard, and run metadata. The hosted platform adds crisis diagnostics, interactive dashboards, execution traces, and PDF tear sheets.
What Stays Local
Your strategy code, signals, portfolio accounting, order simulation, metrics, plots, generated reports, and run artifacts stay local. QuantJourney Cloud is used for market-data preparation and authentication.
What It Is Not
This is not a broker, not a live trading system, not investment advice, and not a guarantee that a strategy will work out of sample. Some examples intentionally simplify assumptions such as borrow cost, financing, liquidity and market impact. Those assumptions are documented so they can be changed, not hidden.
Example Output
Every run produces a review-ready research packet — equity curves, monthly returns heatmaps, drawdowns, risk and rolling statistics, and walk-forward / optimization summaries (see Reports for the exact file list). A few examples:
Cumulative returns vs benchmark
Monthly returns heatmap
Crisis analysis across historical stress periods (hosted platform report pack)
Walk-forward out-of-sample equity (hosted platform report pack)
The first two charts come from the open-source report pack in this repository. Charts marked hosted platform report pack — crisis analysis, trade blotters, execution traces, PDF factsheets, and interactive dashboards — are generated by the hosted QuantJourney platform on top of the same engine results. More examples at backtester.quantjourney.cloud.
Install
pip install quantjourney-bt
The wheel installs the backtester library. The runnable strategy catalog and
the strategy.sh (macOS/Linux) and strategy.bat (Windows) launchers are
repository assets; clone this repository when you
want to run or modify the examples below.
The install also exposes qj-bt data, an unauthenticated terminal helper for
browsing public backtester metadata such as sources, granularities, datasets,
example universes, and the symbols referenced by those examples.
Optional extras: pip install "quantjourney-bt[wf]" adds Optuna for the
walk-forward optimization examples (WF05); [data] adds the yfinance
benchmark fallback.
For local development:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e ".[dev,data]"
pytest
Do not install dependencies into the Homebrew/system Python. Use a virtual
environment; otherwise macOS/Homebrew may raise an
externally-managed-environment error and the launcher may miss packages such
as quantjourney_ti.
Reproducible Demo Without API Key
Run the first strategy against deterministic bundled sample data:
./strategy.sh example_weights_01_sma_daily --sample-data --output /tmp/qj-sample
The sample dataset is intentionally small and reproducible. It is useful for
install checks, report generation, and reading the engine flow without creating
an account. For real market data, set QuantJourney API credentials and run the
same strategy without --sample-data.
qj-bt Data Catalog
Open the optional keyboard-driven browser in an interactive terminal:
qj-bt data
Use deterministic section commands in scripts, CI, or an agent workflow:
qj-bt data overview
qj-bt data sources
qj-bt data granularities
qj-bt data datasets
qj-bt data asset-classes
qj-bt data universes
qj-bt data example-symbols
qj-bt data sources --json
qj-bt data uses the public metadata endpoints without reading or transmitting an
API key. When no section is supplied, it opens the interactive browser only when
stdin and stdout are attached to a terminal; otherwise it emits the overview table.
The example-symbol index is illustrative and is not an exhaustive market-data
availability catalog.
Repository Layout
backtester/ Runtime package imported as backtester
strategies/ Runnable strategy examples
strategy.sh macOS/Linux strategy launcher
strategy.bat Windows strategy launcher
strategy.py Shared cross-platform launcher logic
benchmarks/ Benchmark-suite notes
skills/ Strategy-authoring skill materials
tests/ Import, packaging, and report smoke checks
docs/ Roadmap and supporting documentation
CHANGELOG.md Release history
The public runtime includes the shared execution simulator, contract-aware portfolio ledger, portfolio-of-strategies book, and pre-trade risk controls. Hosted data, orchestration, and extended report packs remain outside this repository; see Public Scope.
The tests/ directory is intentionally kept. It is not required at runtime, but
it gives the package a quick install/import/report safety check before release.
Documentation
- Windows setup - native Windows installation and
strategy.batusage without WSL. - Roadmap - direction of travel by theme, without delivery dates or ordering commitments.
- Strategy catalog - runnable examples with source and result links.
- Contributing - how to add example strategies, fixes, and docs (fork, branch, pull request).
- Release process - clean-tag publishing and exact artifact boundary checks.
AI Co-Pilot Skills
The skills/ directory holds guidance packs for AI-assisted research. When you
work with an AI coding assistant, point it at the relevant SKILL.md so it
follows the engine's conventions instead of guessing:
| Skill | Use it to |
|---|---|
qj-strategy-ideas |
Turn an idea into a runnable strategy — weights vs orders, the nearest example, the two-method pattern. |
qj-strategy-author |
Write a clean, focused example strategy. |
qj-strategy-reviewer |
Review a strategy for look-ahead, exposure, cost realism, and mode fit. |
qj-report-analyst |
Read a report and its plots and judge whether the result is trustworthy. |
qj-config-helper |
Configure the engine — parameters, rebalance policy, risk overlays, granularity. |
For example: when writing a new strategy, open
skills/qj-strategy-ideas/SKILL.md and follow the pattern; when reviewing one,
use skills/qj-strategy-reviewer/SKILL.md; to make sense of the output, use
skills/qj-report-analyst/SKILL.md.
Quick Start
For a full catalog of all 50 example strategies — each with a one-line description, a link to its source, and a link to its results page — see strategies/README.md or the summary below.
List available strategies:
./strategy.sh --list
On Windows use strategy.bat --list in Command Prompt or
.\strategy.bat --list in PowerShell. See WINDOWS.md for the
complete native Windows workflow.
Check one strategy import without credentials or a data call:
./strategy.sh example_weights_01_sma_daily --check
Run a deterministic demo without API credentials:
./strategy.sh example_weights_01_sma_daily --sample-data --output /tmp/qj-sample
Run repository checks:
pytest -q
Run a real backtest after setting credentials:
export QJ_API_KEY="..."
./strategy.sh example_weights_01_sma_daily --output /tmp/qj-reports
Run all strategies sequentially through the same launcher:
export QJ_API_KEY="..."
./strategy.sh --all --output ./reports
The batch continues after individual failures and writes per-strategy logs plus
a tab-separated summary under reports/_batch/<timestamp>/. Use
./strategy.sh --all --check to import-check the full catalog without data
calls.
API key auth is preferred for CLI runs. Email/password auth also works; if the
auth service returns an active-session conflict, the launcher retries with
replace_existing_session=true by default. Set
QJ_REPLACE_EXISTING_SESSION=0 if you do not want a CLI run to replace an
existing web session.
Strategy Catalog
The repository ships 50 runnable example strategies — 25 weight-based, 20 order-based, and 5 walk-forward / optimization. Each has source and results-page links in the full catalog; a summary follows.
Weight-based (25) — target-weight portfolios, market-neutral long/short, and risk overlays:
| # | Strategy | Idea | Code | Results |
|---|---|---|---|---|
| W01 | Daily SMA Trend | Hold each sector ETF while SMA(50) > SMA(200); daily rebalance | source | view |
| W02 | Monthly ETF Trend + Drift | SMA(50/200) trend on ETFs; month-end + 5% drift band | source | view |
| W03 | Weekly RSI Reversion | Enter RSI(14) < 35, exit RSI > 60; weekly (Fri) | source | view |
| W04 | Quarterly Dual Momentum | Rank ETFs by 12-month return, hold top 2 if positive; quarter-end | source | view |
| W05 | Monthly Inverse Volatility | Size each ETF by inverse 63-day volatility; month-end | source | view |
| W06 | Signal-Change Defensive Rotation | SPY > SMA(200) -> risk-on ETFs, else defensive; on signal change | source | view |
| W07 | Intraday RSI 15m | Equal-weight basket when RSI oversold; 15-minute bars | source | view |
| W08 | Intraday EMA Scalp 1m | EMA(9/21) trend/cash; 1-minute bars | source | view |
| W09 | Intraday SMA Trend 1h | SMA(10/30) trend/cash; hourly bars | source | view |
| W10 | Monthly + Circuit Breaker | Monthly ETF trend; flatten on a 15% drawdown + cooldown | source | view |
| W11 | Quarterly TE + Cost Gate | Momentum with tracking-error trigger and turnover budget | source | view |
| W12 | Daily Partial Drift | Momentum tilt; trade only names past a 10% drift band | source | view |
| W13 | Pairs Trading (Ratio Z-Score) | Market-neutral KO/PEP on a log-ratio z-score | source | view |
| W14 | Pairs Trading (Hedge Ratio) | Market-neutral EWA/EWC on a rolling OLS hedge-ratio spread | source | view |
| W15 | Cross-Sectional Momentum (L/S) | Long top-3 / short bottom-3 by 12-month return; monthly | source | view |
| W16 | Cross-Sectional Reversal (L/S) | Long losers / short winners by 1-month return; weekly | source | view |
| W17 | Vol-Targeted Trend | SMA trend basket scaled to a 10% volatility target | source | view |
| W18 | Vol-Targeted Momentum | Momentum basket scaled to a 15% volatility target | source | view |
| W19 | Risk Parity (Multi-Asset ERC) | Equal risk contribution across a multi-asset basket | source | view |
| W20 | Risk Parity + Position Cap | Sector ERC chained with a 25% per-position cap | source | view |
| W21 | Bollinger Band Reversion | Buy below the lower band, exit at the midline | source | view |
| W22 | MACD Trend | Long while MACD is above its signal line | source | view |
| W23 | FX Time-Series Momentum | Six-month trend across USD-quoted spot pairs; inverse-vol weights | source | view |
| W24 | FX Cross-Sectional Momentum | Long strongest / short weakest XXX/USD pair; monthly | source | view |
| W25 | Continuous Futures Trend Proxy | Diversified long/short trend on provider continuous series | source | view |
W23-W25 are price-return research proxies. They do not apply FX lots or futures multipliers to PnL and do not model financing, margin, or controlled futures rolls.
Order-based (20) — explicit orders through the fill engine (slippage, commissions, blotter):
| # | Strategy | Order type | Idea | Code | Results |
|---|---|---|---|---|---|
| O01 | Market SMA Crossover | Market | Buy SMA(20) crossing above SMA(50), sell on reverse | source | view |
| O02 | Market RSI Reversion | Market | Buy RSI(14) < 35, sell RSI > 60 | source | view |
| O03 | Limit RSI Dip Buyer | Limit | Passive buy-limit below the close on weak RSI | source | view |
| O04 | Limit Trend Pullback | Limit | In an uptrend, wait for a 1% pullback to enter | source | view |
| O05 | Stop Breakout Entry | Stop | Buy-stop above the recent 20-day high | source | view |
| O06 | Protective Stop Loss | Market + Stop | Trend entry with a 5% protective stop | source | view |
| O07 | Stop-Limit Breakout | Stop-Limit | Enter breakouts but cap the maximum fill price | source | view |
| O08 | Stop-Limit Protection | Market + Stop-Limit | Trend entry, downside protected by a stop-limit sell | source | view |
| O09 | Trailing Stop Trend | Trailing Stop | Trend entry, 4% trailing stop manages the exit | source | view |
| O10 | RSI + Trailing Stop | Trailing Stop | Oversold RSI entry, 5% trailing stop for risk | source | view |
| O11 | Trailing Stop-Limit | Trailing Stop-Limit | Trailing stop that converts to a limit on trigger | source | view |
| O12 | Bracket Trend | Bracket | Trend entry with a +6% / -3% bracket | source | view |
| O13 | Bracket RSI Reversion | Bracket | RSI dip with a +4% / -2% bracket | source | view |
| O14 | OCO Dip or Breakout | OCO | Competing buy-limit (dip) and buy-stop (breakout) | source | view |
| O15 | Intraday 5m Bracket Reversion | Bracket | Oversold-RSI dips with a tight +0.6% / -0.4% bracket; 5-min bars | source | view |
| O16 | Intraday 30m Stop Breakout | Stop | Buy-stop above the 12-bar high, fixed holding period; 30-min bars | source | view |
| O17 | Monthly Rotation (orders) | Market | Event-driven monthly momentum rotation, executed with orders | source | view |
| O18 | Signal-Change Rotation (orders) | Market | Trade only on SMA trend-signal flips (no calendar) | source | view |
| O19 | FX Momentum with Standard Lots | Market | Contract-aware whole-lot momentum on USD-quoted spot pairs | source | view |
| O20 | Futures Donchian Contracts | Market | Whole-contract ATR sizing on provider continuous futures | source | view |
O19-O20 consume provider contract specifications. Multipliers and lot sizes are applied. Cross-currency conversion, FX swaps, dated-futures selection, and controlled roll execution remain out of scope; unsupported cross-currency FX accounting is rejected rather than approximated.
Walk-forward & optimization (5) — prove a strategy generalizes:
| # | Example | Idea | Code | Results |
|---|---|---|---|---|
| WF01 | Rolling Walk-Forward | Sliding fixed-length train/test windows with a pre-OOS purge | source | view |
| WF02 | Expanding Walk-Forward | Ever-growing training window vs sliding test window | source | view |
| WF03 | Anchored + Pre-OOS Purging | Fixed and percentage-based exclusions before each test window | source | view |
| WF04 | Grid Search | Exhaustive SMA fast/slow tuning scored by real backtests | source | view |
| WF05 | Optuna TPE + Walk-Forward | Bayesian parameter search, then out-of-sample validation | source | view |
WF01-WF03 default to slice_diagnostics: train/test metrics are computed from
one full-period NAV so examples run quickly and remain easy to inspect. To run
a fuller per-fold strategy re-run/refit, enable:
QJ_WF_MODE=per_fold_refit ./strategy.sh example_wf_01_rolling_walkforward
The logs and walk-forward summary report the active mode. Per-fold refit is slower because each fold runs the strategy again through the data/preparation pipeline. Factories receive ISO date strings, and the runner fails closed if the returned NAV escapes the requested fold bounds. Learned preprocessing must still be fitted on training data only.
extra_pre_oos_purge_pct extends the exclusion immediately before OOS; the
legacy embargo_pct alias does not implement classical post-test embargo.
Optimizer runs report DSR with raw/effective trial counts plus a rolling top-K
rank-failure diagnostic. The latter is not canonical CSCV PBO.
WF04-WF05 are optimization workflows and should be read as selection diagnostics rather than a simple winner-takes-all backtest.
Long/short examples (W13–W16) are market-neutral; short borrow/financing is not modeled (a documented research approximation).
Data Granularity
Backtester(..., granularity="1d") remains the default. For yfinance-backed
/bt/prepare data you can request historical intraday bars with values such as
1m, 5m, 15m, 30m, or 1h; numeric aliases like granularity=5 are
normalized to 5m.
strategy = MyStrategy(
api_key="...",
instruments=["AAPL", "MSFT"],
backtest_period={"start": "2026-06-01", "end": "2026-06-05"},
source="yfinance",
granularity="5m",
)
Intraday availability depends on yfinance history coverage for the requested symbols and dates.
Strategy Skeleton
import asyncio
import os
import pandas as pd
from backtester import Backtester
class MyStrategy(Backtester):
def _compute_signals(self) -> pd.DataFrame:
close = self.instruments_data.get_feature("adj_close")
fast = close.rolling(20).mean()
slow = close.rolling(60).mean()
return ((fast > slow) & fast.notna() & slow.notna()).astype(float)
def _compute_weights(self) -> pd.DataFrame:
signals = self.instruments_data.get_feature(
"strategies", self.strategy_name, "signals"
)
active = signals.sum(axis=1).replace(0, pd.NA)
return signals.div(active, axis=0).fillna(0.0)
def _compute_positions(self) -> None:
pass
async def main() -> None:
strategy = MyStrategy(
api_key=os.environ["QJ_API_KEY"],
strategy_name="sma_research",
instruments=["AAPL", "MSFT", "NVDA"],
backtest_period={"start": "2024-01-01", "end": "2025-01-01"},
source="yfinance",
granularity="1d",
initial_capital=100_000,
)
await strategy.run_strategy()
strategy.print_summary()
if __name__ == "__main__":
asyncio.run(main())
Reports
By default a strategy run writes outputs under reports/<strategy_name>/ or
the directory passed to --output:
summary.txtsummary.jsonmetrics.csvequity_curve.csvequity_curve.pngdashboard.html- selected PNG charts under
plots/ run_metadata.json
Use --no-reports when you only want calculation and run metadata:
./strategy.sh example_weights_01_sma_daily --no-reports --output /tmp/qj-reports
Assumptions & Limitations
- Example strategies are research templates, not production trading systems.
- Long/short examples do not model borrow fees, stock-loan availability, financing, or margin interest by default.
- Commissions, slippage, volume participation, and market impact are model assumptions. Treat them as part of the research contract.
- The deterministic sample dataset is illustrative. It is not historical market data and should not be used to judge strategy quality.
- Historical intraday availability depends on the upstream provider and the requested symbols, dates, and granularity.
- Walk-forward and optimization diagnostics help expose overfit risk, but a
good in-sample or single out-of-sample result is not proof of robustness.
Check whether a run used
slice_diagnosticsorper_fold_refit.
The runtime package is imported as backtester.
License
Apache License 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quantjourney_bt-0.12.2.tar.gz.
File metadata
- Download URL: quantjourney_bt-0.12.2.tar.gz
- Upload date:
- Size: 335.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cdff045d83850acc30fd14d06489f739e269ffcf1d0c7486b6b823a8d98def00
|
|
| MD5 |
08d5373c6a7eeed1081766420c643f5d
|
|
| BLAKE2b-256 |
51bca05debe3ea8af03dc6b172670fa266dd92e27bdf56242ae91e5c6321a4c2
|
Provenance
The following attestation bundles were made for quantjourney_bt-0.12.2.tar.gz:
Publisher:
publish.yml on QuantJourneyOrg/quantjourney-bt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quantjourney_bt-0.12.2.tar.gz -
Subject digest:
cdff045d83850acc30fd14d06489f739e269ffcf1d0c7486b6b823a8d98def00 - Sigstore transparency entry: 2195814216
- Sigstore integration time:
-
Permalink:
QuantJourneyOrg/quantjourney-bt@b09cd205372380efa807d637ca7467278aa19b89 -
Branch / Tag:
refs/tags/v0.12.2 - Owner: https://github.com/QuantJourneyOrg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b09cd205372380efa807d637ca7467278aa19b89 -
Trigger Event:
push
-
Statement type:
File details
Details for the file quantjourney_bt-0.12.2-py3-none-any.whl.
File metadata
- Download URL: quantjourney_bt-0.12.2-py3-none-any.whl
- Upload date:
- Size: 302.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a85fd4b0ea6a5ab3395cce574668cefed8dd684f0c054b6cf53f78ea1c407ab
|
|
| MD5 |
6e2780fe8eae79bcec89230505b14268
|
|
| BLAKE2b-256 |
3074582664029fc95cee473248fd77792683ab9ae9c2d91bad63d40d0a338310
|
Provenance
The following attestation bundles were made for quantjourney_bt-0.12.2-py3-none-any.whl:
Publisher:
publish.yml on QuantJourneyOrg/quantjourney-bt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quantjourney_bt-0.12.2-py3-none-any.whl -
Subject digest:
7a85fd4b0ea6a5ab3395cce574668cefed8dd684f0c054b6cf53f78ea1c407ab - Sigstore transparency entry: 2195814247
- Sigstore integration time:
-
Permalink:
QuantJourneyOrg/quantjourney-bt@b09cd205372380efa807d637ca7467278aa19b89 -
Branch / Tag:
refs/tags/v0.12.2 - Owner: https://github.com/QuantJourneyOrg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b09cd205372380efa807d637ca7467278aa19b89 -
Trigger Event:
push
-
Statement type: