From a21b962bf165925f35ea5f6004752409e3e6749e Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 15 Jul 2026 22:24:52 +0800 Subject: [PATCH] Fix options mark price float junk and target index input rollback. Skip full card redraw while the monitor target field is focused, and format mark/avg without relying on dusty server strings. Co-authored-by: Cursor --- lib/common/static/options_panel.js | 44 +++++++++++---------- lib/common/static/options_position_cards.js | 9 +++-- lib/exchange/okx_options_lib.py | 4 +- lib/options/templates/options_panel.html | 2 +- tests/test_options_pricing.py | 2 + 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index b03545c..679b722 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -47,7 +47,11 @@ if (v === null || v === undefined || Number.isNaN(Number(v))) return "—"; const n = Number(v); const tick = Number(tickSz); - if (!tickSz || Number.isNaN(tick) || tick <= 0) return String(n); + if (!tickSz || Number.isNaN(tick) || tick <= 0) { + // 无 tick 时裁掉浮点毛刺,勿 482.4881990066513 + let s = n.toFixed(4).replace(/\.?0+$/, ""); + return s || "0"; + } let decimals = 0; if (tick < 1) decimals = Math.max(0, -Math.round(Math.log10(tick))); else if (String(tick).indexOf(".") >= 0) decimals = String(tick).split(".")[1].length; @@ -1006,8 +1010,9 @@ const closeSheets = p.avail_pos != null && Number(p.avail_pos) > 0 ? p.avail_pos : p.pos; const tickSz = p.tick_sz; const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null); - const avgTxt = fmtDisplay(p.avg_px_fmt, fmtOptionPx(p.avg_px, tickSz)); - const markTxt = fmtDisplay(p.mark_px_fmt, fmtOptionPx(p.mark_px, tickSz)); + // 优先用数值+tick 现算,避免接口侧 mark_px_fmt 带着浮点毛刺直出 + const avgTxt = p.avg_px != null ? fmtOptionPx(p.avg_px, tickSz) : fmtDisplay(p.avg_px_fmt); + const markTxt = p.mark_px != null ? fmtOptionPx(p.mark_px, tickSz) : fmtDisplay(p.mark_px_fmt); return ( '
' + '
' + (p.inst_id || "") + '' + @@ -1430,12 +1435,22 @@ const livePane = document.getElementById("opt-pos-live"); if (!wrap) return; const active = document.activeElement; - const draftFocusInst = - active && active.classList && active.classList.contains("opt-pos-target-input") - ? active.getAttribute("data-inst") - : null; - const draftSelStart = draftFocusInst != null ? active.selectionStart : null; - const draftSelEnd = draftFocusInst != null ? active.selectionEnd : null; + // 正在输入目标指数:先落到草稿,本轮不重绘整卡,避免数字往回退 + if (active && active.classList && active.classList.contains("opt-pos-target-input")) { + const focusInst = active.getAttribute("data-inst") || ""; + if (focusInst) { + state.targetDraftByInst[focusInst] = String(active.value || ""); + } + return; + } + // 未聚焦时也同步可见输入,防止漏掉 input 事件 + wrap.querySelectorAll(".opt-pos-target-input").forEach(function (inp) { + const id = inp.getAttribute("data-inst") || ""; + if (!id) return; + const v = String(inp.value || ""); + if (v.trim() === "") delete state.targetDraftByInst[id]; + else state.targetDraftByInst[id] = v; + }); wrap.innerHTML = ""; if (!list.length) { if (empty) empty.style.display = ""; @@ -1469,17 +1484,6 @@ if (window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) { OptionsExpiryCountdown.ensureTimer(); } - if (draftFocusInst) { - const reinp = wrap.querySelector('.opt-pos-target-input[data-inst="' + draftFocusInst + '"]'); - if (reinp) { - reinp.focus(); - try { - if (draftSelStart != null && draftSelEnd != null) { - reinp.setSelectionRange(draftSelStart, draftSelEnd); - } - } catch (e) { /* ignore */ } - } - } } async function refreshPositions() { diff --git a/lib/common/static/options_position_cards.js b/lib/common/static/options_position_cards.js index a48b6c9..e4e6d61 100644 --- a/lib/common/static/options_position_cards.js +++ b/lib/common/static/options_position_cards.js @@ -16,7 +16,10 @@ if (v === null || v === undefined || Number.isNaN(Number(v))) return "—"; const n = Number(v); const tick = Number(tickSz); - if (!tickSz || Number.isNaN(tick) || tick <= 0) return String(n); + if (!tickSz || Number.isNaN(tick) || tick <= 0) { + let s = n.toFixed(4).replace(/\.?0+$/, ""); + return s || "0"; + } let decimals = 0; if (tick < 1) decimals = Math.max(0, -Math.round(Math.log10(tick))); else if (String(tick).indexOf(".") >= 0) decimals = String(tick).split(".")[1].length; @@ -131,8 +134,8 @@ const closePreview = p.close_preview || {}; const tickSz = p.tick_sz; const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null); - const avgTxt = fmtDisplay(p.avg_px_fmt, fmtOptionPx(p.avg_px, tickSz)); - const markTxt = fmtDisplay(p.mark_px_fmt, fmtOptionPx(p.mark_px, tickSz)); + const avgTxt = p.avg_px != null ? fmtOptionPx(p.avg_px, tickSz) : fmtDisplay(p.avg_px_fmt); + const markTxt = p.mark_px != null ? fmtOptionPx(p.mark_px, tickSz) : fmtDisplay(p.mark_px_fmt); let headActions = ""; if (!readOnly) { const closeSheets = p.avail_pos != null && Number(p.avail_pos) > 0 ? p.avail_pos : p.pos; diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py index 9c9d2aa..eab0f4d 100644 --- a/lib/exchange/okx_options_lib.py +++ b/lib/exchange/okx_options_lib.py @@ -115,7 +115,9 @@ def round_option_px(px: float, tick_sz: Any, side: str) -> float: def format_option_px(px: float, tick_sz: Any) -> str: tick = _safe_float(tick_sz) if tick is None or tick <= 0: - return str(px) + # 无 tick 时裁到 4 位并去尾零,避免 482.4881990066513 这类浮点毛刺 + s = f"{float(px):.4f}".rstrip("0").rstrip(".") + return s or "0" if tick < 1: decimals = max(0, -int(round(math.log10(tick)))) return f"{px:.{decimals}f}".rstrip("0").rstrip(".") or "0" diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index bf562bd..99c4e17 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -273,4 +273,4 @@
- + diff --git a/tests/test_options_pricing.py b/tests/test_options_pricing.py index 077c16b..28ff4eb 100644 --- a/tests/test_options_pricing.py +++ b/tests/test_options_pricing.py @@ -21,6 +21,8 @@ def test_round_option_px(): assert format_option_px(1370, "5") == "1370" assert format_option_px(1160, 5) == "1160" assert format_option_px(1000, "5") == "1000" + # 无 tick 时不得透出浮点毛刺 + assert format_option_px(482.4881990066513, None) == "482.4882" def test_premium_per_sheet():