Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
<!DOCTYPE html>
< html lang = "en" data-bs-theme = "dark" >
< head >
< meta charset = "UTF-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
< title > Strategy P/L — Options Pricer< / title >
< link rel = "stylesheet" href = "/assets/tabler.min.css" / >
< link rel = "stylesheet" href = "/assets/tabler-vendors.min.css" / >
< script src = "/assets/apexcharts.min.js" > < / script >
< style >
.chart-card { background:#1e2030; border:1px solid #2d3045; border-radius:.5rem; }
.chart-card .card-header { background:transparent; border-bottom:1px solid #2d3045; }
#plChart { background:#1e2030; border-radius:0 0 .5rem .5rem; }
[x-cloak] { display:none !important; }
.leg-table th { background:#1a1c2e; color:#8b95a7; font-size:.7rem; text-transform:uppercase; letter-spacing:.05em; border-bottom:1px solid #2d3045; }
.leg-table td { border-bottom:1px solid #1e2030; color:#d0d5e0; font-size:.85rem; vertical-align:middle; }
.leg-table input, .leg-table select { background:#1e2030; border-color:#2d3045; color:#fff; }
.stat-box { background:#1e2030; border:1px solid #2d3045; border-radius:.5rem; padding:.75rem 1rem; }
.stat-box .lbl { color:#8b95a7; font-size:.72rem; text-transform:uppercase; letter-spacing:.05em; font-weight:600; }
.stat-box .val { font-size:1.15rem; font-weight:700; color:#fff; }
.val.pos { color:#51cf66; } .val.neg { color:#ff6b6b; } .val.amber { color:#ffd43b; }
.mono { font-family:'JetBrains Mono','Fira Code',monospace; }
.apexcharts-tooltip { background:#1e2030 !important; border:1px solid #2d3045 !important; color:#fff !important; }
.apexcharts-tooltip-title { background:#2d3045 !important; border-bottom:1px solid #3a3f5a !important; }
.toast-mini { position:fixed; bottom:1rem; right:1rem; z-index:1080; }
< / style >
< / head >
< body class = "antialiased" >
< div class = "wrapper" x-data = "strategyApp()" x-init = "init()" >
<!-- Sidebar -->
< aside class = "navbar navbar-vertical navbar-expand-lg" data-bs-theme = "dark" >
< div class = "container-fluid" >
< button class = "navbar-toggler" type = "button" data-bs-toggle = "collapse" data-bs-target = "#sb-menu" aria-label = "Toggle navigation" > < span class = "navbar-toggler-icon" > < / span > < / button >
< h1 class = "navbar-brand navbar-brand-autodark" >
< a href = "index.html" class = "text-decoration-none d-flex align-items-center gap-2" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "28" height = "28" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" > < path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / > < rect x = "4" y = "8" width = "4" height = "8" rx = "1" / > < line x1 = "6" y1 = "4" x2 = "6" y2 = "8" / > < line x1 = "6" y1 = "16" x2 = "6" y2 = "20" / > < rect x = "16" y = "6" width = "4" height = "10" rx = "1" / > < line x1 = "18" y1 = "2" x2 = "18" y2 = "6" / > < line x1 = "18" y1 = "16" x2 = "18" y2 = "22" / > < / svg >
< span class = "fw-bold" > Options Pricer< / span >
< / a >
< / h1 >
< div class = "collapse navbar-collapse" id = "sb-menu" >
< ul class = "navbar-nav pt-lg-3" >
< li class = "nav-item" > < a class = "nav-link" href = "index.html" > < span class = "nav-link-icon d-md-none d-lg-inline-block" > < svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" > < path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / > < path d = "M5 12l-2 0l9 -9l9 9l-2 0" / > < path d = "M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" / > < path d = "M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" / > < / svg > < / span > < span class = "nav-link-title" > Dashboard< / span > < / a > < / li >
< li class = "nav-item" > < a class = "nav-link" href = "chain.html" > < span class = "nav-link-icon d-md-none d-lg-inline-block" > < svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" > < path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / > < rect x = "3" y = "5" width = "18" height = "14" rx = "2" / > < path d = "M3 10l18 0" / > < path d = "M10 5v14" / > < / svg > < / span > < span class = "nav-link-title" > Options Chain< / span > < / a > < / li >
< li class = "nav-item" > < a class = "nav-link" href = "surface.html" > < span class = "nav-link-icon d-md-none d-lg-inline-block" > < svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" > < path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / > < path d = "M3 12c1.333 -4.667 2.667 -7 4 -7s2.667 2.333 4 7s2.667 7 4 7s2.667 -2.333 4 -7" / > < / svg > < / span > < span class = "nav-link-title" > Vol Surface< / span > < / a > < / li >
< li class = "nav-item active" > < a class = "nav-link" href = "strategy.html" > < span class = "nav-link-icon d-md-none d-lg-inline-block" > < svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" > < path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / > < path d = "M4 19l4 -6l4 2l4 -8l4 5" / > < path d = "M4 4v16h16" / > < / svg > < / span > < span class = "nav-link-title" > Strategy P/L< / span > < / a > < / li >
< li class = "nav-item" > < a class = "nav-link" href = "tracker.html" > < span class = "nav-link-icon d-md-none d-lg-inline-block" > < svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" > < path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / > < path d = "M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" / > < path d = "M12 12m-5 0a5 5 0 1 0 10 0a5 5 0 1 0 -10 0" / > < path d = "M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" / > < path d = "M15 12l-3 -3" / > < / svg > < / span > < span class = "nav-link-title" > Tracker< / span > < / a > < / li >
< / ul >
< / div >
< / div >
< / aside >
<!-- Page -->
< div class = "page-wrapper" >
< div class = "page-header d-print-none" >
< div class = "container-xl" >
< div class = "row g-2 align-items-center" >
2026-05-13 04:47:16 +00:00
< div class = "col-auto" x-show = "symbols.length > 1" x-cloak >
< label class = "form-label text-secondary mb-1" for = "symPicker" > Strategy< / label >
< select id = "symPicker" class = "form-select form-select-sm fw-bold" style = "min-width:7rem" :value = "symbol" @ change = "switchSymbol($event.target.value)" aria-label = "Pick strategy symbol" >
< template x-for = "s in symbols" :key = "s" > < option :value = "s" x-text = "s" > < / option > < / template >
< / select >
< / div >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< div class = "col" >
< h2 class = "page-title" > Strategy P/L Analyzer< / h2 >
< div class = "text-secondary mt-1" x-show = "legs.length > 0" x-cloak >
< span class = "badge bg-purple-lt fs-6 me-2" x-text = "strategyName" > < / span >
2026-05-13 04:47:16 +00:00
< span x-show = "symbols.length <= 1" x-text = "symbol" > < / span > < span x-show = "symbols.length <= 1" > · < / span >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< span x-text = "legs.length + ' leg' + (legs.length===1?'':'s')" > < / span > ·
Spot < strong class = "mono" x-text = "spot > 0 ? '$'+spot.toFixed(2) : '—'" > < / strong >
2026-05-13 04:47:16 +00:00
< span x-show = "symbols.length > 1" class = "ms-2 text-secondary" x-text = "'(' + symbols.length + ' symbols saved)'" > < / span >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< / div >
< / div >
< div class = "col-auto" x-show = "legs.length > 0" x-cloak >
2026-05-13 04:22:59 +00:00
< button class = "btn btn-outline-primary btn-sm me-1" @ click = "reloadMarket()" :disabled = "refreshing" title = "Re-fetch spot, marks & IVs; refresh entry price on unlocked legs" >
< span x-show = "refreshing" class = "spinner-border spinner-border-sm me-1" role = "status" > < / span > Reload
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< / button >
< button class = "btn btn-outline-danger btn-sm" @ click = "clearAll()" > Clear all< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "page-body" >
< div class = "container-xl" >
<!-- Empty state -->
< div class = "card text-center py-5" x-show = "legs.length === 0" x-cloak >
< div class = "card-body" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "48" height = "48" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" class = "text-secondary mb-3" > < path stroke = "none" d = "M0 0h24v24H0z" fill = "none" / > < path d = "M4 19l4 -6l4 2l4 -8l4 5" / > < path d = "M4 4v16h16" / > < / svg >
< h3 class = "text-secondary" > No strategy built yet< / h3 >
< p class = "text-muted" > Go to the < a href = "chain.html" > Options Chain< / a > , then click < span class = "badge bg-success" > B< / span > (buy) or < span class = "badge bg-danger" > S< / span > (sell) on options to add legs — or add one manually below.< / p >
< button class = "btn btn-primary btn-sm mt-2" @ click = "showManual = !showManual" > + Add leg manually< / button >
< / div >
< / div >
2026-05-13 04:17:07 +00:00
<!-- P/L chart -->
< div class = "chart-card mb-3" x-show = "legs.length > 0" x-cloak >
2026-05-13 04:49:23 +00:00
< div class = "card-header d-flex align-items-center justify-content-between flex-wrap gap-3" >
2026-05-13 04:17:07 +00:00
< h3 class = "card-title text-white mb-0" > Profit / Loss vs. Underlying Price< / h3 >
2026-05-13 04:49:23 +00:00
< div class = "d-flex align-items-center flex-wrap gap-3" >
<!-- price - range zoom -->
< div class = "btn-group btn-group-sm" role = "group" aria-label = "Zoom price range" >
< button class = "btn btn-outline-secondary" @ click = "zoomOut()" title = "Zoom out — wider price range" > − < / button >
< button class = "btn btn-outline-secondary" disabled style = "min-width:5rem" x-text = "'±' + lastHalfPct + '%'" > < / button >
< button class = "btn btn-outline-secondary" @ click = "zoomIn()" title = "Zoom in — narrower price range" > +< / button >
< button class = "btn btn-outline-secondary" @ click = "zoomFit()" title = "Reset zoom" > Fit< / button >
< / div >
<!-- time slider -->
< div class = "d-flex align-items-center gap-2" style = "min-width:300px;" >
< span class = "text-secondary small text-nowrap" > Now< / span >
< input type = "range" class = "form-range" min = "0" :max = "maxDTE" step = "1" x-model . number = "dteOffset" @ input = "scheduleRender()" style = "min-width:150px;" >
< span class = "text-secondary small text-nowrap" > Exp< / span >
< span class = "badge bg-blue-lt text-nowrap" x-text = "dteLabel" > < / span >
< / div >
2026-05-13 04:17:07 +00:00
< / div >
< / div >
< div class = "card-body p-0" >
2026-05-13 05:03:49 +00:00
< div id = "plChart" :style = "'min-height:380px;user-select:none;cursor:' + (_drag ? 'grabbing' : 'grab')"
@mousedown="startPan($event)" @mousemove.window="onPan($event)" @mouseup.window="endPan()" @mouseleave.window="endPan()"
role="img" aria-label="Profit and loss diagram (drag left/right to pan the price range)">< / div >
2026-05-13 04:17:07 +00:00
< / div >
< / div >
<!-- Stats -->
< div class = "row g-2 mb-4" x-show = "legs.length > 0" x-cloak >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Net< / div > < div class = "val" :class = "netCost>=0?'neg':'pos'" > < span x-text = "netCost>=0?'Debit ':'Credit '" > < / span > < span x-text = "fmtMoney(Math.abs(netCost))" > < / span > < / div > < / div > < / div >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Max Profit< / div > < div class = "val pos" x-text = "stats.maxProfit" > < / div > < / div > < / div >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Max Loss< / div > < div class = "val neg" x-text = "stats.maxLoss" > < / div > < / div > < / div >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Break-even(s)< / div > < div class = "val amber mono" style = "font-size:.95rem" x-text = "stats.breakevens" > < / div > < / div > < / div >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Net Δ (shares)< / div > < div class = "val mono" :class = "stats.delta>=0?'pos':'neg'" x-text = "stats.delta.toFixed(1)" > < / div > < / div > < / div >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Net Γ< / div > < div class = "val mono" x-text = "stats.gamma.toFixed(2)" > < / div > < / div > < / div >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Net Θ / day< / div > < div class = "val mono" :class = "stats.theta>=0?'pos':'neg'" x-text = "fmtMoney(stats.theta)" > < / div > < / div > < / div >
< div class = "col-6 col-md-3 col-xl" > < div class = "stat-box" > < div class = "lbl" > Net Vega / 1%< / div > < div class = "val mono" :class = "stats.vega>=0?'pos':'neg'" x-text = "fmtMoney(stats.vega)" > < / div > < / div > < / div >
< / div >
<!-- Legs table (at the bottom) -->
< div class = "card mb-4" x-show = "legs.length > 0 || showManual" x-cloak >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< div class = "card-header d-flex align-items-center justify-content-between" >
2026-05-13 04:38:07 +00:00
< h3 class = "card-title mb-0" > Legs < span class = "text-secondary small fw-normal" > — Mark = live mid · 🔒 locks entry price · uncheck to drop from chart< / span > < / h3 >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< div >
< span class = "me-3" :class = "netCost >= 0 ? 'text-danger' : 'text-success'" >
Net < strong x-text = "netCost >= 0 ? 'debit' : 'credit'" > < / strong > :
< strong class = "mono" x-text = "fmtMoney(Math.abs(netCost))" > < / strong >
< / span >
< button class = "btn btn-outline-primary btn-sm" @ click = "showManual = !showManual" x-text = "showManual ? 'Hide manual entry' : '+ Add leg manually'" > < / button >
< / div >
< / div >
< div class = "table-responsive" >
< table class = "table table-sm leg-table mb-0" >
< thead >
< tr >
2026-05-13 04:17:07 +00:00
< th class = "text-center" style = "width:3rem" > Show< / th >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< th > Side< / th > < th > Qty< / th > < th > Type< / th > < th class = "text-end" > Strike< / th > < th > Expiry< / th >
2026-05-13 04:22:59 +00:00
< th class = "text-end" > Entry $< / th > < th class = "text-end" > Mark< / th > < th class = "text-end" > IV< / th >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< th class = "text-end" > Cost< / th > < th class = "text-end" > Δ< / th > < th class = "text-end" > Θ/d< / th > < th > < / th >
< / tr >
< / thead >
< tbody >
< template x-for = "lv in legsView" :key = "lv.id" >
2026-05-13 04:17:07 +00:00
< tr :style = "lv.enabled === false ? 'opacity:.4' : ''" >
< td class = "text-center" > < input type = "checkbox" class = "form-check-input m-0" :checked = "lv.enabled !== false" @ change = "toggleLeg(lv.id)" :aria-label = "'Show leg '+lv.strike+lv.type" > < / td >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< td style = "width:7rem" >
< select class = "form-select form-select-sm" :value = "lv.side" @ change = "updateLeg(lv.id, { side: $event.target.value })" >
< option value = "long" > Long< / option > < option value = "short" > Short< / option >
< / select >
< / td >
< td style = "width:5rem" > < input type = "number" min = "1" step = "1" class = "form-control form-control-sm" :value = "lv.qty" @ change = "updateLeg(lv.id, { qty: Math.max(1, Math.round(+$event.target.value||1)) })" > < / td >
< td > < span class = "badge" :class = "lv.type==='call' ? 'bg-success-lt text-success' : 'bg-danger-lt text-danger'" x-text = "lv.type" > < / span > < / td >
2026-05-13 05:00:34 +00:00
< td class = "text-end mono" style = "width:7rem" >
< select x-show = "hasStrikeOpts(lv)" class = "form-select form-select-sm text-end" :value = "lv.strike" @ change = "changeStrike(lv.id, +$event.target.value)" title = "Change strike — entry price, IV & mark update from the loaded chain" >
< template x-for = "k in strikeOpts(lv)" :key = "k" > < option :value = "k" x-text = "k" > < / option > < / template >
< / select >
< span x-show = "!hasStrikeOpts(lv)" x-text = "lv.strike" > < / span >
< / td >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< td class = "mono small" x-text = "lv.expiry" > < / td >
2026-05-13 04:22:59 +00:00
< td style = "width:8.5rem" >
< div class = "input-group input-group-sm flex-nowrap" >
< input type = "number" min = "0" step = "0.01" class = "form-control form-control-sm text-end" :class = "lv.locked ? 'opacity-75' : ''" :value = "lv.entryPrice" :disabled = "lv.locked" @ change = "updateLeg(lv.id, { entryPrice: Math.max(0, +$event.target.value||0) })" >
< button class = "btn btn-sm btn-outline-secondary px-1" @ click = "updateLeg(lv.id, { locked: !lv.locked })" :title = "lv.locked ? 'Entry price locked — click to unlock (reload will update it)' : 'Click to lock entry price (reload won\'t change it)'" x-text = "lv.locked ? '🔒' : '🔓'" > < / button >
< / div >
< / td >
< td class = "text-end mono small" >
< template x-if = "lv.currentMark != null" >
< span > $< span x-text = "lv.currentMark.toFixed(2)" > < / span > < span x-show = "Math.abs(lv.currentMark - lv.entryPrice) > 0.005" :class = "(lv.currentMark - lv.entryPrice) >= 0 ? 'text-success' : 'text-danger'" x-text = "(lv.currentMark - lv.entryPrice >= 0 ? ' +' : ' ') + (lv.currentMark - lv.entryPrice).toFixed(2)" > < / span > < / span >
< / template >
< template x-if = "lv.currentMark == null" > < span class = "text-secondary" > —< / span > < / template >
< / td >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< td class = "text-end mono small" x-text = "lv.iv > 0 ? (lv.iv*100).toFixed(1)+'%' : '—'" > < / td >
< td class = "text-end mono" :class = "lv.cost >= 0 ? 'text-danger' : 'text-success'" x-text = "fmtMoney(lv.cost)" > < / td >
< td class = "text-end mono small" :class = "lv.delta>=0?'text-success':'text-danger'" x-text = "lv.delta.toFixed(1)" > < / td >
< td class = "text-end mono small text-danger" x-text = "lv.theta.toFixed(1)" > < / td >
< td class = "text-end" > < button class = "btn btn-sm btn-ghost-danger" @ click = "removeLeg(lv.id)" aria-label = "Remove leg" > ✕< / button > < / td >
< / tr >
< / template >
<!-- Manual entry row -->
< template x-if = "showManual" >
< tr style = "background:#161824;" >
2026-05-13 04:17:07 +00:00
< td > < / td >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< td > < select class = "form-select form-select-sm" x-model = "manual.side" > < option value = "long" > Long< / option > < option value = "short" > Short< / option > < / select > < / td >
< td > < input type = "number" min = "1" step = "1" class = "form-control form-control-sm" x-model . number = "manual.qty" > < / td >
< td > < select class = "form-select form-select-sm" x-model = "manual.type" > < option value = "call" > call< / option > < option value = "put" > put< / option > < / select > < / td >
< td > < input type = "number" step = "0.5" class = "form-control form-control-sm text-end" placeholder = "strike" x-model . number = "manual.strike" > < / td >
< td > < input type = "date" class = "form-control form-control-sm" x-model = "manual.expiry" > < / td >
< td > < input type = "number" min = "0" step = "0.01" class = "form-control form-control-sm text-end" placeholder = "entry $" x-model . number = "manual.entryPrice" > < / td >
2026-05-13 04:22:59 +00:00
< td > < / td >
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
< td > < input type = "number" min = "0" step = "0.5" class = "form-control form-control-sm text-end" placeholder = "IV %" x-model . number = "manual.ivPct" > < / td >
< td colspan = "4" class = "text-end" > < button class = "btn btn-sm btn-primary" @ click = "addManualLeg()" > Add leg< / button > < / td >
< / tr >
< / template >
< / tbody >
< / table >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Toast -->
< div class = "toast-mini" x-show = "toast" x-transition x-cloak >
< div class = "alert alert-success py-2 px-3 mb-0" x-text = "toast" > < / div >
< / div >
< / div >
< script src = "/assets/tabler.min.js" defer > < / script >
< script src = "/assets/blackscholes.js" > < / script >
< script src = "/assets/strategy-store.js" > < / script >
< script src = "/assets/alpine.min.js" defer > < / script >
< script >
const R = 0.05; // risk-free rate (matches backend)
const MULT = 100; // contract multiplier
const DAY_MS = 86400000;
const CHART_BG='#1e2030', CHART_GRID='#2d3045', CHART_LABEL='#8b95a7';
const COLOR_EXP='#4dd4ac', COLOR_TN='#a98eda';
function legDTE(leg) {
// days from now to expiry; can be negative if expired
const t = Date.parse(leg.expiry + 'T00:00:00Z');
return (t - Date.now()) / DAY_MS;
}
function legSign(leg) { return leg.side === 'short' ? -1 : 1; }
function legCost(leg) { return legSign(leg) * leg.qty * MULT * (leg.entryPrice || 0); }
/** Value (per share) of a leg at underlying S, evaluated `offsetDays` from now. */
function legValueAt(leg, S, offsetDays) {
const remDays = legDTE(leg) - offsetDays;
if (remDays < = 0) return BS.intrinsic(S, leg.strike, leg.type);
const sigma = leg.iv > 0 ? leg.iv : 0.0001;
return BS.bsPrice(S, leg.strike, remDays / 365, R, sigma, leg.type);
}
/** Position P/L at underlying S, evaluated `offsetDays` from now. */
function plAt(legs, netCost, S, offsetDays) {
let v = 0;
for (const leg of legs) v += legSign(leg) * leg.qty * MULT * legValueAt(leg, S, offsetDays);
return v - netCost;
}
function detectStrategy(legs) {
const n = legs.length;
if (n === 0) return 'Empty';
const cap = s => s.charAt(0).toUpperCase() + s.slice(1);
const sw = s => s === 'long' ? 'Long' : 'Short';
if (n === 1) return `${sw(legs[0].side)} ${cap(legs[0].type)}`;
const ls = [...legs].sort((a,b)=> a.type.localeCompare(b.type) || a.strike-b.strike || a.expiry.localeCompare(b.expiry));
const allSameExp = ls.every(l => l.expiry === ls[0].expiry);
const calls = ls.filter(l=>l.type==='call'), puts = ls.filter(l=>l.type==='put');
if (n === 2) {
const [a,b] = ls;
if (a.type===b.type & & a.expiry===b.expiry & & a.side!==b.side & & a.qty===b.qty) {
const L = a.side==='long'?a:b, Sh = a.side==='long'?b:a;
if (a.type==='call') return L.strike < Sh.strike ? ' Bull Call Spread ( debit ) ' : ' Bear Call Spread ( credit ) ' ;
return L.strike > Sh.strike ? 'Bear Put Spread (debit)' : 'Bull Put Spread (credit)';
}
if (calls.length===1 & & puts.length===1 & & a.side===b.side & & a.expiry===b.expiry)
return calls[0].strike===puts[0].strike ? `${sw(a.side)} Straddle` : `${sw(a.side)} Strangle`;
if (a.type===b.type & & a.strike===b.strike & & a.expiry!==b.expiry & & a.side!==b.side) return 'Calendar Spread';
if (a.type===b.type & & a.strike!==b.strike & & a.expiry!==b.expiry & & a.side!==b.side) return 'Diagonal Spread';
return 'Custom (2 legs)';
}
if (n === 3) {
if (allSameExp & & (calls.length===3 || puts.length===3)) {
const [lo,mid,hi] = ls;
if (lo.side===hi.side & & lo.side!==mid.side & & lo.qty===hi.qty & & mid.qty===2*lo.qty)
return `${lo.side==='long'?'Long':'Short'} ${cap(ls[0].type)} Butterfly`;
}
return 'Custom (3 legs)';
}
if (n === 4) {
if (allSameExp & & calls.length===2 & & puts.length===2) {
const [pL,pH] = puts, [cL,cH] = calls;
const ic = pL.side==='long'&&pH.side==='short'&&cL.side==='short'&&cH.side==='long';
if (ic & & pH.strike===cL.strike) return 'Iron Butterfly';
if (ic) return 'Iron Condor';
const ric = pL.side==='short'&&pH.side==='long'&&cL.side==='long'&&cH.side==='short';
if (ric) return 'Reverse Iron Condor';
}
if (allSameExp & & calls.length===4) return 'Call Condor';
if (allSameExp & & puts.length===4) return 'Put Condor';
return 'Custom (4 legs)';
}
return `Custom (${n} legs)`;
}
function fmtMoney(v) {
if (v == null || !isFinite(v)) return '—';
const sign = v < 0 ? ' - ' : ' ' ;
const a = Math.abs(v);
return sign + '$' + a.toLocaleString('en-US', { maximumFractionDigits: a < 100 ? 2 : 0 } ) ;
}
function strategyApp() {
return {
// state
2026-05-13 04:47:16 +00:00
symbol: '', symbols: [], spot: 0, legs: [],
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
dteOffset: 0,
2026-05-13 05:03:49 +00:00
xZoom: 1, xPan: 0, lastHalfPct: 0,
_drag: null,
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
refreshing: false, showManual: false, toast: '',
manual: { side:'long', type:'call', qty:1, strike:null, expiry:'', entryPrice:null, ivPct:null },
chart: null, _renderTimer: null,
2026-05-13 05:00:34 +00:00
_chainCache: {}, // "SYMBOL@EXPIRY" -> { "strike@type": optionRow }
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
init() {
this.reload();
2026-05-13 05:00:34 +00:00
this._chainCache = this._seedChainCache();
// pull live spot / marks / IVs (and per-expiry chains) on open
2026-05-13 04:38:07 +00:00
if (this.legs.length > 0 & & this.symbol) this.reloadMarket(false);
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
// re-sync if another tab changed the basket
window.addEventListener('storage', (e) => { if (e.key === StrategyStore.KEY) this.reload(); });
},
2026-05-13 05:00:34 +00:00
// seed the strike picker from whatever chain was last loaded on chain.html
_seedChainCache() {
try {
const vs = (typeof ViewState !== 'undefined') ? ViewState.load('chain') : null;
if (!vs || !vs.symbol || !vs.expiry) return {};
const map = {};
for (const o of (vs.calls || [])) map[Number(o.strike) + '@call'] = o;
for (const o of (vs.puts || [])) map[Number(o.strike) + '@put'] = o;
return Object.keys(map).length ? { [vs.symbol + '@' + vs.expiry]: map } : {};
} catch { return {}; }
},
_legMap(lv) { return this._chainCache[lv.symbol + '@' + lv.expiry] || null; },
hasStrikeOpts(lv) {
const m = this._legMap(lv);
if (!m) return false;
for (const k of Object.keys(m)) if (k.endsWith('@' + lv.type)) return true;
return false;
},
strikeOpts(lv) {
const m = this._legMap(lv);
const out = [];
if (m) for (const k of Object.keys(m)) { if (k.endsWith('@' + lv.type)) out.push(parseFloat(k)); }
if (!out.includes(lv.strike)) out.push(lv.strike);
return out.sort((a, b) => a - b);
},
changeStrike(id, newStrike) {
const leg = this.legs.find(l => l.id === id);
if (!leg || !Number.isFinite(newStrike) || newStrike === leg.strike) return;
const m = this._legMap(leg);
const o = m & & m[Number(newStrike) + '@' + leg.type];
const patch = { strike: newStrike };
if (o) {
const mid = Math.round(((o.midPrice ?? o.mid ?? o.bsPrice ?? leg.entryPrice) || 0) * 100) / 100;
patch.entryPrice = mid;
patch.currentMark = mid;
if (o.iv > 0) patch.iv = o.iv;
patch.locked = false; // it's a different contract now — start fresh
}
this.updateLeg(id, patch);
},
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
reload() {
const st = StrategyStore.load();
this.symbol = st.symbol || '';
2026-05-13 04:47:16 +00:00
this.symbols = st.symbols || [];
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
this.spot = st.spotSnapshot || 0;
this.legs = st.legs || [];
2026-05-13 04:22:59 +00:00
if (this.dteOffset > this.maxDTE) this.dteOffset = this.maxDTE;
2026-05-13 04:47:16 +00:00
if (this.legs.length > 0) this.$nextTick(() => this.renderChart()); else if (this.chart) this.chart.updateSeries([{name:'P/L',data:[]},{name:'P/L',data:[]}]);
},
switchSymbol(sym) {
if (!sym || sym === this.symbol) return;
StrategyStore.setActive(sym);
2026-05-13 05:03:49 +00:00
this.dteOffset = 0; this.xPan = 0; this.xZoom = 1;
2026-05-13 04:47:16 +00:00
this.reload();
if (this.legs.length > 0) this.reloadMarket(false);
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
},
// ── derived ───────────────────────────────────────────
2026-05-13 04:17:07 +00:00
// only legs with the "Show" checkbox ticked drive the chart / stats / Greeks
get activeLegs() { return this.legs.filter(l => l.enabled !== false); },
get netCost() { return this.activeLegs.reduce((s,l)=> s + legCost(l), 0); },
get strategyName() { return detectStrategy(this.activeLegs); },
get maxDTE() { const a = this.activeLegs; return a.length ? Math.max(1, Math.ceil(Math.max(0, ...a.map(legDTE)))) : 1; },
2026-05-13 04:07:26 +00:00
// 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
2026-05-13 04:17:07 +00:00
get minDTE() { const a = this.activeLegs; return a.length ? Math.max(0, Math.min(...a.map(legDTE))) : 0; },
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
get dteLabel() {
const d = new Date(Date.now() + this.dteOffset * DAY_MS);
const ds = d.toISOString().slice(0,10);
if (this.dteOffset === 0) return 'Today (' + ds + ')';
return 'T+' + this.dteOffset + 'd · ' + ds;
},
get legsView() {
return this.legs.map(l => {
const dteY = Math.max(legDTE(l), 0) / 365;
const sigma = l.iv > 0 ? l.iv : 0.0001;
const g = (this.spot > 0)
? BS.bsGreeks(this.spot, l.strike, dteY, R, sigma, l.type)
: { delta:0, gamma:0, theta:0, vega:0 };
const k = legSign(l) * l.qty * MULT;
return {
...l,
cost: legCost(l),
delta: g.delta * k,
gamma: g.gamma * k,
theta: g.theta * k,
vega: g.vega * k,
};
});
},
get stats() {
2026-05-13 04:17:07 +00:00
const legs = this.activeLegs, net = this.netCost, spot = this.spot;
if (legs.length === 0) return { maxProfit:'—', maxLoss:'—', breakevens:'—', delta:0, gamma:0, theta:0, vega:0 };
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
// dense expiration-curve sample for breakevens / extremes / unbounded
const lo0 = 0.01;
const hi0 = Math.max(spot * 2, ...legs.map(l=>l.strike)) * 1.5 + 10;
const Nd = 600;
const xs = [], ys = [];
for (let i = 0; i < = Nd; i++) {
const x = lo0 + (hi0 - lo0) * i / Nd;
xs.push(x); ys.push(plAt(legs, net, x, this.minDTE));
}
// breakevens (sign changes)
const bes = [];
for (let i = 1; i < = Nd; i++) {
const y0 = ys[i-1], y1 = ys[i];
if (y0 === 0) { bes.push(xs[i-1]); continue; }
if ((y0 < 0 & & y1 > 0) || (y0 > 0 & & y1 < 0 ) ) {
const x = xs[i-1] + (0 - y0) * (xs[i]-xs[i-1]) / (y1 - y0);
bes.push(x);
}
}
const uniqBE = bes.filter((v,i)=> i===0 || Math.abs(v - bes[i-1]) > 1e-6);
2026-05-13 04:12:16 +00:00
// extremes — include the grid samples AND the exact kink points (strikes)
// plus the sample bounds, since piecewise payoffs peak/trough at strikes
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
let maxY = -Infinity, minY = Infinity;
for (const y of ys) { if (y > maxY) maxY = y; if (y < minY ) minY = y; }
2026-05-13 04:12:16 +00:00
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;
}
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
// 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]);
const maxProfit = slopeR > 1e-2 ? 'Unlimited' : fmtMoney(maxY);
const maxLoss = slopeR < -1e-2 ? ' Unlimited ' : fmtMoney ( minY ) ;
// net greeks now
let d=0,g=0,t=0,v=0;
for (const l of legs) {
const dteY = Math.max(legDTE(l), 0) / 365;
const sigma = l.iv > 0 ? l.iv : 0.0001;
const gr = (spot>0) ? BS.bsGreeks(spot, l.strike, dteY, R, sigma, l.type) : {delta:0,gamma:0,theta:0,vega:0};
const k = legSign(l) * l.qty * MULT;
d += gr.delta*k; g += gr.gamma*k; t += gr.theta*k; v += gr.vega*k;
}
return {
maxProfit, maxLoss,
breakevens: uniqBE.length ? uniqBE.map(x=>'$'+x.toFixed(2)).join(' / ') : '—',
delta:d, gamma:g, theta:t, vega:v,
};
},
// ── actions ───────────────────────────────────────────
updateLeg(id, patch) {
StrategyStore.updateLeg(id, patch);
this.reload();
},
2026-05-13 04:17:07 +00:00
toggleLeg(id) {
const leg = this.legs.find(l => l.id === id);
const cur = leg ? leg.enabled !== false : true;
StrategyStore.updateLeg(id, { enabled: !cur });
// refresh legs without resetting the date slider
const st = StrategyStore.load();
this.legs = st.legs || [];
if (this.dteOffset > this.maxDTE) this.dteOffset = this.maxDTE;
this.$nextTick(() => this.renderChart());
},
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
removeLeg(id) {
StrategyStore.removeLeg(id);
this.reload();
},
clearAll() {
2026-05-13 04:47:16 +00:00
if (!confirm('Clear all ' + (this.symbol || '') + ' legs?')) return;
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
StrategyStore.clear();
2026-05-13 05:03:49 +00:00
this.dteOffset = 0; this.xPan = 0; this.xZoom = 1;
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
if (this.chart) { this.chart.destroy(); this.chart = null; }
2026-05-13 04:47:16 +00:00
this.reload(); // re-renders the chart if another symbol's basket is now active
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
},
addManualLeg() {
const m = this.manual;
if (!m.strike || m.strike < = 0 || !m.expiry) { alert('Need a strike and expiry.'); return; }
StrategyStore.addLeg({
symbol: this.symbol || 'MANUAL',
expiry: m.expiry, type: m.type, strike: +m.strike,
side: m.side, qty: Math.max(1, Math.round(+m.qty||1)),
entryPrice: Math.max(0, +m.entryPrice||0),
iv: (+m.ivPct||0) / 100,
});
this.manual = { side:'long', type:'call', qty:1, strike:null, expiry:'', entryPrice:null, ivPct:null };
this.reload();
this.flash('Leg added');
},
2026-05-13 04:38:07 +00:00
// Re-fetch spot + each leg's current mark & IV.
// When repriceUnlocked (default — the Reload button), unlocked legs also
// get their entry price reset to the current mark; locked legs always keep it.
async reloadMarket(repriceUnlocked = true) {
2026-05-13 04:22:59 +00:00
if (!this.symbol || this.legs.length === 0) return;
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
this.refreshing = true;
try {
2026-05-13 04:22:59 +00:00
const expiries = [...new Set(this.legs.map(l => l.expiry).filter(Boolean))];
const byExpiry = {}; // expiry -> { strike@type -> option }
let spot = 0;
for (const exp of expiries) {
const r = await fetch('/api/chain?symbol=' + encodeURIComponent(this.symbol) + '& expiry=' + encodeURIComponent(exp));
if (!r.ok) continue;
const e = await r.json();
const snap = e.data?.snapshots?.[0];
if (!snap) continue;
if (snap.spot > 0) spot = snap.spot;
const map = {};
for (const o of (snap.chain || [])) {
const t = (o.type || o.optionType || '').toLowerCase();
map[Number(o.strike) + '@' + t] = o;
}
byExpiry[exp] = map;
}
2026-05-13 05:00:34 +00:00
// make these chains available to the strike picker
const cacheAdds = {};
for (const exp of Object.keys(byExpiry)) cacheAdds[this.symbol + '@' + exp] = byExpiry[exp];
this._chainCache = { ...this._chainCache, ...cacheAdds };
2026-05-13 04:22:59 +00:00
const st = StrategyStore.load();
let updated = 0, relinked = 0;
for (const leg of st.legs) {
const map = byExpiry[leg.expiry];
if (!map) continue;
const o = map[Number(leg.strike) + '@' + leg.type];
if (!o) continue;
2026-05-13 04:28:21 +00:00
const mark = Math.round((o.midPrice ?? o.mid ?? o.bsPrice ?? 0) * 100) / 100;
2026-05-13 04:22:59 +00:00
leg.currentMark = mark;
if (o.iv > 0) leg.iv = o.iv;
2026-05-13 04:38:07 +00:00
if (!leg.locked & & repriceUnlocked & & mark > 0) { leg.entryPrice = mark; relinked++; }
2026-05-13 04:22:59 +00:00
updated++;
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
}
2026-05-13 04:22:59 +00:00
if (spot > 0) st.spotSnapshot = spot;
StrategyStore.save(st);
this.legs = st.legs;
if (spot > 0) this.spot = spot;
if (this.dteOffset > this.maxDTE) this.dteOffset = this.maxDTE;
this.$nextTick(() => this.renderChart());
2026-05-13 04:38:07 +00:00
if (repriceUnlocked) {
this.flash(updated > 0
? `Reloaded ${updated} leg${updated===1?'':'s'}${relinked?` (${relinked} re-priced)`:''} · spot $${(spot||this.spot).toFixed(2)}`
: 'Reloaded — no matching contracts found in the current chain');
}
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
} catch (e) {
2026-05-13 04:38:07 +00:00
if (repriceUnlocked) this.flash('Reload failed: ' + e.message);
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
} finally {
this.refreshing = false;
}
},
flash(msg) { this.toast = msg; setTimeout(()=>{ this.toast=''; }, 2500); },
scheduleRender() {
clearTimeout(this._renderTimer);
this._renderTimer = setTimeout(() => this.renderChart(), 40);
},
2026-05-13 04:49:23 +00:00
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(); },
2026-05-13 05:03:49 +00:00
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; },
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
renderChart() {
2026-05-13 04:17:07 +00:00
const legs = this.activeLegs, net = this.netCost;
if (legs.length === 0) {
if (this.chart) this.chart.updateSeries([{ name: 'P/L', data: [] }, { name: 'P/L', data: [] }]);
return;
}
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
const spot = this.spot > 0 ? this.spot : (legs.reduce((s,l)=>s+l.strike,0)/legs.length);
const strikes = legs.map(l=>l.strike);
const minK = Math.min(...strikes), maxK = Math.max(...strikes);
const span = maxK - minK;
2026-05-13 04:49:23 +00:00
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);
2026-05-13 05:03:49 +00:00
const center = spot + (this.xPan || 0);
const lo = Math.max(0.01, center - half), hi = center + half;
2026-05-13 04:49:23 +00:00
this.lastHalfPct = spot > 0 ? Math.round(half / spot * 100) : 0;
2026-05-13 05:03:49 +00:00
this._lastHalf = half; this._lastSpot = spot;
2026-05-13 04:07:26 +00:00
const expAt = this.minDTE;
2026-05-13 04:49:23 +00:00
const N = 221;
2026-05-13 04:12:16 +00:00
// 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);
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
const expData = [], tnData = [];
let yMin = Infinity, yMax = -Infinity;
2026-05-13 04:12:16 +00:00
for (const x of xGrid) {
2026-05-13 04:07:26 +00:00
const ye = plAt(legs, net, x, expAt);
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
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);
}
2026-05-13 04:07:26 +00:00
// 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;
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
const pad = Math.max((yMax - yMin) * 0.08, 1);
yMin -= pad; yMax += pad;
// breakevens for light vertical lines
const bes = [];
2026-05-13 04:12:16 +00:00
for (let i = 1; i < expData.length ; i + + ) {
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
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);
bes.push(x);
}
}
2026-05-13 04:52:47 +00:00
const labelPad = { left: 6, right: 6, top: 3, bottom: 3 };
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
const xAnnos = [{
x: spot, borderColor: '#ffd43b', strokeDashArray: 4,
2026-05-13 04:52:47 +00:00
label: { text: 'Spot $'+spot.toFixed(2), position:'top', orientation:'horizontal', offsetY: -2,
borderColor:'#ffd43b', borderWidth:1,
style:{ color:'#1b1d27', background:'#ffd43b', fontSize:'11px', fontWeight:700, padding: labelPad } }
}].concat(bes.map(x => ({ x, borderColor:'#5aa9ff', strokeDashArray:3,
label:{ text:'BE $'+x.toFixed(2), position:'bottom', orientation:'horizontal', offsetY: 2,
borderColor:'#2f6fd0', borderWidth:1,
style:{ color:'#ffffff', background:'#2f6fd0', fontSize:'10px', fontWeight:700, padding: labelPad } } })));
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
const opts = {
chart: { type:'line', height:380, background:CHART_BG, foreColor:CHART_LABEL, fontFamily:'inherit', toolbar:{show:false}, animations:{enabled:false}, zoom:{enabled:false} },
series: [
{ name: this.dteOffset===0 ? 'P/L Today' : 'P/L '+this.dteLabel, type:'line', data: tnData, color: COLOR_TN },
{ name: 'P/L at Expiration', type:'line', data: expData, color: COLOR_EXP },
],
stroke: { width:[2,3], curve:['smooth','straight'], dashArray:[5,0] },
markers: { size:0 },
grid: { borderColor:CHART_GRID, strokeDashArray:3 },
dataLabels: { enabled:false },
legend: { labels:{ colors:'#d0d5e0' }, position:'top', horizontalAlign:'right' },
xaxis: {
type:'numeric', tickAmount:10,
labels:{ style:{colors:CHART_LABEL}, formatter:v=>'$'+Number(v).toFixed(0) },
title:{ text:'Underlying price', style:{color:CHART_LABEL} },
axisBorder:{color:CHART_GRID}, axisTicks:{color:CHART_GRID},
},
yaxis: {
min:yMin, max:yMax,
labels:{ style:{colors:CHART_LABEL}, formatter:v=>fmtMoney(v) },
title:{ text:'Profit / Loss ($)', style:{color:CHART_LABEL} },
},
tooltip: {
theme:'dark', shared:true, intersect:false,
x:{ formatter:v=>'Underlying $'+Number(v).toFixed(2) },
y:{ formatter:v=>fmtMoney(v) },
},
annotations: {
yaxis: [
{ y:0, y2:yMax, fillColor:'#2fb344', opacity:0.05, borderColor:'transparent' },
{ y:yMin, y2:0, fillColor:'#d63939', opacity:0.05, borderColor:'transparent' },
2026-05-13 04:52:47 +00:00
{ y:0, borderColor:'#aeb6c4', strokeDashArray:0,
label:{ text:'P/L = 0', position:'left', borderColor:'#3a3f5a', borderWidth:1,
style:{ color:'#e8ebf2', background:'#2a2e42', fontSize:'10px', fontWeight:600, padding:labelPad } } },
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
],
xaxis: xAnnos,
},
};
if (this.chart) {
this.chart.updateOptions(opts, true, false);
} else {
this.chart = new ApexCharts(document.getElementById('plChart'), opts);
this.chart.render();
}
2026-05-13 05:03:49 +00:00
// 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 {}
Add strategy P/L analyzer + view-state persistence
- New strategy.html: thinkorswim-style P/L diagram (expiration + T+N curves
via Black-Scholes, days-to-expiry slider, net debit/credit, max profit/loss
with unbounded detection, breakevens, net Greeks, auto-detected strategy name)
- chain.html: per-row Buy/Sell buttons add legs to a localStorage basket;
basket badge in toolbar; auto-scroll to ATM row on load
- Persist per-page view state (symbol, expiry, loaded data, charts) across
navigation via viewstate-store.js for chain/surface/tracker/dashboard
- New assets: blackscholes.js (frontend BS port), strategy-store.js, viewstate-store.js
- Strategy P/L nav link added to all sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 04:01:57 +00:00
},
fmtMoney,
};
}
< / script >
< / body >
< / html >