"""钱在哪个账户里。 用来解一个具体的矛盾:你确认往 U 本位合约充了钱,但 `/api/v2/mix/account/account` 报的 accountEquity 只有一小部分。 `accountEquity` 是**总权益**而不是可用余额,locked 也是 0,所以不是被挂单 或持仓占着。剩下的可能都是「这把 key 看到的不是你充钱的那个账户」: · key 属于子账户,钱在主账户(或反过来) · 钱在现货账户,没划转到合约 · 钱在 USDC 本位 / 币本位合约,不是 USDT 本位 · 账户已迁到统一账户(UTA),经典 mix 接口读到的不是同一个池子 `/api/v2/account/all-account-balance` 会按账户类型列出全部余额,一次看清。 sudo -u chan bash -c 'set -a; . /etc/chan-live/live.env; set +a; \ /opt/chan/.venv/bin/python /opt/chan/live/deploy/whereismoney.py' 只读,不下单、不划转。 """ from __future__ import annotations import asyncio import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from bitget_rest import MARGIN_COIN, PRODUCT, Bitget # noqa: E402 def _perm_hint(e: Exception) -> str: """把 40014 说成"正常"而不是"故障"。 这把 key 刻意只开合约权限,所以现货类端点必然报 40014。**不要**为了让 这个探针看全就去加现货权限——那是白扩爆炸半径,而同样的信息在 App 里 看一眼就有。 """ s = str(e) if "40014" in s: return ("跳过:这把 key 没开现货权限(刻意的,最小权限)。" "这一栏改用 Bitget App 看,别为了探针去加权限") return f"查不了:{type(e).__name__}: {e}" async def main() -> None: api = Bitget() if not (api.key and api.secret and api.passphrase): raise SystemExit("⛔ 没读到密钥。要 source /etc/chan-live/live.env") try: print("── 跨账户类型总览 ──") try: for b in await api._req("GET", "/api/v2/account/all-account-balance") or []: amt = float(b.get("usdtBalance") or 0) flag = " ← 钱在这里" if amt > 1 else "" print(f" {b.get('accountType'):<16} {amt:>12.2f} USDT{flag}") except Exception as e: # noqa: BLE001 print(f" {_perm_hint(e)}") print(f"\n── {PRODUCT} 下的各保证金币种 ──") try: rows = await api._req("GET", "/api/v2/mix/account/accounts", {"productType": PRODUCT}) or [] for a in rows: eq = float(a.get("accountEquity") or 0) if eq or a.get("marginCoin") == MARGIN_COIN: print(f" {a.get('marginCoin'):<8} 权益 {eq:>12.4f} · " f"可用 {float(a.get('available') or 0):>12.4f}") except Exception as e: # noqa: BLE001 print(f" {_perm_hint(e)}") print("\n── 其他合约类型(钱可能充错了本位)──") for pt in ("coin-futures", "usdc-futures"): try: rows = await api._req("GET", "/api/v2/mix/account/accounts", {"productType": pt}) or [] hit = [(a.get("marginCoin"), float(a.get("accountEquity") or 0)) for a in rows if float(a.get("accountEquity") or 0) > 0] print(f" {pt:<14} {hit if hit else '空'}") except Exception as e: # noqa: BLE001 print(f" {pt:<14} 查不了:{type(e).__name__}") print("\n── 现货 ──") try: rows = await api._req("GET", "/api/v2/spot/account/assets") or [] hit = [(a.get("coin"), float(a.get("available") or 0)) for a in rows if float(a.get("available") or 0) > 0] print(f" {hit if hit else '空'}" f"{' ← 要划转到 U 本位合约' if hit else ''}") except Exception as e: # noqa: BLE001 print(f" {_perm_hint(e)}") print("\n── 这把 key 属于哪个账户 ──") for path in ("/api/v2/spot/account/info", "/api/v2/user/account-info"): try: d = await api._req("GET", path) or {} except Exception as e: # noqa: BLE001 print(f" {path} 查不了:{type(e).__name__}: {e}") continue uid, par = d.get("userId"), d.get("parentId") print(f" userId {uid}") if par: print(f" parentId {par} → **这是子账户**。主账户的钱这把 key" f" 看不到也动不了,这是好事(爆炸半径被账户边界封住)。" f"\n 但充值要充到 userId {uid} 的 U 本位合约里。" f"\n 注意主→子划转默认落在子账户的**现货**钱包," f"还要在子账户内部再划一次到 U 本位合约") else: print(" 没有 parentId → 这是主账户") print(f" 权限 {d.get('authorities')}(没有现货权限是刻意的)") print(f" IP 白名单 {d.get('ips')}") break finally: await api.close() if __name__ == "__main__": asyncio.run(main())