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

Uploaded CPython 3.12Windows x86-64

quantcore-0.2.5-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.5-cp312-cp312-macosx_15_0_arm64.whl (344.9 kB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

quantcore-0.2.5-cp311-cp311-win_amd64.whl (389.4 kB view details)

Uploaded CPython 3.11Windows x86-64

quantcore-0.2.5-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.5-cp311-cp311-macosx_15_0_arm64.whl (343.3 kB view details)

Uploaded CPython 3.11macOS 15.0+ ARM64

quantcore-0.2.5-cp310-cp310-win_amd64.whl (388.6 kB view details)

Uploaded CPython 3.10Windows x86-64

quantcore-0.2.5-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.5-cp310-cp310-macosx_15_0_arm64.whl (342.4 kB view details)

Uploaded CPython 3.10macOS 15.0+ ARM64

quantcore-0.2.5-cp39-cp39-win_amd64.whl (405.8 kB view details)

Uploaded CPython 3.9Windows x86-64

quantcore-0.2.5-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.5-cp39-cp39-macosx_15_0_arm64.whl (342.5 kB view details)

Uploaded CPython 3.9macOS 15.0+ ARM64

File details

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

File metadata

  • Download URL: quantcore-0.2.5-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 391.0 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.5-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 acdbb7538de34751eaaa584720c47e6590fb205fbfd0155d8c90031c1fdd9cf2
MD5 47af52fd122704b877861b211ee836da
BLAKE2b-256 de9b2ee83904a60800154a6dd2f0a55095d1f257b13ba977dfc6c948844569ef

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 21868e788f9a3cef89878e4b1d9d34533312dfa81f2c3ec57587bb9b2787d0b7
MD5 d76d792978846238cf5e45e3641172e6
BLAKE2b-256 e3b7da5afcc2f177e660fc65a9b48eeb9e3c3c3205d0016ef6c94f2639c70397

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 fd821d8dc6618556c5bce06c38d0a32f968818cb2969ea787793edf429c8f8e1
MD5 ea11d8ec72b1ad69189687e4ef378f16
BLAKE2b-256 d66b0e38d1dc0fbcde89bcd278c676847005abbfda6155d0dac0c0d7a542809e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.5-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 389.4 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.5-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 adf0dc05ffa8915c01118e215ffa6165116ffe2722ef5bc66b3a7d504f75b11d
MD5 ccc38855797874f84bf8afc2f6fb6829
BLAKE2b-256 20458405bf83d005cf054cc2e96dac1cb0b1cc73ecec6972dc56a0b5895637e4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d875d588afbfb3213338e9e10e3cb1b1b7c7ca19e9b0e1d813b8a873658d97f9
MD5 b5311fbfba756ea869b31382b38d471a
BLAKE2b-256 b0e8b4340c90eed26de78966950fc4095877c12f1b8d24f0404ac183aa3b1d0d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp311-cp311-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 bf2f5812d54b0509ad3c9289c61d5491225d60cf6f724c0e37c4580c7a5ef4dd
MD5 c0eb8472e9a8187685947f61da1637af
BLAKE2b-256 64342cb4deb742e63053f55e98bd229faa3de2b61e4fca4d806d9b2fdc4ba1bf

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.5-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 388.6 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.5-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 d65107a2c410558301e59c12dce6206926167dc0ae20fa398da03b5ae99d1f5d
MD5 a74bf8ea5f18a85902808c094ad727d1
BLAKE2b-256 b5c6cf32a623afa36d5c0cb3a6702c06610c19da5d786d89a44af72b8a63603b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2b12564d9ea4c9cb406ba04630256b53bb2d050cc3e441838bd8deab2c025e7a
MD5 5f7c1fc46e137eba064a5d37905b35d6
BLAKE2b-256 a49a2e030b87575052b1e6c2373b155dc21a2bbb92a4e9be4c5cd521dc9e85c0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp310-cp310-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 057b2db0a7e901871816399add1fbe9f48f467a1e3b5ed0f2b19fbe12c347d92
MD5 1478eb418a5d63298ffb7ba0a61e6615
BLAKE2b-256 b22340a5016f5a6b730140ad7b22bd1efd263babf3a5a477265c3cedb1167b03

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quantcore-0.2.5-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 405.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.2.5-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 a8ac6ce553913f250d1d5ebe57676aa14e07f32cd33d7067ab123be0845e2212
MD5 08361298211e6d61dac339f08f1b30ba
BLAKE2b-256 fb407a1f6712a9e3020d6fbf83df8453ebd662f9490ced6db322a5c4b6b13c24

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b7f91576bcb3783cef7833813206f0a967d21ee9cf2aa3e39ce524992c086414
MD5 b33b55d57d1b5c0aba81c4afb1b8e7ae
BLAKE2b-256 e5756ef9d95f4c6f9ab6846ee83f72698ed415e4cebeb1c484c72884b5728ca2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for quantcore-0.2.5-cp39-cp39-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 84281b1d3e32a41b2e65fcbac4464773db59c1c7b730632c0663176017138ee5
MD5 a761b259e855298fdccf833d677a604b
BLAKE2b-256 a9b87e5d1229aeb961c9e07c8360e41bf8ca3d4cc8209fd3cbbc88fbd8ed4a42

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