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 <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-04 07:12:28 +08:00
parent dd74770c66
commit b1952dcd6e
3 changed files with 132 additions and 22 deletions
+94 -19
View File
@@ -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/balancequoteAsset=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 WalletUSDT 总额free+冻结+锁定,与 APP 资金账户一致"""
"""Binance 资金账户(Funding WalletUSDT 总额,与 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():