/** * Strategy basket persistence — localStorage-backed. * Exposed as window.StrategyStore. No modules, no deps. * * Stored shape (key "optionsPricer:strategy"): * { * v: 1, * symbol: "SPY", * spotSnapshot: 738.18, // spot when last leg was added (reference only) * updatedAt: "2026-05-13T...Z", * legs: [ * { id, symbol, expiry:"YYYY-MM-DD", type:"call"|"put", * strike, side:"long"|"short", qty, entryPrice, iv } * ] * } */ (function () { "use strict"; const KEY = "optionsPricer:strategy"; const VERSION = 1; const MULTIPLIER = 100; // standard equity option contract size function empty() { return { v: VERSION, symbol: "", spotSnapshot: 0, updatedAt: null, legs: [] }; } function load() { try { const raw = localStorage.getItem(KEY); if (!raw) return empty(); const obj = JSON.parse(raw); if (!obj || obj.v !== VERSION || !Array.isArray(obj.legs)) return empty(); // sanitize: drop legs with no usable strike / type obj.legs = obj.legs.filter( (l) => l && Number.isFinite(Number(l.strike)) && (l.type === "call" || l.type === "put") ); return obj; } catch { return empty(); } } function save(state) { const s = state || empty(); s.v = VERSION; s.updatedAt = new Date().toISOString(); try { localStorage.setItem(KEY, JSON.stringify(s)); } catch (e) { console.warn("[StrategyStore] save failed:", e); } return s; } function clear() { try { localStorage.removeItem(KEY); } catch {} return empty(); } /** * Add a leg. `leg` should provide: symbol, expiry, type, strike, side, qty, entryPrice, iv. * Returns { state, replacedSymbol } — replacedSymbol is the old symbol if the basket * was wiped due to a symbol mismatch (caller should have confirmed first via mismatch()). */ function addLeg(leg) { const strike = Number(leg && leg.strike); if (!Number.isFinite(strike) || strike <= 0) { console.warn("[StrategyStore] addLeg: invalid strike", leg); return { state: load(), replacedSymbol: null }; } let state = load(); let replacedSymbol = null; if (state.legs.length > 0 && state.symbol && leg.symbol && state.symbol !== leg.symbol) { replacedSymbol = state.symbol; state = empty(); } state.symbol = leg.symbol || state.symbol || ""; if (typeof leg.spotSnapshot === "number" && leg.spotSnapshot > 0) { state.spotSnapshot = leg.spotSnapshot; } state.legs.push({ id: ((typeof crypto !== "undefined" && crypto.randomUUID) ? crypto.randomUUID() : "id-" + Date.now() + "-" + Math.random().toString(36).slice(2)), symbol: leg.symbol || state.symbol, expiry: leg.expiry, type: leg.type, strike: strike, side: leg.side === "short" ? "short" : "long", qty: Math.max(1, Math.round(Number(leg.qty) || 1)), entryPrice: Number(leg.entryPrice) || 0, iv: Number(leg.iv) || 0, }); save(state); return { state, replacedSymbol }; } function removeLeg(id) { const state = load(); state.legs = state.legs.filter((l) => l.id !== id); if (state.legs.length === 0) { return clear(); } save(state); return state; } function updateLeg(id, patch) { const state = load(); const leg = state.legs.find((l) => l.id === id); if (leg) Object.assign(leg, patch); save(state); return state; } /** True if adding a leg of `symbol` would wipe an existing different-symbol basket. */ function mismatch(symbol) { const state = load(); return state.legs.length > 0 && state.symbol && symbol && state.symbol !== symbol ? state.symbol : null; } function count() { return load().legs.length; } window.StrategyStore = { KEY, VERSION, MULTIPLIER, empty, load, save, clear, addLeg, removeLeg, updateLeg, mismatch, count, }; })();