Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.10", "3.11", "3.12" ]
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
steps:
- name: Check out repository code
uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ benchmark_history.svg
__pycache__/
venv/
site/
docs/reference/
*.egg-info
.coverage
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pip install order-matching
## Usage

```python
>>> from datetime import datetime, timedelta
>>> from pprint import pp
>>> import pandas as pd

Expand All @@ -37,8 +38,8 @@ pip install order-matching
>>> from order_matching.orders import Orders

>>> matching_engine = MatchingEngine(seed=123)
>>> timestamp = pd.Timestamp("2023-01-01")
>>> transaction_timestamp = timestamp + pd.Timedelta(1, unit="D")
>>> timestamp = datetime(2023, 1, 1)
>>> transaction_timestamp = timestamp + timedelta(days=1)
>>> buy_order = LimitOrder(side=Side.BUY, price=1.2, size=2.3, timestamp=timestamp, order_id="a", trader_id="x")
>>> sell_order = LimitOrder(side=Side.SELL, price=0.8, size=1.6, timestamp=timestamp, order_id="b", trader_id="y")
>>> executed_trades = matching_engine.match(orders=Orders([buy_order, sell_order]), timestamp=transaction_timestamp)
Expand All @@ -51,7 +52,7 @@ pip install order-matching
book_order_id='a',
execution=LIMIT,
trade_id='c4da537c-1651-4dae-8486-7db30d67b366',
timestamp=Timestamp('2023-01-02 00:00:00'))]
timestamp=datetime.datetime(2023, 1, 2, 0, 0))]


```
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
requires-python = ">=3.10"
dependencies = ["pandas", "pandera", "faker"]
Expand All @@ -21,6 +22,9 @@ dependencies = ["pandas", "pandera", "faker"]
Source = "https://github.com/khrapovs/OrderBookMatchingEngine"
Documentation = "https://order-book-matching-engine.readthedocs.io/"

[tool.uv]
default-groups = ["dev", "test", "doc"]

[dependency-groups]
test = ["pytest", "pytest-cov", "pytest-xdist", "pytest-benchmark[histogram]"]
doc = [
Expand Down
5 changes: 3 additions & 2 deletions src/order_matching/executed_trades.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from collections import defaultdict
from dataclasses import asdict
from datetime import datetime

import pandas as pd
from pandera.typing import DataFrame
Expand All @@ -22,7 +23,7 @@ class ExecutedTrades:
"""

def __init__(self, trades: list[Trade] | None = None) -> None:
self._trades: dict[pd.Timestamp, list[Trade]] = defaultdict(list)
self._trades: dict[datetime, list[Trade]] = defaultdict(list)
if trades:
self.add(trades=trades)

Expand All @@ -45,7 +46,7 @@ def add(self, trades: list[Trade]) -> None:
for trade in trades:
self._trades[trade.timestamp].append(trade)

def get(self, timestamp: pd.Timestamp) -> list[Trade]:
def get(self, timestamp: datetime) -> list[Trade]:
"""Get subset by timestamp.

Returns
Expand Down
13 changes: 7 additions & 6 deletions src/order_matching/matching_engine.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import pandas as pd
from datetime import datetime

from order_matching.executed_trades import ExecutedTrades
from order_matching.order import Order
Expand All @@ -19,13 +19,14 @@ class MatchingEngine:

Examples
--------
>>> from datetime import datetime, timedelta
>>> from pprint import pp
>>> from order_matching.matching_engine import MatchingEngine
>>> from order_matching.order import LimitOrder
>>> from order_matching.side import Side
>>> matching_engine = MatchingEngine(seed=123)
>>> timestamp = pd.Timestamp("2023-01-01")
>>> transaction_timestamp = timestamp + pd.Timedelta(1, unit="D")
>>> timestamp = datetime(2023, 1, 1)
>>> transaction_timestamp = timestamp + timedelta(days=1)
>>> buy_order = LimitOrder(side=Side.BUY, price=1.2, size=2.3, timestamp=timestamp, order_id="a", trader_id="x")
>>> sell_order = LimitOrder(side=Side.SELL, price=0.8, size=1.6, timestamp=timestamp, order_id="b", trader_id="y")
>>> executed_trades = matching_engine.match(orders=Orders([buy_order, sell_order]), timestamp=transaction_timestamp)
Expand All @@ -37,17 +38,17 @@ class MatchingEngine:
book_order_id='a',
execution=LIMIT,
trade_id='c4da537c-1651-4dae-8486-7db30d67b366',
timestamp=Timestamp('2023-01-02 00:00:00'))]
timestamp=datetime.datetime(2023, 1, 2, 0, 0))]
"""

def __init__(self, seed: int | None = None) -> None:
self._seed = seed
self._faker = get_faker(seed=seed)
self._queue = Orders()
self.unprocessed_orders = OrderBook()
self._timestamp: pd.Timestamp | None = None
self._timestamp: datetime

def match(self, timestamp: pd.Timestamp, orders: Orders | None = None) -> ExecutedTrades:
def match(self, timestamp: datetime, orders: Orders | None = None) -> ExecutedTrades:
"""Match incoming orders in price-time priority.

Parameters
Expand Down
7 changes: 3 additions & 4 deletions src/order_matching/order.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from dataclasses import dataclass, field

import pandas as pd
from datetime import datetime

from order_matching.execution import Execution
from order_matching.side import Side
Expand All @@ -14,11 +13,11 @@ class Order:
side: Side
price: float
size: float
timestamp: pd.Timestamp
timestamp: datetime
order_id: str
trader_id: str
execution: Execution
expiration: pd.Timestamp = pd.NaT
expiration: datetime = datetime.max
status: Status = Status.OPEN
price_number_of_digits: int = 1

Expand Down
5 changes: 3 additions & 2 deletions src/order_matching/order_book.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import defaultdict
from datetime import datetime
from typing import cast

import pandas as pd
Expand All @@ -18,7 +19,7 @@ class OrderBook:
def __init__(self) -> None:
self.bids: OrderBookOrdersType = defaultdict(Orders)
self.offers: OrderBookOrdersType = defaultdict(Orders)
self.orders_by_expiration: dict[pd.Timestamp, Orders] = defaultdict(Orders)
self.orders_by_expiration: dict[datetime, Orders] = defaultdict(Orders)

def append(self, incoming_order: Order) -> None:
"""Add one order to the order book.
Expand Down Expand Up @@ -101,7 +102,7 @@ def get_opposite_side_orders(self, incoming_order: Order) -> OrderBookOrdersType
case Side.BUY:
return self.offers

def get_subset(self, expiration: pd.Timestamp) -> Orders:
def get_subset(self, expiration: datetime) -> Orders:
"""Get orders with given expiration time.

Parameters
Expand Down
6 changes: 6 additions & 0 deletions src/order_matching/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ def to_frame(self) -> DataFrame[OrderDataSchema]:
OrderDataSchema.side: lambda df: df[OrderDataSchema.side].astype(str),
OrderDataSchema.execution: lambda df: df[OrderDataSchema.execution].astype(str),
OrderDataSchema.status: lambda df: df[OrderDataSchema.status].astype(str),
OrderDataSchema.timestamp: lambda df: pd.to_datetime(
df[OrderDataSchema.timestamp], errors="coerce"
),
OrderDataSchema.expiration: lambda df: pd.to_datetime(
df[OrderDataSchema.expiration], errors="coerce"
),
}
)

Expand Down
5 changes: 2 additions & 3 deletions src/order_matching/trade.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from dataclasses import dataclass

import pandas as pd
from datetime import datetime

from order_matching.execution import Execution
from order_matching.side import Side
Expand All @@ -17,4 +16,4 @@ class Trade:
book_order_id: str
execution: Execution
trade_id: str
timestamp: pd.Timestamp = None
timestamp: datetime
5 changes: 3 additions & 2 deletions tests/test_executed_trades.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from copy import deepcopy
from datetime import datetime, timedelta

import pandas as pd

Expand All @@ -10,7 +11,7 @@


class TestExecutedTrades:
timestamp = pd.Timestamp.now()
timestamp = datetime.now()

def test_init(self) -> None:
executed_trades = ExecutedTrades()
Expand Down Expand Up @@ -45,7 +46,7 @@ def test_get(self) -> None:
assert executed_trades.get(timestamp=self.timestamp) == [first_trade, second_trade]

third_trade = deepcopy(first_trade)
third_trade.timestamp += pd.Timedelta(1, unit="D")
third_trade.timestamp += timedelta(days=1)
executed_trades.add(trades=[third_trade])

assert executed_trades.get(timestamp=self.timestamp) == [first_trade, second_trade]
Expand Down
Loading
Loading