From 2ebc0eeb20e1e96b4fde34ddf0c7c42847664228 Mon Sep 17 00:00:00 2001 From: ojy Date: Wed, 13 May 2026 04:07:26 +0000 Subject: [PATCH] Fix expiration P/L curve in strategy analyzer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use exact (not floored) days-to-earliest-expiry so the "Expiration P/L" curve uses true intrinsic value — long call/put now show a flat loss floor at -premium, a sharp kink at the strike, and the correct breakeven, instead of a soft-cornered curve with ~1 day of residual time value - Clamp the y-axis when one tail is unbounded so the loss floor stays visible instead of being squished to a sliver by the runaway profit/loss tail - Slightly tighter default x-window; denser sampling (161 pts) Co-Authored-By: Claude Sonnet 4.6 --- frontend/strategy.html | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/strategy.html b/frontend/strategy.html index c6c3f5e..9210248 100644 --- a/frontend/strategy.html +++ b/frontend/strategy.html @@ -301,7 +301,9 @@ get netCost() { return this.legs.reduce((s,l)=> s + legCost(l), 0); }, get strategyName() { return detectStrategy(this.legs); }, get maxDTE() { return Math.max(1, Math.ceil(Math.max(0, ...this.legs.map(legDTE)))); }, - get minDTE() { return Math.max(0, Math.floor(Math.min(...this.legs.map(legDTE)))); }, + // exact (not floored) days to the earliest expiry — so the "expiration" + // curve uses true intrinsic value (sharp hockey stick), not a near-expiry BS approx + get minDTE() { return Math.max(0, Math.min(...this.legs.map(legDTE))); }, get dteLabel() { const d = new Date(Date.now() + this.dteOffset * DAY_MS); const ds = d.toISOString().slice(0,10); @@ -441,19 +443,25 @@ const strikes = legs.map(l=>l.strike); const minK = Math.min(...strikes), maxK = Math.max(...strikes); const span = maxK - minK; - const half = Math.max(spot*0.30, span*1.4, (maxK-spot)*1.3, (spot-minK)*1.3, 5); + const half = Math.max(spot*0.22, span*1.5, (maxK-spot)*1.4, (spot-minK)*1.4, 5); const lo = Math.max(0.01, spot - half), hi = spot + half; - const N = 141; + const expAt = this.minDTE; + const N = 161; const expData = [], tnData = []; let yMin = Infinity, yMax = -Infinity; for (let i = 0; i <= N; i++) { const x = lo + (hi-lo)*i/N; - const ye = plAt(legs, net, x, this.minDTE); + const ye = plAt(legs, net, x, expAt); const yt = plAt(legs, net, x, this.dteOffset); expData.push([x, +ye.toFixed(2)]); tnData.push([x, +yt.toFixed(2)]); yMin = Math.min(yMin, ye, yt); yMax = Math.max(yMax, ye, yt); } + // Keep the chart readable when one tail is (near-)unbounded: don't let it + // dominate the y-axis so badly the rest of the curve is a flat sliver. + const aHi = Math.max(yMax, 0), aLo = Math.max(-yMin, 0); + if (aLo > 1 && aHi > 6 * aLo) yMax = 6 * aLo; + if (aHi > 1 && aLo > 6 * aHi) yMin = -6 * aHi; const pad = Math.max((yMax - yMin) * 0.08, 1); yMin -= pad; yMax += pad;