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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (973.1 kB view details)

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

quantcore-0.1.8-cp312-cp312-macosx_15_0_arm64.whl (285.1 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8-cp311-cp311-win_amd64.whl (333.8 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (972.4 kB view details)

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

quantcore-0.1.8-cp311-cp311-macosx_15_0_arm64.whl (283.7 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.8-cp310-cp310-win_amd64.whl (333.1 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (971.3 kB view details)

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

quantcore-0.1.8-cp310-cp310-macosx_15_0_arm64.whl (282.7 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.8-cp39-cp39-win_amd64.whl (346.6 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (971.5 kB view details)

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

quantcore-0.1.8-cp39-cp39-macosx_15_0_arm64.whl (282.7 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

  • Download URL: quantcore-0.1.8-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 335.1 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.8-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 2ba028fc126d0ab641677e7cb5f1d9a8991c3cb7663e24bdf01c1c2ac5b62e19
MD5 4dd940d85fa1eb68e752fd24509eb152
BLAKE2b-256 64a929f2a4e580aa9051930dc5ec0e3998e0fdcf9c491e9bc472c70968e79d79

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2b2ace98fce49c1ed9bc155688cb2f9d3483f00252dbabac7318b110c11be43e
MD5 920b501980c10a4ae95e39324def8822
BLAKE2b-256 51222ce822b6b202529055beb1352035bd8c51be6bc2e4fd10856bf2335b9529

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp312-cp312-macosx_15_0_arm64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 2f2b15cb23d014227613f69f052c7f63ea5e2848d7b7adc8cc256976eb961bfb
MD5 a5977127a3208488ca5463f289001191
BLAKE2b-256 93ff4b6d8f3f9075710f939e48026301a352159cf834a177c773a94ddea71e98

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 333.8 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.8-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 0aa76645680d84156ea85d705b38a91ecb7377553d70d6f5831eba5c14fdd4dd
MD5 18a917ca5c53da0c631973428032e7e4
BLAKE2b-256 54c2ac145aee20bcee8ba549b23ec97a42478cc8d142b4eaf43c61d8123c93c9

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 601ce2842a01ce1042c6136257ecba7ed876f68c3f0570d286555bc803d520a5
MD5 8e0e40365a1a1ff86d688c261006d67d
BLAKE2b-256 843d587d9c24d4c88419af3aaa8f4272bda0b29b5be4de539bafb9e996026a42

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp311-cp311-macosx_15_0_arm64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 06a608e6d28c1eab8678c9de90ed336d9f50e1f1b30d99fed07ac745650fd30c
MD5 70470ff3911a902d6a1ac1b37136bf64
BLAKE2b-256 84304e077dcb1d5b0325de4ce34b429ab78a7af5ad6f8ab594a34ad7896b92b2

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 333.1 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.8-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 dc4e123b6c23e22a5dd6bf04251fb210f4e7d633e4986704f78fa36d42effc2f
MD5 d56d70983ed168d2fecb8497c394fe31
BLAKE2b-256 866eca7ea7fbb28074cd83e5b481cc9b65d90f64f0713540041d4ffc95f4e086

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ccab7af66f93f2c4601b3b0f46fb2474668c53fe5079e59c6f0fc37e88478c2d
MD5 21666b303ca3c295bc59c59682896df2
BLAKE2b-256 ebf92eb71dab6378434955912992decaf8f92bc7a7a5605b7d8b7fe09c41fd1f

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp310-cp310-macosx_15_0_arm64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 928ce6dd6db38756a7d9e0da416db9929dcaab0a2615ccd77caa4aacd870ab6e
MD5 26c91db163a53ce616168b1e73a94274
BLAKE2b-256 da869aa5b0a9401f2d9998d530b4ff5a3f068ea439c17fa269f383718abeecf1

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 346.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.8-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 30e03a7d7dffe530c26144c3156679ab847bc586089a3a7f9128f7a81dcf8026
MD5 7ba2e4197c19226be12632d1244de4ca
BLAKE2b-256 cb03cef2725eebbba4c87468bb8924001a01a14962f0b795b1ffec03937ed30d

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 dc95867bd328514f97a7de5e70fe12461bd918cbc7cddc1ec718652c62430a7a
MD5 032006cb2655fda92f365ac7f867f752
BLAKE2b-256 b69c7415e45090c99b5b0125ff15334bba5b9371fef3b38621a61c8ad2a4b90f

See more details on using hashes here.

File details

Details for the file quantcore-0.1.8-cp39-cp39-macosx_15_0_arm64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.8-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 0f1716f7b1cf0662c3d7f37e91805008d2efc26d31b05a6da11cb8472e316720
MD5 5a76bb6adf16330ff5dc0de9a3218181
BLAKE2b-256 01f917ab006916b8ea40bd7c65d3085e3c1104d7dae4e9a638e2af76a07894a1

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