Add confirm dialogs and busy states for swap/transfer all actions.
Use available (free) balances for full-amount ops, support sub-account scope, and show clear success/failure feedback. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -384,6 +384,21 @@ def fetch_option_instrument_meta(ex: ccxt.okx, inst_id: str) -> dict[str, Any] |
|
||||
return None
|
||||
|
||||
|
||||
def _extract_ccy_free(balance: dict[str, Any], ccy: str) -> float | None:
|
||||
ccy = (ccy or "").upper()
|
||||
if not isinstance(balance, dict):
|
||||
return None
|
||||
info = balance.get(ccy)
|
||||
if isinstance(info, dict):
|
||||
v = _safe_float(info.get("free"))
|
||||
if v is not None:
|
||||
return v
|
||||
free_map = balance.get("free") or {}
|
||||
if isinstance(free_map, dict):
|
||||
return _safe_float(free_map.get(ccy))
|
||||
return None
|
||||
|
||||
|
||||
def _extract_ccy_balance(balance: dict[str, Any], ccy: str) -> float | None:
|
||||
ccy = (ccy or "").upper()
|
||||
if not isinstance(balance, dict):
|
||||
@@ -407,40 +422,91 @@ def _extract_ccy_balance(balance: dict[str, Any], ccy: str) -> float | None:
|
||||
return None
|
||||
|
||||
|
||||
def fetch_account_balances_by_type(ex: ccxt.okx, account_type: str) -> dict[str, float | None]:
|
||||
def fetch_account_balances_by_type(
|
||||
ex: ccxt.okx,
|
||||
account_type: str,
|
||||
) -> tuple[dict[str, float | None], dict[str, float | None]]:
|
||||
out: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||
avail: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||
try:
|
||||
bal = ex.fetch_balance(params={"type": account_type})
|
||||
for c in out:
|
||||
out[c] = _extract_ccy_balance(bal, c)
|
||||
avail[c] = _extract_ccy_free(bal, c)
|
||||
except Exception:
|
||||
pass
|
||||
return out, avail
|
||||
|
||||
|
||||
def fetch_subaccount_asset_balances(ex: ccxt.okx, sub_acct: str) -> dict[str, float | None]:
|
||||
"""子账户各币种可用余额(主/子划转「全部」用)."""
|
||||
sub = (sub_acct or "").strip()
|
||||
out: dict[str, float | None] = {"USDT": None, "USDC": None}
|
||||
if not sub:
|
||||
return out
|
||||
try:
|
||||
resp = ex.private_get_asset_subaccount_balances({"subAcct": sub})
|
||||
for row in (resp or {}).get("data") or []:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
ccy = str(row.get("ccy") or "").upper()
|
||||
if ccy not in out:
|
||||
continue
|
||||
out[ccy] = _safe_float(row.get("availBal")) or _safe_float(row.get("bal"))
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def fetch_options_balances(ex: ccxt.okx, *, force: bool = False) -> dict[str, Any]:
|
||||
def fetch_options_balances(
|
||||
ex: ccxt.okx,
|
||||
*,
|
||||
force: bool = False,
|
||||
scope: str = "main",
|
||||
sub_acct: str = "",
|
||||
) -> dict[str, Any]:
|
||||
import os
|
||||
|
||||
if (scope or "").strip().lower() == "sub":
|
||||
sub_bal = fetch_subaccount_asset_balances(ex, sub_acct)
|
||||
return {
|
||||
"scope": "sub",
|
||||
"funding_usdt": sub_bal.get("USDT"),
|
||||
"funding_usdc": sub_bal.get("USDC"),
|
||||
"funding_usdt_avail": sub_bal.get("USDT"),
|
||||
"funding_usdc_avail": sub_bal.get("USDC"),
|
||||
"trading_usdt": sub_bal.get("USDT"),
|
||||
"trading_usdc": sub_bal.get("USDC"),
|
||||
"trading_usdt_avail": sub_bal.get("USDT"),
|
||||
"trading_usdc_avail": sub_bal.get("USDC"),
|
||||
}
|
||||
|
||||
ttl = float(os.getenv("OKX_OPTIONS_BALANCE_REFRESH_SEC", "30"))
|
||||
now = time.time()
|
||||
cached = _OPTIONS_BALANCE_CACHE.get("data")
|
||||
if not force and cached is not None and now - float(_OPTIONS_BALANCE_CACHE.get("updated_at") or 0) < ttl:
|
||||
return dict(cached)
|
||||
|
||||
funding = fetch_account_balances_by_type(ex, "funding")
|
||||
trading = fetch_account_balances_by_type(ex, "trading")
|
||||
# 统一账户部分 USDC 可能在 swap 类型
|
||||
funding, funding_avail = fetch_account_balances_by_type(ex, "funding")
|
||||
trading, trading_avail = fetch_account_balances_by_type(ex, "trading")
|
||||
if trading.get("USDC") is None:
|
||||
swap_bal = fetch_account_balances_by_type(ex, "swap")
|
||||
swap_bal, swap_avail = fetch_account_balances_by_type(ex, "swap")
|
||||
if swap_bal.get("USDC") is not None:
|
||||
trading["USDC"] = swap_bal["USDC"]
|
||||
if trading_avail.get("USDC") is None and swap_avail.get("USDC") is not None:
|
||||
trading_avail["USDC"] = swap_avail["USDC"]
|
||||
result = {
|
||||
"scope": "main",
|
||||
"funding_usdt": funding.get("USDT"),
|
||||
"funding_usdc": funding.get("USDC"),
|
||||
"funding_usdg": funding.get("USDG"),
|
||||
"funding_usdt_avail": funding_avail.get("USDT"),
|
||||
"funding_usdc_avail": funding_avail.get("USDC"),
|
||||
"trading_usdt": trading.get("USDT"),
|
||||
"trading_usdc": trading.get("USDC"),
|
||||
"trading_usdg": trading.get("USDG"),
|
||||
"trading_usdt_avail": trading_avail.get("USDT"),
|
||||
"trading_usdc_avail": trading_avail.get("USDC"),
|
||||
}
|
||||
_OPTIONS_BALANCE_CACHE["updated_at"] = now
|
||||
_OPTIONS_BALANCE_CACHE["data"] = result
|
||||
|
||||
Reference in New Issue
Block a user