diff --git a/lib/common/static/hedge_plan.js b/lib/common/static/hedge_plan.js
index a04cf6a..3ae37b6 100644
--- a/lib/common/static/hedge_plan.js
+++ b/lib/common/static/hedge_plan.js
@@ -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 = '
| 请选择到期日 |
';
+ tbody.innerHTML = '| 请选择到期日 |
';
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 = '| 该筛选下暂无平值/虚值合约 |
';
+ 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 =
'' +
callAsk +
+ ' | ' +
+ callLev +
' | ' +
(callOk ? moneynessBadge(call) : "—") +
" | " +
@@ -986,6 +1089,8 @@
(putOk ? moneynessBadge(put) : "—") +
' | ' +
putAsk +
+ ' | ' +
+ putLev +
" | " +
(putOk
? ' |