"""Periodic call auction (robustness alternative to continuous matching). Every `interval` ticks the engine collects the resting limit orders of a book and clears them at the uniform price that maximizes executable volume (ties: the price closest to the previous mid). Market orders submitted during the batch are treated as limit orders at the price band edge. Orders rest between auctions without being matched continuously (the engine skips `match` while in batch mode).""" from __future__ import annotations from .book import LimitOrderBook from .orders import Order, Side def clearing_price(book: LimitOrderBook, ref_mid: float, collar: float = 0.02) -> tuple[int | None, int]: """Uniform price maximizing executable volume among limit prices inside a collar of ±`collar` around the reference (market orders converted to band-edge limits are price takers, not candidates).""" bids = sorted(((-k, sum(o.remaining for o in q)) for k, q in book.bids.items()), reverse=True) asks = sorted(((k, sum(o.remaining for o in q)) for k, q in book.asks.items())) if not bids or not asks or bids[0][0] < asks[0][0]: return None, 0 lo, hi = ref_mid * (1 - collar), ref_mid * (1 + collar) prices = sorted({p for p, _ in bids if lo <= p <= hi} | {p for p, _ in asks if lo <= p <= hi} | {int(round(ref_mid))}) best, best_vol, best_dist = None, 0, float("inf") for p in prices: demand = sum(q for bp, q in bids if bp >= p) supply = sum(q for ap, q in asks if ap <= p) vol = min(demand, supply) dist = abs(p - ref_mid) if vol > best_vol or (vol == best_vol and vol > 0 and dist < best_dist): best, best_vol, best_dist = p, vol, dist return best, best_vol def executable_orders(book: LimitOrderBook, price: int) -> tuple[list[Order], list[Order]]: """Orders that trade at `price`, in price-time priority (bids high→low, asks low→high).""" bids = [o for k, q in book.bids.items() for o in q if -k >= price] asks = [o for k, q in book.asks.items() for o in q if k <= price] return bids, asks