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 余额,不再读取此变量
|
# 已废弃:资金账户仅显示交易所 funding 余额,不再读取此变量
|
||||||
# TOTAL_CAPITAL=100
|
# TOTAL_CAPITAL=100
|
||||||
|
# 页顶「资金账户」默认仅 Binance Funding 钱包;若 USDT 主要在现货,可改为 true 合并 Spot
|
||||||
|
# BINANCE_FUNDING_INCLUDE_SPOT=false
|
||||||
# 每天起始基数(U)
|
# 每天起始基数(U)
|
||||||
DAILY_START_CAPITAL=30
|
DAILY_START_CAPITAL=30
|
||||||
# 日内回撤后基数(U)
|
# 日内回撤后基数(U)
|
||||||
|
|||||||
@@ -288,6 +288,13 @@ def build_binance_ccxt_proxies():
|
|||||||
|
|
||||||
|
|
||||||
BINANCE_CCXT_PROXIES = 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(UPLOAD_FOLDER, exist_ok=True)
|
||||||
os.makedirs(ORDER_CHART_DIR, exist_ok=True)
|
os.makedirs(ORDER_CHART_DIR, exist_ok=True)
|
||||||
@@ -2660,6 +2667,61 @@ def _parse_binance_funding_asset_rows(rows):
|
|||||||
return None
|
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):
|
def _extract_usdt_free(balance):
|
||||||
usdt_info = balance.get("USDT", {}) if isinstance(balance, dict) else {}
|
usdt_info = balance.get("USDT", {}) if isinstance(balance, dict) else {}
|
||||||
free_map = balance.get("free", {}) 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():
|
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:
|
try:
|
||||||
ensure_markets_loaded()
|
ensure_markets_loaded()
|
||||||
bal = exchange.fetch_balance(params={"type": "funding"})
|
raw = exchange.sapiPostAssetGetFundingAsset({"asset": TRANSFER_CCY})
|
||||||
val = _extract_usdt_total(bal)
|
|
||||||
if val is not None:
|
|
||||||
return float(val)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
ensure_markets_loaded()
|
|
||||||
raw = exchange.sapiPostAssetGetFundingAsset({"asset": "USDT"})
|
|
||||||
val = _parse_binance_funding_asset_rows(raw)
|
val = _parse_binance_funding_asset_rows(raw)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
return float(val)
|
candidates.append(float(val))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
if not candidates:
|
||||||
try:
|
try:
|
||||||
ensure_markets_loaded()
|
ensure_markets_loaded()
|
||||||
raw = exchange.sapiPostAssetGetFundingAsset({})
|
raw = exchange.sapiPostAssetGetFundingAsset({})
|
||||||
val = _parse_binance_funding_asset_rows(raw)
|
val = _parse_binance_funding_asset_rows(raw)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
return float(val)
|
candidates.append(float(val))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
try:
|
||||||
|
ensure_markets_loaded()
|
||||||
|
bal = exchange.fetch_balance(params={"type": "funding"})
|
||||||
|
val = _extract_usdt_total(bal)
|
||||||
|
if val is not None:
|
||||||
|
candidates.append(float(val))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
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():
|
def get_available_trading_usdt():
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
python scripts/verify_binance_funding.py
|
python scripts/verify_binance_funding.py
|
||||||
|
|
||||||
打印 BINANCE_API_KEY 前 8 位便于与 Binance 控制台核对(不含 Secret)。用于服务器自检。
|
打印 BINANCE_API_KEY 前 8 位便于与 Binance 控制台核对(不含 Secret)。用于服务器自检。
|
||||||
|
对比 App:资产 → 资金账户(Funding) / 现货账户(Spot) / U本位合约。
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -33,19 +34,51 @@ def main():
|
|||||||
if not s or "REPLACE" in s.upper():
|
if not s or "REPLACE" in s.upper():
|
||||||
print("WARN: BINANCE_API_SECRET 为空或仍像占位符,请核对 .env")
|
print("WARN: BINANCE_API_SECRET 为空或仍像占位符,请核对 .env")
|
||||||
print("BINANCE_API_KEY prefix (8 chars):", (k[:8] + "…") if len(k) > 8 else "(short)")
|
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
|
import app as mod # noqa: E402
|
||||||
|
|
||||||
mod.ensure_markets_loaded()
|
mod.ensure_markets_loaded()
|
||||||
fu = mod._fetch_binance_funding_usdt()
|
ccy = getattr(mod, "TRANSFER_CCY", "USDT")
|
||||||
print(">>> _fetch_binance_funding_usdt() =", fu)
|
|
||||||
try:
|
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()
|
sw = mod._fetch_binance_swap_usdt_total()
|
||||||
print(">>> _fetch_binance_swap_usdt_total() (合约账户) =", sw)
|
print(">>> _fetch_binance_swap_usdt_total() (合约账户) =", sw)
|
||||||
sf = mod._fetch_binance_swap_usdt_free()
|
sf = mod._fetch_binance_swap_usdt_free()
|
||||||
print(">>> _fetch_binance_swap_usdt_free() (合约可用) =", sf)
|
print(">>> _fetch_binance_swap_usdt_free() (合约可用) =", sf)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(">>> swap balance fetch error:", e)
|
print(">>> balance fetch error:", e)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user