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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8.5-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (979.4 kB view details)

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

quantcore-0.1.8.5-cp312-cp312-macosx_15_0_arm64.whl (288.6 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8.5-cp311-cp311-win_amd64.whl (338.1 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8.5-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (978.3 kB view details)

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

quantcore-0.1.8.5-cp311-cp311-macosx_15_0_arm64.whl (287.2 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.8.5-cp310-cp310-win_amd64.whl (337.5 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8.5-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (977.1 kB view details)

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

quantcore-0.1.8.5-cp310-cp310-macosx_15_0_arm64.whl (286.2 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.8.5-cp39-cp39-win_amd64.whl (351.5 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8.5-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (976.2 kB view details)

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

quantcore-0.1.8.5-cp39-cp39-macosx_15_0_arm64.whl (286.3 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 a22da71b49a07d0febc2587063dc9f97212883885c7e4a55067544dc16cb7d6b
MD5 bb95c97cd0be9a441975640333b32921
BLAKE2b-256 f03893e993230d64e996844d48a8e5ee6a83b77b4ac70311b548fd59122578c0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e83d707a4b93fe13477ef0967bd67b981dc80e1d5ce5f60a74b78d05ef9dbd35
MD5 e3ac4e15dc88eb10b5b6e5b9bee04275
BLAKE2b-256 ca8e30f62d943363094c69634edadb696f7a547887a911c21727fdd3f81a5667

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 dd8de6638a9009e2f534ec059844cb09eb19998bbbee447dd00bebeaf8889da4
MD5 55470fd213d01a896b25bb91739297b1
BLAKE2b-256 e568c2959cf5841a25e2d3dc484816656af52082897db2dfde77051febbe12a6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 c4fb668ab6e05920473b6251f7d454318b6e9004e6da4f6a06ba2dbafa5c595b
MD5 9276628e99fdeae74c802b769148d52b
BLAKE2b-256 65bd1871f465f77f66d5ce09c9da236d515708390eda1d9f91023cfe1126a030

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b9dd616e7efd0f5b21ecaa3281b1011c6523cc352ebcb5874f92727988bed06c
MD5 00cecbfbb437e8f0389ce0993c7a07ec
BLAKE2b-256 b723e4d7a86a7d65f10ebd205ddc9ec8104adb798b8851aed3a8f1b8d36a39ac

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 15651ac3ecf33bd2d7d7eba3911aae63af96657f9319be505177b7b2d819ede0
MD5 306fe5baf444a417a8b4d50b66541420
BLAKE2b-256 fc2a7117622537ec0204f2e98c53235a00d91df482856fb30dc4710b356cc253

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 f0ec9a244c16236989f07c154a1d2b8a347020fa3e61e7ecc9685ecb5c9a2a6a
MD5 cdbe1207d094e256e50ad42c4ad85c70
BLAKE2b-256 54aa7b9140bb85293347d350b453fa1230ce15acb93b11d3aa5462559bd5420d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2caf000ad4eecef0b41294b68a3eb1ea7db78fb9f31e90559b669ea68b72438b
MD5 febcea94731c187eafdca0e86c54546f
BLAKE2b-256 6948bf5b45c8166471b1d3508cf46553c3d5f37ec2dd8567f28472b1a7956bc4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 97f43ba7a6b372c1ed28de627c1dbeaaceb0d8ef072008af7aaf545ff39549f0
MD5 27de00edc5fbf75e7f159353bb419e13
BLAKE2b-256 fbb9b225b93631cc4ff83b54dfe0e6112c243eda72ae1b8e1482fe8ead3fa627

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8.5-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 351.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.5-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 842e9306614c8286fd1e0497e835fcb8eeed7e9901f93cd8ab235dfca042a17e
MD5 e3bdbf86b0262d3e94e12dae51c81877
BLAKE2b-256 60f29f6eb78cc7c318740611b4b09a3d7c844c4428192aaa07abe5aeecfea19c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 853a7ef511cf05a84a4e9f9e5391299957722d1b94e4e29dee53949e8c9ddaac
MD5 121383454f4a1e5c5df3723dc7962c49
BLAKE2b-256 667533d299e8e541f92c77e5550623f973d6fce512c5a28fa2cc5d10937dce50

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.5-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 62824a31e80a70e57d7ef6a4de9774e5918ce50a88f72e47e016d7109734100e
MD5 eb99204a144a68440cdb61fca48ff5a3
BLAKE2b-256 f5d300b0da28ca9ffd5726a132b4574355b8ff54a8f6ec85669b98705a028cd1

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