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)

For the full API reference and usage guide, see docs/usage.md.


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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8.7-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (985.2 kB view details)

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

quantcore-0.1.8.7-cp312-cp312-macosx_15_0_arm64.whl (294.5 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8.7-cp311-cp311-win_amd64.whl (341.8 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8.7-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (984.0 kB view details)

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

quantcore-0.1.8.7-cp311-cp311-macosx_15_0_arm64.whl (293.0 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.8.7-cp310-cp310-win_amd64.whl (341.3 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8.7-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (982.3 kB view details)

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

quantcore-0.1.8.7-cp310-cp310-macosx_15_0_arm64.whl (291.8 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.8.7-cp39-cp39-win_amd64.whl (355.6 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8.7-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (982.7 kB view details)

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

quantcore-0.1.8.7-cp39-cp39-macosx_15_0_arm64.whl (291.9 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 3a56bb6e839451590270079ba4f828753bf90eb337a6471e1eed2614371697a9
MD5 6c29304ea95fccfc7a74f79db315c08a
BLAKE2b-256 24eb3d5f3f4b9643b168ccdb25ede5d1d7010d7ffe2c1dbd93528a795ea8c76e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9f632421dd890049e7d1e064a19c4f5975b184de74c322f84a000930d253135b
MD5 99a4bd1d72cb5884bd9e7f91265c1e3d
BLAKE2b-256 f222dd9685afb463f386b21060fc010d67db701f971819c052e2b8867cbbc404

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 4c37ce95bbb6ecd1b0abff94016ad4062ca4eb9e1301b4e04d83710a5c2dc52c
MD5 8a7ee108b6fa51ad77d980fcecbaa7dc
BLAKE2b-256 83776a3a4b0af2a1311d3047818a886aba60aeb25e25b6008bffacbe314ea58f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 2988e937783d4fa1bea9dbf8d3c805dfa59f0dece79ed35109159e55da371663
MD5 7807c36732f2b1e6c6691671dadf5de7
BLAKE2b-256 d13737a490d94467b787620cc90e29d1539d4e7e9010950ac2a29bd57a0f7afa

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e7754adcee82148376ee9c3b87cdd5b049921b0cbef273f0dc1972d09c09945a
MD5 bbdad0f20be98b0e6c81ad3d13ee41dc
BLAKE2b-256 d174e8eafc0d2144445450ed629fde25ca2320d803f75738f0b378b64a7c20a4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 24e0a70216caf72a93cf350d75bee306fd36c552b2e15dec1311d14de0e594b7
MD5 4560f443085f292c5933295ee869e132
BLAKE2b-256 7bd24da30a74768af6ef718496082c637875bbb7f08a8477711c11a51c756696

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 76a67c587bb38fc5eb064d48de8161a86ee4780743db62045edd00b82dd957ec
MD5 86f5ae49c1b7d694059ce35d0c325075
BLAKE2b-256 4cd6cb4d739ae30375bb97e330432a52dba15572d1ba19049ed7be54f15e1b64

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 4cbb476dab7b73e743435195bc9700c7998c934b114d82216eb397dec29b3a47
MD5 edc6f70acbabe3460b16bd1e05a9e111
BLAKE2b-256 bc3f2d906057024bf071ef07fdd39d340e4f108b7dccae2bfdb6f9b4dfd866c5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 17f61e6e7ff85042a361005697c015b5dd6c318752ac9b2548de3d9496f96272
MD5 5dc0cf068998df7835ee23518dced045
BLAKE2b-256 ce34ef31cfdb8d0b778a216b64dffe76777f895b77b65061255f2765c908fc9d

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8.7-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 355.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.7-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 df4ae645e342010242db5c396ff34c62934a3e272a0503c32b30757cd037540c
MD5 022d6163252cb67260653bde86276926
BLAKE2b-256 f3e5d0c0f3014d903cb5de3055190e7c60b67cccb0e3e5a685f4029cdc2d8b9b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 65098b05bad56abb67fb5322c7478995ece7936432fc067a5d149e4c22a91c82
MD5 de2b5f6035062156736da054d6dd6c82
BLAKE2b-256 9a55d4136dab0490577a95e70a9f8add8cb65dda0bd401c0796e93b50f0f8cb2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.7-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 dd5316ad7137a71946e1dab411508bda5941cf828068d29f3047bdbb70809537
MD5 f21c77321398fe36a9243404132848ed
BLAKE2b-256 19ed4c31487c844a18faf41ae54de6941d4f2a12de83f574fd89f1ed68c27cbd

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