From e2a28e6c94f1ba7ea909cbe78caafa44984f2d1b Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 13 Oct 2026 19:18:52 +0800 Subject: [PATCH] =?UTF-8?q?feat=E6=9D=A1=E4=BB=B6=E5=8D=95=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=E6=8C=89=E6=A0=87=E7=9A=84=E8=BF=87=E6=BB=A4=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E5=B8=81=E7=A7=8D=E7=9A=84=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- lib/cond_orders_cache_lib.py | 19 +++++ static/cond_orders.js | 120 ++++++++++++++++++++++------ templates/cond_orders.html | 4 +- tests/test_cond_candidates_funds.py | 24 ++++++ 4 files changed, 140 insertions(+), 27 deletions(-) diff --git a/lib/cond_orders_cache_lib.py b/lib/cond_orders_cache_lib.py index b2c4b4a..9be1063 100644 --- a/lib/cond_orders_cache_lib.py +++ b/lib/cond_orders_cache_lib.py @@ -72,6 +72,23 @@ def _candidates_from_snaps(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: transfer_block_msg = str(snap.get("transfer_block_msg") or "").strip() if has_pos and not transfer_block_msg: transfer_block_msg = "期权持仓中禁止划转" + allowed_raw = snap.get("options_allowed_underlys") + if not isinstance(allowed_raw, list): + allowed_raw = inst.get("allowed_underlys") + allowed_underlys: list[str] = [] + if isinstance(allowed_raw, list): + for x in allowed_raw: + u = str(x or "").strip().upper() + if u in ("ETH", "BTC") and u not in allowed_underlys: + allowed_underlys.append(u) + if not allowed_underlys: + # 旧实例未上报白名单:仍显示在 ETH/BTC 下(兼容) + allowed_underlys = ["ETH", "BTC"] + default_underly = str( + snap.get("options_underly") or inst.get("underly") or allowed_underlys[0] + ).strip().upper() + if default_underly not in allowed_underlys: + default_underly = allowed_underlys[0] out.append( { "id": row.get("id"), @@ -86,6 +103,8 @@ def _candidates_from_snaps(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: "funding_usdt": funding_usdt, "transfer_blocked": transfer_blocked, "transfer_block_msg": transfer_block_msg, + "allowed_underlys": allowed_underlys, + "default_underly": default_underly, } ) return out diff --git a/static/cond_orders.js b/static/cond_orders.js index 756a739..53f17b4 100644 --- a/static/cond_orders.js +++ b/static/cond_orders.js @@ -203,6 +203,31 @@ } var candidatesById = {}; + var allCandidates = []; + + function selectedCreateUnderlying() { + const el = document.getElementById("cf-underlying"); + const u = el ? String(el.value || "ETH").trim().toUpperCase() : "ETH"; + return u === "BTC" ? "BTC" : "ETH"; + } + + function candidateMatchesUnderlying(it, uly) { + const u = String(uly || "ETH").toUpperCase(); + const raw = it && it.allowed_underlys; + const allow = Array.isArray(raw) + ? raw + .map(function (x) { + return String(x || "") + .trim() + .toUpperCase(); + }) + .filter(function (x) { + return x === "ETH" || x === "BTC"; + }) + : []; + if (!allow.length) return true; + return allow.indexOf(u) >= 0; + } function fmtTradingU(v) { if (v == null || v === "") return "—"; @@ -412,6 +437,60 @@ ); } + function paintCandidates(checkedIds) { + const box = document.getElementById("cf-instances"); + if (!box) return; + const uly = selectedCreateUnderlying(); + const list = (allCandidates || []).filter(function (it) { + return candidateMatchesUnderlying(it, uly); + }); + if (!(allCandidates || []).length) { + box.innerHTML = '暂无启用实例'; + return; + } + if (!list.length) { + box.innerHTML = + '当前标的 ' + + escapeHtml(uly) + + " 下暂无匹配客户端(请确认实例 OKX_OPTIONS_UNDERLYINGS)"; + return; + } + const keep = {}; + (checkedIds || []).forEach(function (id) { + keep[String(id)] = true; + }); + const filteredChecked = list + .map(function (it) { + return String(it.id); + }) + .filter(function (id) { + return keep[id]; + }); + const sim = []; + const live = []; + const other = []; + list.forEach(function (it) { + const m = instanceMode(it); + if (m === "sim") sim.push(it); + else if (m === "live") live.push(it); + else other.push(it); + }); + box.innerHTML = + renderCandidateGroup("sim", sim, filteredChecked) + + renderCandidateGroup("live", live, filteredChecked) + + renderCandidateGroup("", other, filteredChecked); + box.querySelectorAll(".cond-inst-xfer").forEach(function (btn) { + btn.addEventListener("click", function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + transferInternal( + btn.getAttribute("data-inst-id") || "", + btn.getAttribute("data-dir") || "to_trading" + ); + }); + }); + } + async function loadCandidates() { const box = document.getElementById("cf-instances"); if (!box) return; @@ -423,35 +502,12 @@ const r = await fetch("/api/cond_orders/candidates"); const d = await r.json(); const list = d.instances || []; - if (!list.length) { - box.innerHTML = '暂无启用实例'; - return; - } - const sim = []; - const live = []; - const other = []; + allCandidates = list; candidatesById = {}; list.forEach(function (it) { - const m = instanceMode(it); candidatesById[String(it.id)] = it; - if (m === "sim") sim.push(it); - else if (m === "live") live.push(it); - else other.push(it); - }); - box.innerHTML = - renderCandidateGroup("sim", sim, checkedIds) + - renderCandidateGroup("live", live, checkedIds) + - renderCandidateGroup("", other, checkedIds); - box.querySelectorAll(".cond-inst-xfer").forEach(function (btn) { - btn.addEventListener("click", function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - transferInternal( - btn.getAttribute("data-inst-id") || "", - btn.getAttribute("data-dir") || "to_trading" - ); - }); }); + paintCandidates(checkedIds); } function renderDesktopRows(list) { @@ -729,6 +785,20 @@ const pxEl = document.getElementById("cf-trigger-px"); if (typeEl) typeEl.addEventListener("change", syncTriggerHint); if (pxEl) pxEl.addEventListener("input", syncTriggerHint); + const ulyEl = document.getElementById("cf-underlying"); + if (ulyEl) { + ulyEl.addEventListener("change", function () { + const box = document.getElementById("cf-instances"); + const checkedIds = box + ? Array.prototype.slice + .call(box.querySelectorAll('input[name="cf-inst"]:checked')) + .map(function (el) { + return decodeURIComponent(el.value || ""); + }) + : []; + paintCandidates(checkedIds); + }); + } syncTriggerHint(); // 读缓存秒开 + SSE;不再前端定时现场拉实例 refresh(); diff --git a/templates/cond_orders.html b/templates/cond_orders.html index a41e1e6..0fce971 100644 --- a/templates/cond_orders.html +++ b/templates/cond_orders.html @@ -68,7 +68,7 @@
客户端
-

展示交易/资金余额;「划入交易 / 划入资金」本页双向划转,表单不丢。

+

按上方标的过滤客户端(读实例标的白名单)。展示交易/资金余额;「划入交易 / 划入资金」本页双向划转,表单不丢。

@@ -95,6 +95,6 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/tests/test_cond_candidates_funds.py b/tests/test_cond_candidates_funds.py index e47bb8e..9a85834 100644 --- a/tests/test_cond_candidates_funds.py +++ b/tests/test_cond_candidates_funds.py @@ -46,6 +46,30 @@ class TestCondCandidatesFunds(unittest.TestCase): self.assertFalse(out[1]["selectable"]) self.assertIsNone(out[1]["trading_usdt"]) self.assertTrue(out[1]["transfer_blocked"]) + self.assertEqual(out[0]["allowed_underlys"], ["ETH", "BTC"]) + + def test_candidates_carry_allowed_underlys(self): + from lib.cond_orders_cache_lib import _candidates_from_snaps + + rows = [ + { + "id": 3, + "name": "eth-only", + "online": True, + "error": "", + "snapshot": { + "positions_brief": [], + "cond_order_locked": False, + "instance": {"mode": "sim", "underly": "ETH", "allowed_underlys": ["ETH"]}, + "options_allowed_underlys": ["ETH"], + "options_underly": "ETH", + "funds": {"trading_usdt": 1.0, "funding_usdt": 2.0}, + }, + } + ] + out = _candidates_from_snaps(rows) + self.assertEqual(out[0]["allowed_underlys"], ["ETH"]) + self.assertEqual(out[0]["default_underly"], "ETH") def test_open_url_tab_appended(self): from lib.sso_lib import build_embed_open_url