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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (976.5 kB view details)

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

quantcore-0.1.8.3-cp312-cp312-macosx_15_0_arm64.whl (287.4 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8.3-cp311-cp311-win_amd64.whl (336.1 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8.3-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (975.4 kB view details)

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

quantcore-0.1.8.3-cp311-cp311-macosx_15_0_arm64.whl (285.9 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.8.3-cp310-cp310-win_amd64.whl (335.5 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8.3-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (974.9 kB view details)

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

quantcore-0.1.8.3-cp310-cp310-macosx_15_0_arm64.whl (284.8 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.8.3-cp39-cp39-win_amd64.whl (349.5 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8.3-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (974.9 kB view details)

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

quantcore-0.1.8.3-cp39-cp39-macosx_15_0_arm64.whl (284.9 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 b45d833514eccac5485b36b21687129f370d62013d92e293cb0591761d77867e
MD5 b04539345e320b31b8db8456dbc366db
BLAKE2b-256 9a915dabf158871d661cb9c37b56da238b4faa332eab0a86492ba86b5556bfaa

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 836b05533d3de0161fb428b929eda908451a599aeedc687c1965c8ca5cf46737
MD5 bd84d4d77ab8b32547295fc7d3741980
BLAKE2b-256 cf07082b8a6e7ef8bf4862417699cbe83ee95dea31b0e35014b897f4b274b881

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 d351afbe701bc26f2e4f902e3f812bb0c779ce3aae073438486f1dc5e45b2e9b
MD5 427a7cd6936b2595fecbe3f69b2051ee
BLAKE2b-256 84aab2a1298a042331f05ff38e0e8ebe5552607d22c718b5dc2d1c6fc5f00553

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 5b4c9efe662dbaf62a2633d6e9a3a7eb4c5b8ab2ceebad45257e729f43b67aa2
MD5 1d5ffdb11fea9557cdd738664ad8a1d3
BLAKE2b-256 3644f1f221ad86a735e484a30fec6277caaf634ed77c463ab29eb19831a2bce4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 03212bb1ea650e3067518f27d411f86e13b72db82ee5c79167667035d4fad1d3
MD5 74ff8a1144a9c50cdd8d9222f5dc4e78
BLAKE2b-256 5615a5aced71718cb591f7662a707759e04a78dd5773c43b6e8ce81f973be824

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 7101ea7cc8cf2d0229df9d6e88026c1e85c005bc92c320382122cd951df92daa
MD5 358b0936ccef4bb6f40656c542710d05
BLAKE2b-256 dd3a4265bd8db6b65199381200145f1ab393cc0895780af7265e1b77129c578c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 52611e1ee7684df105d2fef1122efeccdb9f120dbdabc360fc30a5de917c8aa2
MD5 48df3bc627e72b7faa502cdd781398e0
BLAKE2b-256 83c5816cc12adfde521a7c0f9a3b5f9a4ac9a1477c053800d1139eb7498490a9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 be8abce6acb05bacf9367bf14c9633f6cd4c9510e5b120defd5f9bdd82a50329
MD5 1f0453dc8a1fea0772d75035c83ff9c4
BLAKE2b-256 b5e8b8a19b608a3d45daa85d3c4693bf099960ef14749ffaef35a12d1ef407de

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 d73c595066d0f942fa0d4728a7f8b51f961f5de37f08079933259693a5c56255
MD5 15548ae55575a9fa6c20460b81da6482
BLAKE2b-256 468d16c2d5d5815ea830f57915ee7b9a3778fcf6b3966912f59da6eef67ac215

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8.3-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 349.5 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.3-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 e7de79e1666bffbd88e6d0e3cea5dbb5ba3031d44aa2b1bc064ca20685af2f55
MD5 bfb68e03897ef61066a1b8a097e6ba25
BLAKE2b-256 79ccc5b3698afa37edb152d3cf2e4f56757d7b48dc721893cde147ac71dad026

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 8087e69104fbf52285c8e25e7db1157cd4838deab5aa7c83aee2e3b9f20e57b9
MD5 cb325b9047b5077b8330a25e99879212
BLAKE2b-256 8c2bdb41ca36cbc28163cfc789c999a0eb5fed173ac4d874ec21e6112278aa3c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.3-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 a5eac6780d661ecbb2392ce4394cdd01690e13fc2a0d326cb4e643a322bd599a
MD5 d39f7465b9afcaac2251aa202c5da782
BLAKE2b-256 b028c39886c80944b8da97494d569dfb370c923374ceb2a51d7daa89f0d3ed37

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