diff --git a/lib/common/static/options_settings.js b/lib/common/static/options_settings.js index 04db499..e89b497 100644 --- a/lib/common/static/options_settings.js +++ b/lib/common/static/options_settings.js @@ -16,6 +16,43 @@ el.classList.toggle("opt-error", !!isErr); } + function floorAmount(v) { + const n = Number(v); + if (!Number.isFinite(n) || n <= 0) return null; + return Math.floor(n * 100) / 100; + } + + async function loadBalances(force) { + const q = force ? "?force=1" : ""; + const d = await apiJson("/api/options/balances" + q); + if (!d.ok) throw new Error(d.msg || "余额拉取失败"); + return d; + } + + function pickBalance(bal, account, ccy) { + const acct = account === "trading" ? "trading" : "funding"; + const key = acct + "_" + String(ccy || "").toLowerCase(); + return floorAmount(bal[key]); + } + + async function resolveMaxAmount(account, ccy) { + const bal = await loadBalances(true); + return pickBalance(bal, account, ccy); + } + + async function submitSwap(amount) { + const d = await apiJson("/api/options/spot/swap", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + direction: document.getElementById("opt-set-swap-dir").value, + amount: amount, + }), + }); + setMsg("opt-set-swap-msg", d.ok ? "兑换单已提交" : (d.msg || "失败"), !d.ok); + if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot(); + } + const swapBtn = document.getElementById("opt-set-swap-btn"); if (swapBtn) { swapBtn.addEventListener("click", async function () { @@ -24,18 +61,44 @@ setMsg("opt-set-swap-msg", "请输入有效数量", true); return; } - const d = await apiJson("/api/options/spot/swap", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - direction: document.getElementById("opt-set-swap-dir").value, - amount: amount, - }), - }); - setMsg("opt-set-swap-msg", d.ok ? "兑换单已提交" : (d.msg || "失败"), !d.ok); + await submitSwap(amount); }); } + const swapAllBtn = document.getElementById("opt-set-swap-all-btn"); + if (swapAllBtn) { + swapAllBtn.addEventListener("click", async function () { + try { + const dir = document.getElementById("opt-set-swap-dir").value; + const ccy = dir === "usdc_to_usdt" ? "USDC" : "USDT"; + const amount = await resolveMaxAmount("funding", ccy); + if (!amount) { + setMsg("opt-set-swap-msg", "资金账户可用余额不足", true); + return; + } + document.getElementById("opt-set-swap-amount").value = String(amount); + await submitSwap(amount); + } catch (e) { + setMsg("opt-set-swap-msg", e.message || "余额拉取失败", true); + } + }); + } + + async function submitInternalTransfer(amount) { + const d = await apiJson("/api/options/transfer", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + ccy: document.getElementById("opt-set-int-ccy").value, + from: document.getElementById("opt-set-int-from").value, + to: document.getElementById("opt-set-int-to").value, + amount: amount, + }), + }); + setMsg("opt-set-int-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok); + if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot(); + } + const intBtn = document.getElementById("opt-set-int-btn"); if (intBtn) { intBtn.addEventListener("click", async function () { @@ -44,20 +107,45 @@ setMsg("opt-set-int-msg", "请输入有效数量", true); return; } - const d = await apiJson("/api/options/transfer", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - ccy: document.getElementById("opt-set-int-ccy").value, - from: document.getElementById("opt-set-int-from").value, - to: document.getElementById("opt-set-int-to").value, - amount: amount, - }), - }); - setMsg("opt-set-int-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok); + await submitInternalTransfer(amount); }); } + const intAllBtn = document.getElementById("opt-set-int-all-btn"); + if (intAllBtn) { + intAllBtn.addEventListener("click", async function () { + try { + const ccy = document.getElementById("opt-set-int-ccy").value; + const from = document.getElementById("opt-set-int-from").value; + const amount = await resolveMaxAmount(from, ccy); + if (!amount) { + setMsg("opt-set-int-msg", "划出账户可用余额不足", true); + return; + } + document.getElementById("opt-set-int-amount").value = String(amount); + await submitInternalTransfer(amount); + } catch (e) { + setMsg("opt-set-int-msg", e.message || "余额拉取失败", true); + } + }); + } + + async function submitCrossTransfer(amount) { + const d = await apiJson("/api/options/cross-transfer", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + ccy: document.getElementById("opt-set-cross-ccy").value, + amount: amount, + from_account: document.getElementById("opt-set-cross-from").value, + to_account: document.getElementById("opt-set-cross-to").value, + direction: document.getElementById("opt-set-cross-dir").value, + }), + }); + setMsg("opt-set-cross-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok); + if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot(); + } + const crossBtn = document.getElementById("opt-set-cross-btn"); if (crossBtn) { crossBtn.addEventListener("click", async function () { @@ -66,19 +154,26 @@ setMsg("opt-set-cross-msg", "请输入有效数量", true); return; } - const d = await apiJson("/api/options/cross-transfer", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - ccy: document.getElementById("opt-set-cross-ccy").value, - amount: amount, - from_account: document.getElementById("opt-set-cross-from").value, - to_account: document.getElementById("opt-set-cross-to").value, - direction: document.getElementById("opt-set-cross-dir").value, - }), - }); - setMsg("opt-set-cross-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok); - if (d.ok && typeof refreshAccountSnapshot === "function") refreshAccountSnapshot(); + await submitCrossTransfer(amount); + }); + } + + const crossAllBtn = document.getElementById("opt-set-cross-all-btn"); + if (crossAllBtn) { + crossAllBtn.addEventListener("click", async function () { + try { + const ccy = document.getElementById("opt-set-cross-ccy").value; + const from = document.getElementById("opt-set-cross-from").value; + const amount = await resolveMaxAmount(from, ccy); + if (!amount) { + setMsg("opt-set-cross-msg", "划出账户可用余额不足", true); + return; + } + document.getElementById("opt-set-cross-amount").value = String(amount); + await submitCrossTransfer(amount); + } catch (e) { + setMsg("opt-set-cross-msg", e.message || "余额拉取失败", true); + } }); } })(); diff --git a/lib/instance/instance_embed_context_lib.py b/lib/instance/instance_embed_context_lib.py index e184a61..ba52a80 100644 --- a/lib/instance/instance_embed_context_lib.py +++ b/lib/instance/instance_embed_context_lib.py @@ -90,9 +90,9 @@ def options_funding_label( funding_usdt: float | None = None, ) -> str: parts: list[str] = [] - if funding_usdc is not None: + if funding_usdc is not None and float(funding_usdc) > 0: parts.append(f"{float(funding_usdc):.2f} USDC") - if funding_usdt is not None: + if funding_usdt is not None and float(funding_usdt) > 0: parts.append(f"{float(funding_usdt):.2f} USDT") return " · ".join(parts) if parts else "—" diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index 195e934..3d8ea40 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1134,8 +1134,8 @@ function paintRealtimePnlFromSnapshot(data){ function formatOptionsFundingLabel(usdc, usdt) { const parts = []; - if (usdc !== null && usdc !== undefined) parts.push(`${Number(usdc).toFixed(2)} USDC`); - if (usdt !== null && usdt !== undefined) parts.push(`${Number(usdt).toFixed(2)} USDT`); + if (usdc !== null && usdc !== undefined && Number(usdc) > 0) parts.push(`${Number(usdc).toFixed(2)} USDC`); + if (usdt !== null && usdt !== undefined && Number(usdt) > 0) parts.push(`${Number(usdt).toFixed(2)} USDT`); return parts.length ? parts.join(" · ") : "—"; } diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 3bfa6da..d379f1d 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -1779,8 +1779,8 @@ function paintRealtimePnlFromSnapshot(data){ function formatOptionsFundingLabel(usdc, usdt) { const parts = []; - if (usdc !== null && usdc !== undefined) parts.push(`${Number(usdc).toFixed(2)} USDC`); - if (usdt !== null && usdt !== undefined) parts.push(`${Number(usdt).toFixed(2)} USDT`); + if (usdc !== null && usdc !== undefined && Number(usdc) > 0) parts.push(`${Number(usdc).toFixed(2)} USDC`); + if (usdt !== null && usdt !== undefined && Number(usdt) > 0) parts.push(`${Number(usdt).toFixed(2)} USDT`); return parts.length ? parts.join(" · ") : "—"; } diff --git a/lib/options/options_register.py b/lib/options/options_register.py index 89621eb..584f425 100644 --- a/lib/options/options_register.py +++ b/lib/options/options_register.py @@ -280,7 +280,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None: ex, err = _require_options_ex(cfg) if ex is None: return jsonify({"ok": False, "msg": err}) - bal = cfg["fetch_options_balances"](ex) + force = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes") + bal = cfg["fetch_options_balances"](ex, force=force) return jsonify({"ok": True, **bal, "trade_budget": cfg["trade_budget"]}) @app.route("/api/options/chain") diff --git a/lib/options/templates/options_settings_panel.html b/lib/options/templates/options_settings_panel.html index 4138502..e00a4ad 100644 --- a/lib/options/templates/options_settings_panel.html +++ b/lib/options/templates/options_settings_panel.html @@ -1,4 +1,4 @@ {# 期权设置脚本挂载点(卡片在 settings_panel 中拆分) #}
- + diff --git a/lib/options/templates/options_settings_swap.html b/lib/options/templates/options_settings_swap.html index 7c53959..ed3fb27 100644 --- a/lib/options/templates/options_settings_swap.html +++ b/lib/options/templates/options_settings_swap.html @@ -6,6 +6,7 @@ + diff --git a/lib/options/templates/options_settings_transfer.html b/lib/options/templates/options_settings_transfer.html index 92a4d12..9d00a27 100644 --- a/lib/options/templates/options_settings_transfer.html +++ b/lib/options/templates/options_settings_transfer.html @@ -14,6 +14,7 @@ + @@ -42,6 +43,7 @@ + diff --git a/tests/test_instance_header_stats_lib.py b/tests/test_instance_header_stats_lib.py index b5168cd..6b7db32 100644 --- a/tests/test_instance_header_stats_lib.py +++ b/tests/test_instance_header_stats_lib.py @@ -35,6 +35,7 @@ class TestHeaderStatsLib(unittest.TestCase): def test_options_funding_label(self): self.assertEqual(options_funding_label(1.5, 10), "1.50 USDC · 10.00 USDT") + self.assertEqual(options_funding_label(10.19, 0), "10.19 USDC") self.assertEqual(options_funding_label(None, 10), "10.00 USDT") self.assertEqual(options_funding_label(None, None), "—")