Skip to main content

High-performance C++20 backtesting engine with Python interface

Project description

QuantCore

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

Uploaded CPython 3.12Windows x86-64

quantcore-0.2.4-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.4-cp312-cp312-macosx_15_0_arm64.whl (335.9 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.2.4-cp311-cp311-win_amd64.whl (380.2 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.2.4-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.4-cp311-cp311-macosx_15_0_arm64.whl (334.5 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.2.4-cp310-cp310-win_amd64.whl (379.3 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.2.4-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.4-cp310-cp310-macosx_15_0_arm64.whl (333.1 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.2.4-cp39-cp39-win_amd64.whl (396.6 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.2.4-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.4-cp39-cp39-macosx_15_0_arm64.whl (333.2 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

  • Download URL: quantcore-0.2.4-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 382.7 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.4-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 57af9b2f55abc63994bd45f388db7d2e15030978a6daed583f900c1031263243
MD5 a855dcd33cfb0c6689a5c2f3a2b91363
BLAKE2b-256 ceb288a5e0cbdea2c1599387860fddbbf96dbd00ad0fda9f83febbd4b219eb5d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7cb6228c72e7ebc8ce0092a947056d62438f93d748b0d9787e042212052abbae
MD5 774841c5dce9ed97e61afb67111b7e6a
BLAKE2b-256 52c0f905f9a96ae671c0a0fbe2aa9e4a0f9f1cc67d0db120877a2dd798c9b262

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 805871d0a310ac98f8f69b41edf6cbbcd36855866871de2a1002bba61b761d49
MD5 1a8452c6c083cf552f68630c4e57a9a8
BLAKE2b-256 7e5a9e05e2c8f5a9f19ca6bb118534bcec4d051cf3754b8b05e15f563a42e827

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.4-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 380.2 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.4-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 3643438469954ca23721008b0b6714f24e340f7afa274e1752c237d33e5303b1
MD5 498fd3634c5dd62c9d7b228ce0a18f11
BLAKE2b-256 d29ba34226fc20cb596cf93a0c165c003d4296dafa766067ad6025a3855a0256

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9a83782a98c0ff75cea9d1ad5106ac7375d949cb62028523a6d02d4d32658849
MD5 455555bc37aa9ddf584c3ee524634efe
BLAKE2b-256 439af6c29ab11a1947a743aaed6d428bef81a09de904bfb3c5b845a070f68c39

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 7ff2a251829b6c1dea4f5a38222904d8e1f67878e22d079547caf5eda16199bb
MD5 3015b20be8be7384d6a1a9effe5c6cfa
BLAKE2b-256 b2b6fa08245885d5b0f4ee6fabe7e488c1a7a0430b7c46ba59ce2b7d18872730

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.4-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 379.3 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.4-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 a96ed410c45ebb12d57554161bec8232629610293a8201cb6796bf133d782f49
MD5 9b0644614f3e51126d324a4c943d4cae
BLAKE2b-256 ee17ff50542216d7fcb8c7c04589eb92f83412ea4d42168c738426039217ed54

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c3351c783b4e3eb8d2a66dce17aca97656da6ad9f22c8aefd716d26b252f3194
MD5 5a6737c1c41b91b3e587f3a51df4ccbe
BLAKE2b-256 ed1e48f01ad7beac3ebecd4cda142cb74a303890c95cf44cf7d8c6aed0f6eda7

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 ded95e5975d14e4823b0b35cd1c8d0e8f5dab74dadcace8f313d2228024f1526
MD5 81d08218d3f60be8cef55439f8397be5
BLAKE2b-256 b46192b61becd0dd4bd6407c3f692f5d684292622f89da2930fceb321819bd35

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.4-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 396.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.2.4-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 136be433192312a12fa57c011f7c0ed181b19313daee6520f8ced04f3bbd93e2
MD5 c84eaf762b10673699309ed29de57d3f
BLAKE2b-256 2c872ab310a9796c6c499a64f72470e333aac7657b12ec8d82b699b1dd0b0e04

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3b78fee6613598179945f2673b9628d61cd81d1accb81179da8a11cf76b74377
MD5 e1e496bf4daa2c24938f13c94ed19e24
BLAKE2b-256 e927185087e2e928a505ce25972a7c938b1b097eff017f01988aac4a221344fa

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.4-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 4522a1c3035675dbfa0c6ad1518c19703692e2d9eebdac0140ec96b0519499fd
MD5 3e52c9ba38a4217ec0136aa4da67fee4
BLAKE2b-256 2c44f26523c538902d4bc8db9bb46fe6760ef78e14b5a28c8614dff24d72a081

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