Skip to content
AuthoredWritten by hand against the code; every example on it is executed by the docs checks. No human has reviewed this page yet.

Quickstart

Three steps, ending in a number OpenQuant computed for you. Budget 20–30 minutes, nearly all of it the Rust compile in step 2.

You need a Rust toolchain, a linker, and uv. If you do not have them, start at Prerequisites and come back — it is four commands.

Terminal window
git clone https://github.com/Open-Quant/openquant.git
cd openquant

OpenQuant is not on PyPI yet: the Python package sits on a compiled PyO3 extension, so installing it means building it. Releases are published on PyPI as pyopenquant (from v0.1.0, pip install pyopenquant); the import name is openquant. Do not pip install openquant — that name on PyPI belongs to an unrelated project.

Terminal window
uv venv --python 3.11 .venv
uv run --python .venv/bin/python --with maturin \
maturin develop --manifest-path crates/pyopenquant/Cargo.toml
uv run --python .venv/bin/python python -c "import openquant; print('openquant bindings import ok')"

This compiles the whole Rust workspace — polars, nalgebra and their dependency trees — so expect 10–20 minutes on a cold cache. Later builds are incremental and take seconds.

The third command should print:

openquant bindings import ok

If it does not, Troubleshooting has the failures worth knowing about, and the architecture mismatch one is the most common on macOS.

Save this as first_run.py in the repository root:

from openquant.research import make_synthetic_futures_dataset, run_flywheel_iteration
# Deterministic synthetic multi-asset futures data: 192 one-minute bars,
# four instruments. No market data feed required.
dataset = make_synthetic_futures_dataset(n_bars=192, seed=7)
# One full AFML iteration: events -> sizing -> backtest -> portfolio ->
# risk, then trading costs and promotion gates.
result = run_flywheel_iteration(dataset)
print("leakage checks:")
for k, v in result["leakage_checks"].items():
print(f" {k:<24} {v}")
print("\nsummary:")
summary = result["summary"].transpose(include_header=True, header_name="metric", column_names=["value"])
for metric, value in summary.iter_rows():
print(f" {metric:<26} {value:>12.6f}")
print("\npromotion gates:")
for k, v in result["promotion"].items():
print(f" {k:<26} {v}")

Run it:

Terminal window
uv run --python .venv/bin/python python first_run.py

Here is what it printed on the machine this page was written on. The dataset is seeded, so you should see the same numbers:

leakage checks:
inputs_aligned True
timestamps_increasing True
event_indices_sorted True
has_forward_look_bias False
summary:
portfolio_sharpe 25.303744
portfolio_return 6.956303
portfolio_risk 0.274912
realized_sharpe -2.701140
value_at_risk -0.000209
expected_shortfall -0.000312
conditional_drawdown_risk 0.002713
inputs_aligned 1.000000
timestamps_increasing 1.000000
event_indices_sorted 1.000000
has_forward_look_bias 0.000000
turnover 3.600000
realized_vol 0.037197
estimated_cost 0.001314
gross_total_return -0.000197
net_total_return -0.001511
net_sharpe -20.589471
promotion gates:
passed_realized_sharpe False
passed_net_sharpe False
passed_alignment_guard True
passed_event_order_guard True
promote_candidate False

The candidate was rejected, and that is the correct result. The synthetic dataset’s “model probabilities” are a sine wave, not a model. A quickstart that printed a promoted strategy would be teaching you to trust the wrong thing.

run_flywheel_iteration executed the full AFML loop: CUSUM event sampling, probability-to-position sizing, a backtest, portfolio allocation and risk metrics — then charged the result for commission, spread and slippage, and applied promotion gates.

Three things in that output are worth understanding now, because they are the habits the rest of the library is built around.

The ordering checks come first. timestamps_increasing and event_indices_sorted are computed from the data; two of the four promotion gates are these checks, not performance thresholds, so a candidate cannot be promoted on returns alone. inputs_aligned and has_forward_look_bias are deprecated constants: mismatched input lengths raise an error instead, and the pipeline cannot see look-ahead inside the model probabilities you hand it. That part is your job.

Gross and net are different numbers. gross_total_return is -0.000197; net_total_return is -0.001511. The gap is estimated_cost, which is turnover (3.6) times a per-turn charge built from commission, spread and a volatility-scaled slippage term. net_sharpe is recomputed after that charge. For a mid-frequency strategy this gap is usually the whole story.

portfolio_sharpe is not your strategy’s Sharpe. It reads 25.3 above while realized_sharpe is -2.70. They answer different questions: portfolio_sharpe is the in-sample optimum of the allocator across the four synthetic assets, and realized_sharpe is what the traded strategy actually returned. Reading the first as the second is the single easiest way to convince yourself you have found something.

Every Sharpe ratio and volatility here is annualised for one-minute bars. The synthetic bars are one minute apart, so the loop annualises with dataset.periods_per_year = 390 bars a session × 252 sessions = 98,280 bars a year. On your own data the factor is derived from the timestamps; pass config={"periods_per_year": ...} to override it.

If you want to…Go here
Understand each stage of that loopPython Core Workflow
Do the same thing in RustRust Core Workflow
Run it on your own CSVExamples Catalog
Build and test the repo itselfLocal Build Setup

The repo’s own test suite and documentation gates are maintainer commands, not first-run commands. They live on Local Build Setup; running them proves the repository is healthy, which is a different question from whether OpenQuant is useful to you.