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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.2-cp312-cp312-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

quantcore-0.1.2-cp312-cp312-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ i686

quantcore-0.1.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (456.1 kB view details)

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

quantcore-0.1.2-cp311-cp311-win_amd64.whl (330.1 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.2-cp311-cp311-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ x86-64

quantcore-0.1.2-cp311-cp311-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ i686

quantcore-0.1.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (456.3 kB view details)

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

quantcore-0.1.2-cp310-cp310-win_amd64.whl (329.4 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.2-cp310-cp310-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ x86-64

quantcore-0.1.2-cp310-cp310-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ i686

quantcore-0.1.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (455.1 kB view details)

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

quantcore-0.1.2-cp39-cp39-win_amd64.whl (342.0 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.2-cp39-cp39-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ x86-64

quantcore-0.1.2-cp39-cp39-musllinux_1_2_i686.whl (1.4 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ i686

quantcore-0.1.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (455.4 kB view details)

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

File details

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

File metadata

  • Download URL: quantcore-0.1.2-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 332.3 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.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 1c6cc7452d2296710233a9ccef70425c7da0a17628cdb4165195c06f2ee863eb
MD5 cb215c58300411ee7e9557e8994863d9
BLAKE2b-256 53d8c4e74bf9040d1c49dabe128afa73127da23bc09465891e0d083222a11161

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1281cbd6c42307822adc718d867ef8ae6cfca4d569407709911f91c523f532b2
MD5 76cf8fa906c384bf0d9ca39ed9c0a14a
BLAKE2b-256 46ecae9bd7a870c4686b265f4810deababc712dd45f359da32a587b1c9cfa080

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp312-cp312-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 e324c12a5b7e143e881374c60eba092cc40a3a2334fa1aadd1d56d8cffd72954
MD5 3237298af0bb53327fc04906e595cdbd
BLAKE2b-256 39d020d3f8dffaca83d214fcc4b1ff01391746c0ba7eaf9475061daf91c82dc9

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 09178f206816f4a9afe85ec35c7c526cdf50caed3c1554ec914a3adbc1a4975a
MD5 c5a40d4453f2a79050936fe25e6cacf2
BLAKE2b-256 e305f4f4558964dda0904e3dc56eb7829293f42b68cab9bb7ab0c2359e39a469

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.2-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 330.1 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.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 0ca27be62c8e20bcd47ffeb58aa48c974a6304bc104178d77537b013723c765d
MD5 923f969542293dedbd1ab1af20be5baf
BLAKE2b-256 aaa86b7808cedbd248197f1dd682156f0c9811d91f66aae7a64c7b88c01f7a16

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 a08342d7865df89a8904aff135b037e78cb6175720c6582bd7890c93230ec916
MD5 8024714b4d37ffc52f87eaa3e710b173
BLAKE2b-256 defe1a7f49a77a01d7570d8a176a36358755ed92b27ed9a81b9335865c1cca81

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp311-cp311-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 deb1a6f707124475cc90fcf2e7ea39ff2ce69611c0e70f475b5cfd8a52ea1a18
MD5 8be10665ad81da102341c8c2ae2b9b55
BLAKE2b-256 91c03d7cc484fbe0e1a5afe96ebbc8fd4f3f95823ba0133cd74fce044f6e0489

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b7e55a5e04aa589b55733b196072bb4571474662f5e40bb48b50c8b1f357b9f2
MD5 8b689040f169f0b549138ff7364c0ae6
BLAKE2b-256 230f4cafb9d8edbe092581c52e81d33dbd952c4ee5773dc6fa9c5bbc4f3c4476

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.2-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 329.4 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.2-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 3831171e5f6d8b8e52c34027fbdf8fc5733bad453818d404216e7497aa99f3c4
MD5 56c49126c7dc0a24128e6f20c121152a
BLAKE2b-256 4203e88851e6439d9ba6b2e5c7c3e2dc2e9c230caf5ab420807c35474d7c126b

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp310-cp310-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 6a3df19f8ff4338d757c094ef9fa51ce909472914c55e4d492122dcd760f54a8
MD5 1166f447b40ab22dd951eb4cd84dc720
BLAKE2b-256 43076ec8867aca73ca5772874abdc1036eb309fc889566346b9d3290c1716bce

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp310-cp310-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 9df98c1e6f7cc9ecdb92de6fedc34d69c4a6ca742ceaaf5a32468a91211f746c
MD5 eef08ec6d424165e728279f8b39bb23c
BLAKE2b-256 9b3f3f47dc51bd74f27f0e3fd18e3ec2d67b89f19c8f899494d8a065132d3fd6

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2601d78a1aa99eadd81bcc56a5af9fa9a8a993a5d417b6b44561967eff6286fe
MD5 9731ad9e9239996af096a68f4a7acae6
BLAKE2b-256 14b93c308255e6efee6e0f7b2119d0ae9692a963abe55d172e19e1ef10ee10f9

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.2-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 342.0 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.2-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 b88b00f2019d8a109e005ee570d38f62612f8205a9b3f639c188d0158ac89d13
MD5 57df3771545c3dfe9a8bb151af46d9af
BLAKE2b-256 d55a1c945ea07fe93032c2e55d0b6e5373fa0a2890702bbbe007e3684dadf151

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp39-cp39-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 d7fb87f98a8e61cc4de3fd0c851b2f82ba2c7497228583d62e100d576b6321f6
MD5 dc54828e082939a3b8b9b769417b01f6
BLAKE2b-256 e579637af841023df17e3334a65b4258cfc7d912622d2d95bcfac08605b8f9e9

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp39-cp39-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp39-cp39-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 f2532a9ada582a6b2bc62155d5c25dcc4f993ac0dadec6495f816d41cce27763
MD5 dd545cb1b283447f1f93f150a49567cf
BLAKE2b-256 58ff98d0054821f9efd18cde7e51a46c48807538bbd677c51645d3fdab7d801d

See more details on using hashes here.

File details

Details for the file quantcore-0.1.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for quantcore-0.1.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f17b2a377d5f1817a0cf008631c6de2a52c11264cc429ab9ff51e7f59914a41d
MD5 cffbe217d6910b765b19e0cd169db9e2
BLAKE2b-256 c62a7d804a6c0104ab68099d8401bfa6c44f69e34f1378ba111284c916a64058

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