dita_v2: VenueTelemetrySnapshot + Zinc venue plane (4th shm partition) + local Rust target dir
Upstream-canonical commit of the ~366 lines that sat uncommitted in the working tree (surveyed in DITAV2_SOA_SURVEY_20260702). Venue telemetry published at every venue boundary (submit/cancel/reconcile) over the zinc venue_region; SOA per UV DITAv2 adjudication 2026-07-03. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ from .contracts import (
|
||||
KernelEventKind,
|
||||
KernelIntent,
|
||||
TradeSide,
|
||||
VenueTelemetrySnapshot,
|
||||
VenueEvent,
|
||||
VenueEventStatus,
|
||||
VenueOrder,
|
||||
@@ -226,7 +227,7 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
)
|
||||
return cls._EXECUTOR
|
||||
|
||||
def __init__(self, backend: Any | None = None, *, config: Any | None = None) -> None:
|
||||
def __init__(self, backend: Any | None = None, *, config: Any | None = None, zinc_plane: Any | None = None) -> None:
|
||||
if backend is None:
|
||||
if config is None:
|
||||
raise ValueError("BingxVenueAdapter requires a backend or config")
|
||||
@@ -234,6 +235,7 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
|
||||
backend = BingxDirectExecutionAdapter(config)
|
||||
self.backend = backend
|
||||
self._telemetry_plane = zinc_plane
|
||||
self._event_seq = itertools.count(1)
|
||||
# Thread-safe snapshot cache — reads from a snapshot may arrive from
|
||||
# the kernel thread while _backend_snapshot writes from the pool thread.
|
||||
@@ -279,6 +281,73 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def set_telemetry_plane(self, zinc_plane: Any | None) -> None:
|
||||
self._telemetry_plane = zinc_plane
|
||||
|
||||
def _publish_telemetry(
|
||||
self,
|
||||
*,
|
||||
phase: str,
|
||||
status: str,
|
||||
intent: KernelIntent | None = None,
|
||||
order: VenueOrder | None = None,
|
||||
endpoint: str = "",
|
||||
method: str = "",
|
||||
message: str = "",
|
||||
retry_after_ms: int = 0,
|
||||
venue_order_status: str = "",
|
||||
venue_event_kind: str = "",
|
||||
details: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
plane = self._telemetry_plane
|
||||
publish = getattr(plane, "publish_venue", None) if plane is not None else None
|
||||
if publish is None:
|
||||
return
|
||||
slot_id = 0
|
||||
trade_id = ""
|
||||
asset = ""
|
||||
side = TradeSide.FLAT
|
||||
action = ""
|
||||
intent_id = ""
|
||||
if intent is not None:
|
||||
slot_id = int(getattr(intent, "slot_id", 0) or 0)
|
||||
trade_id = str(getattr(intent, "trade_id", "") or "")
|
||||
asset = str(getattr(intent, "asset", "") or "")
|
||||
side = getattr(intent, "side", TradeSide.FLAT)
|
||||
action = str(getattr(intent, "action", "") or "")
|
||||
intent_id = str(getattr(intent, "intent_id", "") or "")
|
||||
if order is not None:
|
||||
slot_id = int(order.metadata.get("slot_id", slot_id) or slot_id)
|
||||
trade_id = str(order.internal_trade_id or trade_id)
|
||||
asset = str(order.metadata.get("asset") or asset)
|
||||
side = order.side or side
|
||||
try:
|
||||
publish(
|
||||
VenueTelemetrySnapshot(
|
||||
phase=phase,
|
||||
status=status,
|
||||
venue="bingx",
|
||||
endpoint=endpoint,
|
||||
method=method,
|
||||
intent_id=intent_id,
|
||||
trade_id=trade_id,
|
||||
slot_id=slot_id,
|
||||
asset=asset,
|
||||
side=side,
|
||||
action=action,
|
||||
order_id=str(getattr(order, "venue_order_id", "") or ""),
|
||||
client_order_id=str(getattr(order, "venue_client_id", "") or ""),
|
||||
venue_order_status=venue_order_status,
|
||||
venue_event_kind=venue_event_kind,
|
||||
message=message,
|
||||
retry_after_ms=int(retry_after_ms or 0),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
details=dict(details or {}),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _call_backend(self, method_name: str, *args: Any, **kwargs: Any) -> Any:
|
||||
method = getattr(self.backend, method_name, None)
|
||||
if method is None:
|
||||
@@ -368,18 +437,37 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
was fixed for submit via submit_async. This version awaits backend.cancel()
|
||||
directly in the caller's (main) event loop.
|
||||
"""
|
||||
self._publish_telemetry(
|
||||
phase="cancel:start",
|
||||
status="REQUESTED",
|
||||
order=order,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="DELETE",
|
||||
message=reason,
|
||||
details={"asset": str(order.metadata.get("asset") or "")},
|
||||
)
|
||||
cancel_fn = getattr(self.backend, "cancel", None)
|
||||
if cancel_fn is not None:
|
||||
response = await cancel_fn(order, reason=reason)
|
||||
else:
|
||||
response = None
|
||||
return self._events_from_cancel(order, response, None, None, reason=reason)
|
||||
events = self._events_from_cancel(order, response, None, None, reason=reason)
|
||||
return events
|
||||
|
||||
def cancel(self, order: VenueOrder, *, reason: str = "") -> List[VenueEvent]:
|
||||
# _events_from_cancel never reads before/after — snapshots are dead weight.
|
||||
# NOTE: if backend.cancel is async (BingxDirectExecutionAdapter), this sync
|
||||
# path goes through the thread-pool and will deadlock in a running event loop.
|
||||
# Use cancel_async() from async contexts (process_intent_async already does).
|
||||
self._publish_telemetry(
|
||||
phase="cancel:start",
|
||||
status="REQUESTED",
|
||||
order=order,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="DELETE",
|
||||
message=reason,
|
||||
details={"asset": str(order.metadata.get("asset") or "")},
|
||||
)
|
||||
response = None
|
||||
if hasattr(self.backend, "cancel"):
|
||||
response = self._call_backend("cancel", order, reason=reason)
|
||||
@@ -410,7 +498,8 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
except BingxHttpError as exc:
|
||||
# W10: map HTTP error class to status — 429/5xx are transient, 4xx are real rejections
|
||||
response = {"status": _http_error_status(str(exc)), "msg": str(exc), "orderId": order.venue_order_id, "clientOrderId": order.venue_client_id}
|
||||
return self._events_from_cancel(order, response, None, None, reason=reason)
|
||||
events = self._events_from_cancel(order, response, None, None, reason=reason)
|
||||
return events
|
||||
|
||||
def open_orders(self) -> List[VenueOrder]:
|
||||
# Use backend._state (populated by await backend.connect()) rather than
|
||||
@@ -458,6 +547,13 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
# entirely: the FSM stayed fill-blind (slot size 0 in ENTRY_WORKING),
|
||||
# the DecisionEngine saw "no position", and re-entered → the live
|
||||
# double-entries at 15:20 and 17:24 UTC.
|
||||
self._publish_telemetry(
|
||||
phase="reconcile:start",
|
||||
status="REQUESTED",
|
||||
endpoint="/openApi/swap/v2/trade/openOrders",
|
||||
method="GET",
|
||||
details={"include_history": True},
|
||||
)
|
||||
recon_symbol = None
|
||||
kernel = getattr(self, "_kernel_ref", None)
|
||||
if kernel is not None:
|
||||
@@ -474,13 +570,60 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
except Exception as exc:
|
||||
import logging as _log
|
||||
_log.getLogger(__name__).warning("reconcile: refresh_state failed: %s", exc)
|
||||
self._publish_telemetry(
|
||||
phase="reconcile:error",
|
||||
status="ERROR",
|
||||
endpoint="/openApi/swap/v2/trade/openOrders",
|
||||
method="GET",
|
||||
message=str(exc),
|
||||
details={"symbol": recon_symbol or ""},
|
||||
)
|
||||
return []
|
||||
self._publish_telemetry(
|
||||
phase="reconcile:done",
|
||||
status="OK",
|
||||
endpoint="/openApi/swap/v2/trade/openOrders",
|
||||
method="GET",
|
||||
details={
|
||||
"symbol": recon_symbol or "",
|
||||
"open_orders": len(getattr(snapshot, "open_orders", []) or []),
|
||||
"positions": len(getattr(snapshot, "open_positions", {}) or {}),
|
||||
"fills": len(getattr(snapshot, "all_fills", []) or []),
|
||||
},
|
||||
)
|
||||
return self._events_from_snapshot(snapshot)
|
||||
|
||||
def submit(self, intent: KernelIntent) -> List[VenueEvent]:
|
||||
# Snapshots dropped: receipt executedQty fields take precedence (same as submit_async)
|
||||
self._publish_telemetry(
|
||||
phase="submit:start",
|
||||
status="REQUESTED",
|
||||
intent=intent,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="POST",
|
||||
details={"action": intent.action.value, "order_type": str(getattr(intent, "order_type", "MARKET") or "MARKET")},
|
||||
)
|
||||
receipt = self._call_backend("submit_intent", self._legacy_intent(intent))
|
||||
return self._events_from_submit(intent, receipt, None, None)
|
||||
events = self._events_from_submit(intent, receipt, None, None)
|
||||
ack_row = dict(getattr(receipt, "raw_ack", {}) or {})
|
||||
self._publish_telemetry(
|
||||
phase="submit:done",
|
||||
status=str(getattr(receipt, "status", "") or _row_text(ack_row, "status", default="NEW")),
|
||||
intent=intent,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="POST",
|
||||
message=_row_text(ack_row, "msg", "message", default=""),
|
||||
order_id=_row_text(ack_row, "orderId", "orderID", default=str(getattr(receipt, "order_id", "") or "")),
|
||||
client_order_id=_row_text(ack_row, "clientOrderID", "clientOrderId", default=str(getattr(receipt, "client_order_id", "") or intent.intent_id)),
|
||||
venue_order_status=str(getattr(receipt, "status", "") or _row_text(ack_row, "status", default="")),
|
||||
venue_event_kind=events[1].kind.value if len(events) > 1 else events[0].kind.value,
|
||||
details={
|
||||
"filled_size": float((events[1].filled_size if len(events) > 1 else events[0].filled_size) or 0.0),
|
||||
"event_count": len(events),
|
||||
"asset": intent.asset,
|
||||
},
|
||||
)
|
||||
return events
|
||||
|
||||
async def submit_async(self, intent: KernelIntent) -> List[VenueEvent]:
|
||||
"""Async submit — runs in the caller's event loop, no thread-pool deadlock.
|
||||
@@ -495,8 +638,35 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
Passing None for snapshots makes _filled_size_from_snapshots return 0.0
|
||||
(a safe fallback; the receipt fields take precedence).
|
||||
"""
|
||||
self._publish_telemetry(
|
||||
phase="submit:start",
|
||||
status="REQUESTED",
|
||||
intent=intent,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="POST",
|
||||
details={"action": intent.action.value, "order_type": str(getattr(intent, "order_type", "MARKET") or "MARKET")},
|
||||
)
|
||||
receipt = await self.backend.submit_intent(self._legacy_intent(intent))
|
||||
return self._events_from_submit(intent, receipt, None, None)
|
||||
events = self._events_from_submit(intent, receipt, None, None)
|
||||
ack_row = dict(getattr(receipt, "raw_ack", {}) or {})
|
||||
self._publish_telemetry(
|
||||
phase="submit:done",
|
||||
status=str(getattr(receipt, "status", "") or _row_text(ack_row, "status", default="NEW")),
|
||||
intent=intent,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="POST",
|
||||
message=_row_text(ack_row, "msg", "message", default=""),
|
||||
order_id=_row_text(ack_row, "orderId", "orderID", default=str(getattr(receipt, "order_id", "") or "")),
|
||||
client_order_id=_row_text(ack_row, "clientOrderID", "clientOrderId", default=str(getattr(receipt, "client_order_id", "") or intent.intent_id)),
|
||||
venue_order_status=str(getattr(receipt, "status", "") or _row_text(ack_row, "status", default="")),
|
||||
venue_event_kind=events[1].kind.value if len(events) > 1 else events[0].kind.value,
|
||||
details={
|
||||
"filled_size": float((events[1].filled_size if len(events) > 1 else events[0].filled_size) or 0.0),
|
||||
"event_count": len(events),
|
||||
"asset": intent.asset,
|
||||
},
|
||||
)
|
||||
return events
|
||||
|
||||
def _events_from_submit(self, intent: KernelIntent, receipt: Any, before, after) -> List[VenueEvent]: # noqa: ANN001
|
||||
ack_row = dict(getattr(receipt, "raw_ack", {}) or {})
|
||||
@@ -612,6 +782,18 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
raw = response if isinstance(response, dict) else {}
|
||||
status = _normalize_status(_row_text(raw, "status", default="CANCELED"))
|
||||
if status in {"RATE_LIMITED", "THROTTLED"}:
|
||||
self._publish_telemetry(
|
||||
phase="cancel:done",
|
||||
status=status,
|
||||
order=order,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="DELETE",
|
||||
message=reason or _row_text(raw, "msg", "message", default="BINGX_RATE_LIMITED"),
|
||||
venue_order_status=VenueEventStatus.RATE_LIMITED.value,
|
||||
venue_event_kind=KernelEventKind.RATE_LIMITED.value,
|
||||
retry_after_ms=_rate_limit_retry_after_ms(raw),
|
||||
details={"order_status": status, "asset": str(order.metadata.get("asset") or "")},
|
||||
)
|
||||
return [
|
||||
VenueEvent(
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
@@ -637,6 +819,18 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
kind = KernelEventKind.CANCEL_ACK if event_status == VenueEventStatus.CANCELED else KernelEventKind.CANCEL_REJECT
|
||||
if event_status == VenueEventStatus.CANCELED_REJECTED:
|
||||
kind = KernelEventKind.CANCEL_REJECT
|
||||
self._publish_telemetry(
|
||||
phase="cancel:done",
|
||||
status=status or event_status.value,
|
||||
order=order,
|
||||
endpoint="/openApi/swap/v2/trade/order",
|
||||
method="DELETE",
|
||||
message=reason or _row_text(raw, "msg", "message", default=""),
|
||||
venue_order_status=event_status.value,
|
||||
venue_event_kind=kind.value,
|
||||
retry_after_ms=_rate_limit_retry_after_ms(raw),
|
||||
details={"order_status": status, "asset": str(order.metadata.get("asset") or "")},
|
||||
)
|
||||
return [
|
||||
VenueEvent(
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
|
||||
Reference in New Issue
Block a user