From b1952dcd6e276f618665b462ab0c2a9a27d40f18 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 4 Jun 2026 07:12:28 +0800 Subject: [PATCH] fix(binance): read funding balance from wallet/balance API Prefer sapi asset/wallet/balance Funding USDT (matches App), merge get-funding-asset and ccxt as fallbacks, and add optional BINANCE_FUNDING_INCLUDE_SPOT plus a richer verify script. Co-authored-by: Cursor --- crypto_monitor_binance/.env.example | 2 + crypto_monitor_binance/app.py | 113 +++++++++++++++--- .../scripts/verify_binance_funding.py | 39 +++++- 3 files changed, 132 insertions(+), 22 deletions(-) diff --git a/crypto_monitor_binance/.env.example b/crypto_monitor_binance/.env.example index 2698024..74f0652 100644 --- a/crypto_monitor_binance/.env.example +++ b/crypto_monitor_binance/.env.example @@ -48,6 +48,8 @@ UPLOAD_DIR=static/images # 已废弃:资金账户仅显示交易所 funding 余额,不再读取此变量 # TOTAL_CAPITAL=100 +# 页顶「资金账户」默认仅 Binance Funding 钱包;若 USDT 主要在现货,可改为 true 合并 Spot +# BINANCE_FUNDING_INCLUDE_SPOT=false # 每天起始基数(U) DAILY_START_CAPITAL=30 # 日内回撤后基数(U) diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index e77a41b..828eac0 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -288,6 +288,13 @@ def build_binance_ccxt_proxies(): BINANCE_CCXT_PROXIES = build_binance_ccxt_proxies() +# 页顶「资金账户」是否合并现货 USDT(部分用户把现货当资金仓;默认仅 Funding) +BINANCE_FUNDING_INCLUDE_SPOT = os.getenv("BINANCE_FUNDING_INCLUDE_SPOT", "false").lower() in ( + "1", + "true", + "yes", + "on", +) os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(ORDER_CHART_DIR, exist_ok=True) @@ -2660,6 +2667,61 @@ def _parse_binance_funding_asset_rows(rows): return None +def _parse_binance_wallet_balance_usdt(rows, wallet_names): + """解析 /sapi/v1/asset/wallet/balance(quoteAsset=USDT):按 walletName 取折合 USDT 余额。""" + if isinstance(rows, dict): + rows = [rows] + if not isinstance(rows, list): + return None + want = {str(n).strip().lower() for n in (wallet_names or []) if str(n).strip()} + for row in rows: + if not isinstance(row, dict): + continue + name = str(row.get("walletName") or row.get("name") or "").strip().lower() + if name not in want: + continue + if row.get("activate") is False: + continue + bal = _float_balance_field(row.get("balance")) + if bal is not None: + return bal + return None + + +def _fetch_binance_funding_usdt_from_wallet_overview(): + """与币安 App 资产页「资金/Funding」钱包 USDT 估值一致(wallet/balance)。""" + try: + ensure_markets_loaded() + raw = exchange.sapiGetAssetWalletBalance({"quoteAsset": TRANSFER_CCY}) + val = _parse_binance_wallet_balance_usdt(raw, ("Funding",)) + if val is not None: + return float(val) + except Exception: + pass + return None + + +def _fetch_binance_spot_usdt_total(): + """现货账户 USDT 总额(free+locked)。""" + try: + ensure_markets_loaded() + raw = exchange.sapiGetAssetWalletBalance({"quoteAsset": TRANSFER_CCY}) + val = _parse_binance_wallet_balance_usdt(raw, ("Spot",)) + if val is not None: + return float(val) + except Exception: + pass + try: + ensure_markets_loaded() + bal = exchange.fetch_balance(params={"type": "spot"}) + val = _extract_usdt_total(bal) + if val is not None: + return float(val) + except Exception: + pass + return None + + def _extract_usdt_free(balance): usdt_info = balance.get("USDT", {}) if isinstance(balance, dict) else {} free_map = balance.get("free", {}) if isinstance(balance, dict) else {} @@ -2732,32 +2794,45 @@ def _fetch_binance_swap_usdt_free(): def _fetch_binance_funding_usdt(): - """Binance 资金账户(Funding Wallet)USDT 总额(free+冻结+锁定,与 APP 资金账户一致)。""" + """Binance 资金账户(Funding Wallet)USDT 总额,与 App「资金账户」一致。""" + candidates = [] + wallet_val = _fetch_binance_funding_usdt_from_wallet_overview() + if wallet_val is not None: + candidates.append(wallet_val) + try: + ensure_markets_loaded() + raw = exchange.sapiPostAssetGetFundingAsset({"asset": TRANSFER_CCY}) + val = _parse_binance_funding_asset_rows(raw) + if val is not None: + candidates.append(float(val)) + except Exception: + pass + if not candidates: + try: + ensure_markets_loaded() + raw = exchange.sapiPostAssetGetFundingAsset({}) + val = _parse_binance_funding_asset_rows(raw) + if val is not None: + candidates.append(float(val)) + except Exception: + pass try: ensure_markets_loaded() bal = exchange.fetch_balance(params={"type": "funding"}) val = _extract_usdt_total(bal) if val is not None: - return float(val) + candidates.append(float(val)) except Exception: pass - try: - ensure_markets_loaded() - raw = exchange.sapiPostAssetGetFundingAsset({"asset": "USDT"}) - val = _parse_binance_funding_asset_rows(raw) - if val is not None: - return float(val) - except Exception: - pass - try: - ensure_markets_loaded() - raw = exchange.sapiPostAssetGetFundingAsset({}) - val = _parse_binance_funding_asset_rows(raw) - if val is not None: - return float(val) - except Exception: - pass - return None + if not candidates: + base = None + else: + base = max(candidates) + if BINANCE_FUNDING_INCLUDE_SPOT: + spot_val = _fetch_binance_spot_usdt_total() + if spot_val is not None: + base = (base or 0.0) + float(spot_val) + return base def get_available_trading_usdt(): diff --git a/crypto_monitor_binance/scripts/verify_binance_funding.py b/crypto_monitor_binance/scripts/verify_binance_funding.py index bbe7908..9788fe8 100644 --- a/crypto_monitor_binance/scripts/verify_binance_funding.py +++ b/crypto_monitor_binance/scripts/verify_binance_funding.py @@ -3,6 +3,7 @@ python scripts/verify_binance_funding.py 打印 BINANCE_API_KEY 前 8 位便于与 Binance 控制台核对(不含 Secret)。用于服务器自检。 +对比 App:资产 → 资金账户(Funding) / 现货账户(Spot) / U本位合约。 """ import os import sys @@ -33,19 +34,51 @@ def main(): if not s or "REPLACE" in s.upper(): print("WARN: BINANCE_API_SECRET 为空或仍像占位符,请核对 .env") print("BINANCE_API_KEY prefix (8 chars):", (k[:8] + "…") if len(k) > 8 else "(short)") + print("BINANCE_FUNDING_INCLUDE_SPOT:", os.getenv("BINANCE_FUNDING_INCLUDE_SPOT", "false")) import app as mod # noqa: E402 mod.ensure_markets_loaded() - fu = mod._fetch_binance_funding_usdt() - print(">>> _fetch_binance_funding_usdt() =", fu) + ccy = getattr(mod, "TRANSFER_CCY", "USDT") + try: + raw = mod.exchange.sapiGetAssetWalletBalance({"quoteAsset": ccy}) + print(f"\n>>> sapi/v1/asset/wallet/balance (quoteAsset={ccy}):") + if isinstance(raw, list): + for row in raw: + if isinstance(row, dict): + print( + " ", + row.get("walletName"), + "activate=", + row.get("activate"), + "balance=", + row.get("balance"), + ) + else: + print(" ", raw) + except Exception as e: + print(">>> wallet/balance error:", e) + + try: + raw = mod.exchange.sapiPostAssetGetFundingAsset({"asset": ccy}) + print(f"\n>>> get-funding-asset (asset={ccy}):", raw) + except Exception as e: + print(">>> get-funding-asset error:", e) + + fu = mod._fetch_binance_funding_usdt() + print("\n>>> _fetch_binance_funding_usdt() (页顶资金账户) =", fu) + try: + fw = mod._fetch_binance_funding_usdt_from_wallet_overview() + print(">>> _fetch_binance_funding_usdt_from_wallet_overview() =", fw) + sp = mod._fetch_binance_spot_usdt_total() + print(">>> _fetch_binance_spot_usdt_total() =", sp) sw = mod._fetch_binance_swap_usdt_total() print(">>> _fetch_binance_swap_usdt_total() (合约账户) =", sw) sf = mod._fetch_binance_swap_usdt_free() print(">>> _fetch_binance_swap_usdt_free() (合约可用) =", sf) except Exception as e: - print(">>> swap balance fetch error:", e) + print(">>> balance fetch error:", e) if __name__ == "__main__":