111 lines
3.3 KiB
Python
111 lines
3.3 KiB
Python
"""Tests for market-WS parsing and the book service routing."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from polymaker.domain import Side
|
|
from polymaker.marketdata.parse import (
|
|
parse_book,
|
|
parse_last_trade,
|
|
parse_price_changes,
|
|
parse_tick_size_change,
|
|
)
|
|
from polymaker.marketdata.service import MarketDataService
|
|
|
|
# frames modeled on live captures (2026-07-05)
|
|
BOOK = {
|
|
"event_type": "book",
|
|
"market": "0xcond",
|
|
"asset_id": "yes-tok",
|
|
"timestamp": "1783270000000",
|
|
"hash": "abc123",
|
|
"tick_size": "0.01",
|
|
"bids": [{"price": "0.48", "size": "100"}, {"price": "0.49", "size": "200"}],
|
|
"asks": [{"price": "0.52", "size": "150"}, {"price": "0.51", "size": "80"}],
|
|
}
|
|
PRICE_CHANGE = {
|
|
"event_type": "price_change",
|
|
"market": "0xcond",
|
|
"timestamp": "1783270001000",
|
|
"price_changes": [
|
|
{"asset_id": "yes-tok", "price": "0.49", "size": "0", "side": "BUY", "hash": "h"},
|
|
{"asset_id": "yes-tok", "price": "0.50", "size": "300", "side": "BUY", "hash": "h"},
|
|
],
|
|
}
|
|
LAST_TRADE = {
|
|
"event_type": "last_trade_price",
|
|
"market": "0xcond",
|
|
"asset_id": "yes-tok",
|
|
"price": "0.50",
|
|
"size": "42",
|
|
"side": "BUY",
|
|
"timestamp": "1783270002000",
|
|
}
|
|
|
|
|
|
def test_parse_book_converts_ms_and_levels():
|
|
upd = parse_book(BOOK)
|
|
assert upd is not None
|
|
assert upd.asset_id == "yes-tok"
|
|
assert upd.condition_id == "0xcond"
|
|
assert (0.49, 200) in upd.bids
|
|
assert upd.ts == 1783270000.0 # ms -> s
|
|
assert upd.tick_size == 0.01
|
|
|
|
|
|
def test_parse_price_changes():
|
|
changes = parse_price_changes(PRICE_CHANGE)
|
|
assert len(changes) == 2
|
|
assert changes[0].side is Side.BUY
|
|
assert changes[1].price == 0.50 and changes[1].size == 300
|
|
|
|
|
|
def test_parse_last_trade_aggressor():
|
|
tp = parse_last_trade(LAST_TRADE)
|
|
assert tp is not None
|
|
assert tp.aggressor is Side.BUY
|
|
assert tp.size == 42
|
|
|
|
|
|
def test_parse_tick_size_change():
|
|
tc = parse_tick_size_change(
|
|
{"event_type": "tick_size_change", "asset_id": "yes-tok", "new_tick_size": "0.001"}
|
|
)
|
|
assert tc is not None and tc.tick_size == 0.001
|
|
|
|
|
|
def test_service_routes_book_and_wakes_quoter():
|
|
woken: list[tuple[str, str]] = []
|
|
svc = MarketDataService(on_dirty=lambda c, t: woken.append((c, t)))
|
|
svc.set_markets([("0xcond", ["yes-tok", "no-tok"])])
|
|
svc._dispatch(BOOK)
|
|
book = svc.book("yes-tok")
|
|
assert book is not None
|
|
assert book.best_bid().price == 0.49
|
|
assert book.best_ask().price == 0.51
|
|
assert woken == [("0xcond", "yes-tok")]
|
|
|
|
|
|
def test_service_applies_price_change_delta():
|
|
svc = MarketDataService()
|
|
svc.set_markets([("0xcond", ["yes-tok"])])
|
|
svc._dispatch(BOOK)
|
|
svc._dispatch(PRICE_CHANGE) # removes 0.49 bid, adds 0.50 bid
|
|
book = svc.book("yes-tok")
|
|
assert book.best_bid().price == 0.50
|
|
assert 0.49 not in book.bids
|
|
|
|
|
|
def test_service_ignores_unsubscribed_asset():
|
|
svc = MarketDataService()
|
|
svc.set_markets([("0xcond", ["yes-tok"])])
|
|
svc._dispatch({**BOOK, "asset_id": "stranger"})
|
|
assert svc.book("stranger") is None
|
|
|
|
|
|
def test_service_forwards_trades_for_flow():
|
|
trades = []
|
|
svc = MarketDataService(on_trade=trades.append)
|
|
svc.set_markets([("0xcond", ["yes-tok"])])
|
|
svc._dispatch(LAST_TRADE)
|
|
assert len(trades) == 1 and trades[0].aggressor is Side.BUY
|