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

Both bar data and tick data are supported. The engine is bar-agnostic internally - ticks become MarketDataEvent objects like bars do, so strategies work unchanged across both data types.


Quick Start

Bar data

# pip install quantcore
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('data/aapl.csv', 'AAPL')},
    initial_capital=100_000.0,
)
print(results)

Tick data

results = qc.run_tick_backtest(
    strategy=MyStrategy(),
    tick_data={'AAPL': qc.load_tick_csv('data/aapl_ticks.csv', 'AAPL')},
    initial_capital=100_000.0,
    mm_refresh_interval_ns=1_000_000_000,   # refresh MM quotes once per second
    equity_snapshot_interval_ns=60_000_000_000,  # snapshot equity once per minute
)
print(results)

The same strategy class works for both. event.close is the tick price when running on tick data.

You can also aggregate ticks to bars before running:

ticks = qc.load_tick_csv('data/aapl_ticks.csv', 'AAPL')
bars  = qc.aggregate_ticks_to_bars(ticks, bar_duration_ns=60_000_000_000)  # 1-minute bars

results = qc.run_backtest(strategy=MyStrategy(), data={'AAPL': bars}, initial_capital=100_000.0)

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. The same strategy works for bar and tick data - event.close is the close price for bars and the trade price for ticks.

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.


Tick Data

Loading

# from CSV - columns: timestamp, price, quantity  (or with side: B/S/buy/sell)
ticks = qc.load_tick_csv('data/aapl_ticks.csv', 'AAPL')

# from Parquet
ticks = qc.load_tick_parquet('data/aapl_ticks.parquet', 'AAPL')

# numpy fast path - (N, 4) array: [timestamp_ns, price, quantity, side]
# side: 0.0 = Buy, 1.0 = Sell
arr = qc.load_tick_parquet('data/aapl_ticks.parquet', use_numpy=True)
engine.add_tick_data('AAPL', arr)

Data Normalization

QuantCore does not adjust raw prices for corporate actions internally. Feed it adjusted data, where close prices are continuous across splits and dividends, for correct results.

Most data vendors offer adjusted data:

  • Yahoo Finance: use Adj Close instead of Close
  • Polygon.io: use the adjusted=true parameter
  • CRSP: adjusted by default
  • Quandl/NASDAQ Data Link: WIKI/PRICES table uses adjusted prices

If you have raw unadjusted data, use CorporateActionsAdjuster:

from quantcore import CorporateActionsAdjuster

adjuster = CorporateActionsAdjuster.from_csv(
    splits_csv='data/aapl_splits.csv',
    dividends_csv='data/aapl_dividends.csv',
)
raw_bars    = qc.load_csv_data('data/aapl_raw.csv', 'AAPL')
adj_bars    = adjuster.adjust(raw_bars)
results     = qc.run_backtest(strategy=MyStrategy(), data={'AAPL': adj_bars}, ...)

Aggregation

# aggregate to any bar duration
bars_1min  = qc.aggregate_ticks_to_bars(ticks, bar_duration_ns=60_000_000_000)
bars_1hour = qc.aggregate_ticks_to_bars(ticks, bar_duration_ns=3_600_000_000_000)
bars_1day  = qc.aggregate_ticks_to_bars(ticks, bar_duration_ns=86_400_000_000_000)

Engine configuration for tick data

Two settings matter most for tick performance:

engine = qc.BacktestEngine(100_000.0)
engine.add_tick_data('AAPL', ticks)

# How often the market maker refreshes its quotes.
# 0 = every tick (default, slowest). 1s interval gives ~5x speedup on 1-second tick data.
engine.set_mm_refresh_interval(1_000_000_000)       # 1 second

# How often the equity curve is snapshotted.
# 0 = every tick (default). Has negligible performance impact but keeps the
# equity curve manageable for large tick datasets.
engine.set_equity_snapshot_interval(60_000_000_000) # 1 minute

run_tick_backtest() sets both to sensible defaults (1s and 60s respectively).

CSV format

timestamp,price,quantity
1700000000,150.25,100
1700000001,150.30,50

# with aggressor side
timestamp,price,quantity,side
1700000000,150.25,100,buy
1700000001,150.30,50,sell

Timestamps in seconds, milliseconds, microseconds, or nanoseconds - detected automatically.


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

End-to-end backtest - bar mode

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 -

End-to-end backtest - tick mode

Scenario Ticks/s Wall time
10K ticks, no MM throttle 315 K/s 31.7 ms
10K ticks, 1s MM throttle 1.66 M/s 6.0 ms
10K ticks, 10s MM throttle 2.78 M/s 3.6 ms
1M ticks → 1-min bars (aggregation) 247 M/s 4.1 ms

The market-maker refresh interval is the main performance lever for tick data. With a 1-second throttle the engine processes 1-second tick data at ~5x the unthrottled rate. See benchmarks/RESULTS.md for the full breakdown.

Run the benchmarks yourself:

cmake --build build --target bench_backtest_engine bench_tick_data
./build/bench_backtest_engine
./build/bench_tick_data

python benchmarks/bench_python.py
python benchmarks/bench_tick_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
│   │   ├── tick_data.h       # TickData struct and TickSeries
│   │   └── tick_data_loader.h# CSV loader and aggregate_to_bars
│   ├── 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
│   │   └── tick_parquet_loader.py  # Parquet loader for tick data
│   ├── bindings.cpp          # pybind11 bindings
│   └── build_module.py       # Build helper
├── examples/                 # Jupyter notebooks
├── benchmarks/               # Benchmark suite
│   ├── bench_backtest_engine.cpp
│   ├── bench_tick_data.cpp
│   ├── bench_python.py
│   ├── bench_tick_python.py
│   └── RESULTS.md
├── CMakeLists.txt
└── README.md

vs. Alternatives

QuantCore Backtrader Zipline
Core language C++20 Python Python
Order book simulation ✅ Real LOB
Tick data support ✅ Native
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/qcVsBacktrader.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
  • 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.2.2-cp312-cp312-win_amd64.whl (370.5 kB view details)

Uploaded CPython 3.12Windows x86-64

quantcore-0.2.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (1.0 MB view details)

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

quantcore-0.2.2-cp312-cp312-macosx_15_0_arm64.whl (323.7 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.2.2-cp311-cp311-win_amd64.whl (368.0 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.2.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (1.0 MB view details)

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

quantcore-0.2.2-cp311-cp311-macosx_15_0_arm64.whl (322.3 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.2.2-cp310-cp310-win_amd64.whl (367.1 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.2.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (1.0 MB view details)

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

quantcore-0.2.2-cp310-cp310-macosx_15_0_arm64.whl (321.0 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.2.2-cp39-cp39-win_amd64.whl (384.4 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.2.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl (1.0 MB view details)

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

quantcore-0.2.2-cp39-cp39-macosx_15_0_arm64.whl (321.1 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

  • Download URL: quantcore-0.2.2-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 370.5 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.2.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 57d776d9df9076750ca38c9937af7144360a6b27c1952dcd0b9ba17acda16547
MD5 7dcbea757667c2451e7178dff7bab5c9
BLAKE2b-256 9eb80879e5eff95c0e5a02d06cf9a73f56ed4acf0f0656fdf60470941a21a135

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d7d965b01ea525c7a499b3e144be13dd72548de916a3875f0883a75d351accfc
MD5 3c34f26ac4b542cc0316404cba0790ba
BLAKE2b-256 63d3d3249fcd8cdabc8a08f401beb49a009bd3b9e47fcc8381b1ce8a370c4bb0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 00abb4faf7ff32d3218dd94199278d3e5f84f014b46607f8c419a81a31165689
MD5 f6fce5bd812da0cc2ebe180ae9a34b45
BLAKE2b-256 8b756c6527116aa0f76cd6b79b7b1b1124a57842f350de4e73cccafcbeeefbc7

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.2-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 368.0 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.2.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 7b203c303cc4cb26fbd73ec02f4c34feed313847aacd89aefbd03b1ad6c04bb5
MD5 aa1c38cd717cd4b21a1b2e2a396f4343
BLAKE2b-256 b60484fc389e698581cf86c909b7c2a6d837f80c9adc423c2a56c97922667eab

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e1cb332fb8acf34df34fddcf5ba55574284c8d0375dc3fa9e996af3f2c0d3ad7
MD5 4474002dc079dc3d1fc78686b7c51624
BLAKE2b-256 7475419b3416dff9ceac792a71f566bcde41b0e2a30e1c426c80a0a2a2ac1581

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 4e81e6be138d225bc67b46f0ec626fb3b807261da23a4ba028638b2f41461c92
MD5 ea2945542eed3ceb8a57cafd4e8d4ead
BLAKE2b-256 bf3fe87803ece871e362655aea87f4e772e67743a53353a0fa2f9013075cc050

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.2-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 367.1 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.2.2-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 9070b7602742425fe6c620904594718de000e0abdbd261a9b271c123494f9499
MD5 6d4313afde0de8d07e2377327f5834b7
BLAKE2b-256 0a6d017e55da306fc5ad1b4c2770fc162c42d7ec2f750b8b9f94082537147313

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f086ccb19ba3f7deebc9929aeefe416ef8a6cdde4a20ef26ea3140f11cf6ec96
MD5 46800382a004bdc8b3344b8862ed30c3
BLAKE2b-256 10716e4ca3aa09010816e4e1e745473f9f226a06e796efad8fa3e6103af1a8d3

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 caa2fc8e456661cef7b52b4ff5b54ff5b63adc95586fbbb02c11ef3995ed6f74
MD5 cf6bb1068e39be9313d32a51e903a918
BLAKE2b-256 9b1411745c24c47b314ff6e4d24c39b4e757888862c6fded5e972ac572f53863

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.2-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 384.4 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.2.2-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 c063df39fd5b66d517a79e21f5d89026e952ae8f0475a93a566c6f9239e8102b
MD5 f02d7520725b4b29695b3d820a3ca449
BLAKE2b-256 71a2f97ac2bf207f9e0de04a8a704a95ec5b1ea1a419f46cf3c0885dba091bb5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3716bf8a476f07d973021651b48228a5a9c49e530eaeae3358e364f63bd11676
MD5 09439c59113a4eb9878e0f4752938b77
BLAKE2b-256 4a04c7c48feaf0025943fc757c02c40489a6b4524215d86afaab5d1098b54ef8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.2-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 7ad525c6a610cbdd20c0307fbfd2b4063f870d772736a9a300fe81054561600b
MD5 2b4693a2e57faef7b030116d39e0b001
BLAKE2b-256 260c0591aed582c96613b61c6fcc2aa0e115783183eb5802f9fff5014bc9f8b7

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