Build 060.6: add REST trade mapper

This commit is contained in:
2026-07-19 09:49:01 +03:00
parent a0ae22cb2e
commit 6b0d5badce
4 changed files with 2599 additions and 2 deletions

View File

@@ -13,6 +13,7 @@ from src.market_data.acquisition.adapters.dzengi.models import (
DzengiLotSizeFilter,
DzengiMinNotionalFilter,
DzengiRawNumeric,
DzengiRestAggTrade,
DzengiTicker24hrResponse,
DzengiWebSocketOhlcEvent,
DzengiWebSocketQuoteResponse,
@@ -22,11 +23,16 @@ from src.market_data.acquisition.exceptions import (
CandleWebSocketMappingError,
InstrumentReferenceMappingError,
QuoteMappingError,
TradeMappingError,
)
from src.market_data.acquisition.models.candle import Candle
from src.market_data.acquisition.models.candle_close import CandleCloseEvent
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,
TradeAggressorSide,
)
_DZENGI_SOURCE_NAME = "dzengi"
@@ -506,4 +512,98 @@ def _required_candle_decimal(
f"Поле {field_name} свечи должно быть конечным числом."
)
return result
return result
def map_dzengi_rest_agg_trades_to_trades(
trades: tuple[DzengiRestAggTrade, ...],
*,
symbol: str,
) -> tuple[Trade, ...]:
"""
Преобразовать проверенные transport-модели Dzengi aggTrades
в канонический immutable-набор Trade.
Функция предполагает, что до mapper уже были выполнены:
schema validation, parsing и value validation.
"""
return tuple(
_map_dzengi_rest_agg_trade(
trade,
symbol=symbol,
)
for trade in trades
)
def _map_dzengi_rest_agg_trade(
trade: DzengiRestAggTrade,
*,
symbol: str,
) -> Trade:
return Trade(
symbol=symbol.strip(),
trade_id=trade.aggregate_trade_id,
price=_required_trade_decimal(
trade.price,
field_name="price",
),
quantity=_required_trade_decimal(
trade.quantity,
field_name="quantity",
),
executed_at=_trade_timestamp_ms_to_utc_datetime(
trade.timestamp,
),
aggressor_side=_trade_aggressor_side(
trade.buyer_is_maker,
),
source=_DZENGI_SOURCE_NAME,
)
def _trade_aggressor_side(
buyer_is_maker: bool,
) -> TradeAggressorSide:
if buyer_is_maker:
return TradeAggressorSide.SELL
return TradeAggressorSide.BUY
def _required_trade_decimal(
value: DzengiRawNumeric,
*,
field_name: str,
) -> Decimal:
try:
result = Decimal(str(value))
except (InvalidOperation, ValueError) as exc:
raise TradeMappingError(
f"Поле {field_name} сделки невозможно "
"преобразовать в Decimal."
) from exc
if not result.is_finite():
raise TradeMappingError(
f"Поле {field_name} сделки должно быть "
"конечным числом."
)
return result
def _trade_timestamp_ms_to_utc_datetime(
value: int,
) -> datetime:
try:
return datetime.fromtimestamp(
value / 1000,
tz=timezone.utc,
)
except (OverflowError, OSError, ValueError) as exc:
raise TradeMappingError(
"Поле timestamp сделки невозможно "
"преобразовать в UTC datetime."
) from exc

View File

@@ -134,6 +134,12 @@ class TradeValueError(MarketDataAcquisitionError):
pass
# Ошибка преобразования raw-модели источника
# во внутреннюю модель Trade.
class TradeMappingError(MarketDataAcquisitionError):
pass
# Ошибка определения типа входящего WebSocket-сообщения
# и выбора специализированного адаптера.
class WebSocketMessageRoutingError(MarketDataAcquisitionError):