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) ~300K ~7.7K unverified
Maintenance Active Stale Inactive
*Throughput measured on SMA(50/200) crossover, 50K daily bars, Release build, Windows.
Reproduce: python benchmarks/benchmark_quantcore_vs_backtrader.py --bars 50000 --runs 20*

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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.9-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (984.5 kB view details)

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

quantcore-0.1.9-cp312-cp312-macosx_15_0_arm64.whl (294.2 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.9-cp311-cp311-win_amd64.whl (341.6 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.9-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (983.1 kB view details)

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

quantcore-0.1.9-cp311-cp311-macosx_15_0_arm64.whl (292.6 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.9-cp310-cp310-win_amd64.whl (341.0 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.9-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (981.7 kB view details)

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

quantcore-0.1.9-cp310-cp310-macosx_15_0_arm64.whl (291.2 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.9-cp39-cp39-win_amd64.whl (355.2 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.9-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (982.1 kB view details)

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

quantcore-0.1.9-cp39-cp39-macosx_15_0_arm64.whl (291.4 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

  • Download URL: quantcore-0.1.9-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 342.7 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.9-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 7a5eb215ff9358eb8dee51906c068da0c186ffa94c7a8a51b5cd0aba0c1ba942
MD5 416ebf39dfa82d3a9dc306c242913569
BLAKE2b-256 2c3d78a6b1beefa42761941836185d46ada03d5a6cb794efbd7c2d15f6c7fd77

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 759d4cecf79a429b13f0fb9da163e8db321500b5976830450b5cf16a8d6b03c3
MD5 20679b95607e453a5d16a961b2355fac
BLAKE2b-256 c1f7c8e3c04b68b2e9973306336b7e24405b97a7206cce1ec198e441fddd531d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 8c5c44e002ccc4d860af6a1d58154179961de113b9e56344dfb5b61bb28a8ce8
MD5 0085f7cb78cfa09be324e6002bbf68bc
BLAKE2b-256 d25a46c22ae2f58ce233ccf9baa1302e03ca8c933cc54c65671939227bd8fca4

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.9-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 341.6 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.9-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 57a2c3d965ffc61409d6c10792ddc83c03edc026641c4210b377651125c0f54b
MD5 aeeb24a38479f30ca5102af6ee5ee322
BLAKE2b-256 ff47e99e063060a7cd391dabef0b55c0eaf005c8f5f6ba6e866848aa2a1c8ff9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 26705560b7cfb76c710ddc525157b48f7f87f467b479571fdab483a7cf02bd22
MD5 81f0feb2a062fecd9949bd0a6c3d4300
BLAKE2b-256 426a10846a396f102a8c73b7e1fb7cdf85c9e3240843d80f22be1b55c2213a6b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 85e192864ff62d0c2955740ff3473b22000b857c6cbb4f8925643e68abd152dc
MD5 20d704348adc21d36d905fb38468f56c
BLAKE2b-256 0c2d79170a2a74fa6f7a70b1313ef4f2536b0186ad3aea7925520f7258c7c7ec

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.9-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 341.0 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.9-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 6d83316a77baf8f8dbc8e9a0c93b2435fc3481459f57c8a6e14b1592aaaae5de
MD5 126db2828460e022361cee959d899e37
BLAKE2b-256 2befe055e65eeaf1c708de14ebe85e640c33883a5b52dd7718208ffdc61203df

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c19ed39020763da7eb6dc2d3c22fcb24c1516d10153b0c5a20f4607fb3054394
MD5 cd7628e8e07742d7e9a81f04d3cbdcc4
BLAKE2b-256 aa34a379c22189c83cc6bbf17e18579300c539909aebfc7c660518c1106173b6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 6fa164fbaeb09ed078c1d569ac0c8c7b134d3c59844b3fc58dce85f435971a58
MD5 e1556c17684b6760b209789d998a0505
BLAKE2b-256 e712a073c541a5f13cb7aea560607645a0c0a5609f034489c1dfce18a6eee887

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.9-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 355.2 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.9-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 c38dfa9c8981df68cb1f320d68f2dbbd57e4df81c2268e036d8ad1cabd804fc1
MD5 efb467fb40dfcc9e90698ce337e898b5
BLAKE2b-256 7166757f101b3254295acda62b1fd324a11e06627eea7e781ad4873c942dd188

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 6740339a31e2f45ea29b274f3260d0eccdc647a90d85d97c5f023b41b8ef647c
MD5 b23c02ef86bd77bca1d58f0be3f2022c
BLAKE2b-256 8edcf2580d97e42c8bf753f30aac072ab61d467671e047b0215e73bee152d512

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.9-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 cba25bfd24922b3db211c6a8a3bacfa2df093978b856cb8ca3f08e4a21bcc422
MD5 2df8ddcfbe8eb6da2ed89e525f98d632
BLAKE2b-256 5f465bab4b3671feee7d701c1ba6333b7c26f131c990110180cf6a3c2a9577b3

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