Build 060.18: implement Trade Stream Consistency Controller

This commit is contained in:
2026-07-20 19:16:19 +03:00
parent fe308b9e60
commit 618b716e71
7 changed files with 525 additions and 0 deletions

View File

@@ -0,0 +1 @@
# app/src/market_data/acquisition/consistency/__init__.py

View File

@@ -0,0 +1,45 @@
# app/src/market_data/acquisition/consistency/trade_stream_consistency_controller.py
from __future__ import annotations
from src.market_data.acquisition.consistency.trade_stream_protocol import (
TradeStreamConsistencyProtocol,
)
from src.market_data.acquisition.consistency.trade_stream_state import (
TradeStreamState,
)
from src.market_data.acquisition.models.trade import Trade
class TradeStreamConsistencyController(
TradeStreamConsistencyProtocol,
):
"""
Контроллер проверки согласованности Canonical Trade Stream.
Для каждого торгового инструмента поддерживается
независимое состояние проверки.
"""
def __init__(self) -> None:
self._states: dict[str, TradeStreamState] = {}
def accept(
self,
trade: Trade,
) -> Trade | None:
state = self._get_state(trade.symbol)
return state.accept(trade)
def _get_state(
self,
symbol: str,
) -> TradeStreamState:
state = self._states.get(symbol)
if state is None:
state = TradeStreamState(symbol=symbol)
self._states[symbol] = state
return state

View File

@@ -0,0 +1,17 @@
# app/src/market_data/acquisition/consistency/trade_stream_exceptions.py
from __future__ import annotations
from src.market_data.acquisition.exceptions import (
MarketDataAcquisitionError,
)
# Нарушение монотонности Canonical Trade Stream.
class TradeOrderingError(MarketDataAcquisitionError):
pass
# Обнаружен конфликтующий дубликат сделки.
class TradeConsistencyError(MarketDataAcquisitionError):
pass

View File

@@ -0,0 +1,29 @@
# app/src/market_data/acquisition/consistency/trade_stream_protocol.py
from __future__ import annotations
from typing import Protocol, runtime_checkable
from src.market_data.acquisition.models.trade import Trade
# Контракт проверки согласованности Canonical Trade Stream.
@runtime_checkable
class TradeStreamConsistencyProtocol(Protocol):
def accept(
self,
trade: Trade,
) -> Trade | None:
"""
Проверить согласованность поступившей сделки.
Возвращает исходную модель Trade, если сделка принята.
Возвращает None, если обнаружен полный дубликат.
Генерирует TradeOrderingError при нарушении порядка.
Генерирует TradeConsistencyError при обнаружении
конфликтующего дубликата.
"""
...

View File

@@ -0,0 +1,103 @@
# app/src/market_data/acquisition/consistency/trade_stream_state.py
from __future__ import annotations
from collections import deque
from dataclasses import dataclass, field
from src.market_data.acquisition.models.trade import Trade
from src.market_data.acquisition.consistency.trade_stream_exceptions import (
TradeConsistencyError,
TradeOrderingError,
)
DEFAULT_DEDUPLICATION_WINDOW_SIZE = 10_000
@dataclass(slots=True)
class TradeStreamState:
"""
Состояние проверки согласованности Canonical Trade Stream
для одного торгового инструмента.
"""
symbol: str
deduplication_window_size: int = DEFAULT_DEDUPLICATION_WINDOW_SIZE
last_trade_id: int | None = None
_trade_window: deque[int] = field(init=False, repr=False)
_trades: dict[int, Trade] = field(init=False, repr=False)
def __post_init__(self) -> None:
if not self.symbol:
raise ValueError("symbol must not be empty")
if self.deduplication_window_size <= 0:
raise ValueError(
"deduplication_window_size must be positive"
)
self._trade_window = deque(
maxlen=self.deduplication_window_size,
)
self._trades = {}
def accept(
self,
trade: Trade,
) -> Trade | None:
"""
Проверить сделку на согласованность.
Возвращает принятую сделку.
Возвращает None при полном дубликате.
Генерирует исключение при нарушении порядка
либо конфликтующем дубликате.
"""
if trade.symbol != self.symbol:
raise ValueError(
f"Unexpected symbol: {trade.symbol!r}"
)
trade_id = trade.trade_id
if self.last_trade_id is not None:
if trade_id < self.last_trade_id:
previous = self._trades.get(trade_id)
if previous is None:
raise TradeOrderingError()
if previous == trade:
return None
raise TradeConsistencyError()
previous = self._trades.get(trade_id)
if previous is not None:
if previous == trade:
return None
raise TradeConsistencyError()
self._append(trade)
self.last_trade_id = trade_id
return trade
def _append(
self,
trade: Trade,
) -> None:
if len(self._trade_window) == self.deduplication_window_size:
oldest_trade_id = self._trade_window[0]
self._trades.pop(oldest_trade_id, None)
self._trade_window.append(trade.trade_id)
self._trades[trade.trade_id] = trade