Limit OO T-quote to 3 Call and 3 Put by default with leverage columns.

Drop the scroll box, add a show-all toggle after refresh, and show K/ask leverage on both sides.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-06 09:27:01 +08:00
parent 2181c81aba
commit 993189ce13
5 changed files with 167 additions and 23 deletions
+127 -5
View File
@@ -25,6 +25,7 @@
moneyFilter: "itm", // 永期锁定:实值+平值
ooMoneyFilter: "atm_otm", // 期期锁定:平值+虚值
ooRecommend: null, // atm_straddle | double_otm | null
ooStrikeExpandAll: false, // 默认 Call/Put 各 3 档
chain: null,
selected: null,
legA: null,
@@ -267,6 +268,87 @@
});
}
function syncOoExpandUI() {
const btn = $("hp-oo-expand-all");
if (!btn) return;
const on = !!state.ooStrikeExpandAll;
btn.classList.toggle("active", on);
btn.classList.toggle("is-selected", on);
btn.setAttribute("aria-pressed", on ? "true" : "false");
}
function calcStrikeAskLeverage(strike, ask) {
const k = Number(strike);
const a = Number(ask);
if (!Number.isFinite(k) || k <= 0 || !Number.isFinite(a) || a <= 0) return "—";
return (Math.round((k / a) * 10) / 10).toFixed(1) + "×";
}
function findAtmStrikeFromRows(rows, idx) {
if (!rows.length) return null;
if (idx == null || Number.isNaN(Number(idx))) return rows[0].strike;
let best = rows[0].strike;
let bestDist = Math.abs(Number(rows[0].strike) - Number(idx));
rows.forEach(function (row) {
const d = Math.abs(Number(row.strike) - Number(idx));
if (d < bestDist || (d === bestDist && Number(row.strike) < Number(best))) {
bestDist = d;
best = row.strike;
}
});
return best;
}
function sliceOoTRows(prepared, idx) {
// prepared: [{strike, call, put, callOk, putOk}]
if (state.ooStrikeExpandAll || !prepared.length) return prepared;
const n = 3;
const anchor = Number(idx) || Number(findAtmStrikeFromRows(prepared, idx)) || 0;
function nearest(list, count) {
return list
.slice()
.sort(function (a, b) {
return Math.abs(Number(a.strike) - anchor) - Math.abs(Number(b.strike) - anchor);
})
.slice(0, count);
}
const byStrike = {};
nearest(
prepared.filter(function (r) {
return r.callOk;
}),
n
).forEach(function (r) {
byStrike[String(r.strike)] = r;
});
nearest(
prepared.filter(function (r) {
return r.putOk;
}),
n
).forEach(function (r) {
byStrike[String(r.strike)] = r;
});
// 已选用腿始终保留,避免折叠后看不到选中项
[state.legA, state.legB].forEach(function (leg) {
if (!leg) return;
const hit = prepared.find(function (r) {
return (
(r.call && r.call.inst_id === leg.inst_id) ||
(r.put && r.put.inst_id === leg.inst_id)
);
});
if (hit) byStrike[String(hit.strike)] = hit;
});
return Object.keys(byStrike)
.map(function (k) {
return byStrike[k];
})
.sort(function (a, b) {
return Number(a.strike) - Number(b.strike);
});
}
function syncTabUI() {
let tab = state.tab || pickDefaultTab();
if (tab === "perp_options" && !showPerp) tab = pickDefaultTab();
@@ -945,27 +1027,48 @@
const tbody = $("hp-oo-tbody");
if (!tbody) return;
const exp = currentExp("hp-oo-exp-select");
const cols = 9;
tbody.innerHTML = "";
if (!exp) {
tbody.innerHTML = '<tr><td colspan="7" class="muted">请选择到期日</td></tr>';
tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">请选择到期日</td></tr>';
return;
}
const rows = buildStraddleRows(exp.contracts);
const prepared = [];
buildStraddleRows(exp.contracts).forEach(function (row) {
const callOk = !!(row.call && matchesOoMoneyFilter(row.call));
const putOk = !!(row.put && matchesOoMoneyFilter(row.put));
if (!callOk && !putOk) return;
prepared.push({
strike: row.strike,
call: row.call,
put: row.put,
callOk: callOk,
putOk: putOk,
});
});
const rows = sliceOoTRows(prepared, indexPx());
const selected = selectedOoLegMap();
if (!rows.length) {
tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">该筛选下暂无平值/虚值合约</td></tr>';
return;
}
rows.forEach(function (row) {
const tr = document.createElement("tr");
const call = row.call;
const put = row.put;
const callOk = call && matchesOoMoneyFilter(call);
const putOk = put && matchesOoMoneyFilter(put);
if (!callOk && !putOk) return;
const callOk = row.callOk;
const putOk = row.putOk;
const callAsk = callOk ? fmtPxSz(call.ask, call.ask_sz, call.ask_estimated, call.tick_sz) : "—";
const putAsk = putOk ? fmtPxSz(put.ask, put.ask_sz, put.ask_estimated, put.tick_sz) : "—";
const callLev = callOk ? calcStrikeAskLeverage(row.strike, call.ask) : "—";
const putLev = putOk ? calcStrikeAskLeverage(row.strike, put.ask) : "—";
const callLeg = callOk ? selected[String(call.inst_id)] : "";
const putLeg = putOk ? selected[String(put.inst_id)] : "";
tr.innerHTML =
'<td class="opt-t-call opt-px-sz">' +
callAsk +
'</td><td class="opt-t-call opt-chain-lev" title="K÷卖一">' +
callLev +
'</td><td class="opt-t-call">' +
(callOk ? moneynessBadge(call) : "—") +
"</td><td>" +
@@ -986,6 +1089,8 @@
(putOk ? moneynessBadge(put) : "—") +
'</td><td class="opt-t-put opt-px-sz">' +
putAsk +
'</td><td class="opt-t-put opt-chain-lev" title="K÷卖一">' +
putLev +
"</td><td>" +
(putOk
? '<button type="button" class="btn-secondary hp-oo-pick' +
@@ -1001,6 +1106,15 @@
"</td>";
tbody.appendChild(tr);
});
if (!state.ooStrikeExpandAll && prepared.length > rows.length) {
const hint = document.createElement("tr");
hint.className = "opt-strike-hint-row";
hint.innerHTML =
'<td colspan="' +
cols +
'" class="muted opt-strike-hint">默认显示 Call/Put 各最近 3 档 · 勾选「显示全部」查看更多</td>';
tbody.appendChild(hint);
}
tbody.querySelectorAll(".hp-oo-pick").forEach(function (btn) {
btn.addEventListener("click", function () {
const inst = btn.getAttribute("data-inst");
@@ -1411,6 +1525,13 @@
$("hp-oo-load-chain").addEventListener("click", function () {
void loadChain();
});
if ($("hp-oo-expand-all")) {
$("hp-oo-expand-all").addEventListener("click", function () {
state.ooStrikeExpandAll = !state.ooStrikeExpandAll;
syncOoExpandUI();
renderTStrikes();
});
}
if ($("hp-exp-select")) $("hp-exp-select").addEventListener("change", renderListStrikes);
if ($("hp-oo-exp-select")) $("hp-oo-exp-select").addEventListener("change", renderTStrikes);
if ($("hp-sheets")) $("hp-sheets").addEventListener("input", updatePremiumLine);
@@ -2076,6 +2197,7 @@
syncUnderlyingUI();
syncMoneyUI();
syncOoRecommendUI();
syncOoExpandUI();
syncPoDirUI();
syncOoSizeModeUI();
syncOoCloseModeUI();
+30 -9
View File
@@ -3645,7 +3645,8 @@ html[data-theme="light"] .opt-be-dist-down {
.hedge-plan-page-wrap .hp-uly-btn-oo,
.hedge-plan-page-wrap .hp-money-btn,
.hedge-plan-page-wrap .hp-oo-money-btn,
.hedge-plan-page-wrap .hp-oo-recommend-btn {
.hedge-plan-page-wrap .hp-oo-recommend-btn,
.hedge-plan-page-wrap .hp-oo-expand-btn {
min-width: 52px;
font-weight: 600;
border: 1px solid rgba(255, 255, 255, 0.14);
@@ -3658,7 +3659,9 @@ html[data-theme="light"] .opt-be-dist-down {
.hedge-plan-page-wrap .hp-oo-money-btn.is-selected,
.hedge-plan-page-wrap .hp-oo-money-btn.active,
.hedge-plan-page-wrap .hp-oo-recommend-btn.is-selected,
.hedge-plan-page-wrap .hp-oo-recommend-btn.active {
.hedge-plan-page-wrap .hp-oo-recommend-btn.active,
.hedge-plan-page-wrap .hp-oo-expand-btn.is-selected,
.hedge-plan-page-wrap .hp-oo-expand-btn.active {
border-color: var(--accent, #00d4ff);
color: var(--text, #fff);
background: rgba(0, 212, 255, 0.16);
@@ -3666,7 +3669,8 @@ html[data-theme="light"] .opt-be-dist-down {
font-weight: 700;
}
.hedge-plan-page-wrap .hp-oo-money-btn .hp-oo-check,
.hedge-plan-page-wrap .hp-oo-recommend-btn .hp-oo-check {
.hedge-plan-page-wrap .hp-oo-recommend-btn .hp-oo-check,
.hedge-plan-page-wrap .hp-oo-expand-btn .hp-oo-check {
display: none;
margin-right: 4px;
font-weight: 700;
@@ -3674,7 +3678,9 @@ html[data-theme="light"] .opt-be-dist-down {
.hedge-plan-page-wrap .hp-oo-money-btn.is-selected .hp-oo-check,
.hedge-plan-page-wrap .hp-oo-money-btn.active .hp-oo-check,
.hedge-plan-page-wrap .hp-oo-recommend-btn.is-selected .hp-oo-check,
.hedge-plan-page-wrap .hp-oo-recommend-btn.active .hp-oo-check {
.hedge-plan-page-wrap .hp-oo-recommend-btn.active .hp-oo-check,
.hedge-plan-page-wrap .hp-oo-expand-btn.is-selected .hp-oo-check,
.hedge-plan-page-wrap .hp-oo-expand-btn.active .hp-oo-check {
display: inline;
color: var(--accent, #00d4ff);
}
@@ -3698,20 +3704,33 @@ html[data-theme="light"] .opt-be-dist-down {
.hedge-plan-page-wrap .hp-opt-toolbar .btn-secondary,
.hedge-plan-page-wrap .hp-opt-toolbar .hp-money-btn,
.hedge-plan-page-wrap .hp-oo-money-btn,
.hedge-plan-page-wrap .hp-oo-recommend-btn {
.hedge-plan-page-wrap .hp-oo-recommend-btn,
.hedge-plan-page-wrap .hp-oo-expand-btn {
padding: 3px 8px;
min-height: 26px;
font-size: 0.72rem;
}
/* 视口约 5 行数据 + 表头超出表内滚动 */
.hedge-plan-page-wrap .hp-strike-table-wrap--5,
.hedge-plan-page-wrap .options-strike-table-wrap--t {
/* 永期期权列表:视口约 5 行 + 表头,超出滚动 */
.hedge-plan-page-wrap .hp-strike-table-wrap--5 {
max-height: 248px;
min-height: 248px;
overflow-y: auto;
margin-top: 4px;
flex: 1 1 auto;
}
/* 期期 T 型:默认 3+3 一页展示,无下滑框 */
.hedge-plan-page-wrap .hp-oo-table-wrap {
max-height: none;
min-height: 0;
overflow: visible;
margin-top: 4px;
flex: 0 0 auto;
}
.hedge-plan-page-wrap .hp-oo-table-wrap .opt-chain-lev {
text-align: center;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.hedge-plan-page-wrap .hp-opt-bal-line {
margin-top: 4px;
}
@@ -3933,7 +3952,9 @@ html[data-theme="light"] .hedge-plan-page-wrap .hp-money-btn.active,
html[data-theme="light"] .hedge-plan-page-wrap .hp-oo-money-btn.is-selected,
html[data-theme="light"] .hedge-plan-page-wrap .hp-oo-money-btn.active,
html[data-theme="light"] .hedge-plan-page-wrap .hp-oo-recommend-btn.is-selected,
html[data-theme="light"] .hedge-plan-page-wrap .hp-oo-recommend-btn.active {
html[data-theme="light"] .hedge-plan-page-wrap .hp-oo-recommend-btn.active,
html[data-theme="light"] .hedge-plan-page-wrap .hp-oo-expand-btn.is-selected,
html[data-theme="light"] .hedge-plan-page-wrap .hp-oo-expand-btn.active {
color: #0b1220;
background: linear-gradient(180deg, #ffffff 0%, #b9d2ff 100%);
border-color: #2f6fed;
@@ -191,23 +191,24 @@
<button type="button" class="btn-secondary hp-oo-recommend-btn" id="hp-oo-recommend-atm" data-oo-rec="atm_straddle" title="最近平值 Call+Put" aria-pressed="false"><span class="hp-oo-check" aria-hidden="true"></span>推荐跨式</button>
<button type="button" class="btn-secondary hp-oo-recommend-btn" id="hp-oo-recommend-otm" data-oo-rec="double_otm" title="最近虚值 Call+Put" aria-pressed="false"><span class="hp-oo-check" aria-hidden="true"></span>推荐双虚</button>
<button type="button" class="btn-secondary" id="hp-oo-load-chain">刷新链</button>
<button type="button" class="btn-secondary hp-oo-expand-btn" id="hp-oo-expand-all" title="默认只显示 Call/Put 各最近 3 档" aria-pressed="false"><span class="hp-oo-check" aria-hidden="true"></span>显示全部</button>
</div>
<div class="options-strike-table-wrap options-strike-table-wrap--t">
<div class="options-strike-table-wrap hp-oo-table-wrap" id="hp-oo-table-wrap">
<table class="options-strike-table options-strike-table--t" id="hp-oo-table">
<thead>
<tr>
<th colspan="3" class="opt-t-head-call">Call</th>
<th colspan="4" class="opt-t-head-call">Call</th>
<th class="opt-t-head-mid">行权</th>
<th colspan="3" class="opt-t-head-put">Put</th>
<th colspan="4" class="opt-t-head-put">Put</th>
</tr>
<tr>
<th>卖一/张</th><th>实虚值</th><th>选用</th>
<th>卖一/张</th><th title="行权价÷卖一">杠杆</th><th>实虚值</th><th>选用</th>
<th>K</th>
<th>实虚值</th><th>卖一/张</th><th>选用</th>
<th>实虚值</th><th>卖一/张</th><th title="行权价÷卖一">杠杆</th><th>选用</th>
</tr>
</thead>
<tbody id="hp-oo-tbody">
<tr><td colspan="7" class="muted">请刷新期权链</td></tr>
<tr><td colspan="9" class="muted">请刷新期权链</td></tr>
</tbody>
</table>
</div>
@@ -327,4 +328,4 @@
</div>
</div>
</div>
<script src="/static/hedge_plan.js?v=35"></script>
<script src="/static/hedge_plan.js?v=36"></script>
+1 -1
View File
@@ -8,7 +8,7 @@
<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/instance_page.css?v=13">
<link rel="stylesheet" href="/static/instance_theme.css?v=113">
<link rel="stylesheet" href="/static/instance_theme.css?v=114">
<script src="/static/account_risk_badge.js?v=4"></script>
<script src="/static/open_submit_gate.js?v=1"></script>
<meta name="theme-color" content="#0b0d14">
+1 -1
View File
@@ -19,7 +19,7 @@
<link rel="manifest" href="/static/icons/manifest.webmanifest">
<title>{{ pwa_app_name }}</title>
<link rel="stylesheet" href="/static/instance_page.css?v=13">
<link rel="stylesheet" href="/static/instance_theme.css?v=113">
<link rel="stylesheet" href="/static/instance_theme.css?v=114">
</head>
<body