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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.6-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (967.9 kB view details)

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

quantcore-0.1.6-cp312-cp312-macosx_15_0_arm64.whl (281.9 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.6-cp311-cp311-win_amd64.whl (330.8 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.6-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (967.9 kB view details)

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

quantcore-0.1.6-cp311-cp311-macosx_15_0_arm64.whl (280.7 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

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

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.6-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (967.0 kB view details)

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

quantcore-0.1.6-cp310-cp310-macosx_15_0_arm64.whl (279.2 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

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

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.6-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (967.2 kB view details)

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

quantcore-0.1.6-cp39-cp39-macosx_15_0_arm64.whl (279.5 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

  • Download URL: quantcore-0.1.6-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 332.9 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.6-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 af152b8d0f85efc260f70905680256724005cbf48f001e46167d8dbe5fd2e64c
MD5 4ccdbd0639394a3e8d1977fb12e2fb12
BLAKE2b-256 7364abdcc18c58e9e27c630af7f9159a979113467e92334d3a1853a1a10a6149

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 839dc664b35eb606976bcd2a7df813e565c5eebad593a685517794ce97b75308
MD5 aac3522cb6ddaedec672dc2acd15284c
BLAKE2b-256 8fe335be801dcb6bd241a6193f421c3e2831de5bc99c72b40611bee7afac9412

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 2bf922df73b4b6f30d75253607453d3180e9e3bd4c878d7fa4baebafb18e39ca
MD5 fd3fc844851e89bcde517472f43b869e
BLAKE2b-256 b0a752c1218faa1f20ae80cded110f46b71bb48b2d38cc6a95d9f1051a88b23f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.6-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 330.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.6-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 4eecc93e958ca4a7bdcea5818ba40ebc4fe18aa596d9dfa8e9b7419c36f79d00
MD5 6ff57df065c820bb24fcfc8c399e7564
BLAKE2b-256 20c972cb8bfcddaf6361e0e3371ebd3ea11f9d518f62d8c598621e515561577c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 cdfc679a1d6ab3f66560eea015c97cc3cf8445251184e25c2250d9b57d268be4
MD5 42a300279ee7d5242be906860f51c6b8
BLAKE2b-256 d29c5c52271d48f2b27c20639ab5c41d274424a2a4fcd765d035cae7e11bfa06

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 eb1c2bddcde6bb54c54cc116075704786b0fbb3fdb7fd431cfb4e417c682914c
MD5 315c65792d6957a6067ac7c00faafceb
BLAKE2b-256 98ad4044a426571e3a08148569fd6124ba21193f8d6d49f86bd6ccb099918598

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.6-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.6-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 7dc761f4908789185eb5e4ff62cb07b451a72da407b312b84fdae1f52517069f
MD5 a0ade5bc534dd20269278d32faba2d3b
BLAKE2b-256 c52cfb628a53bed8210fc2778ebaa2451b8c30776dcfa2af4c6e6263eff12c10

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7b7fb64e02a7fb06df0f33f87780926c872509725882a46b2f279870d175978d
MD5 295e30bbe76273efee17ed085fa29a9f
BLAKE2b-256 32fa2574170c93fc3aa87347f8cfa19cbc19c114e33591f43b7a1fef552f16b5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 28272be2779438bcd71b1a766b213f4ff8c03d0d0d6fcbcf0ca0026caf825785
MD5 7d7d72969916b1662ef37ba855f865f1
BLAKE2b-256 eb33a0c269306d47c07ac0a755e30c7845d2b729316507bfb53175a9ea1304be

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.6-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.6-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 37b8877065e00ca95a978ae6af0a437fed1917df09a453e7cc1e3ed266666a0b
MD5 a0ee4427a6ba7163bfce5d4f62d1491f
BLAKE2b-256 5b5575c5a040b1a47e9d2b1c420249079f9cc12498e522b59f9e4ab3371663d4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 138aabb7a0cb31c2ea027f45b7c3c0ce654e4162013f2e5b5726f667da65e9c3
MD5 db7c4b1d9d9d5ba4355869c23791b482
BLAKE2b-256 9f87713425abdb49eb2303db68b3e6dd44e226e131e80cce336ad92e4a29a8f3

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.6-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 247ad934d15826056514c63ca2387de3df4e5823388346347d67abe9796d362e
MD5 26c32d23b9f29c408f168bf3d9541469
BLAKE2b-256 9bd967589bf9c122dce838b89ccfafcf3753cf53262fced3c729be6b4b5d83c4

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