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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8.6-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (984.2 kB view details)

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

quantcore-0.1.8.6-cp312-cp312-macosx_15_0_arm64.whl (294.6 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8.6-cp311-cp311-win_amd64.whl (341.7 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8.6-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (984.3 kB view details)

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

quantcore-0.1.8.6-cp311-cp311-macosx_15_0_arm64.whl (293.1 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.8.6-cp310-cp310-win_amd64.whl (341.2 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8.6-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (982.4 kB view details)

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

quantcore-0.1.8.6-cp310-cp310-macosx_15_0_arm64.whl (291.9 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

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

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8.6-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (982.8 kB view details)

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

quantcore-0.1.8.6-cp39-cp39-macosx_15_0_arm64.whl (292.0 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 42f09e1a6346f75f77a5c032c3b00c955e3770ae50e6c2ef99aab89a3ca6f757
MD5 5a93376633660718a5669608d3c0ae93
BLAKE2b-256 4e902ab6629beacb83ed788b614629ce2ba3506ebbcc250c3449dcecc2c125c4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 4311ed22a4a0e5e26278eca2856d10e092df160d366b1955b981ad893a8941e9
MD5 5730e9dba169b2bb50006aa5b34be499
BLAKE2b-256 b3ade5722a3375da99dbf3204449f515597e10d10e2ecf96fe8474e0ce45709d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 9a8e865e56c2f4446876438a8419e26ee19cd831a6fcd2dd05472c357af68111
MD5 631bde9918db0e24a367fcc441f04ba8
BLAKE2b-256 3cb2bc623c4e0a371d118faa73d353db39fedbe4a314abec4baa3416d944323a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 96c675ea3a48ef379fd3e1e6cec389695d1df5a4dc91adc8f43e5f638bf36cba
MD5 a8cb8484d9bfb4d092ddd59653bf0553
BLAKE2b-256 5ecf8567e9d8590a6fab0da0502e4ad21b1eb74f1dd996d869c862456ca6dd62

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2fca6c4c59913ec76a8494e97268ba473b0b943971695802b64c9a686f17b3bb
MD5 d2c9b2ec765a0af7d360d041063b917c
BLAKE2b-256 7e37b4cdd6100a92c6ad586233e65da4aa26b63b7ab97fbc3e8e55f017d9e493

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 a24833c61250519c6f34b76eb651fbecd298947ea5eb8838eb74bb8afef61019
MD5 6dcf9aa4dfd44627aa3774fa1588e3ee
BLAKE2b-256 bb3d37a5f5162ea9f1a2763488e316619cadb3adbbdffbcf2c8f8f7848e26f8d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 cf37ce41628bcb3a6d92d098bb08c6df53710c31d32656876c5b4c242cc8178a
MD5 05e101670c3aa5ee010e610785445798
BLAKE2b-256 58ad9a73c5fc7a456a8970c9591f9bd285f8f034d35365f106539208b5ecbb1a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5c56f88757bdad3d6611e9a8d7fc13d88c866a31aa478466d9258ea135de7f34
MD5 da5790a12cd8655d1f9683809c97326e
BLAKE2b-256 8806aa338d167358617e1c9da90d33bc839f142a421b8c2977111713ceeab70c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 abec1749074b42f1dbf54c5b6846877e1d19a1164b916aeed5773e6c1e862ae4
MD5 e2e2cfc950f53beb5d98611413b7d480
BLAKE2b-256 a0f352509082c758471cbdd130bb9a8094db2a707e251eaacf722ae8e091d921

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8.6-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.6-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 752e509607f459174f3c3c3893a77a8aeafe1993d1c3adfdfd5191bbbab59651
MD5 1ff77a8b722205f1b5968df3fe9039fa
BLAKE2b-256 d97c59a51b794ff415eeed5714c8ff732fad05a3a1acd7a0561f5751a7ad7da6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 27bb28dcd7e8a9e000bdb13f6fe595026ed5a4d5bc93c4e1b3cce6b400c71d4a
MD5 e5a74730ebb94d755333ce32d6ec56d5
BLAKE2b-256 906a03754d2b535e843b296bca9aabf9c025a4366c17b029b93e63a9ec23a27d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.6-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 5946455e1be7caa4a29db8af45e876179918e428e099c08957955b26ce71cde3
MD5 d29169d13a241a74d06fa4b28679bea5
BLAKE2b-256 9ff96d8fdc6c36e05d8a2c3f5f4d3dd514b5eeffe1bbf4371b11b94e2cc0f195

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