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 ? ' + -
+
- + - + - + - + - +
CallCall 行权PutPut
卖一/张实虚值选用卖一/张杠杆实虚值选用 K实虚值卖一/张选用实虚值卖一/张杠杆选用
请刷新期权链
请刷新期权链
@@ -327,4 +328,4 @@
- + diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 727692c..9be09b6 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -8,7 +8,7 @@ - + diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 8992ee5..958cda0 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -19,7 +19,7 @@ {{ pwa_app_name }} - +