PINK: seed capital from BingX ledger of record on startup

Previously: set_seed_capital(hardcoded_25000) then on_account_event(BingX_100K+)
→ reconcile delta ~75K → capital_frozen=True → no trades allowed.

Fix: _fetch_exchange_wallet_balance() queries BingX wallet balance BEFORE
seeding the kernel. set_seed_capital() and the subsequent ACCOUNT_UPDATE
reconcile now agree → delta ≈ 0 → capital_frozen=False → sizing correct.

Falls back to DOLPHIN_INITIAL_CAPITAL if BingX is unreachable, with WARNING.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-04 17:53:34 +02:00
parent f78cc0d3f9
commit 9acaeafc8b

View File

@@ -296,12 +296,15 @@ class PinkDirectRuntime:
await result await result
except Exception as exc: except Exception as exc:
self.logger.warning("Venue connect failed: %s", exc) self.logger.warning("Venue connect failed: %s", exc)
_reconcile_position_slot(self.kernel, initial_capital, slot_id=0) # BingX is the ledger of record. Fetch wallet balance BEFORE seeding
# the kernel so set_seed_capital() and reconcile agree with the exchange.
# Using a hardcoded fallback (e.g. 25000) while the VST account holds
# 100K+ would cause a ~75K reconcile delta → capital_frozen=True.
live_capital = await self._fetch_exchange_wallet_balance(initial_capital)
_reconcile_position_slot(self.kernel, live_capital, slot_id=0)
# Seed the kernel's atomic K-account from exchange truth. # Seed the kernel's atomic K-account from exchange truth.
# This is the crash/restart recovery point: if the kernel restarted self.kernel.set_seed_capital(live_capital)
# it re-reads exchange state here before accepting any ENTERs.
self.kernel.set_seed_capital(initial_capital)
await self._seed_account_from_exchange() await self._seed_account_from_exchange()
# Restore fee calibration + account state from the previous session if the # Restore fee calibration + account state from the previous session if the
@@ -340,6 +343,46 @@ class PinkDirectRuntime:
"funding_interval_secs": 28_800, # 8 h BingX perps "funding_interval_secs": 28_800, # 8 h BingX perps
}) })
async def _fetch_exchange_wallet_balance(self, fallback: float) -> float:
"""Query BingX for the current wallet balance to use as the capital seed.
BingX VST (and live) is the ledger of record. Seeding the kernel from
a hardcoded constant while the exchange holds a different balance causes
a large reconcile delta → capital_frozen=True on every startup.
Falls back to *fallback* if the HTTP client is unavailable or the
request fails, logging a WARNING so operators know sizing is approximate.
"""
http_client = self._venue_http_client()
if http_client is None:
self.logger.warning(
"No HTTP client — capital seeded from fallback=%.2f (BingX unreachable)",
fallback,
)
return fallback
try:
from prod.clean_arch.dita_v2.bingx_user_stream import BingxUserStream
stream = BingxUserStream(http_client=http_client, ws_base_url="")
ev = await stream.account_snapshot()
balance = float(ev.wallet_balance or 0.0)
if balance <= 0:
self.logger.warning(
"BingX returned wallet_balance=%.2f ≤ 0 — using fallback=%.2f",
balance, fallback,
)
return fallback
self.logger.info(
"Capital seeded from BingX ledger: wallet=%.2f (fallback was %.2f)",
balance, fallback,
)
return balance
except Exception as exc:
self.logger.warning(
"BingX wallet fetch failed (%s) — capital seeded from fallback=%.2f",
exc, fallback,
)
return fallback
async def _seed_account_from_exchange(self) -> None: async def _seed_account_from_exchange(self) -> None:
""" """
Startup/crash-recovery: Startup/crash-recovery: