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,