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

Uploaded CPython 3.12Windows x86-64

quantcore-0.1.8.4-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (979.0 kB view details)

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

quantcore-0.1.8.4-cp312-cp312-macosx_15_0_arm64.whl (288.4 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.1.8.4-cp311-cp311-win_amd64.whl (337.3 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.1.8.4-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (977.6 kB view details)

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

quantcore-0.1.8.4-cp311-cp311-macosx_15_0_arm64.whl (286.9 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.1.8.4-cp310-cp310-win_amd64.whl (336.7 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.1.8.4-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (975.4 kB view details)

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

quantcore-0.1.8.4-cp310-cp310-macosx_15_0_arm64.whl (285.9 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.1.8.4-cp39-cp39-win_amd64.whl (350.8 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.1.8.4-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (975.7 kB view details)

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

quantcore-0.1.8.4-cp39-cp39-macosx_15_0_arm64.whl (286.0 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 4bc24f497e2490b42cead06443e19c8c904a57affb7e1dc8e0c141f0380ecdee
MD5 f8fe141675f2743c1eb9a5e5d0643fa1
BLAKE2b-256 ca382f754aaa5c4ae53d292cc08cfb6f7d41c1e645ac7061a177337facae50ea

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d38b1559b7c32c808c273160863cba54acf99284a3a94271b3b03cf34a3f6bfb
MD5 29a8575e587d90f4c81576f4a7faa4d7
BLAKE2b-256 d9cda9aec430bd892811b03c3ad65f811f18bce16e160bf44a5687c62f6cf202

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 ed2a724a40809586bcc6e77dcf8abbfbb40b1a4627807aaa6574aa5c239f4306
MD5 d35c49dbe2531e96d7267a0239f9f177
BLAKE2b-256 bc9671b1029947c4138c7a34940c04d25a714db2fba9489ec81a61c22c1cbad9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 7d12fbb6089aa7d563b9730466cbaef839280b691f8aa4b59b39cbfd3ba8ef50
MD5 9de5df579780b913219ec1e8e7764810
BLAKE2b-256 04010d53e8323d7866394a656501f24ae8d9c9d0db7830c91ba8ca1f37f3a0c9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 cb9e04c99c9f99f88fd9be37c5567e1e7dede9b47de30219b9064b8f43389dcf
MD5 67a0f86b3f30afa4322603686af415e2
BLAKE2b-256 643878da204490703531aa177df4f4920cb9985a95a7a88d8f87e2e01b4c631a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 8ab9516221389e0e778ce245db0408c023f8a66f66c6ee446d84575b995fb360
MD5 1a9b4a19ddfdbc04e80e86b675ee1336
BLAKE2b-256 76b9c232a6fdccc946cdf269c0f47f3084a427e4ebe8a15de7f758b65039d991

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 28568743fcbc99740eda122b4aff19f1fd2563576a0bdb0c790d80ff4c5e116e
MD5 171d95efe6078f49ba7b5fe199890382
BLAKE2b-256 a6dfde341d037aa40653a43a1891faa58d6ea53ae149fa9db2849fc04c12ef1a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7226cff8abe5a1fbdddcd6c4449b833db8f5486439643370d4b052a21e24f842
MD5 2de5c15aa07cb4cc1355c5923d3bdca1
BLAKE2b-256 ac70449d8bf94ff367cf95b40a6a5e7d73ab1f391d7b389c32ca9403d39baaba

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 ad414a995791af6cd7b18ad90f2196a1b10392923da2ddfb363916f26bc7b61e
MD5 3f0473b11cd5ae71c386ab5e81685267
BLAKE2b-256 c9095c32d13dca670950de773421efa714bc2aab69c71a32da31627ebdd2a607

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.1.8.4-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 350.8 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.4-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 1980433867529fe45c6e276b183bf0837cd7a2762d4fd51cfe429877bf8333d0
MD5 1c2d8c5f6fbc337c609f259213dd676e
BLAKE2b-256 8099e4ea6e5c172404dc54d17af869887ec0ba02ed03946ab8b5b62138f444de

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9e8a4ebca5c73711bd1bb28ad8f140d3f2d316ed14017c34d6e5356aee8ac5c5
MD5 4e699ed4e539cec93fc7f1bb24187bcf
BLAKE2b-256 d3b55a0abe56a159c7cdbd14fad03c52cf52b00feb7f9aa193bbac8119a77ecc

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.1.8.4-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 ab722fe19b9362ff7cd034112a417f2a33fdcdd27fdd4a376a4ced4e223ebedf
MD5 008a9aa70bb3b796d5709868b4703fde
BLAKE2b-256 c95d795bc1065cc746ca63e11c40a0ba760ec82efbe9e502ced2929189ab6999

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