From 6ffde1039178e65ad6c556ed981424f5e2996bd6 Mon Sep 17 00:00:00 2001 From: ojy Date: Wed, 13 May 2026 05:00:34 +0000 Subject: [PATCH] =?UTF-8?q?Editable=20strike=20in=20legs=20=E2=80=94=20re-?= =?UTF-8?q?prices=20from=20loaded=20chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Strike cell becomes a dropdown of available strikes (for that leg's symbol+expiry+type) drawn from the cached chain — seeded from chain.html's last-loaded chain (localStorage) and refreshed by the strategy page's own per-expiry chain fetch on open / Reload. Picking a new strike updates the leg's entry price, IV and mark from that contract and clears the lock. Co-Authored-By: Claude Sonnet 4.6 --- frontend/strategy.html | 57 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/frontend/strategy.html b/frontend/strategy.html index 3de3dc2..022f694 100644 --- a/frontend/strategy.html +++ b/frontend/strategy.html @@ -165,7 +165,12 @@ - + + + +
@@ -314,15 +319,59 @@ refreshing: false, showManual: false, toast: '', manual: { side:'long', type:'call', qty:1, strike:null, expiry:'', entryPrice:null, ivPct:null }, chart: null, _renderTimer: null, + _chainCache: {}, // "SYMBOL@EXPIRY" -> { "strike@type": optionRow } init() { this.reload(); - // pull live spot / marks / IVs on open (does NOT touch entry prices) + this._chainCache = this._seedChainCache(); + // pull live spot / marks / IVs (and per-expiry chains) on open if (this.legs.length > 0 && this.symbol) this.reloadMarket(false); // re-sync if another tab changed the basket window.addEventListener('storage', (e) => { if (e.key === StrategyStore.KEY) this.reload(); }); }, + // seed the strike picker from whatever chain was last loaded on chain.html + _seedChainCache() { + try { + const vs = (typeof ViewState !== 'undefined') ? ViewState.load('chain') : null; + if (!vs || !vs.symbol || !vs.expiry) return {}; + const map = {}; + for (const o of (vs.calls || [])) map[Number(o.strike) + '@call'] = o; + for (const o of (vs.puts || [])) map[Number(o.strike) + '@put'] = o; + return Object.keys(map).length ? { [vs.symbol + '@' + vs.expiry]: map } : {}; + } catch { return {}; } + }, + + _legMap(lv) { return this._chainCache[lv.symbol + '@' + lv.expiry] || null; }, + hasStrikeOpts(lv) { + const m = this._legMap(lv); + if (!m) return false; + for (const k of Object.keys(m)) if (k.endsWith('@' + lv.type)) return true; + return false; + }, + strikeOpts(lv) { + const m = this._legMap(lv); + const out = []; + if (m) for (const k of Object.keys(m)) { if (k.endsWith('@' + lv.type)) out.push(parseFloat(k)); } + if (!out.includes(lv.strike)) out.push(lv.strike); + return out.sort((a, b) => a - b); + }, + changeStrike(id, newStrike) { + const leg = this.legs.find(l => l.id === id); + if (!leg || !Number.isFinite(newStrike) || newStrike === leg.strike) return; + const m = this._legMap(leg); + const o = m && m[Number(newStrike) + '@' + leg.type]; + const patch = { strike: newStrike }; + if (o) { + const mid = Math.round(((o.midPrice ?? o.mid ?? o.bsPrice ?? leg.entryPrice) || 0) * 100) / 100; + patch.entryPrice = mid; + patch.currentMark = mid; + if (o.iv > 0) patch.iv = o.iv; + patch.locked = false; // it's a different contract now — start fresh + } + this.updateLeg(id, patch); + }, + reload() { const st = StrategyStore.load(); this.symbol = st.symbol || ''; @@ -492,6 +541,10 @@ } byExpiry[exp] = map; } + // make these chains available to the strike picker + const cacheAdds = {}; + for (const exp of Object.keys(byExpiry)) cacheAdds[this.symbol + '@' + exp] = byExpiry[exp]; + this._chainCache = { ...this._chainCache, ...cacheAdds }; const st = StrategyStore.load(); let updated = 0, relinked = 0; for (const leg of st.legs) {