diff --git a/frontend/strategy.html b/frontend/strategy.html index 9210248..8e36b21 100644 --- a/frontend/strategy.html +++ b/frontend/strategy.html @@ -352,9 +352,14 @@ } } const uniqBE = bes.filter((v,i)=> i===0 || Math.abs(v - bes[i-1]) > 1e-6); - // extremes within sample + // extremes — include the grid samples AND the exact kink points (strikes) + // plus the sample bounds, since piecewise payoffs peak/trough at strikes let maxY = -Infinity, minY = Infinity; for (const y of ys) { if (y > maxY) maxY = y; if (y < minY) minY = y; } + for (const k of new Set([...legs.map(l=>l.strike), lo0, hi0])) { + const y = plAt(legs, net, k, this.minDTE); + if (y > maxY) maxY = y; if (y < minY) minY = y; + } // unbounded detection from the far-right slope of the expiration curve // (downside is always bounded since the underlying can't go below 0) const slopeR = (ys[Nd] - ys[Nd-1]) / (xs[Nd] - xs[Nd-1]); @@ -447,10 +452,15 @@ const lo = Math.max(0.01, spot - half), hi = spot + half; const expAt = this.minDTE; const N = 161; + // x-grid = evenly spaced points + the exact strike kinks (so payoff + // vertices — butterfly peak, condor body, etc. — render sharply) + const xGrid = []; + for (let i = 0; i <= N; i++) xGrid.push(lo + (hi-lo)*i/N); + for (const k of strikes) if (k > lo && k < hi) xGrid.push(k); + xGrid.sort((a,b)=>a-b); const expData = [], tnData = []; let yMin = Infinity, yMax = -Infinity; - for (let i = 0; i <= N; i++) { - const x = lo + (hi-lo)*i/N; + for (const x of xGrid) { const ye = plAt(legs, net, x, expAt); const yt = plAt(legs, net, x, this.dteOffset); expData.push([x, +ye.toFixed(2)]); @@ -467,7 +477,7 @@ // breakevens for light vertical lines const bes = []; - for (let i = 1; i <= N; i++) { + for (let i = 1; i < expData.length; i++) { const y0 = expData[i-1][1], y1 = expData[i][1]; if ((y0 < 0 && y1 > 0) || (y0 > 0 && y1 < 0)) { const x = expData[i-1][0] + (0 - y0) * (expData[i][0]-expData[i-1][0]) / (y1 - y0);