diff --git a/frontend/strategy.html b/frontend/strategy.html index 022f694..e2ffc78 100644 --- a/frontend/strategy.html +++ b/frontend/strategy.html @@ -116,7 +116,9 @@
- +
@@ -315,7 +317,8 @@ // state symbol: '', symbols: [], spot: 0, legs: [], dteOffset: 0, - xZoom: 1, lastHalfPct: 0, + xZoom: 1, xPan: 0, lastHalfPct: 0, + _drag: null, refreshing: false, showManual: false, toast: '', manual: { side:'long', type:'call', qty:1, strike:null, expiry:'', entryPrice:null, ivPct:null }, chart: null, _renderTimer: null, @@ -385,7 +388,7 @@ switchSymbol(sym) { if (!sym || sym === this.symbol) return; StrategyStore.setActive(sym); - this.dteOffset = 0; + this.dteOffset = 0; this.xPan = 0; this.xZoom = 1; this.reload(); if (this.legs.length > 0) this.reloadMarket(false); }, @@ -499,7 +502,7 @@ clearAll() { if (!confirm('Clear all ' + (this.symbol || '') + ' legs?')) return; StrategyStore.clear(); - this.dteOffset = 0; + this.dteOffset = 0; this.xPan = 0; this.xZoom = 1; if (this.chart) { this.chart.destroy(); this.chart = null; } this.reload(); // re-renders the chart if another symbol's basket is now active }, @@ -583,7 +586,23 @@ }, zoomIn() { this.xZoom = Math.max(0.2, +(this.xZoom / 1.4).toFixed(3)); this.renderChart(); }, zoomOut() { this.xZoom = Math.min(8, +(this.xZoom * 1.4).toFixed(3)); this.renderChart(); }, - zoomFit() { this.xZoom = 1; this.renderChart(); }, + zoomFit() { this.xZoom = 1; this.xPan = 0; this.renderChart(); }, + + // click-and-drag to pan the price window + startPan(e) { + if (e.button !== 0) return; + const pw = (this._lastPlotW && this._lastPlotW > 50) ? this._lastPlotW + : ((document.getElementById('plChart')?.clientWidth || 600) - 70); + this._drag = { x: e.clientX, pan: this.xPan || 0, half: this._lastHalf || ((this.spot || 100) * 0.22), pw }; + e.preventDefault(); + }, + onPan(e) { + if (!this._drag) return; + const dx = e.clientX - this._drag.x; // drag right -> show lower prices + this.xPan = this._drag.pan - (dx / this._drag.pw) * (2 * this._drag.half); + this.scheduleRender(); + }, + endPan() { if (this._drag) this._drag = null; }, renderChart() { const legs = this.activeLegs, net = this.netCost; @@ -597,8 +616,10 @@ const span = maxK - minK; const baseHalf = Math.max(spot*0.22, span*1.5, (maxK-spot)*1.4, (spot-minK)*1.4, 5); const half = baseHalf * (this.xZoom || 1); - const lo = Math.max(0.01, spot - half), hi = spot + half; + const center = spot + (this.xPan || 0); + const lo = Math.max(0.01, center - half), hi = center + half; this.lastHalfPct = spot > 0 ? Math.round(half / spot * 100) : 0; + this._lastHalf = half; this._lastSpot = spot; const expAt = this.minDTE; const N = 221; // x-grid = evenly spaced points + the exact strike kinks (so payoff @@ -690,6 +711,11 @@ this.chart = new ApexCharts(document.getElementById('plChart'), opts); this.chart.render(); } + // remember the plot-area width for drag-to-pan math + try { + const gw = this.chart && this.chart.w && this.chart.w.globals && this.chart.w.globals.gridWidth; + this._lastPlotW = (gw && gw > 50) ? gw : ((document.getElementById('plChart')?.clientWidth || 600) - 70); + } catch {} }, fmtMoney,