Editable strike in legs — re-prices from loaded chain

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 <noreply@anthropic.com>
This commit is contained in:
ojy
2026-05-13 05:00:34 +00:00
parent 957558fc76
commit 6ffde10391

View File

@@ -165,7 +165,12 @@
</td>
<td style="width:5rem"><input type="number" min="1" step="1" class="form-control form-control-sm" :value="lv.qty" @change="updateLeg(lv.id, { qty: Math.max(1, Math.round(+$event.target.value||1)) })"></td>
<td><span class="badge" :class="lv.type==='call' ? 'bg-success-lt text-success' : 'bg-danger-lt text-danger'" x-text="lv.type"></span></td>
<td class="text-end mono" x-text="lv.strike"></td>
<td class="text-end mono" style="width:7rem">
<select x-show="hasStrikeOpts(lv)" class="form-select form-select-sm text-end" :value="lv.strike" @change="changeStrike(lv.id, +$event.target.value)" title="Change strike — entry price, IV & mark update from the loaded chain">
<template x-for="k in strikeOpts(lv)" :key="k"><option :value="k" x-text="k"></option></template>
</select>
<span x-show="!hasStrikeOpts(lv)" x-text="lv.strike"></span>
</td>
<td class="mono small" x-text="lv.expiry"></td>
<td style="width:8.5rem">
<div class="input-group input-group-sm flex-nowrap">
@@ -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) {