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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (973.2 kB view details)

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

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

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8.1-cp311-cp311-win_amd64.whl (333.7 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8.1-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.1-cp311-cp311-macosx_15_0_arm64.whl (283.8 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

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

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (971.1 kB view details)

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

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

Uploaded CPython 3.10macOS 15.0+ ARM64

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

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (971.4 kB view details)

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

quantcore-0.1.8.1-cp39-cp39-macosx_15_0_arm64.whl (282.8 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 2a058fb517dda3b9b1c3aa99000966edc3efd25f905c979c52256f2a54fa3711
MD5 9ec6594eed9da26d20d431b98f51d4b8
BLAKE2b-256 ddb39e6bc6c693c17bc8e699938de46fbe79bd1e41fc5f0c893c6c786e8b09e8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 36328532a8ece77609c161550277995291f13c9e340791caac6291cb690b5f81
MD5 b3396c6723854f693d7a52c22c603cba
BLAKE2b-256 83287b037e9334a55522b6684e645d42dfe748de6775230962b367ced1481b5e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 05c340cf79de095e4fe629e4fe09c927fab6cb304549de0be71fa003c96196dc
MD5 a11e5947b4a4eb102776bc2cf2d837b8
BLAKE2b-256 1771b7d3dddc2f6e58094e1a65a5a348f20de8bfb62a017c91f2c66442afa06c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 5449620101cf74caa20387d6babbb765fac9f4928fc8c86b6c0051dff203aaa9
MD5 7d785b7446a2faede58fc789d3fd779d
BLAKE2b-256 2b52ba0c8712ececd16d0e22062ac8e4e1ba415ce7111d51e6dba16a90f1968a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 45245938c3d945baba26ace7aa543eb2ad9d71ccaa2271c6be66e5eaaa36a464
MD5 14493b7d42b33c39854b1dafb52053ed
BLAKE2b-256 92f04a300f700ff0b500bf3cd35b56be8b39b716054567d79848103876845d9b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 c10494380033b41a99e83f630717209ee5516949ccc36665119a32301e18ed93
MD5 60705f6e1947e2595b41433e84ddab9e
BLAKE2b-256 d7940337c3e4a4137b892d4319f291044c3db401bc5afaf6bfd154c359ba1e41

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 06593a5374a65d753aa6c4f777b6151555920ede6ca1c191b23849fca36d546e
MD5 292a7f21702dc64f795445a7a354cc6b
BLAKE2b-256 094248114a6c87b742836fb9109a038ae4d15d9e3f3b68022e9067f8f2e2d038

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 90252a26cc424bdff609f281c9572d148bef9b68f6f8cf2f6560de3dfe1cfd84
MD5 b05c102b14f829dcd835cc012cbdf629
BLAKE2b-256 db537c8b4af5d37031257f0616ca2a2635d3a363d42c0c59e52ae7a9db74e869

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 2ff86d3a6208eaca50c68c6f1070c23b9532e49761c3c238c91d642280b4fba3
MD5 b5f4ff671b01487e4a7652b550e983f4
BLAKE2b-256 9fe80eb22f7b032c16f53220b64f94a0facac618e55de6f5fb643f2400a424aa

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8.1-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.1-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 b9bbf94429209e4b4c6601100a72d2b3fb7e84ad39158e1e6e724a543d14ad40
MD5 0b3d7c8e4b148dc688648d7018601d4b
BLAKE2b-256 b40f720356a148abcecac28852e06665f9124b1bc6fe014b3e843532f095e323

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 8b6c072fa2b01e32a431a6b365ba5506ad4f7eb6003be092f686edebba9fea69
MD5 084b765bfbf03686de43849d1558311a
BLAKE2b-256 23a08c9923e6e192c104e47fd59d8647471650c3a9c6498fa9e90c07d939707d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.1-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 bfde635f36cf43b4cb8adb6caf55dbf0c3240e096d7e7750ce02b0bf3c964d94
MD5 ca2f29a8be1e8af513d4dab8df14f42d
BLAKE2b-256 0388aa1aba83905213f0468378de18d0c421f27222e04610af3c32ea3340edd7

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