Coverage for adrs/oms/ops/order_utils.py: 78%
37 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-29 05:07 +0800
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-29 05:07 +0800
1import uuid
2import logging
4from decimal import Decimal
5from datetime import datetime, timezone
6from typing import TYPE_CHECKING
8from cybotrade import Symbol
9from cybotrade.io import ExchangeClient
11from adrs.oms.rate_limit.exchange_limit_profiles import OMS_DEPTH_LIMIT, Endpoints
13if TYPE_CHECKING:
14 from adrs.oms.price_feed import PriceFeed
15 from adrs.oms.rate_limit.rate_limiter import RateLimiter
17logger = logging.getLogger(__name__)
20class OrderUtils:
21 def __init__(self):
22 pass
24 @staticmethod
25 async def get_order_book(
26 exchange: ExchangeClient,
27 pair: Symbol,
28 need_log: bool,
29 rate_limiter: "RateLimiter",
30 endpoint: Endpoints = Endpoints.GET_ORDERBOOK_SNAPSHOT,
31 price_feed: "PriceFeed | None" = None,
32 ) -> list[Decimal]:
33 # The websocket feed answers for free when it is proven live. A miss
34 # (feed down, symbol unseen, or past the staleness backstop) falls
35 # through to REST below, which is exactly the old behaviour.
36 if price_feed is not None:
37 quote = price_feed.get(pair)
38 if quote is not None:
39 if need_log:
40 logger.info(f"best_bid: {quote.bid}, best_ask: {quote.ask} (ws)")
41 return [quote.bid, quote.ask]
43 # reserve() waits for a rate-limit slot rather than failing, so
44 # contention no longer needs a retry loop. Genuine fetch errors
45 # propagate to the caller (placement backlogs it, expiry skips it).
46 #
47 # This is the OMS's only depth call site, and the limit it sends is the
48 # same constant the limiter charges for — the two cannot drift.
49 async with rate_limiter.reserve(endpoint=endpoint):
50 orderbook = await exchange.get_orderbook_snapshot(
51 symbol=pair, limit=OMS_DEPTH_LIMIT
52 )
53 (best_bid, best_ask) = (
54 max(map(lambda level: level.price, orderbook.bids)),
55 min(map(lambda level: level.price, orderbook.asks)),
56 )
57 if need_log:
58 logger.info(f"best_bid: {best_bid}, best_ask: {best_ask}")
59 return [best_bid, best_ask]
61 @staticmethod
62 def make_client_order_id():
63 return f"{uuid.uuid4().hex}"
65 @staticmethod
66 def convert_ms_to_datetime(milliseconds):
67 try:
68 seconds = milliseconds / 1000.0
69 return datetime.fromtimestamp(seconds, tz=timezone.utc)
70 except Exception as e:
71 logger.error(
72 f"[CONVERT_DATETIME] Failed to convert_ms_to_datetime due to: {e}"
73 )
74 return datetime.now(timezone.utc)