Fix expiration P/L curve in strategy analyzer
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -301,7 +301,9 @@
|
|||||||
get netCost() { return this.legs.reduce((s,l)=> s + legCost(l), 0); },
|
get netCost() { return this.legs.reduce((s,l)=> s + legCost(l), 0); },
|
||||||
get strategyName() { return detectStrategy(this.legs); },
|
get strategyName() { return detectStrategy(this.legs); },
|
||||||
get maxDTE() { return Math.max(1, Math.ceil(Math.max(0, ...this.legs.map(legDTE)))); },
|
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() {
|
get dteLabel() {
|
||||||
const d = new Date(Date.now() + this.dteOffset * DAY_MS);
|
const d = new Date(Date.now() + this.dteOffset * DAY_MS);
|
||||||
const ds = d.toISOString().slice(0,10);
|
const ds = d.toISOString().slice(0,10);
|
||||||
@@ -441,19 +443,25 @@
|
|||||||
const strikes = legs.map(l=>l.strike);
|
const strikes = legs.map(l=>l.strike);
|
||||||
const minK = Math.min(...strikes), maxK = Math.max(...strikes);
|
const minK = Math.min(...strikes), maxK = Math.max(...strikes);
|
||||||
const span = maxK - minK;
|
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 lo = Math.max(0.01, spot - half), hi = spot + half;
|
||||||
const N = 141;
|
const expAt = this.minDTE;
|
||||||
|
const N = 161;
|
||||||
const expData = [], tnData = [];
|
const expData = [], tnData = [];
|
||||||
let yMin = Infinity, yMax = -Infinity;
|
let yMin = Infinity, yMax = -Infinity;
|
||||||
for (let i = 0; i <= N; i++) {
|
for (let i = 0; i <= N; i++) {
|
||||||
const x = lo + (hi-lo)*i/N;
|
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);
|
const yt = plAt(legs, net, x, this.dteOffset);
|
||||||
expData.push([x, +ye.toFixed(2)]);
|
expData.push([x, +ye.toFixed(2)]);
|
||||||
tnData.push([x, +yt.toFixed(2)]);
|
tnData.push([x, +yt.toFixed(2)]);
|
||||||
yMin = Math.min(yMin, ye, yt); yMax = Math.max(yMax, ye, yt);
|
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);
|
const pad = Math.max((yMax - yMin) * 0.08, 1);
|
||||||
yMin -= pad; yMax += pad;
|
yMin -= pad; yMax += pad;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user