Build 060.7: implement REST Trade Adapter
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# src/market_data/acquisition/adapters/dzengi/rest_trade_adapter.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.market_data.acquisition.adapters.dzengi.mapper import (
|
||||
map_dzengi_rest_agg_trades_to_trades,
|
||||
)
|
||||
from src.market_data.acquisition.adapters.dzengi.parser import (
|
||||
parse_rest_agg_trades,
|
||||
)
|
||||
from src.market_data.acquisition.models.trade import Trade
|
||||
from src.market_data.acquisition.validation.schema import (
|
||||
ValidatedRestAggTradesDocument,
|
||||
)
|
||||
from src.market_data.acquisition.validation.values import (
|
||||
validate_rest_agg_trade_values,
|
||||
)
|
||||
|
||||
|
||||
def adapt_rest_agg_trades_document(
|
||||
document: ValidatedRestAggTradesDocument,
|
||||
*,
|
||||
symbol: str,
|
||||
) -> tuple[Trade, ...]:
|
||||
"""
|
||||
Преобразовать структурно проверенный документ Dzengi aggTrades
|
||||
в канонический immutable-набор Trade.
|
||||
|
||||
Функция последовательно выполняет parsing, value validation
|
||||
и mapping. Schema validation должна быть выполнена вызывающим слоем.
|
||||
Исключения отдельных этапов не перехватываются и не оборачиваются.
|
||||
"""
|
||||
|
||||
trades = parse_rest_agg_trades(document)
|
||||
|
||||
validate_rest_agg_trade_values(trades)
|
||||
|
||||
return map_dzengi_rest_agg_trades_to_trades(
|
||||
trades,
|
||||
symbol=symbol,
|
||||
)
|
||||
@@ -0,0 +1,188 @@
|
||||
# tests/unit/market_data/acquisition/adapters/dzengi/test_rest_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 rest_trade_adapter
|
||||
from src.market_data.acquisition.adapters.dzengi.rest_trade_adapter import (
|
||||
adapt_rest_agg_trades_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 (
|
||||
ValidatedRestAggTradesDocument,
|
||||
)
|
||||
|
||||
|
||||
def test_adapt_rest_agg_trades_document_returns_canonical_trades() -> None:
|
||||
document = ValidatedRestAggTradesDocument(
|
||||
items=(
|
||||
MappingProxyType(
|
||||
{
|
||||
"a": 101,
|
||||
"p": "43210.50",
|
||||
"q": "0.125",
|
||||
"T": 1_700_000_000_000,
|
||||
"m": False,
|
||||
}
|
||||
),
|
||||
MappingProxyType(
|
||||
{
|
||||
"a": 102,
|
||||
"p": "43211.75",
|
||||
"q": "0.250",
|
||||
"T": 1_700_000_001_000,
|
||||
"m": True,
|
||||
}
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
result = adapt_rest_agg_trades_document(
|
||||
document,
|
||||
symbol=" BTCUSDT ",
|
||||
)
|
||||
|
||||
assert isinstance(result, tuple)
|
||||
assert len(result) == 2
|
||||
|
||||
first_trade = result[0]
|
||||
|
||||
assert first_trade.symbol == "BTCUSDT"
|
||||
assert first_trade.trade_id == 101
|
||||
assert first_trade.price == Decimal("43210.50")
|
||||
assert first_trade.quantity == Decimal("0.125")
|
||||
assert first_trade.executed_at == datetime(
|
||||
2023,
|
||||
11,
|
||||
14,
|
||||
22,
|
||||
13,
|
||||
20,
|
||||
tzinfo=timezone.utc,
|
||||
)
|
||||
assert first_trade.aggressor_side is TradeAggressorSide.BUY
|
||||
assert first_trade.source == "dzengi"
|
||||
|
||||
second_trade = result[1]
|
||||
|
||||
assert second_trade.symbol == "BTCUSDT"
|
||||
assert second_trade.trade_id == 102
|
||||
assert second_trade.price == Decimal("43211.75")
|
||||
assert second_trade.quantity == Decimal("0.250")
|
||||
assert second_trade.executed_at == datetime(
|
||||
2023,
|
||||
11,
|
||||
14,
|
||||
22,
|
||||
13,
|
||||
21,
|
||||
tzinfo=timezone.utc,
|
||||
)
|
||||
assert second_trade.aggressor_side is TradeAggressorSide.SELL
|
||||
assert second_trade.source == "dzengi"
|
||||
|
||||
|
||||
def test_adapt_rest_agg_trades_document_propagates_parse_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
document = ValidatedRestAggTradesDocument(items=())
|
||||
expected_error = TradeParseError("test parse error")
|
||||
|
||||
def raise_parse_error(
|
||||
_: ValidatedRestAggTradesDocument,
|
||||
) -> tuple[()]:
|
||||
raise expected_error
|
||||
|
||||
monkeypatch.setattr(
|
||||
rest_trade_adapter,
|
||||
"parse_rest_agg_trades",
|
||||
raise_parse_error,
|
||||
)
|
||||
|
||||
with pytest.raises(TradeParseError) as exc_info:
|
||||
adapt_rest_agg_trades_document(
|
||||
document,
|
||||
symbol="BTCUSDT",
|
||||
)
|
||||
|
||||
assert exc_info.value is expected_error
|
||||
|
||||
|
||||
def test_adapt_rest_agg_trades_document_propagates_value_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
document = ValidatedRestAggTradesDocument(items=())
|
||||
expected_error = TradeValueError("test value error")
|
||||
|
||||
monkeypatch.setattr(
|
||||
rest_trade_adapter,
|
||||
"parse_rest_agg_trades",
|
||||
lambda _: (),
|
||||
)
|
||||
|
||||
def raise_value_error(_: tuple[object, ...]) -> None:
|
||||
raise expected_error
|
||||
|
||||
monkeypatch.setattr(
|
||||
rest_trade_adapter,
|
||||
"validate_rest_agg_trade_values",
|
||||
raise_value_error,
|
||||
)
|
||||
|
||||
with pytest.raises(TradeValueError) as exc_info:
|
||||
adapt_rest_agg_trades_document(
|
||||
document,
|
||||
symbol="BTCUSDT",
|
||||
)
|
||||
|
||||
assert exc_info.value is expected_error
|
||||
|
||||
|
||||
def test_adapt_rest_agg_trades_document_propagates_mapping_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
document = ValidatedRestAggTradesDocument(items=())
|
||||
expected_error = TradeMappingError("test mapping error")
|
||||
|
||||
monkeypatch.setattr(
|
||||
rest_trade_adapter,
|
||||
"parse_rest_agg_trades",
|
||||
lambda _: (),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
rest_trade_adapter,
|
||||
"validate_rest_agg_trade_values",
|
||||
lambda _: None,
|
||||
)
|
||||
|
||||
def raise_mapping_error(
|
||||
_: tuple[object, ...],
|
||||
*,
|
||||
symbol: str,
|
||||
) -> tuple[()]:
|
||||
del symbol
|
||||
raise expected_error
|
||||
|
||||
monkeypatch.setattr(
|
||||
rest_trade_adapter,
|
||||
"map_dzengi_rest_agg_trades_to_trades",
|
||||
raise_mapping_error,
|
||||
)
|
||||
|
||||
with pytest.raises(TradeMappingError) as exc_info:
|
||||
adapt_rest_agg_trades_document(
|
||||
document,
|
||||
symbol="BTCUSDT",
|
||||
)
|
||||
|
||||
assert exc_info.value is expected_error
|
||||
Reference in New Issue
Block a user