diff --git a/prod/clean_arch/runtime/pink_direct.py b/prod/clean_arch/runtime/pink_direct.py index d835a89..61dcb67 100644 --- a/prod/clean_arch/runtime/pink_direct.py +++ b/prod/clean_arch/runtime/pink_direct.py @@ -296,12 +296,15 @@ class PinkDirectRuntime: await result except Exception as 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. - # This is the crash/restart recovery point: if the kernel restarted - # it re-reads exchange state here before accepting any ENTERs. - self.kernel.set_seed_capital(initial_capital) + self.kernel.set_seed_capital(live_capital) await self._seed_account_from_exchange() # 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 }) + 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: """ Startup/crash-recovery: