Skip to content

Commit

Permalink
Benchmark order matching (#23)
Browse files Browse the repository at this point in the history
* Benchmark order matching

* Add benchmark to github action

* Add benchmark to github action

* fix actions setup

* fix actions setup

* Save histogram

* Save histogram
  • Loading branch information
Stanislav Khrapov authored Feb 7, 2023
1 parent 2584d5f commit 33fd5f2
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 3 deletions.
34 changes: 33 additions & 1 deletion .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,38 @@ jobs:
- name: Run test suite
run: pytest --cov=order_matching

benchmark:
name: Run benchmark
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: pip install -e .[test]
- name: Run benchmark
run: ./benchmark.sh
- name: Download previous benchmark data
uses: actions/cache@v3
with:
path: ./cache
key: ${{ runner.os }}-benchmark
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
tool: 'pytest'
output-file-path: benchmark.json
external-data-json-path: ./cache/benchmark-data.json
fail-on-alert: false
github-token: ${{ secrets.GITHUB_TOKEN }}
comment-on-alert: true
alert-comment-cc-users: '@${GITHUB_ACTOR},@khrapovs'

code-quality:
name: Code quality checks
runs-on: ubuntu-latest
Expand Down Expand Up @@ -69,7 +101,7 @@ jobs:

build:
name: Build wheels
needs: [ test, code-quality, docs ]
needs: [ test, benchmark, code-quality, docs ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
.mypy_cache/
.pytest_cache/
.ruff_cache/
.benchmarks/
benchmark.json
benchmark_history.svg
__pycache__/*
venv/
site/
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ Run tests:
pip install -e ".[test]"
pytest
```
Run benchmark and see the result either in the terminal or as a plot in `benchmark_history.svg`:
```shell
./benchmark.sh
```
Build and serve documentation website:
```shell
pip install -e ".[doc]"
Expand Down
11 changes: 11 additions & 0 deletions benchmark.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

# Run pytest benchmark. Results are saved in .benchmarks
pytest tests/test_matching_engine.py::TestMatchingEngine::test_matching_with_benchmark \
--dist no \
--benchmark-enable \
--benchmark-autosave \
--benchmark-json=benchmark.json

# Compare saved results
pytest-benchmark compare --histogram=benchmark_history
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Documentation = "https://order-book-matching-engine.readthedocs.io/"
test = [
"pytest",
"pytest-cov",
"pytest-xdist"
"pytest-xdist",
"pytest-benchmark[histogram]"
]
doc = [
"mkdocs-material",
Expand All @@ -54,7 +55,7 @@ requires = [
build-backend = "setuptools.build_meta"

[tool.pytest.ini_options]
addopts = "--cov-config=.coveragerc --doctest-modules --doctest-glob='*.md' -n=auto"
addopts = "--cov-config=.coveragerc --doctest-modules --doctest-glob='*.md' -n=auto --benchmark-disable"
testpaths = ["tests", "src", "README.md"]

[tool.setuptools_scm]
Expand Down
29 changes: 29 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import pandas as pd
import pytest

from order_matching.order import LimitOrder
from order_matching.orders import Orders
from order_matching.random import get_faker, get_random_generator
from order_matching.side import Side


@pytest.fixture
def random_orders() -> Orders:
faker = get_faker()
rng = get_random_generator(seed=42)
orders_per_timestamp, number_of_time_points = 100, 10
time_intervals = rng.uniform(low=0, high=1, size=number_of_time_points)
random_timestamps = pd.Timestamp(2023, 1, 1) + pd.to_timedelta(time_intervals.cumsum(), unit="D")
orders = list()
for timestamp in random_timestamps:
prices = rng.lognormal(mean=1, size=orders_per_timestamp).round(decimals=1)
sizes = rng.lognormal(mean=1, size=orders_per_timestamp).round(decimals=4)
sides = rng.choice([Side.SELL, Side.BUY], size=orders_per_timestamp)
new_orders = [
LimitOrder(
side=side, price=price, size=size, timestamp=timestamp, order_id=faker.uuid4(), trader_id=faker.uuid4()
)
for side, price, size in zip(sides, prices, sizes, strict=True)
]
orders.extend(new_orders)
return Orders(orders)
5 changes: 5 additions & 0 deletions tests/test_matching_engine.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from copy import deepcopy

import pandas as pd
from pytest_benchmark.fixture import BenchmarkFixture

from order_matching.matching_engine import MatchingEngine
from order_matching.order import LimitOrder, MarketOrder
Expand Down Expand Up @@ -742,3 +743,7 @@ def test_cancellation_of_expired_orders(self) -> None:

assert matching_engine.unprocessed_orders.bids == dict()
assert matching_engine.unprocessed_orders.offers == dict()

def test_matching_with_benchmark(self, random_orders: Orders, benchmark: BenchmarkFixture) -> None:
order_book = MatchingEngine()
benchmark(order_book.match, orders=random_orders, timestamp=random_orders.orders[-1].timestamp)

0 comments on commit 33fd5f2

Please sign in to comment.