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.4-cp312-cp312-win_amd64.whl (332.6 kB view details)

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.4-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.4-cp312-cp312-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ i686

quantcore-0.1.4-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.4-cp311-cp311-win_amd64.whl (330.6 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.4-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.4-cp311-cp311-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ i686

quantcore-0.1.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (457.1 kB view details)

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

quantcore-0.1.4-cp310-cp310-win_amd64.whl (329.8 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.4-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.4-cp310-cp310-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ i686

quantcore-0.1.4-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (456.1 kB view details)

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

quantcore-0.1.4-cp39-cp39-win_amd64.whl (342.6 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.4-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.4-cp39-cp39-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ i686

quantcore-0.1.4-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (456.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.4-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: quantcore-0.1.4-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 332.6 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.4-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 8fa69e4d973dd67741f042e80046f83fc7c8c786dbf50c33cc6f0251f7a508b4
MD5 3d9c995fc2367afb5c8067daeccf3b5c
BLAKE2b-256 ad431cd06d8b0f4aafb053bfc48746c0591adb6798110dc170070aacb64adc98

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 4550310ea4be6ca2d4c9b10ff54a5bf0dc3667593aed6d984121555f69d5193f
MD5 85e25d4d0e0d7913f62e459bad830a1d
BLAKE2b-256 7110dccafe873477c5964761271257a69a77458df0f550a428b47a65152e1f0a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 7a217a8953cd5e551b259774674f59991f8333be514ead25dcc73ce759fd6f19
MD5 9cb6e8baf676de3e6bc1635d4e87750f
BLAKE2b-256 24efd268337045e42375f7efcfb3de151fb446decb5c3ff838d8470085d387c2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f7c90a02d329e1a0d11787a76b6d4dc1eabdc6f733cb60656fa61ca49c885f45
MD5 6bc9a1739e821196be3a4765ca22d0bc
BLAKE2b-256 7ecc57876cb3331c301ddfd59b3721a350bb6fca22fb72d58518c99a8652462f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.4-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 330.6 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.4-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 f63d415c326e25d510129f6865a1a911a57caa21a5c37640a374471f7f046f89
MD5 2113bd8d5fdb99d7e446d5e89a07940a
BLAKE2b-256 82cdd663fd91c9ef5c31a0f556ae673ef307323cf38d3c74ddd37e64aef5a216

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 4683ff60323175c76bd6ed3bcf2c9b14762beb1e20edc42d33ec7821a7784d0e
MD5 3bfa2fa498fca2dbfd91fd2c8c6ea21c
BLAKE2b-256 5ed32c1a3792fde5b143e76d534ed3f81b3b937c690f0820e358fc878ad0c879

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 d318fd4e613f525f9ab5340f0c605735b8d2fb836d2cf947961b654c5fbafa63
MD5 bf9442dc8bf8ffca43170ad2de6a8ffc
BLAKE2b-256 ab1a27590b7c44ed0a776b42fa611296583ed032540f5a7f6fc905526a293c43

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 485372cafbc9f2e8676a1446b9378adc80aa445e2dd2c43a617156f220625c87
MD5 6bf9cd39f43e88359335937c14150298
BLAKE2b-256 8c8f5d7331c6aa787d24d0bc80ddb57e72b5d4b41c7bb6200708d59df5397205

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.4-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 329.8 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.4-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 20078cfe27092aff6a1765316500732d5358fda236f21dd4b0bc0378eca59122
MD5 b8107c387868d73d62fa8e9851a42509
BLAKE2b-256 3693a973fd4e41ffa4f0232485e4cbab1b80cabe0f177179f397da14f0e96638

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 7d76b8a93bc2b9be5c0c09b9d5fe410cda9793bfdcc172b41b5ccb28eaac1b1a
MD5 dd5be8f267b0b5aa50dad10ff32c5ee7
BLAKE2b-256 c7d63758c8909c152b93f9b42430e97d3ae9e8d28ebd845f60455056c2bda230

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 d3a00b859d88e7fdc4947a37612fe25bc5133c20d4110ce82eeb930ebee6c864
MD5 12d98a29b042c56af5ecf6ca1ce78ef3
BLAKE2b-256 c295a301b8a5be4cf0820fc323e827c1fc8e6c75214656f04762ae6203403674

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e5485ac4500435a45e9f67c23d8c787f9642c4a99c4ce7f3db2f07a3299ac85c
MD5 173c9b82661715c39fe3984f2f0173bb
BLAKE2b-256 ef05efb0de5bf37eeedfda46d5ea00357cd96c68b2bf44a3066afc5102fdf35e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.4-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 342.6 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.4-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 e2be87caff9d8f9124d2af71757606194d1d8928506931dc6ea6547e2e5ebf58
MD5 5b081ff9be0caae3dfc3c25956292557
BLAKE2b-256 e339d762c710b39043664a070aedcb97c62cb8ebc704af9cda3896e58688cc24

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 aba28587862304318206cce528f0b01a72743d1b50f476357654342eae7da664
MD5 12b3a57f49d2f5ddf21129162573dd26
BLAKE2b-256 3993b3c39ec67eae1dccfb6d5c47885e8b300a918c235e4b5210ca0a32420d00

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp39-cp39-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 b01495e5172803bb97b9a13ff053493d2ca320a8b3f0f8638aca201edf95e9d3
MD5 55aa7a414964545683a1fc7552af0b1c
BLAKE2b-256 e2b9cf2762bd0a73aaef2b9b865041ae4a6ff5344f208364efedeb579a893838

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.4-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 6e4e7fce9e18e1887fbc255a943aec34f917567d9c98b04be038cb5d858b3052
MD5 f79b1017d4a41cec9b80b9e780762964
BLAKE2b-256 ed68c58e16ff52870f40ad72114fecdcf870a664a1620c2f9315af79971245af

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