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:
@@ -3330,6 +3330,9 @@ html[data-theme="light"] .options-estimate-row {
|
|||||||
.opt-error {
|
.opt-error {
|
||||||
color: #ff6b6b;
|
color: #ff6b6b;
|
||||||
}
|
}
|
||||||
|
.opt-success {
|
||||||
|
color: #3ecf8e;
|
||||||
|
}
|
||||||
.opt-row-actions {
|
.opt-row-actions {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
const root = document.getElementById("options-settings-root");
|
const root = document.getElementById("options-settings-root");
|
||||||
if (!root) return;
|
if (!root) return;
|
||||||
|
|
||||||
|
const SWAP_BTNS = ["opt-set-swap-btn", "opt-set-swap-all-btn"];
|
||||||
|
const INT_BTNS = ["opt-set-int-btn", "opt-set-int-all-btn"];
|
||||||
|
const CROSS_BTNS = ["opt-set-cross-btn", "opt-set-cross-all-btn"];
|
||||||
|
|
||||||
async function apiJson(url, opts) {
|
async function apiJson(url, opts) {
|
||||||
const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {}));
|
const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {}));
|
||||||
return r.json();
|
return r.json();
|
||||||
@@ -22,16 +26,66 @@
|
|||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.textContent = text || "";
|
el.textContent = text || "";
|
||||||
el.classList.toggle("opt-error", !!isErr);
|
el.classList.toggle("opt-error", !!isErr);
|
||||||
|
el.classList.toggle("opt-success", !!text && !isErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
function floorAmount(v) {
|
function fmtAmt(amount, ccy) {
|
||||||
|
return `${Number(amount).toFixed(2)} ${ccy}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function accountLabel(acct) {
|
||||||
|
return acct === "trading" ? "交易账户" : "资金账户";
|
||||||
|
}
|
||||||
|
|
||||||
|
function swapDirLabel(dir) {
|
||||||
|
return dir === "usdc_to_usdt" ? "USDC → USDT" : "USDT → USDC";
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmOk(message) {
|
||||||
|
return window.confirm(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButtonsBusy(btnIds, busy, busyText) {
|
||||||
|
btnIds.forEach(function (id) {
|
||||||
|
const btn = document.getElementById(id);
|
||||||
|
if (!btn) return;
|
||||||
|
if (busy) {
|
||||||
|
if (!btn.dataset.origText) btn.dataset.origText = btn.textContent;
|
||||||
|
btn.disabled = true;
|
||||||
|
if (busyText) btn.textContent = busyText;
|
||||||
|
} else {
|
||||||
|
btn.disabled = false;
|
||||||
|
if (btn.dataset.origText) {
|
||||||
|
btn.textContent = btn.dataset.origText;
|
||||||
|
delete btn.dataset.origText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const amountIds = {
|
||||||
|
"opt-set-swap-btn": "opt-set-swap-amount",
|
||||||
|
"opt-set-swap-all-btn": "opt-set-swap-amount",
|
||||||
|
"opt-set-int-btn": "opt-set-int-amount",
|
||||||
|
"opt-set-int-all-btn": "opt-set-int-amount",
|
||||||
|
"opt-set-cross-btn": "opt-set-cross-amount",
|
||||||
|
"opt-set-cross-all-btn": "opt-set-cross-amount",
|
||||||
|
};
|
||||||
|
btnIds.forEach(function (id) {
|
||||||
|
const input = document.getElementById(amountIds[id]);
|
||||||
|
if (input) input.disabled = busy;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundAvail(v) {
|
||||||
const n = Number(v);
|
const n = Number(v);
|
||||||
if (!Number.isFinite(n) || n <= 0) return null;
|
if (!Number.isFinite(n) || n <= 0) return null;
|
||||||
return Math.floor(n * 100) / 100;
|
return Math.round(n * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadBalances(force) {
|
async function loadBalances(force, scope) {
|
||||||
const q = force ? "?force=1" : "";
|
const parts = [];
|
||||||
|
if (force) parts.push("force=1");
|
||||||
|
if (scope && scope !== "main") parts.push("scope=" + encodeURIComponent(scope));
|
||||||
|
const q = parts.length ? "?" + parts.join("&") : "";
|
||||||
const d = await apiJson("/api/options/balances" + q);
|
const d = await apiJson("/api/options/balances" + q);
|
||||||
if (!d.ok) throw new Error(d.msg || "余额拉取失败");
|
if (!d.ok) throw new Error(d.msg || "余额拉取失败");
|
||||||
return d;
|
return d;
|
||||||
@@ -39,16 +93,21 @@
|
|||||||
|
|
||||||
function pickBalance(bal, account, ccy) {
|
function pickBalance(bal, account, ccy) {
|
||||||
const acct = account === "trading" ? "trading" : "funding";
|
const acct = account === "trading" ? "trading" : "funding";
|
||||||
const key = acct + "_" + String(ccy || "").toLowerCase();
|
const c = String(ccy || "").toLowerCase();
|
||||||
return floorAmount(bal[key]);
|
const availKey = acct + "_" + c + "_avail";
|
||||||
|
const totalKey = acct + "_" + c;
|
||||||
|
return roundAvail(bal[availKey] != null ? bal[availKey] : bal[totalKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveMaxAmount(account, ccy) {
|
async function resolveMaxAmount(account, ccy, scope) {
|
||||||
const bal = await loadBalances(true);
|
const bal = await loadBalances(true, scope || "main");
|
||||||
return pickBalance(bal, account, ccy);
|
return pickBalance(bal, account, ccy);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitSwap(amount) {
|
async function submitSwap(amount) {
|
||||||
|
setButtonsBusy(SWAP_BTNS, true, "兑换中…");
|
||||||
|
setMsg("opt-set-swap-msg", "兑换中,市价成交可能有延时…", false);
|
||||||
|
try {
|
||||||
const d = await apiJson("/api/options/spot/swap", {
|
const d = await apiJson("/api/options/spot/swap", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -57,8 +116,19 @@
|
|||||||
amount: amount,
|
amount: amount,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setMsg("opt-set-swap-msg", d.ok ? "兑换单已提交" : (d.msg || "失败"), !d.ok);
|
if (d.ok) {
|
||||||
if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation();
|
setMsg("opt-set-swap-msg", "兑换成功", false);
|
||||||
|
refreshFundsAfterMutation();
|
||||||
|
} else {
|
||||||
|
setMsg("opt-set-swap-msg", "兑换失败:" + (d.msg || "未知错误"), true);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
} catch (e) {
|
||||||
|
setMsg("opt-set-swap-msg", "兑换失败:" + (e.message || "网络错误"), true);
|
||||||
|
return { ok: false };
|
||||||
|
} finally {
|
||||||
|
setButtonsBusy(SWAP_BTNS, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const swapBtn = document.getElementById("opt-set-swap-btn");
|
const swapBtn = document.getElementById("opt-set-swap-btn");
|
||||||
@@ -79,20 +149,29 @@
|
|||||||
try {
|
try {
|
||||||
const dir = document.getElementById("opt-set-swap-dir").value;
|
const dir = document.getElementById("opt-set-swap-dir").value;
|
||||||
const ccy = dir === "usdc_to_usdt" ? "USDC" : "USDT";
|
const ccy = dir === "usdc_to_usdt" ? "USDC" : "USDT";
|
||||||
const amount = await resolveMaxAmount("funding", ccy);
|
const amount = await resolveMaxAmount("funding", ccy, "main");
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
setMsg("opt-set-swap-msg", "资金账户可用余额不足", true);
|
setMsg("opt-set-swap-msg", "资金账户可用余额不足", true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const msg =
|
||||||
|
"确认全部兑换?\n\n" +
|
||||||
|
"方向:" + swapDirLabel(dir) + "\n" +
|
||||||
|
"金额:" + fmtAmt(amount, ccy) + "\n\n" +
|
||||||
|
"将兑换资金账户全部可用余额。市价成交可能有延时,请确认。";
|
||||||
|
if (!confirmOk(msg)) return;
|
||||||
document.getElementById("opt-set-swap-amount").value = String(amount);
|
document.getElementById("opt-set-swap-amount").value = String(amount);
|
||||||
await submitSwap(amount);
|
await submitSwap(amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg("opt-set-swap-msg", e.message || "余额拉取失败", true);
|
setMsg("opt-set-swap-msg", "兑换失败:" + (e.message || "余额拉取失败"), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitInternalTransfer(amount) {
|
async function submitInternalTransfer(amount) {
|
||||||
|
setButtonsBusy(INT_BTNS, true, "划转中…");
|
||||||
|
setMsg("opt-set-int-msg", "划转中…", false);
|
||||||
|
try {
|
||||||
const d = await apiJson("/api/options/transfer", {
|
const d = await apiJson("/api/options/transfer", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -103,8 +182,19 @@
|
|||||||
amount: amount,
|
amount: amount,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setMsg("opt-set-int-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
if (d.ok) {
|
||||||
if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation();
|
setMsg("opt-set-int-msg", "划转成功", false);
|
||||||
|
refreshFundsAfterMutation();
|
||||||
|
} else {
|
||||||
|
setMsg("opt-set-int-msg", "划转失败:" + (d.msg || "未知错误"), true);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
} catch (e) {
|
||||||
|
setMsg("opt-set-int-msg", "划转失败:" + (e.message || "网络错误"), true);
|
||||||
|
return { ok: false };
|
||||||
|
} finally {
|
||||||
|
setButtonsBusy(INT_BTNS, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const intBtn = document.getElementById("opt-set-int-btn");
|
const intBtn = document.getElementById("opt-set-int-btn");
|
||||||
@@ -125,20 +215,32 @@
|
|||||||
try {
|
try {
|
||||||
const ccy = document.getElementById("opt-set-int-ccy").value;
|
const ccy = document.getElementById("opt-set-int-ccy").value;
|
||||||
const from = document.getElementById("opt-set-int-from").value;
|
const from = document.getElementById("opt-set-int-from").value;
|
||||||
const amount = await resolveMaxAmount(from, ccy);
|
const to = document.getElementById("opt-set-int-to").value;
|
||||||
|
const amount = await resolveMaxAmount(from, ccy, "main");
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
setMsg("opt-set-int-msg", "划出账户可用余额不足", true);
|
setMsg("opt-set-int-msg", "划出账户可用余额不足", true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const msg =
|
||||||
|
"确认全部划转?\n\n" +
|
||||||
|
"币种:" + ccy + "\n" +
|
||||||
|
"划出:" + accountLabel(from) + "\n" +
|
||||||
|
"划入:" + accountLabel(to) + "\n" +
|
||||||
|
"金额:" + fmtAmt(amount, ccy) + "\n\n" +
|
||||||
|
"将划转该账户全部可用余额。";
|
||||||
|
if (!confirmOk(msg)) return;
|
||||||
document.getElementById("opt-set-int-amount").value = String(amount);
|
document.getElementById("opt-set-int-amount").value = String(amount);
|
||||||
await submitInternalTransfer(amount);
|
await submitInternalTransfer(amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg("opt-set-int-msg", e.message || "余额拉取失败", true);
|
setMsg("opt-set-int-msg", "划转失败:" + (e.message || "余额拉取失败"), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitCrossTransfer(amount) {
|
async function submitCrossTransfer(amount) {
|
||||||
|
setButtonsBusy(CROSS_BTNS, true, "划转中…");
|
||||||
|
setMsg("opt-set-cross-msg", "划转中…", false);
|
||||||
|
try {
|
||||||
const d = await apiJson("/api/options/cross-transfer", {
|
const d = await apiJson("/api/options/cross-transfer", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -150,8 +252,19 @@
|
|||||||
direction: document.getElementById("opt-set-cross-dir").value,
|
direction: document.getElementById("opt-set-cross-dir").value,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setMsg("opt-set-cross-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
if (d.ok) {
|
||||||
if (d.ok && typeof refreshAccountSnapshot === "function") refreshFundsAfterMutation();
|
setMsg("opt-set-cross-msg", "划转成功", false);
|
||||||
|
refreshFundsAfterMutation();
|
||||||
|
} else {
|
||||||
|
setMsg("opt-set-cross-msg", "划转失败:" + (d.msg || "未知错误"), true);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
} catch (e) {
|
||||||
|
setMsg("opt-set-cross-msg", "划转失败:" + (e.message || "网络错误"), true);
|
||||||
|
return { ok: false };
|
||||||
|
} finally {
|
||||||
|
setButtonsBusy(CROSS_BTNS, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const crossBtn = document.getElementById("opt-set-cross-btn");
|
const crossBtn = document.getElementById("opt-set-cross-btn");
|
||||||
@@ -172,15 +285,28 @@
|
|||||||
try {
|
try {
|
||||||
const ccy = document.getElementById("opt-set-cross-ccy").value;
|
const ccy = document.getElementById("opt-set-cross-ccy").value;
|
||||||
const from = document.getElementById("opt-set-cross-from").value;
|
const from = document.getElementById("opt-set-cross-from").value;
|
||||||
const amount = await resolveMaxAmount(from, ccy);
|
const to = document.getElementById("opt-set-cross-to").value;
|
||||||
|
const direction = document.getElementById("opt-set-cross-dir").value;
|
||||||
|
const scope = direction === "sub_to_main" ? "sub" : "main";
|
||||||
|
const sideLabel = direction === "sub_to_main" ? "子账户" : "主账户";
|
||||||
|
const amount = await resolveMaxAmount(from, ccy, scope);
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
setMsg("opt-set-cross-msg", "划出账户可用余额不足", true);
|
setMsg("opt-set-cross-msg", sideLabel + "划出账户可用余额不足", true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const msg =
|
||||||
|
"确认全部划转?\n\n" +
|
||||||
|
"方向:" + (direction === "main_to_sub" ? "主 → 子" : "子 → 主") + "\n" +
|
||||||
|
"币种:" + ccy + "\n" +
|
||||||
|
"划出:" + sideLabel + " · " + accountLabel(from) + "\n" +
|
||||||
|
"划入:" + (direction === "main_to_sub" ? "子账户" : "主账户") + " · " + accountLabel(to) + "\n" +
|
||||||
|
"金额:" + fmtAmt(amount, ccy) + "\n\n" +
|
||||||
|
"将划转该账户全部可用余额。";
|
||||||
|
if (!confirmOk(msg)) return;
|
||||||
document.getElementById("opt-set-cross-amount").value = String(amount);
|
document.getElementById("opt-set-cross-amount").value = String(amount);
|
||||||
await submitCrossTransfer(amount);
|
await submitCrossTransfer(amount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg("opt-set-cross-msg", e.message || "余额拉取失败", true);
|
setMsg("opt-set-cross-msg", "划转失败:" + (e.message || "余额拉取失败"), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,6 +384,21 @@ def fetch_option_instrument_meta(ex: ccxt.okx, inst_id: str) -> dict[str, Any] |
|
|||||||
return None
|
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:
|
def _extract_ccy_balance(balance: dict[str, Any], ccy: str) -> float | None:
|
||||||
ccy = (ccy or "").upper()
|
ccy = (ccy or "").upper()
|
||||||
if not isinstance(balance, dict):
|
if not isinstance(balance, dict):
|
||||||
@@ -407,40 +422,91 @@ def _extract_ccy_balance(balance: dict[str, Any], ccy: str) -> float | None:
|
|||||||
return 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}
|
out: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||||
|
avail: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||||
try:
|
try:
|
||||||
bal = ex.fetch_balance(params={"type": account_type})
|
bal = ex.fetch_balance(params={"type": account_type})
|
||||||
for c in out:
|
for c in out:
|
||||||
out[c] = _extract_ccy_balance(bal, c)
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return out
|
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
|
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"))
|
ttl = float(os.getenv("OKX_OPTIONS_BALANCE_REFRESH_SEC", "30"))
|
||||||
now = time.time()
|
now = time.time()
|
||||||
cached = _OPTIONS_BALANCE_CACHE.get("data")
|
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:
|
if not force and cached is not None and now - float(_OPTIONS_BALANCE_CACHE.get("updated_at") or 0) < ttl:
|
||||||
return dict(cached)
|
return dict(cached)
|
||||||
|
|
||||||
funding = fetch_account_balances_by_type(ex, "funding")
|
funding, funding_avail = fetch_account_balances_by_type(ex, "funding")
|
||||||
trading = fetch_account_balances_by_type(ex, "trading")
|
trading, trading_avail = fetch_account_balances_by_type(ex, "trading")
|
||||||
# 统一账户部分 USDC 可能在 swap 类型
|
|
||||||
if trading.get("USDC") is None:
|
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:
|
if swap_bal.get("USDC") is not None:
|
||||||
trading["USDC"] = swap_bal["USDC"]
|
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 = {
|
result = {
|
||||||
|
"scope": "main",
|
||||||
"funding_usdt": funding.get("USDT"),
|
"funding_usdt": funding.get("USDT"),
|
||||||
"funding_usdc": funding.get("USDC"),
|
"funding_usdc": funding.get("USDC"),
|
||||||
"funding_usdg": funding.get("USDG"),
|
"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_usdt": trading.get("USDT"),
|
||||||
"trading_usdc": trading.get("USDC"),
|
"trading_usdc": trading.get("USDC"),
|
||||||
"trading_usdg": trading.get("USDG"),
|
"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["updated_at"] = now
|
||||||
_OPTIONS_BALANCE_CACHE["data"] = result
|
_OPTIONS_BALANCE_CACHE["data"] = result
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=6">
|
<link rel="stylesheet" href="/static/instance_page.css?v=6">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=79">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=80">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=79">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=80">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
|
|||||||
@@ -296,7 +296,13 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
if ex is None:
|
if ex is None:
|
||||||
return jsonify({"ok": False, "msg": err})
|
return jsonify({"ok": False, "msg": err})
|
||||||
force = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
|
force = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
|
||||||
bal = cfg["fetch_options_balances"](ex, force=force)
|
scope = (request.args.get("scope") or "main").strip().lower()
|
||||||
|
bal = cfg["fetch_options_balances"](
|
||||||
|
ex,
|
||||||
|
force=force,
|
||||||
|
scope=scope,
|
||||||
|
sub_acct=cfg.get("sub_account_name") or "",
|
||||||
|
)
|
||||||
return jsonify({"ok": True, **bal, "trade_budget": cfg["trade_budget"]})
|
return jsonify({"ok": True, **bal, "trade_budget": cfg["trade_budget"]})
|
||||||
|
|
||||||
@app.route("/api/options/chain")
|
@app.route("/api/options/chain")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{# 期权设置脚本挂载点(卡片在 settings_panel 中拆分) #}
|
{# 期权设置脚本挂载点(卡片在 settings_panel 中拆分) #}
|
||||||
<div id="options-settings-root" hidden
|
<div id="options-settings-root" hidden
|
||||||
data-sub-account="{{ instance_settings.options_sub_account | default('', true) }}"></div>
|
data-sub-account="{{ instance_settings.options_sub_account | default('', true) }}"></div>
|
||||||
<script src="/static/options_settings.js?v=7"></script>
|
<script src="/static/options_settings.js?v=8"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user