diff --git a/frontend/strategy.html b/frontend/strategy.html index 9c11e09..8429468 100644 --- a/frontend/strategy.html +++ b/frontend/strategy.html @@ -153,7 +153,19 @@ Show - SideQtyTypeStrikeExpiry + SideQtyTypeStrike + +
+ Expiry + + +
+ Entry $MarkIV CostΔΘ/d @@ -177,7 +189,7 @@
- @@ -328,6 +340,7 @@ symbol: '', symbols: [], spot: 0, legs: [], dteOffset: 0, xZoom: 1, xPan: 0, lastHalfPct: 0, + expiryLocked: false, masterExpiry: '', refreshing: false, showManual: false, toast: '', manual: { side:'long', type:'call', qty:1, strike:null, expiry:'', entryPrice:null, ivPct:null }, chart: null, _renderTimer: null, @@ -431,6 +444,37 @@ } catch { return null; } }, + // ---- master expiry (lock-all-legs-to-one-date) ------------------- + get masterExpiryOpts() { + const set = new Set(); + for (const l of this.legs) if (l.expiry) set.add(l.expiry); + const cache = this._expiryCache[this.symbol] || []; + for (const e of cache) set.add(e); + return [...set].sort(); + }, + toggleExpiryLock() { + if (this.expiryLocked) { this.expiryLocked = false; return; } + // turning on — pick the most common leg expiry as the master, then snap + const counts = {}; + for (const l of this.legs) counts[l.expiry] = (counts[l.expiry] || 0) + 1; + let best = '', bestN = -1; + for (const e of Object.keys(counts)) if (counts[e] > bestN) { best = e; bestN = counts[e]; } + this.masterExpiry = best || (this.legs[0] && this.legs[0].expiry) || ''; + this.expiryLocked = true; + if (this.masterExpiry) this.changeAllExpiry(this.masterExpiry); + }, + async onMasterExpiry(newExp) { + if (!this.expiryLocked || !newExp) return; + this.masterExpiry = newExp; + await this.changeAllExpiry(newExp); + }, + async changeAllExpiry(newExp) { + const ids = this.legs.filter(l => l.expiry !== newExp).map(l => l.id); + for (const id of ids) { + await this.changeExpiry(id, newExp); + } + }, + async changeExpiry(id, newExpiry) { const leg = this.legs.find(l => l.id === id); if (!leg || !newExpiry || newExpiry === leg.expiry) return; @@ -470,6 +514,9 @@ this.symbols = st.symbols || []; this.spot = st.spotSnapshot || 0; this.legs = st.legs || []; + if (this.legs.length && (!this.masterExpiry || !this.legs.some(l => l.expiry === this.masterExpiry))) { + this.masterExpiry = this.legs[0].expiry || ''; + } if (this.dteOffset > this.maxDTE) this.dteOffset = this.maxDTE; if (this.legs.length > 0) this.$nextTick(() => this.renderChart()); else if (this.chart) this.chart.updateSeries([{name:'P/L',data:[]},{name:'P/L',data:[]}]); }, @@ -478,6 +525,7 @@ if (!sym || sym === this.symbol) return; StrategyStore.setActive(sym); this.dteOffset = 0; this.xPan = 0; this.xZoom = 1; + this.expiryLocked = false; this.masterExpiry = ''; this.reload(); this._ensureExpiries(sym); if (this.legs.length > 0) this.reloadMarket(false); @@ -617,6 +665,7 @@ if (!confirm('Clear all ' + (this.symbol || '') + ' legs?')) return; StrategyStore.clear(); this.dteOffset = 0; this.xPan = 0; this.xZoom = 1; + this.expiryLocked = false; this.masterExpiry = ''; if (this.chart) { this.chart.destroy(); this.chart = null; } this.reload(); // re-renders the chart if another symbol's basket is now active },