repo hygiene: track the PINK launcher import closure

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>
This commit is contained in:
Codex
2026-06-12 15:09:32 +02:00
parent c3a18f693a
commit 84e4a50e3f
67 changed files with 15090 additions and 0 deletions

183
prod/bingx/observer.py Normal file
View File

@@ -0,0 +1,183 @@
from __future__ import annotations
import asyncio
import contextlib
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from .config import BingxExecClientConfig
from .http import BingxHttpClient
from .websocket import BingxUserStream
TERMINAL_ORDER_STATUSES = {"FILLED", "CANCELED", "CANCELLED", "REJECTED", "EXPIRED"}
@dataclass(frozen=True)
class BingxObservedOrder:
key: str
row: dict[str, Any]
terminal: bool
class BingxOrderUpdateObserver:
def __init__(
self,
client: BingxHttpClient,
config: BingxExecClientConfig,
*,
on_health: Callable[[bool], None] | None = None,
) -> None:
self._client = client
self._config = config
self._stream = BingxUserStream(
client=client,
config=config,
on_event=self._on_event,
on_health=on_health,
)
self._task: asyncio.Task | None = None
self._lock = asyncio.Lock()
self._latest: dict[str, dict[str, Any]] = {}
self._events: dict[str, asyncio.Event] = {}
self._closed = False
async def start(self) -> None:
if self._task is None:
self._task = asyncio.create_task(self._stream.run_forever())
async def close(self) -> None:
self._closed = True
await self._stream.close()
if self._task is not None:
self._task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await self._task
self._task = None
async def stop(self) -> None:
await self.close()
async def latest(self, key: str) -> dict[str, Any] | None:
async with self._lock:
row = self._latest.get(key)
return dict(row) if isinstance(row, dict) else None
async def wait_for_terminal(self, key: str, *, timeout_s: float = 20.0) -> BingxObservedOrder | None:
deadline = asyncio.get_running_loop().time() + timeout_s
last_row: dict[str, Any] | None = None
while not self._closed:
async with self._lock:
row = self._latest.get(key)
if isinstance(row, dict):
last_row = dict(row)
status = str(last_row.get("status") or last_row.get("X") or "").upper()
if status in TERMINAL_ORDER_STATUSES:
return BingxObservedOrder(key=key, row=last_row, terminal=True)
event = self._events.setdefault(key, asyncio.Event())
remaining = deadline - asyncio.get_running_loop().time()
if remaining <= 0:
break
try:
await asyncio.wait_for(event.wait(), timeout=remaining)
except TimeoutError:
break
finally:
event.clear()
if last_row is not None:
return BingxObservedOrder(key=key, row=last_row, terminal=False)
return None
async def wait_for_fill(self, key: str, *, timeout_s: float = 20.0) -> BingxObservedOrder | None:
deadline = asyncio.get_running_loop().time() + timeout_s
last_row: dict[str, Any] | None = None
while not self._closed:
async with self._lock:
row = self._latest.get(key)
if isinstance(row, dict):
last_row = dict(row)
last_fill_qty = str(
last_row.get("lastFilledQty")
or last_row.get("l")
or "0",
)
if last_fill_qty not in {"0", "0.0", "0.00000000", ""}:
return BingxObservedOrder(key=key, row=last_row, terminal=False)
status = str(last_row.get("status") or last_row.get("X") or "").upper()
if status in TERMINAL_ORDER_STATUSES:
return BingxObservedOrder(key=key, row=last_row, terminal=True)
event = self._events.setdefault(key, asyncio.Event())
remaining = deadline - asyncio.get_running_loop().time()
if remaining <= 0:
break
try:
await asyncio.wait_for(event.wait(), timeout=remaining)
except TimeoutError:
break
finally:
event.clear()
if last_row is not None:
return BingxObservedOrder(key=key, row=last_row, terminal=False)
return None
async def _on_event(self, payload: dict[str, Any]) -> None:
data = payload.get("data") if isinstance(payload.get("data"), dict) else None
event_type = str((data or payload).get("e") or "").upper()
data_type = str(payload.get("dataType") or "").lower()
if event_type not in {"ORDER_TRADE_UPDATE", "EXECUTIONREPORT"} and data_type != "spot.executionreport":
return
if event_type == "ORDER_TRADE_UPDATE":
order_update = payload.get("o")
if not isinstance(order_update, dict):
return
else:
source = data or payload
order_update = {
"s": source.get("s"),
"c": source.get("c") or source.get("clientOrderId") or source.get("clientOrderID"),
"i": source.get("i") or source.get("orderId") or source.get("orderID"),
"X": source.get("X"),
"x": source.get("x"),
"p": source.get("p") or source.get("price"),
"ap": source.get("ap") or source.get("avgPrice"),
"z": source.get("z") or source.get("executedQty") or source.get("cumFilledQty"),
"l": source.get("l") or source.get("lastFilledQty") or source.get("lastExecutedQty"),
"L": source.get("L") or source.get("lastFillPrice") or source.get("avgPrice"),
"n": source.get("n") or source.get("commission"),
"N": source.get("N") or source.get("commissionAsset"),
"positionID": source.get("positionID") or source.get("positionId"),
"triggerOrderId": source.get("triggerOrderId"),
"mainOrderId": source.get("mainOrderId"),
}
client_order_id = str(order_update.get("c") or "")
order_id = str(order_update.get("i") or "")
status = str(order_update.get("X") or order_update.get("x") or "").upper()
row = {
"symbol": order_update.get("s"),
"clientOrderId": client_order_id,
"clientOrderID": client_order_id,
"orderId": order_id,
"orderID": order_id,
"status": status,
"price": order_update.get("p"),
"avgPrice": order_update.get("ap") or order_update.get("L") or order_update.get("p"),
"executedQty": order_update.get("z") or "0",
"cumFilledQty": order_update.get("z") or "0",
"lastFilledQty": order_update.get("l") or "0",
"lastFillPrice": order_update.get("L") or order_update.get("ap") or order_update.get("p"),
"commission": order_update.get("n") or "0",
"commissionAsset": order_update.get("N"),
"executionType": order_update.get("x"),
"positionID": order_update.get("positionID") or order_update.get("positionId"),
"triggerOrderId": order_update.get("triggerOrderId"),
"mainOrderId": order_update.get("mainOrderId"),
"raw": order_update,
}
async with self._lock:
if client_order_id:
self._latest[client_order_id] = row
self._events.setdefault(client_order_id, asyncio.Event()).set()
if order_id:
self._latest[order_id] = row
self._events.setdefault(order_id, asyncio.Event()).set()