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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (974.9 kB view details)

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

quantcore-0.1.8.2-cp312-cp312-macosx_15_0_arm64.whl (286.2 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8.2-cp311-cp311-win_amd64.whl (334.8 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (974.1 kB view details)

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

quantcore-0.1.8.2-cp311-cp311-macosx_15_0_arm64.whl (285.0 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.8.2-cp310-cp310-win_amd64.whl (334.3 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (972.4 kB view details)

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

quantcore-0.1.8.2-cp310-cp310-macosx_15_0_arm64.whl (283.8 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.8.2-cp39-cp39-win_amd64.whl (347.9 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (973.1 kB view details)

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

quantcore-0.1.8.2-cp39-cp39-macosx_15_0_arm64.whl (284.0 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 2d52520f19cac7becabdf7d31babaf0c34308fce6318a2a2eefbbdb8e8ecc08b
MD5 782f8b50e2826b8725c91b1748eb5a7b
BLAKE2b-256 fbd6d8c6140ec2f5c466f6e4c5233b1fb5f2c4553138ad00738ecb60cf197c42

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d919d513dc8288aa7b818075fa4621547d998d5318ceb1e2a77ea96b9e6f2c9f
MD5 3c817f1f415c6a937d5c748c0bf47608
BLAKE2b-256 92c3c5b0a0d0ae5c9342a843ffc9a8283b80c1d1ddc96c58c0d2ac75406a15b4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 0485306e4f706e518f7f7440b596e87f3926c272a3d5204201646b28e745686b
MD5 5354ae3d6091e80fb6e43d306b7b237c
BLAKE2b-256 aa4944e18a08c497fec532657b11bf77bfd8a2086be516027c69a5c5b3a3d7ee

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 79c741a546100e3036d2220d2a1040da937755a81faf8cbf3dd4fb6bfa8a2e63
MD5 7756c4b17285cfeeca1fc527f28b8ca7
BLAKE2b-256 61f5d6e404c643e414626b4f6313d08471537daf93ace4755363b59ab2e3baa5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 4d22b408dc7a9efbe035883ea583b43c449104c0f2a8c127a442386ad3f99fd1
MD5 a076a4e4f8925b89c3e2a062bb2438b1
BLAKE2b-256 ee88f9c83f4d30dbb8bdfc295bb3b420fb7503b6749bf8b612ff42cbdf3923c9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 34c5b2e74d817c1a9c59e9a0bac1784507a667b0bb625c6995030df5bbb490b9
MD5 31b43147b8e26a207a94492b5f811a69
BLAKE2b-256 13bfd5fcc8bcf3fff2fb8afa12c5920bdcd31fa249bafd8d0613c8c67bf7806b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 459d60bf0ccbc9b9cee37ae68b9c3d2d58c047406bfbf3725b6418373d172879
MD5 b96f8b97e638111f6c7c82eb0d949fd7
BLAKE2b-256 e43fb3275cf546d9c38371b5ecca7144dd0df6c49dabbf458128ad5cdf3d3a6d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a730c3a0b1b191a60f7c6fcb4ff58604c167b945d170701468f5e7b6d1e9d9a6
MD5 c956e37791e3c3dc80122cbffec2fc38
BLAKE2b-256 ee655e528e32210ebea3cd4ba3535f7355704aee12c7c12f0c58bec1dff6cb41

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 00eb007a3951b60eea63a7085350d82cca71ea841699b03d79e723cdf9e50033
MD5 3777320d6882912415fcf2313b79e930
BLAKE2b-256 27574c5fdb0b84b08f85d8e5b43f87f39efbae89c1108185a81f71caa11d0187

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8.2-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 347.9 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.2-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 fe6fc9d3bf5ead97d1bc99f6cbffe9a4c53c281782548732c5db2112f3d59c95
MD5 5d95070bb38f92ddf620d02fc4ee82d8
BLAKE2b-256 85d7b6aa4243a46b0de224dbb74af4c353a69510842af88653e2ec4f4c448943

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 8b18e21ff113c17453e55a7b0460f06b6e1ebd90f5db64892fc95f8bedbd14d2
MD5 d1e7ea5a47c85c10a6f5c52f50eb1a5a
BLAKE2b-256 f1476187b4de9eaba82384dfeef4669de18ad7cee10b8eda0ba734c2ab1d2672

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.2-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 7c5f4531d0e47b058cbb62b6919926fd7589e1eb6dc8fd188a5a0708d7cb0067
MD5 a8d17cb8f7e8fa2f856d2cc73bd5d276
BLAKE2b-256 86bee0185df393da95d59008db14e46a784776420f9830dbbcfcf88c1746a70b

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