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:
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user