diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index f193369..63fcd86 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -4122,6 +4122,34 @@ html[data-theme="light"] .options-estimate-row { .options-page-wrap .opt-target-row-hint { font-size: 0.7rem; } +.options-page-wrap .opt-target-armed { + font-size: 0.78rem; + color: #9ad0ff; + font-variant-numeric: tabular-nums; +} +.options-page-wrap .opt-target-est { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + font-size: 0.78rem; +} +.options-page-wrap .opt-target-est--idle:empty { + display: none; +} +.options-page-wrap .opt-target-est-item { + display: inline-flex; + align-items: baseline; + gap: 4px; +} +.options-page-wrap .opt-target-est-item .k { + color: #9aa8c7; + font-size: 0.7rem; +} +.options-page-wrap .opt-target-est-item .v { + font-variant-numeric: tabular-nums; + font-weight: 600; +} .opt-target-monitors { margin: 0 0 10px; padding: 10px 12px; diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 1bdfed0..2e38cc0 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -859,23 +859,90 @@ ); } + function posEthAmount(p) { + if (p.eth_amount != null && Number(p.eth_amount) > 0) return Number(p.eth_amount); + const sheets = Number(p.avail_pos != null ? p.avail_pos : p.pos); + const ct = Number(p.ct_mult != null ? p.ct_mult : 0.01); + if (Number.isFinite(sheets) && sheets > 0 && Number.isFinite(ct) && ct > 0) return sheets * ct; + return null; + } + + function formatTargetEstimateHtml(optType, strike, targetIdx, ethAmount, premiumPaid) { + const value = estimateExpiryValue(optType, strike, targetIdx, ethAmount); + const profit = estimateExpiryProfit(optType, strike, targetIdx, ethAmount, premiumPaid); + if (value == null && profit == null) return ""; + let html = ''; + html += '价值' + + (value == null ? "—" : fmtUsdc(value) + " USDC") + ""; + html += '预估盈利' + + (profit == null ? "—" : fmtUsdcSigned(profit)) + ""; + html += ""; + return html; + } + function renderTargetDelegateRow(p) { const inst = p.inst_id || ""; - const tgt = p.target_index != null && p.target_index !== "" ? String(p.target_index) : ""; - const armed = tgt !== ""; + const tgt = p.target_index != null && p.target_index !== "" ? Number(p.target_index) : null; + const armed = tgt != null && Number.isFinite(tgt) && tgt > 0; + const ethAmt = posEthAmount(p); + const prem = p.premium_paid; + const estHtml = armed + ? formatTargetEstimateHtml(p.opt_type, p.strike, tgt, ethAmt, prem) + : ''; return ( - '
' + + '
' + '委托' + - '' + + '' + '' + '" + + (armed + ? '目标 ' + fmt(tgt, 1) + "" + : "") + + estHtml + '' + - (armed ? "监控中 · 到位按买一限价平 · 无止损" : "目标=监控指数 · 到位按买一限价平 · 到期即止损") + + (armed ? "监控中 · 到位按买一限价平" : "输入后设定 · 到位按买一限价平 · 到期即止损") + "" + "
" ); } + function updatePosTargetEstimate(row) { + if (!row) return; + const est = row.querySelector(".opt-target-est"); + if (!est) return; + const inp = row.querySelector(".opt-pos-target-input"); + const typed = inp ? String(inp.value || "").trim() : ""; + const armed = row.getAttribute("data-armed-target") || ""; + const targetRaw = typed !== "" ? typed : armed; + if (targetRaw === "") { + est.className = "opt-target-est opt-target-est--idle"; + est.innerHTML = ""; + return; + } + const html = formatTargetEstimateHtml( + row.getAttribute("data-opt-type"), + row.getAttribute("data-strike"), + targetRaw, + row.getAttribute("data-eth"), + row.getAttribute("data-prem") + ); + if (!html) { + est.className = "opt-target-est opt-target-est--idle"; + est.innerHTML = ""; + return; + } + const tmp = document.createElement("div"); + tmp.innerHTML = html; + const node = tmp.firstChild; + est.className = "opt-target-est"; + est.innerHTML = node ? node.innerHTML : ""; + } + function renderPositionCard(p) { return ( '
' + @@ -953,6 +1020,9 @@ }); container.querySelectorAll(".opt-pos-target-input").forEach(function (inp) { inp.addEventListener("click", function (e) { e.stopPropagation(); }); + inp.addEventListener("input", function () { + updatePosTargetEstimate(inp.closest(".opt-target-row")); + }); inp.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); @@ -979,6 +1049,7 @@ if (!inst) return; const card = document.querySelector('.opt-pos-card[data-inst="' + inst + '"]') || document.querySelector('.opt-pos-accordion-item[data-inst="' + inst + '"]'); + const row = card ? card.querySelector(".opt-target-row") : null; const inp = card ? card.querySelector(".opt-pos-target-input") : null; const raw = inp ? String(inp.value || "").trim() : ""; const tgt = parseFloat(raw); @@ -997,6 +1068,11 @@ alert(d.msg || "设定失败"); return; } + if (inp) inp.value = ""; + if (row) { + row.setAttribute("data-armed-target", String(tgt)); + updatePosTargetEstimate(row); + } await refreshAllPositions(); } finally { if (btn) btn.disabled = false; diff --git a/lib/common/static/options_position_cards.js b/lib/common/static/options_position_cards.js index cf64c96..5c164a9 100644 --- a/lib/common/static/options_position_cards.js +++ b/lib/common/static/options_position_cards.js @@ -115,7 +115,33 @@ '
按买盘回收' + fmtClosePreview(closePreview, p.premium_paid, hub) + "
" + "
" + (p.target_index != null - ? '
委托目标指数 ' + fmt(p.target_index, 1) + "监控中 · 到位按买一限价平
" + ? (function () { + const eth = p.eth_amount != null ? Number(p.eth_amount) + : (Number(p.pos) > 0 ? Number(p.pos) * Number(p.ct_mult || 0.01) : null); + const strike = Number(p.strike); + const tgt = Number(p.target_index); + const prem = Number(p.premium_paid); + let profit = null; + let value = null; + if (Number.isFinite(tgt) && Number.isFinite(strike) && eth > 0) { + const o = String(p.opt_type || "").toUpperCase(); + const intrinsic = o === "C" ? Math.max(0, tgt - strike) : o === "P" ? Math.max(0, strike - tgt) : null; + if (intrinsic != null) { + value = Math.round(intrinsic * eth * 100) / 100; + if (Number.isFinite(prem)) profit = Math.round((value - prem) * 100) / 100; + } + } + const profitTxt = profit == null ? "—" : ((profit > 0 ? "+" : "") + fmtUsdc(profit) + " USDC"); + const profitCls = profit > 0 ? " pnl-pos" : profit < 0 ? " pnl-neg" : ""; + return ( + '
' + + '委托' + + '目标 ' + fmt(p.target_index, 1) + "" + + '价值 ' + (value == null ? "—" : fmtUsdc(value) + " USDC") + "" + + '预估盈利 ' + profitTxt + "" + + '监控中 · 到位按买一限价平
' + ); + })() : "") ); }