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():