Skip to main content

High-performance C++20 backtesting engine with Python interface

Project description

QuantCore

Build Coverage C++ License

High-performance backtesting engine for trading strategies, written in C++20 with a Python research interface.


Overview

QuantCore is an event-driven backtester built around an enhanced version of my limit order book simulator. It processes market events chronologically through a priority queue, ensuring no look-ahead bias and no unrealistic assumptions about fill prices.

The C++ core handles all the performance-critical work: event dispatch, order matching, position tracking, and execution simulation. Python sits on top via pybind11 bindings and handles strategy development, parameter optimization, and visualization.

Market Data → EventQueue → Strategy → Signal → OrderBook → Fill → Portfolio

Quick Start

import quantcore as qc

class MyStrategy(qc.Strategy):
    def on_data(self, event):
        if not self.has_position(event.symbol):
            self.generate_signal(event.symbol, qc.SignalType.BUY, 1.0, event.timestamp_ns)

results = qc.run_backtest(
    strategy=MyStrategy(),
    data={'AAPL': qc.load_csv_data('AAPL', 'data/aapl.csv')},
    initial_capital=100_000.0,
)
print(results)

The engine handles fills, position tracking, and PnL automatically. For a full tearsheet:

from quantcore.analytics import calculate_all_metrics, calculate_returns
from quantcore.plotting import plot_full_tearsheet
import numpy as np

equity = np.array(results['equity_curve'])
returns = calculate_returns(equity)
print(calculate_all_metrics(equity))
plot_full_tearsheet(equity, returns)

Architecture

Every action goes through the event queue. When a strategy calls generate_signal, that signal becomes an OrderEvent, which goes through the order book, produces a FillEvent, which updates the portfolio. All in timestamp order. This is what prevents look-ahead bias: the strategy never sees data from the future.

img.png


Strategy Development

Python Strategy

Subclass qc.Strategy and implement on_data. Signals drive order execution. You don't place orders directly, you generate signals and the engine handles the rest.

class BollingerBreakout(qc.Strategy):
    def __init__(self, window=20, n_std=2.0):
        super().__init__("BollingerBreakout")
        self.window = window
        self.n_std  = n_std
        self.prices = []

    def on_data(self, event):
        self.prices.append(event.close)
        if len(self.prices) < self.window:
            return

        window_prices = self.prices[-self.window:]
        mean = sum(window_prices) / self.window
        std  = (sum((p - mean) ** 2 for p in window_prices) / self.window) ** 0.5

        upper = mean + self.n_std * std
        lower = mean - self.n_std * std
        pos   = self.get_position(event.symbol)

        if event.close > upper and pos <= 0:
            self.generate_signal(event.symbol, qc.SignalType.BUY,  1.0, event.timestamp_ns)
        elif event.close < lower and pos >= 0:
            self.generate_signal(event.symbol, qc.SignalType.SELL, 1.0, event.timestamp_ns)

    def on_fill(self, fill):
        pass  # optional: react to fills

Portfolio Context

Strategies can access full portfolio state:

def on_data(self, event):
    portfolio = self.get_portfolio()
    if portfolio:
        equity     = portfolio.get_portfolio_value()
        cash       = portfolio.get_cash()
        position   = portfolio.get_position(event.symbol)

Position Sizing

The engine ships with several sizing methods:

from quantcore import FixedPercentage, RiskBased, KellyCriterion

engine = qc.BacktestEngine(100_000.0)
engine.set_position_sizer(qc.FixedPercentage(0.10))  # 10% of capital per trade

Built-in sizers: FixedPercentage, RiskBased, KellyCriterion, EqualWeight, VolatilityTargeting, FixedShares.


Execution Simulation

Order Types

GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL, MARKET, GOOD_FOR_DAY

Fees & Slippage

from quantcore import ExecutionConfig

config = ExecutionConfig()
config.maker_fee     = 0.001   # 0.1% maker
config.taker_fee     = 0.002   # 0.2% taker
config.slippage_pct  = 0.0005  # 0.05% slippage
config.latency_ns    = 1_000_000  # 1ms order latency

engine = qc.BacktestEngine(100_000.0, config)

Risk Management

from quantcore import RiskLimits

limits = qc.RiskLimits()
limits.max_position_pct = 0.20   # max 20% per position
limits.max_leverage     = 2.0
limits.max_loss_pct     = 0.15   # halt at 15% drawdown

engine.set_risk_limits(limits)

Performance

Single-threaded. Measured on Windows (Release build, MSVC). Full results in benchmarks/RESULTS.md.

Order book

Pattern Ops/s
Add + cancel (market-maker quote refresh) 13.0 M ops/s
Add + match (taker sweep) 4.9 M ops/s

These are raw order book operations with no engine overhead. The matching engine is not the bottleneck at daily-bar scale.

End-to-end backtest

Scenario Bars/s Latency (p99)
1-year (252 bars) ~270 K bars/s 0.93 ms
5-year (1,260 bars) ~270 K bars/s -
1,000-year stress (252,000 bars) ~290 K bars/s -

Throughput is stable across dataset sizes. A 1-year daily backtest completes in under 1 ms at p99.

Run the benchmarks yourself:

cmake --build build --target bench_backtest_engine
./build/bench_backtest_engine

python benchmarks/bench_python.py

Analytics

After running a backtest, the results dict contains an equity curve and trade log you can feed straight into the analytics module.

from quantcore.analytics import calculate_all_metrics, calculate_returns

equity  = np.array(results['equity_curve'])
returns = calculate_returns(equity)
metrics = calculate_all_metrics(equity)

print(metrics)
# Total Return:     24.31%
# Annualized:       11.82%
# Sharpe Ratio:     1.43
# Sortino Ratio:    2.01
# Max Drawdown:     -8.74%
# Win Rate:         58.3%

Available metrics: total return, CAGR, Sharpe, Sortino, Calmar, max drawdown, drawdown duration, win rate, profit factor, avg win/loss, largest win/loss.

Visualizations

from quantcore.plotting import (
    plot_full_tearsheet,
    plot_equity_curve,
    plot_underwater,
    plot_returns_distribution,
    plot_rolling_metrics,
    plot_monthly_returns_heatmap,
)

plot_full_tearsheet(equity, returns, timestamps=ts)

Example Notebooks

Notebook Strategy Concepts
mean_reversion.ipynb Z-score mean reversion Parameter sensitivity, OU process
sma_crossover.ipynb SMA crossover Trend following, signal generation
pairs_trading.ipynb Statistical arbitrage Cointegration, spread trading
build_your_own_strategy.ipynb Bollinger Band Breakout Full walkthrough from scratch

Installation

Prerequisites

  • CMake 3.15+
  • C++20 compiler (GCC 10+, Clang 12+, MSVC 2022)
  • Python 3.8+
  • pybind11 (pip install pybind11)

Build

git clone https://github.com/SLMolenaar/quantcore.git
cd quantcore

# build the C++ core
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

# build the Python bindings
cd python
pip install pybind11
python build_module.py

# verify
python -c "import quantcore; print(quantcore.version())"

Run Tests

cmake --build build --target quantcore_tests
./build/quantcore_tests

For the full Python API reference, see docs/usage.md.


Project Structure

quantcore/
├── cpp/
│   ├── backtesting/          # Engine, events, portfolio
│   ├── strategies/           # C++ strategy implementations
│   ├── orderbook/            # Order book (from orderbook-simulator-cpp)
│   └── tests/                # GoogleTest suite
├── python/
│   ├── quantcore/            # Python package
│   │   ├── __init__.py       # Public API
│   │   ├── analytics.py      # Performance metrics
│   │   └── plotting.py       # Visualizations
│   ├── bindings.cpp          # pybind11 bindings
│   └── build_module.py       # Build helper
├── examples/                 # Jupyter notebooks
├── benchmarks/               # Benchmark suite
├── CMakeLists.txt
└── README.md

vs. Alternatives

QuantCore Backtrader Zipline
Core language C++20 Python Python
Order book simulation ✅ Real LOB
Event-driven
Look-ahead prevention ✅ Priority queue
Python strategy API ✅ pybind11 ✅ native ✅ native
Throughput (bars/s) ~270 K ~50 K ~100 K
Maintenance Active Stale Inactive

The main differentiator is the order book. Backtrader and Zipline assume you fill at the bar's close price. QuantCore routes orders through a real price-time priority matching engine, which gives you realistic partial fills, spread simulation, and tick-level execution when you have tick data.


Contributing

See CONTRIBUTING.md. Open areas if you want to dig in:

  • Stop / Stop-Limit orders: order type enum and matching engine
  • VWAP / TWAP algos: ExecutionEngine, child order slicing
  • Tick data pipeline: the engine is bar-agnostic internally; the data loader needs extending
  • Trading calendar: holiday/early-close filtering before bars hit the engine
  • Multi-strategy portfolio: shared capital across strategies with a meta-allocator
  • Parallel sweeps on Linux: n_jobs exists but Windows spawn overhead kills it; a Linux worker pool would make it actually useful

The engine doesn't handle corporate actions, survivorship bias, or timezone normalization. That's the data layer's job. Feed it clean adjusted data and none of those are problems.


License

MIT: see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

quantcore-0.1.3-cp312-cp312-win_amd64.whl (332.3 kB view details)

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.3-cp312-cp312-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

quantcore-0.1.3-cp312-cp312-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ i686

quantcore-0.1.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (456.1 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

quantcore-0.1.3-cp311-cp311-win_amd64.whl (330.1 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.3-cp311-cp311-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ x86-64

quantcore-0.1.3-cp311-cp311-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ i686

quantcore-0.1.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (456.3 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

quantcore-0.1.3-cp310-cp310-win_amd64.whl (329.4 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.3-cp310-cp310-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ x86-64

quantcore-0.1.3-cp310-cp310-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ i686

quantcore-0.1.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (455.1 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

quantcore-0.1.3-cp39-cp39-win_amd64.whl (342.0 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.3-cp39-cp39-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ x86-64

quantcore-0.1.3-cp39-cp39-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ i686

quantcore-0.1.3-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (455.4 kB view details)

Uploaded CPython 3.9manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file quantcore-0.1.3-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: quantcore-0.1.3-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 332.3 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for quantcore-0.1.3-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 24b382520a75476981f4e3eac4ad2033f3702e2bcd18f2027a00f015189de9d7
MD5 745b74fd2104b2a1b35bda323a3cb85f
BLAKE2b-256 4b0d945b00544dd1b74c80a4bc7e4661f785bf757eef197425fe2ca83beeab6b

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 700844cb63cf12f9483fe39208086a6a3dac4bb21fa15739befe70560f3b8782
MD5 33b13c5740f8d25ed17a0f41a5bc0669
BLAKE2b-256 920765fa27936105825647a3613af66dcb4b5202649290844ad7e5447ea6b349

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp312-cp312-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 adf81379cab6d857e4501b29b91825d3cbe66b7059693476ceb0c4a6d43287b5
MD5 550da473ccbec526a45aab227c2f39a1
BLAKE2b-256 91649b9ca6425cd2cf2fb0d4ac0b1f4089f56433d13f197114fcefd1bc0f2365

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 798ece1e4c7dbf0a7db3d9f3a60ab67ef2d41a3c97f769ed0d78006a9521d32d
MD5 90a434831ab432a09bac3186717ed408
BLAKE2b-256 e479b2c62437d1cbbca9627c30b676c82b79499a170c92494c468daf9d95f94e

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: quantcore-0.1.3-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 330.1 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for quantcore-0.1.3-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 36ac7ad6786798a6260301bc9fe783ec82cb23c406c00c3e12714f234af6aa98
MD5 b3cf124d25e058386eee961e01810b2a
BLAKE2b-256 a7b9c5c9a13d0a3ee9285d1429793846f190e4dc386822cb96e07b26d6b239fd

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 3b3115281fa833d04cca98d5c4164f1335a87c00406c4cd6cd6c94e928490c24
MD5 793982cc40be401e76fb654353993bce
BLAKE2b-256 5166c84f49252a07749ac6be983f2eda589f1cf02b69af7c76deeba8965e4fe4

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp311-cp311-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 5bb75c888667220e498584d81057bde151963066664ff5869e8acaa7b4469bd1
MD5 4b1529a61979ab1e0781a5a68c526e93
BLAKE2b-256 78e689cf4b476c102ccb2ac79824e0b6ec7c774e0b5cb2521ec9574df78785f6

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 6851f5fddc30d4c8fa1349ef648562c08ccc14cc81b2f6a67180b688d8b2c515
MD5 41df2bbc7aa2f528cccdd017f19033f6
BLAKE2b-256 65a7b8d5381e47d5c1538472aa6aef1a669971961bc9a3485fb66f7120f0e71b

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: quantcore-0.1.3-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 329.4 kB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for quantcore-0.1.3-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 02619a048120235d8865bc48079dc62fbfca9373b51c46cd83c3c96996983cad
MD5 89c0367256d9a309a6755b492f0902a2
BLAKE2b-256 128304776613845f2789f91c6d49b9c4c21b89121fb7826570eeea31b6df1cda

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp310-cp310-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ca5a55638b4f54b64b0cdb0b8468cf3ad3fc097f3b06d4cd0c35f7233974fa02
MD5 ccb51e63935dce0adbaff13b4872da24
BLAKE2b-256 4955e73f22c1d1a5920e6d30e61561e95f91f143ce6f338fac42566eae324e76

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp310-cp310-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 9e0007285967af15b6374cbc84baf300e72201dafa38d62165584fbd668dd803
MD5 585720fd9879a2bc67934302c0a7f378
BLAKE2b-256 09041e66f2725cced3bf32268dabb1bda3c82852b61417df38bf91981cb5cc08

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 faa00c9981518619bca725f9a394b9fb4f727ccc1dbfe555f8f3ebc13ac81322
MD5 138bfb6c92be65aa263b91f22cffd680
BLAKE2b-256 e0af6c1dfa398b5502026dd7d227871d67c040752226f0ba72a2362a81a6371d

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp39-cp39-win_amd64.whl.

File metadata

  • Download URL: quantcore-0.1.3-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 342.0 kB
  • Tags: CPython 3.9, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for quantcore-0.1.3-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 248ccfa7f694f3148ac0935690595c4adc743043ee93da745728380ae35afc3b
MD5 49fbbfd506001425c436365afe66987e
BLAKE2b-256 9692f1d7ba18893f72c378c880cca106397bd26de01a6eb22231b0ad6a20236f

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp39-cp39-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 a7bf060e30b434d6bed4dc6493dc261110a6a7eeec236c07f29a28b9ec8f20a1
MD5 a13a49092c129704ba1c10ec53d26109
BLAKE2b-256 4ba6887c495cd2d702ab516044461c454645ce2ad8ffe25879ed1c080cd1918a

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp39-cp39-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp39-cp39-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 a42b30fbc4d42a4743e7cd7513da3b44fbbbb0d27c816784fde544fab688728f
MD5 33aeed7e6f9d46512a90f97932b130a3
BLAKE2b-256 fe643484f1d62bb0a068827709d89c6a084dadfcf49aa2f4dc662b282b9a9545

See more details on using hashes here.

File details

Details for the file quantcore-0.1.3-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.3-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9ec94b5546df049c3a51d1134d1f40119aaeb05e2ebadbd06f432844c1d4035b
MD5 53fa89dc1528e496c1051ad13bea219e
BLAKE2b-256 2aa955be8bf36729bd2409c368a95795815655cc03e28abdc44c8fd6b5098d1d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page