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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.7-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (967.8 kB view details)

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

quantcore-0.1.7-cp312-cp312-macosx_15_0_arm64.whl (281.4 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.7-cp311-cp311-win_amd64.whl (330.9 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.7-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (967.6 kB view details)

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

quantcore-0.1.7-cp311-cp311-macosx_15_0_arm64.whl (280.1 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.7-cp310-cp310-win_amd64.whl (330.1 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.7-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (966.4 kB view details)

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

quantcore-0.1.7-cp310-cp310-macosx_15_0_arm64.whl (278.9 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.7-cp39-cp39-win_amd64.whl (342.9 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.7-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (966.8 kB view details)

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

quantcore-0.1.7-cp39-cp39-macosx_15_0_arm64.whl (279.0 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

  • Download URL: quantcore-0.1.7-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 333.0 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.7-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 8e51dcfdb236d8105d10a788658f1853454299e6a72d3f7fbacd2d733d5fcb74
MD5 1c12a404efe5a29feed6d41076ab1296
BLAKE2b-256 93f7818ff41340c27ef1afcd12268acfac974e3a4d8ecb057bef4502ab69f06e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 beac15a6ba0b83b8df3bdfdaf3f75a04bf908c9d00fe22af2191b073147f7459
MD5 8698ec0f9100ee14193e0d669321a8c1
BLAKE2b-256 a4967a08e1f83f0f405e26029903aacf4705f59c2837976e4abe4655191da042

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 e750e5a4752334f38da69969d7e33a4cfa09fc9ed85cfeb51175f2142c294154
MD5 e01b89e5de3cad5821132fdcc189ca96
BLAKE2b-256 5e40eec6c4135f88905fc5f17bbc40a100f555178a22963ac2a3c0979d323057

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.7-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 330.9 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.7-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 e7636f8d388965deb66696317862e6b80c14b5385db8f04d7469712777e07ee2
MD5 607245a3dbba6ce1558a7141469995c8
BLAKE2b-256 63bcfb0cedc167678bef3cfcb8568d68616662c3f6f65160f71f872b925a384a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 693ddf919e6ef551c192392a36f2b3ccae27d3c9beaf01e9e113d06c58453acd
MD5 fd6d4d1e59d67a93b7927b058eb1f58d
BLAKE2b-256 f510cd44b3808c534de0080e98f368b4d717d2337f9cbb5edfb0e013ff97b6f2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 a6105d9d5b1e23bf67af265b4f3a2d2069991f36937d4bef5d8e4d5a404ed646
MD5 728b13336d685adc7251d06e657f5274
BLAKE2b-256 443b1beb6f9a40ab2474e34add8e3d13496b3575ab42e728ad007e089c25f1b4

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.7-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 330.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.7-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 8571cabc66ffc017e21c7fd3922925e2f366ba5c31017dbac35cac98010481a4
MD5 5f03290d47c90b5dfeef1654de1e18ab
BLAKE2b-256 4fe70761d088f70146f20a1cc09ff12ac26cdd0e6de0f1efff13e195d77c6720

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a79b72a16d37928b3a723c14d17839c5c0b96b17bfbbd7b2f0b4d67154cdbc7e
MD5 ff1ddb54ab23cd3df5ee01aa53b5e953
BLAKE2b-256 7bc4b5b677d2a8d6430f2f66a6f4f9b04e1431682d40d415acc2b38fbfa0333a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 fa0fde182342fb7bef5d9168a3d0e6ec1e055dd7dd4af1ce711347b14f006950
MD5 1ea1fd5079718456ebfc7b2c9157db5b
BLAKE2b-256 6d01e23dfa1b1600b51d9433178acf96805e05bd8d07dd6b8dfb97abce5ccb7c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.7-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 342.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.7-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 1ad91fc18b92635ea2083bb07cc4508162443ad93a01659aa055f71881bfe312
MD5 f8c6c0bdd0100545045648721516ee3b
BLAKE2b-256 2deee2545ae16527153b117e5ca185f15397b961db458a43fd1645a293f3cb86

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c9b00c345c39ffc968d05c2e37473e31d4dbe341543d4acc5ccddcaa6bf61d3d
MD5 27469e58a87f6f8b68882cda50a2d292
BLAKE2b-256 285e40651b72ce95c7be3178be66fad9b47943d260d8a1a72bb14fb9008ec7a8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.7-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 26c94fba17f40ac179f3f795eb5afffaeb1a3cede9eecabf50ff1855df9773a7
MD5 583487b0b13173f1c635b57c9cf9bacf
BLAKE2b-256 5eea76e276b6d23acf787c27b3bc46faa4d2ed06a9bbad0bf6444e4cfc9f2b6c

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