Skip to content

Repository files navigation

QuantLab β€” Reproducible Quantitative Research and Backtesting

CI Python 3.12 | 3.13 License: MIT

QuantLab is a modular Python platform for designing, backtesting, and validating systematic strategies under configurable, explicitly modelled execution and transaction-cost assumptions. It turns a financial hypothesis into a reproducible, bias-aware experiment: you download and validate market data, build features and signals, run vectorised backtests with explicitly modelled costs, measure performance and risk, validate out-of-sample with walk-forward analysis, and generate an honest research report β€” all driven by one YAML config, with a Python/notebook API for deeper robustness analysis (sensitivity, bootstrap, permutation).

The project focuses on rigorous and reproducible empirical research rather than maximizing historical performance.

πŸ“š Documentation


QuantLab dashboard results


Research methodology

  • Delayed-execution barrier β€” signals are strictly shifted before returns; the separation between signal at t, position at t+1 and realised return is enforced and unit-tested. This mechanically prevents a position from earning the return that ends on the same bar its own signal was formed on β€” the common look-ahead leak β€” but does not, by itself, guarantee that a custom strategy's own feature/signal construction is causal (e.g. a strategy that reads future rows directly out of its input data); that remains the strategy author's own responsibility.
  • Configurable modelled costs β€” explicit commission, spread and (constant or volume-based) slippage; every result reports gross vs net.
  • Walk-forward validation β€” expanding/rolling windows, parameter selection on validation only and out-of-sample stitching; separate robustness tools provide sensitivity heatmaps, bootstrap, permutation and stress tests.
  • Risk analytics β€” Sharpe, Sortino, Calmar, max drawdown, VaR/CVaR, exposures, benchmark alpha/beta, and more, implemented from first principles.
  • Automated reports β€” self-contained HTML research report with an honest limitations section, written to the experiment directory automatically by every quantlab backtest/walk-forward CLI run; the dashboard generates the same report in memory for on-screen preview and download instead of writing it to disk. Calling BacktestEngine directly through the Python API requires the explicit result.to_html(...) call shown below.

Strategies

One BaseStrategy interface behind six reusable, tested strategies: buy & hold, time-series momentum, cross-sectional momentum, mean reversion (z-score, RSI, percentile, Bollinger and distance-to-MA), trend following, and pairs trading (hedge ratio + ADF-gated spread). See docs/strategies.md for each strategy's own assumptions and parameters.

Example research

Robust Cross-Sectional Momentum Across Liquid Multi-Asset ETFs β€” Can cross-sectional momentum generate stable out-of-sample risk-adjusted returns across liquid ETFs after transaction costs and volatility targeting? One example of the kind of study the platform is built for; walk through it in notebooks/02_momentum_research.ipynb and notebooks/05_robustness_analysis.ipynb.

reports/generated/ is a build artefact, deliberately excluded from version control β€” the full HTML report at reports/generated/cross_sectional_momentum_etfs/report.html is not checked into this repository and will not exist until you generate it yourself:

quantlab walk-forward --config configs/momentum_sp500.yaml

Use walk-forward, not quantlab backtest β€” the latter deliberately clears the walk-forward CSVs and out-of-sample metrics this report includes. This produces the full 26-fold report the "walk-forward out-of-sample" row below is drawn from; the other rows in the table come from their own respective experiments/configs.

Results

Representative outputs from the shipped configurations, including negative results, computed from real Yahoo Finance / Binance data (2008–2025 for ETFs, 2018–2025 for BTC), net of modelled transaction costs:

Experiment Universe Period CAGR Sharpe Max DD Fills
Cross-sectional momentum (example above) 8 multi-asset ETFs 2008–2025 5.5% 0.43 βˆ’15.8% 1,514
↳ walk-forward out-of-sample same 26 folds 4.6% 0.31 βˆ’18.5% β€”
Mean reversion (z-score) 5 equity ETFs 2010–2025 4.4% 0.25 βˆ’34.6% 3,351
Pairs trading (EWA/EWC, vs SPY) 2 country ETFs 2010–2025 0.9% βˆ’0.35 βˆ’8.0% 1,314
Trend following (BTC) BTCUSDT 2018–2025 30.3% 0.93 βˆ’43.9% 1,563

Every number above β€” including the negative Sharpe on the pairs trade β€” is reported as computed. These are historical and conditional: they depend on the period, the assumptions, the cost model, the parameters, and data quality. They are not predictions. See each report's Limitations section and docs/limitations.md.


Notebooks

Five executed research notebooks under notebooks/, run for real against cached Yahoo/Binance data :

  1. 01_data_quality.ipynb β€” coverage, gaps, return sanity checks.
  2. 02_momentum_research.ipynb β€” a full study end-to-end, including walk-forward.
  3. 03_mean_reversion_research.ipynb β€” RSI vs Bollinger vs z-score.
  4. 04_pairs_trading_research.ipynb β€” hedge ratio, ADF test, spread trading.
  5. 05_robustness_analysis.ipynb β€” sensitivity heatmap, bootstrap, stress tests, permutation test.

Regenerate them (after quantlab download for each config) with python scripts/build_notebooks.py.


Strategy Explorer

Interactive explanations, assumptions, parameter behavior, diagnostics, and testing tools for each of the six implemented strategies. Each strategy opens its own detail page with a live, editable parameter laboratory (against real downloaded data, a local CSV, or a bundled offline synthetic dataset for zero-setup exploration). See docs/strategy_explorer.md.

QuantLab Strategy Explorer

Dashboard

streamlit run src/quantlab/dashboard/app.py

A Backtest / Walk-forward / Strategies mode switch sits above the sidebar. Walk-forward mode runs the same train/validation/test parameter selection as quantlab walk-forward, with its own sidebar (windows, expanding mode, optimization metric, parameter-grid picker) and Results/Trades/Robustness/Report tabs built from the stitched out-of-sample result, driven by a live progress bar with an ETA while a run is in flight. Both modes' Robustness tab includes stress tests, block bootstrap, a Monte Carlo permutation test and a 2-parameter sensitivity heatmap, individually or via "Run all robustness tests" β€” in Walk-forward mode, sensitivity and most stress-test scenarios re-run the whole selection process per cell/scenario; commission/slippage stress scenarios instead re-score cached candidate weights under the new costs, re-selecting each fold's winner (see docs/validation.md for exactly what's cached vs. recomputed).

Home / configuration screen

QuantLab dashboard home


Architecture

Multiple data sources (Yahoo Finance for equities/ETFs/indices, Binance for crypto OHLCV) are normalised to one canonical schema and cached as Parquet before reaching any strategy.

flowchart LR
    A[Market Data Sources] --> A2[Raw Data Inspection]
    A2 --> B[Data Cleaning]
    B --> C[Final Validation]
    C --> E[Strategy using Feature Functions]
    E --> F[Portfolio Allocation]
    F --> G[Constraints]
    G --> H[Execution Model]
    H --> I[Backtest Engine]
    I --> J[Risk Metrics]
    I --> K[Trade Log]
    I --> L[Equity Curve]
    J --> M[Validation]
    K --> N[Reporting]
    L --> N
    M --> N
    N --> O[Dashboard]
Loading
Data β†’ Inspection β†’ Cleaning & Validation β†’ Feature Functions β†’ Strategy
     Signals β†’ Portfolio Allocation β†’ Execution Costs β†’ Backtest
     Accounting β†’ Risk & Performance β†’ Validation & Reporting

Three interfaces sit on top of this pipeline: a Python API, a Typer CLI, and the Streamlit dashboard above.


Installation

QuantLab supports Python 3.12 and 3.13, the two versions exercised by CI.

git clone https://github.com/sefaav/QuantLab.git
cd QuantLab

# Linux / macOS
python -m venv .venv
source .venv/bin/activate

# Windows (PowerShell)
python -m venv .venv
.venv\Scripts\Activate.ps1

pip install -e ".[dev,dashboard,yahoo,extra,docs,notebooks]"

Quick start (Python API)

Fully self-contained β€” runs offline against the synthetic demo data already committed under data/raw/ (no network access needed):

from quantlab.config import ExperimentConfig
from quantlab.data.loader import DataLoader
from quantlab.strategies.momentum import CrossSectionalMomentumStrategy
from quantlab.portfolio.allocator import InverseVolatilityAllocator
from quantlab.execution.execution_model import ExecutionModel
from quantlab.backtesting.engine import BacktestEngine

config = ExperimentConfig.from_yaml("configs/demo_offline.yaml")
data, _report = DataLoader().load(config)  # loads data/raw/{SPY,QQQ,TLT,GLD}.csv

strategy = CrossSectionalMomentumStrategy(
    lookback_period=189,
    skip_period=21,
    top_fraction=0.5,
    long_short=False,
)
allocator = InverseVolatilityAllocator(volatility_window=63, maximum_weight=0.60)

result = BacktestEngine().run(
    data=data,  # canonical long OHLCV frame
    strategy=strategy,
    allocator=allocator,
    execution_model=ExecutionModel.from_config(config.execution),
    config=config,
)

print(result.summary())
result.to_html("reports/generated/demo/report.html")

To run the same experiment on real Yahoo Finance / Binance data instead, swap in configs/momentum_sp500.yaml (or another shipped config) after making its remote data available with quantlab download --config configs/momentum_sp500.yaml; that command reuses a valid local cache when one already covers the requested period.


Command-line interface

quantlab download          --config configs/momentum_sp500.yaml
quantlab backtest          --config configs/momentum_sp500.yaml
quantlab walk-forward      --config configs/momentum_sp500.yaml
quantlab stress-test       --config configs/momentum_sp500.yaml
quantlab bootstrap         --config configs/momentum_sp500.yaml
quantlab permutation-test  --config configs/momentum_sp500.yaml
quantlab sensitivity       --config configs/momentum_sp500.yaml
quantlab robustness        --config configs/momentum_sp500.yaml
quantlab report            --experiment cross_sectional_momentum_etfs
quantlab dashboard
quantlab --help

stress-test/bootstrap/permutation-test/sensitivity each run one robustness technique (with a matching --n-iterations/--block-size/ --param-x etc. override); robustness runs every technique enabled under a config's robustness: block in one pass. All five branch on validation.method: with walk_forward, each starts from the same walk-forward-stitched out-of-sample result rather than a single backtest, so the evidence never silently comes from a different validation method than the one configured. In that mode, sensitivity and most stress-test scenarios re-run the whole selection process; commission/slippage stress scenarios instead re-score cached candidate weights under the new costs, re-selecting each fold's winner (see docs/validation.md for exactly what's cached vs. recomputed). bootstrap and permutation-test do neither: they resample or permute the walk-forward's already-realised out-of-sample return series statistically, without touching the selection process at all.

walk-forward, stress-test, sensitivity and robustness show a live progress bar with an ETA in the terminal, and checkpoint their progress to disk as they go β€” an interruption (Ctrl+C, a crash, closing the terminal) resumes automatically on the next matching run instead of starting over. Pass --fresh to discard a checkpoint and start clean.

Each backtest, walk-forward or report run writes a structured artefact folder under the generated-reports directory. In a source checkout this is reports/generated/<experiment>/; after a regular package installation it is ~/.quantlab/reports/generated/<experiment>/. The folder contains the config snapshot, metrics, equity curve, trades, positions, figures, HTML report, data hash, best-effort Git state and installed dependency versions. Combined with the pinned uv.lock (restored without modification by uv sync --locked), these artefacts detect input, code and dependency changes, but do not capture the complete operating-system environment (see docs/limitations.md).


Tests & quality

Unit + integration tests, >82% core-library coverage target (CI-enforced, --cov-fail-under=82), Ruff + mypy clean. CLI and dashboard behaviour are exercised separately by integration and Streamlit AppTest tests. The direct commands work in PowerShell, Linux and macOS:

python -m pytest -m "not network"
python -m pytest -m "not network" --cov=quantlab --cov-report=term-missing
python -m ruff check src tests scripts
python -m ruff format --check src tests scripts
python -m mypy src tests scripts

GNU Make is optional. When it is installed, make test, make coverage, make lint and make type-check call the same tools.


Limitations

Adjusted prices, potential survivorship bias (current-composition universes), no real market impact, simplified slippage, no taxes or regulatory constraints, simplified liquidity, possible data-snooping, and non-predictive historical results. These are documented in docs/limitations.md and emitted automatically into every report.

Feedback and bug reports

Found a bug, have a question, or want to suggest an improvement? Please open an issue on GitHub β€” feedback and bug reports are always welcome.

Disclaimer

This project is intended for educational and research purposes only. It does not constitute investment advice, and historical performance does not guarantee future results. The goal is not to claim a profitable strategy β€” it is to demonstrate a rigorous, reproducible research process.

Author

Built by sefaav. Find more projects on GitHub.

License

MIT β€” see LICENSE.

About

Reproducible quantitative research and backtesting platform with walk-forward validation, robustness testing, realistic costs, and automated research reporting.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages