67 production .py modules that the running PINK service imports but which were never committed: prod/bingx/ (HTTP client, market/user streams, journal, config), prod/clean_arch/ adapters/persistence/runtime/dita/dita_v2 production modules and their co-located tests. Rule going forward: every module imported by launch_dolphin_pink.py / pink_direct.py must appear in git ls-files. Excludes _backup dirs, __pycache__, and non-code files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
3.0 KiB
Python
81 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from decimal import Decimal
|
|
|
|
|
|
def _as_decimal(value: object, default: str = "0") -> Decimal:
|
|
if value is None:
|
|
return Decimal(default)
|
|
return Decimal(str(value))
|
|
|
|
|
|
def unwrap_order_payload(payload: dict[str, object]) -> dict[str, object]:
|
|
row = payload.get("order") if isinstance(payload, dict) else None
|
|
return row if isinstance(row, dict) else payload
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BingxContract:
|
|
symbol: str
|
|
venue_symbol: str
|
|
quote_asset: str
|
|
base_asset: str
|
|
price_precision: int
|
|
quantity_precision: int
|
|
min_quantity: Decimal
|
|
min_notional: Decimal
|
|
tick_size: Decimal
|
|
step_size: Decimal
|
|
maker_fee: Decimal
|
|
taker_fee: Decimal
|
|
max_leverage: int
|
|
|
|
@classmethod
|
|
def from_http(cls, payload: dict[str, object]) -> "BingxContract":
|
|
venue_symbol = str(payload.get("symbol") or payload.get("ticker") or "")
|
|
normalized = venue_symbol.replace("-", "")
|
|
price_precision = int(payload.get("pricePrecision") or payload.get("price_scale") or 2)
|
|
quantity_precision = int(
|
|
payload.get("quantityPrecision") or payload.get("quantity_scale") or 3,
|
|
)
|
|
tick_size = _as_decimal(
|
|
payload.get("tickSize") or payload.get("priceStep") or f"1e-{price_precision}",
|
|
)
|
|
step_size = _as_decimal(
|
|
payload.get("stepSize") or payload.get("quantityStep") or f"1e-{quantity_precision}",
|
|
)
|
|
return cls(
|
|
symbol=normalized,
|
|
venue_symbol=venue_symbol,
|
|
quote_asset=str(payload.get("currency") or payload.get("quoteAsset") or "USDT"),
|
|
base_asset=str(payload.get("asset") or payload.get("baseAsset") or normalized[:-4]),
|
|
price_precision=price_precision,
|
|
quantity_precision=quantity_precision,
|
|
min_quantity=_as_decimal(payload.get("minQty") or payload.get("minQuantity") or step_size),
|
|
min_notional=_as_decimal(payload.get("minNotional") or payload.get("minQuoteAmount") or "2"),
|
|
tick_size=tick_size,
|
|
step_size=step_size,
|
|
maker_fee=_as_decimal(payload.get("makerFeeRate") or payload.get("makerFee") or "0.0002"),
|
|
taker_fee=_as_decimal(payload.get("takerFeeRate") or payload.get("takerFee") or "0.0005"),
|
|
max_leverage=int(payload.get("maxLongLeverage") or payload.get("maxLeverage") or 1),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BingxOrderAck:
|
|
order_id: str
|
|
client_order_id: str
|
|
symbol: str
|
|
status: str | None
|
|
|
|
@classmethod
|
|
def from_http(cls, payload: dict[str, object]) -> "BingxOrderAck":
|
|
row = unwrap_order_payload(payload)
|
|
return cls(
|
|
order_id=str(row.get("orderId") or row.get("id") or ""),
|
|
client_order_id=str(row.get("clientOrderID") or row.get("clientOrderId") or ""),
|
|
symbol=str(row.get("symbol") or ""),
|
|
status=str(row.get("status")) if row.get("status") is not None else None,
|
|
)
|