feat条件单新建按标的过滤匹配币种的客户端。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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()
|
transfer_block_msg = str(snap.get("transfer_block_msg") or "").strip()
|
||||||
if has_pos and not transfer_block_msg:
|
if has_pos and not transfer_block_msg:
|
||||||
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(
|
out.append(
|
||||||
{
|
{
|
||||||
"id": row.get("id"),
|
"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,
|
"funding_usdt": funding_usdt,
|
||||||
"transfer_blocked": transfer_blocked,
|
"transfer_blocked": transfer_blocked,
|
||||||
"transfer_block_msg": transfer_block_msg,
|
"transfer_block_msg": transfer_block_msg,
|
||||||
|
"allowed_underlys": allowed_underlys,
|
||||||
|
"default_underly": default_underly,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return out
|
return out
|
||||||
|
|||||||
+95
-25
@@ -203,6 +203,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var candidatesById = {};
|
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) {
|
function fmtTradingU(v) {
|
||||||
if (v == null || v === "") return "—";
|
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() {
|
async function loadCandidates() {
|
||||||
const box = document.getElementById("cf-instances");
|
const box = document.getElementById("cf-instances");
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
@@ -423,35 +502,12 @@
|
|||||||
const r = await fetch("/api/cond_orders/candidates");
|
const r = await fetch("/api/cond_orders/candidates");
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
const list = d.instances || [];
|
const list = d.instances || [];
|
||||||
if (!list.length) {
|
allCandidates = list;
|
||||||
box.innerHTML = '<span class="muted">暂无启用实例</span>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const sim = [];
|
|
||||||
const live = [];
|
|
||||||
const other = [];
|
|
||||||
candidatesById = {};
|
candidatesById = {};
|
||||||
list.forEach(function (it) {
|
list.forEach(function (it) {
|
||||||
const m = instanceMode(it);
|
|
||||||
candidatesById[String(it.id)] = 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) {
|
function renderDesktopRows(list) {
|
||||||
@@ -729,6 +785,20 @@
|
|||||||
const pxEl = document.getElementById("cf-trigger-px");
|
const pxEl = document.getElementById("cf-trigger-px");
|
||||||
if (typeEl) typeEl.addEventListener("change", syncTriggerHint);
|
if (typeEl) typeEl.addEventListener("change", syncTriggerHint);
|
||||||
if (pxEl) pxEl.addEventListener("input", 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();
|
syncTriggerHint();
|
||||||
// 读缓存秒开 + SSE;不再前端定时现场拉实例
|
// 读缓存秒开 + SSE;不再前端定时现场拉实例
|
||||||
refresh();
|
refresh();
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<div class="cond-section">
|
<div class="cond-section">
|
||||||
<div class="cond-section-title">客户端</div>
|
<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 id="cf-instances"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -95,6 +95,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% 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>
|
<script>HubCondOrders.boot();</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -46,6 +46,30 @@ class TestCondCandidatesFunds(unittest.TestCase):
|
|||||||
self.assertFalse(out[1]["selectable"])
|
self.assertFalse(out[1]["selectable"])
|
||||||
self.assertIsNone(out[1]["trading_usdt"])
|
self.assertIsNone(out[1]["trading_usdt"])
|
||||||
self.assertTrue(out[1]["transfer_blocked"])
|
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):
|
def test_open_url_tab_appended(self):
|
||||||
from lib.sso_lib import build_embed_open_url
|
from lib.sso_lib import build_embed_open_url
|
||||||
|
|||||||
Reference in New Issue
Block a user