Build 060.17 — Trades Feed Core
This commit is contained in:
@@ -1 +1,9 @@
|
||||
# app/src/market_data/acquisition/adapters/dzengi/__init__.py
|
||||
# app/src/market_data/acquisition/adapters/dzengi/__init__.py
|
||||
|
||||
from src.market_data.acquisition.adapters.dzengi.websocket_trade_adapter import (
|
||||
adapt_websocket_trade_document,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"adapt_websocket_trade_document",
|
||||
]
|
||||
@@ -145,6 +145,11 @@ class TradeMappingError(MarketDataAcquisitionError):
|
||||
pass
|
||||
|
||||
|
||||
# Ошибка регистрации или получения Trades Feed.
|
||||
class TradeFeedRegistryError(MarketDataAcquisitionError):
|
||||
pass
|
||||
|
||||
|
||||
# Ошибка определения типа входящего WebSocket-сообщения
|
||||
# и выбора специализированного адаптера.
|
||||
class WebSocketMessageRoutingError(MarketDataAcquisitionError):
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# app/src/market_data/acquisition/feeds/trades_feed.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.market_data.acquisition.models.trade import Trade
|
||||
from src.market_data.acquisition.protocol import (
|
||||
TradeDocumentHandler,
|
||||
TradeDocumentSource,
|
||||
)
|
||||
|
||||
|
||||
# Источник готовой внутренней модели Trade.
|
||||
class TradesFeed:
|
||||
def __init__(
|
||||
self,
|
||||
source: TradeDocumentSource,
|
||||
handler: TradeDocumentHandler,
|
||||
) -> None:
|
||||
self._source = source
|
||||
self._handler = handler
|
||||
|
||||
def load_trade(
|
||||
self,
|
||||
symbol: str,
|
||||
) -> Trade:
|
||||
"""
|
||||
Получить последнюю сделку инструмента.
|
||||
|
||||
Feed получает сырой документ у источника и делегирует
|
||||
его преобразование специализированному обработчику.
|
||||
"""
|
||||
|
||||
document = self._source.fetch_trade_document(symbol)
|
||||
|
||||
return self._handler.handle_trade_document(document)
|
||||
@@ -0,0 +1,24 @@
|
||||
# app/src/market_data/acquisition/handlers/trades_handler.py
|
||||
|
||||
# app/src/market_data/acquisition/handlers/trades_handler.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.market_data.acquisition.adapters.dzengi import (
|
||||
adapt_websocket_trade_document,
|
||||
)
|
||||
from src.market_data.acquisition.models.trade import Trade
|
||||
from src.market_data.acquisition.validation.schema import (
|
||||
validate_dzengi_websocket_trade_schema,
|
||||
)
|
||||
|
||||
|
||||
# Обработчик документа Trades Feed формата Dzengi WebSocket internal.trade.
|
||||
class DzengiTradeDocumentHandler:
|
||||
def handle_trade_document(
|
||||
self,
|
||||
document: object,
|
||||
) -> Trade:
|
||||
validated_document = validate_dzengi_websocket_trade_schema(document)
|
||||
|
||||
return adapt_websocket_trade_document(validated_document)
|
||||
@@ -7,6 +7,7 @@ from typing import Protocol, runtime_checkable
|
||||
from src.market_data.acquisition.models.candle import Candle
|
||||
from src.market_data.acquisition.models.instrument import Instrument
|
||||
from src.market_data.acquisition.models.quote import Quote
|
||||
from src.market_data.acquisition.models.trade import Trade
|
||||
|
||||
|
||||
# Источник сырого документа Instrument Reference Data.
|
||||
@@ -87,6 +88,49 @@ class QuoteFeedProtocol(Protocol):
|
||||
...
|
||||
|
||||
|
||||
# Источник сырого документа Trades Feed.
|
||||
@runtime_checkable
|
||||
class TradeDocumentSource(Protocol):
|
||||
def fetch_trade_document(
|
||||
self,
|
||||
symbol: str,
|
||||
) -> object:
|
||||
"""
|
||||
Получить декодированный транспортный документ сделок.
|
||||
|
||||
Источник не выполняет schema validation, parsing,
|
||||
value validation или mapping во внутреннюю модель Trade.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
# Обработчик сырого документа Trades Feed.
|
||||
@runtime_checkable
|
||||
class TradeDocumentHandler(Protocol):
|
||||
def handle_trade_document(
|
||||
self,
|
||||
document: object,
|
||||
) -> Trade:
|
||||
"""
|
||||
Преобразовать сырой документ
|
||||
в проверенную внутреннюю модель Trade.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
# Источник готовой модели Trade.
|
||||
@runtime_checkable
|
||||
class TradesFeedProtocol(Protocol):
|
||||
def load_trade(
|
||||
self,
|
||||
symbol: str,
|
||||
) -> Trade:
|
||||
"""
|
||||
Получить внутреннюю модель последней сделки инструмента.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
# Источник сырого документа Candles Feed.
|
||||
@runtime_checkable
|
||||
class CandlesDocumentSource(Protocol):
|
||||
|
||||
@@ -6,11 +6,13 @@ from src.market_data.acquisition.exceptions import (
|
||||
CandleFeedRegistryError,
|
||||
InstrumentFeedRegistryError,
|
||||
QuoteFeedRegistryError,
|
||||
TradeFeedRegistryError,
|
||||
)
|
||||
from src.market_data.acquisition.protocol import (
|
||||
CandlesFeedProtocol,
|
||||
InstrumentFeedProtocol,
|
||||
QuoteFeedProtocol,
|
||||
TradesFeedProtocol,
|
||||
)
|
||||
|
||||
|
||||
@@ -144,6 +146,71 @@ class QuoteFeedRegistry:
|
||||
return normalized_source_name
|
||||
|
||||
|
||||
# Реестр доступных потоков рыночных сделок.
|
||||
class TradesFeedRegistry:
|
||||
def __init__(self) -> None:
|
||||
self._feeds: dict[str, TradesFeedProtocol] = {}
|
||||
|
||||
def register(
|
||||
self,
|
||||
source_name: str,
|
||||
feed: TradesFeedProtocol,
|
||||
) -> None:
|
||||
"""
|
||||
Зарегистрировать Trades Feed для указанного источника.
|
||||
|
||||
Повторная регистрация того же имени запрещена, чтобы исключить
|
||||
неявную замену production-зависимости.
|
||||
"""
|
||||
|
||||
normalized_source_name = self._normalize_source_name(source_name)
|
||||
|
||||
if not isinstance(feed, TradesFeedProtocol):
|
||||
raise TradeFeedRegistryError(
|
||||
f"Объект для источника '{normalized_source_name}' "
|
||||
"не соответствует TradesFeedProtocol."
|
||||
)
|
||||
|
||||
if normalized_source_name in self._feeds:
|
||||
raise TradeFeedRegistryError(
|
||||
f"Trades Feed для источника "
|
||||
f"'{normalized_source_name}' уже зарегистрирован."
|
||||
)
|
||||
|
||||
self._feeds[normalized_source_name] = feed
|
||||
|
||||
def get(
|
||||
self,
|
||||
source_name: str,
|
||||
) -> TradesFeedProtocol:
|
||||
"""Вернуть зарегистрированный Trades Feed по имени источника."""
|
||||
|
||||
normalized_source_name = self._normalize_source_name(source_name)
|
||||
|
||||
feed = self._feeds.get(normalized_source_name)
|
||||
|
||||
if feed is None:
|
||||
raise TradeFeedRegistryError(
|
||||
f"Trades Feed для источника "
|
||||
f"'{normalized_source_name}' не зарегистрирован."
|
||||
)
|
||||
|
||||
return feed
|
||||
|
||||
def _normalize_source_name(
|
||||
self,
|
||||
source_name: str,
|
||||
) -> str:
|
||||
normalized_source_name = source_name.strip()
|
||||
|
||||
if not normalized_source_name:
|
||||
raise TradeFeedRegistryError(
|
||||
"Имя источника Trades Feed не должно быть пустым."
|
||||
)
|
||||
|
||||
return normalized_source_name
|
||||
|
||||
|
||||
# Реестр доступных потоков рыночных свечей.
|
||||
class CandlesFeedRegistry:
|
||||
def __init__(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user