""" Unified domain events for market data. All providers normalize raw messages into these canonical types. Timestamps are always UTC. ts_exchange may be None if the source doesn't provide exchange timestamps. """ from __future__ import annotations import time from datetime import datetime, timezone from enum import Enum from typing import Optional from pydantic import BaseModel, Field class EventType(str, Enum): TRADE = "trade" QUOTE = "quote" BOOK_L2 = "book_l2" HEARTBEAT = "heartbeat" def _utc_now() -> datetime: return datetime.now(timezone.utc) def _mono_ns() -> int: """Monotonic nanoseconds for internal latency measurement.""" return time.monotonic_ns() class BaseEvent(BaseModel): """Common fields for every market-data event.""" event_type: EventType provider: str ts_recv: datetime = Field(default_factory=_utc_now) ts_recv_mono_ns: int = Field(default_factory=_mono_ns) class TradeEvent(BaseEvent): """A single matched trade (fill).""" event_type: EventType = EventType.TRADE symbol: str price: float size: float ts_exchange: Optional[datetime] = None side: Optional[str] = None # "buy" | "sell" | None trade_id: Optional[str] = None class QuoteEvent(BaseEvent): """Best bid/ask (top-of-book).""" event_type: EventType = EventType.QUOTE symbol: str bid: float ask: float bid_size: float ask_size: float ts_exchange: Optional[datetime] = None class BookLevel(BaseModel): price: float size: float class BookL2Event(BaseEvent): """L2 order-book snapshot (partial depth).""" event_type: EventType = EventType.BOOK_L2 symbol: str bids: list[BookLevel] = Field(default_factory=list) asks: list[BookLevel] = Field(default_factory=list) ts_exchange: Optional[datetime] = None class HeartbeatEvent(BaseEvent): """Provider heartbeat / keep-alive signal.""" event_type: EventType = EventType.HEARTBEAT # Union type for type-safe consumers Event = TradeEvent | QuoteEvent | BookL2Event | HeartbeatEvent