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 <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-15 22:24:52 +08:00
parent 6f934ecbbf
commit a21b962bf1
5 changed files with 36 additions and 25 deletions
+24 -20
View File
@@ -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 (
'<div class="pos-card-head">' +
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + '</strong>' +
@@ -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() {
+6 -3
View File
@@ -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;
+3 -1
View File
@@ -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"
+1 -1
View File
@@ -273,4 +273,4 @@
</div>
</div>
<script src="/static/options_expiry_countdown.js?v=1"></script>
<script src="/static/options_panel.js?v=32"></script>
<script src="/static/options_panel.js?v=33"></script>
+2
View File
@@ -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():