Build 060.14 — WebSocket Trade Adapter

This commit is contained in:
2026-07-19 23:08:51 +03:00
parent b7bef85d20
commit bf7645237e
3 changed files with 1162 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
# src/market_data/acquisition/adapters/dzengi/websocket_trade_adapter.py
from __future__ import annotations
from src.market_data.acquisition.adapters.dzengi.mapper import (
map_dzengi_websocket_trade_to_trade,
)
from src.market_data.acquisition.adapters.dzengi.parser import (
parse_dzengi_websocket_trade,
)
from src.market_data.acquisition.models.trade import Trade
from src.market_data.acquisition.validation.schema import (
ValidatedWebSocketTradeDocument,
)
from src.market_data.acquisition.validation.values import (
validate_dzengi_websocket_trade_values,
)
def adapt_websocket_trade_document(
document: ValidatedWebSocketTradeDocument,
) -> Trade:
"""
Преобразовать структурно проверенный документ Dzengi WebSocket Trade
в каноническую immutable-модель Trade.
Функция последовательно выполняет parsing, value validation
и mapping. Schema validation должна быть выполнена вызывающим слоем.
Исключения отдельных этапов не перехватываются и не оборачиваются.
"""
event = parse_dzengi_websocket_trade(document)
validate_dzengi_websocket_trade_values(event)
return map_dzengi_websocket_trade_to_trade(event)

View File

@@ -0,0 +1,162 @@
# tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_adapter.py
from __future__ import annotations
from datetime import datetime, timezone
from decimal import Decimal
from types import MappingProxyType
import pytest
from src.market_data.acquisition.adapters.dzengi import (
websocket_trade_adapter,
)
from src.market_data.acquisition.adapters.dzengi.websocket_trade_adapter import (
adapt_websocket_trade_document,
)
from src.market_data.acquisition.exceptions import (
TradeMappingError,
TradeParseError,
TradeValueError,
)
from src.market_data.acquisition.models.trade import TradeAggressorSide
from src.market_data.acquisition.validation.schema import (
ValidatedWebSocketTradeDocument,
)
def test_adapt_websocket_trade_document_returns_canonical_trade() -> None:
document = ValidatedWebSocketTradeDocument(
payload=MappingProxyType(
{
"buyer": True,
"id": 101,
"orderId": "00a02503-0079-54c4-0000-000081e62b58",
"price": "43210.50",
"size": "0.125",
"symbol": " BTCUSDT ",
"ts": 1_700_000_000_123,
}
),
status="OK",
destination="internal.trade",
correlation_id=None,
)
result = adapt_websocket_trade_document(document)
assert result.symbol == "BTCUSDT"
assert result.trade_id == 101
assert result.price == Decimal("43210.50")
assert result.quantity == Decimal("0.125")
assert result.executed_at == datetime(
2023,
11,
14,
22,
13,
20,
123000,
tzinfo=timezone.utc,
)
assert result.aggressor_side is TradeAggressorSide.BUY
assert result.source == "dzengi_websocket_trade"
def test_adapt_websocket_trade_document_propagates_parse_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
document = ValidatedWebSocketTradeDocument(
payload=MappingProxyType({}),
status="OK",
destination="internal.trade",
correlation_id=None,
)
expected_error = TradeParseError("test parse error")
def raise_parse_error(
_: ValidatedWebSocketTradeDocument,
) -> object:
raise expected_error
monkeypatch.setattr(
websocket_trade_adapter,
"parse_dzengi_websocket_trade",
raise_parse_error,
)
with pytest.raises(TradeParseError) as exc_info:
adapt_websocket_trade_document(document)
assert exc_info.value is expected_error
def test_adapt_websocket_trade_document_propagates_value_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
document = ValidatedWebSocketTradeDocument(
payload=MappingProxyType({}),
status="OK",
destination="internal.trade",
correlation_id=None,
)
parsed_event = object()
expected_error = TradeValueError("test value error")
monkeypatch.setattr(
websocket_trade_adapter,
"parse_dzengi_websocket_trade",
lambda _: parsed_event,
)
def raise_value_error(_: object) -> None:
raise expected_error
monkeypatch.setattr(
websocket_trade_adapter,
"validate_dzengi_websocket_trade_values",
raise_value_error,
)
with pytest.raises(TradeValueError) as exc_info:
adapt_websocket_trade_document(document)
assert exc_info.value is expected_error
def test_adapt_websocket_trade_document_propagates_mapping_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
document = ValidatedWebSocketTradeDocument(
payload=MappingProxyType({}),
status="OK",
destination="internal.trade",
correlation_id=None,
)
parsed_event = object()
expected_error = TradeMappingError("test mapping error")
monkeypatch.setattr(
websocket_trade_adapter,
"parse_dzengi_websocket_trade",
lambda _: parsed_event,
)
monkeypatch.setattr(
websocket_trade_adapter,
"validate_dzengi_websocket_trade_values",
lambda _: None,
)
def raise_mapping_error(_: object) -> object:
raise expected_error
monkeypatch.setattr(
websocket_trade_adapter,
"map_dzengi_websocket_trade_to_trade",
raise_mapping_error,
)
with pytest.raises(TradeMappingError) as exc_info:
adapt_websocket_trade_document(document)
assert exc_info.value is expected_error