Round leg prices to 2 decimals everywhere

Entry price, mark and strike are now rounded to 2dp on add, on edit, on
market reload, and on load (self-heals legacy entries) — fixes display of
float artifacts like "15.000000001". Also persist the leg's locked/currentMark
fields in the store.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ojy
2026-05-13 04:28:21 +00:00
parent 9219220270
commit 5348bf643a
2 changed files with 27 additions and 8 deletions

View File

@@ -21,6 +21,12 @@
const VERSION = 1; const VERSION = 1;
const MULTIPLIER = 100; // standard equity option contract size const MULTIPLIER = 100; // standard equity option contract size
/** Round a price to 2 decimals (kills float artifacts like 15.000000001). */
function round2(v) {
const n = Number(v);
return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0;
}
function empty() { function empty() {
return { v: VERSION, symbol: "", spotSnapshot: 0, updatedAt: null, legs: [] }; return { v: VERSION, symbol: "", spotSnapshot: 0, updatedAt: null, legs: [] };
} }
@@ -31,10 +37,15 @@
if (!raw) return empty(); if (!raw) return empty();
const obj = JSON.parse(raw); const obj = JSON.parse(raw);
if (!obj || obj.v !== VERSION || !Array.isArray(obj.legs)) return empty(); if (!obj || obj.v !== VERSION || !Array.isArray(obj.legs)) return empty();
// sanitize: drop legs with no usable strike / type // sanitize: drop legs with no usable strike / type; round prices to 2dp (self-heal)
obj.legs = obj.legs.filter( obj.legs = obj.legs
(l) => l && Number.isFinite(Number(l.strike)) && (l.type === "call" || l.type === "put") .filter((l) => l && Number.isFinite(Number(l.strike)) && (l.type === "call" || l.type === "put"))
); .map((l) => ({
...l,
strike: round2(l.strike),
entryPrice: round2(l.entryPrice),
currentMark: (typeof l.currentMark === "number" ? round2(l.currentMark) : (l.currentMark ?? null)),
}));
return obj; return obj;
} catch { } catch {
return empty(); return empty();
@@ -84,11 +95,13 @@
symbol: leg.symbol || state.symbol, symbol: leg.symbol || state.symbol,
expiry: leg.expiry, expiry: leg.expiry,
type: leg.type, type: leg.type,
strike: strike, strike: round2(strike),
side: leg.side === "short" ? "short" : "long", side: leg.side === "short" ? "short" : "long",
qty: Math.max(1, Math.round(Number(leg.qty) || 1)), qty: Math.max(1, Math.round(Number(leg.qty) || 1)),
entryPrice: Number(leg.entryPrice) || 0, entryPrice: round2(leg.entryPrice),
iv: Number(leg.iv) || 0, iv: Number(leg.iv) || 0,
locked: leg.locked === true,
currentMark: (typeof leg.currentMark === "number" ? round2(leg.currentMark) : null),
}); });
save(state); save(state);
return { state, replacedSymbol }; return { state, replacedSymbol };
@@ -105,7 +118,13 @@
function updateLeg(id, patch) { function updateLeg(id, patch) {
const state = load(); const state = load();
const leg = state.legs.find((l) => l.id === id); const leg = state.legs.find((l) => l.id === id);
if (leg) Object.assign(leg, patch); if (leg) {
const p = { ...patch };
if ("entryPrice" in p) p.entryPrice = round2(p.entryPrice);
if ("currentMark" in p && p.currentMark != null) p.currentMark = round2(p.currentMark);
if ("strike" in p) p.strike = round2(p.strike);
Object.assign(leg, p);
}
save(state); save(state);
return state; return state;
} }

View File

@@ -468,7 +468,7 @@
if (!map) continue; if (!map) continue;
const o = map[Number(leg.strike) + '@' + leg.type]; const o = map[Number(leg.strike) + '@' + leg.type];
if (!o) continue; if (!o) continue;
const mark = o.midPrice ?? o.mid ?? o.bsPrice ?? 0; const mark = Math.round((o.midPrice ?? o.mid ?? o.bsPrice ?? 0) * 100) / 100;
leg.currentMark = mark; leg.currentMark = mark;
if (o.iv > 0) leg.iv = o.iv; if (o.iv > 0) leg.iv = o.iv;
if (!leg.locked && mark > 0) { leg.entryPrice = mark; relinked++; } if (!leg.locked && mark > 0) { leg.entryPrice = mark; relinked++; }