feat条件单新建按标的过滤匹配币种的客户端。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-13 19:18:52 +08:00
parent 18d195849c
commit e2a28e6c94
4 changed files with 140 additions and 27 deletions
+19
View File
@@ -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
+95 -25
View File
@@ -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 = '<span class="muted">暂无启用实例</span>';
return;
}
if (!list.length) {
box.innerHTML =
'<span class="muted">当前标的 ' +
escapeHtml(uly) +
" 下暂无匹配客户端(请确认实例 OKX_OPTIONS_UNDERLYINGS)</span>";
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 = '<span class="muted">暂无启用实例</span>';
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();
+2 -2
View File
@@ -68,7 +68,7 @@
<div class="cond-section">
<div class="cond-section-title">客户端</div>
<p class="muted" style="margin:0 0 8px;font-size:.76rem">展示交易/资金余额;「划入交易 / 划入资金」本页双向划转,表单不丢。</p>
<p class="muted" style="margin:0 0 8px;font-size:.76rem">按上方标的过滤客户端(读实例标的白名单)。展示交易/资金余额;「划入交易 / 划入资金」本页双向划转,表单不丢。</p>
<div id="cf-instances"></div>
</div>
@@ -95,6 +95,6 @@
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='cond_orders.js') }}?v=13"></script>
<script src="{{ url_for('static', filename='cond_orders.js') }}?v=14"></script>
<script>HubCondOrders.boot();</script>
{% endblock %}
+24
View File
@@ -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