On page load and on every symbol Lookup, target the 3rd Friday of
the next calendar month (the standard US monthly options expiry).
If that exact date isn't listed (e.g., June 19, 2026 is Juneteenth
so SPY's monthly is the Thursday 06-18), fall back to the nearest
available expiration.
Data flow:
1. Init reads {symbol, expirations} from ViewState.
2. Computes target expiry (3rd Fri next month).
3. Hits a new per-symbol cache at
localStorage['optionsPricer:surfaceCache'][symbol:expiry].
- Hit AND cache.date === today → render instantly, no network.
- Hit but stale (cache.date !== today) → refetch.
- Miss → fetch expirations + load surface.
4. fetchExpirations() now auto-selects the target expiry and
triggers _loadForTargetExpiry (cache-aware) — entering a new
symbol now produces a rendered surface with one Enter press.
5. Successful loadSurface writes the response into the cache
under today's date; cache is pruned to 50 entries.
Analytics is no longer stuffed into ViewState (only the lightweight
symbol/expirations/expiry pointer is), so the per-symbol cache is
the single source of truth for surface data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the toolbar card and the skew-metric badges (ATM IV / RR25 /
Fly25) in a left column, and place the HV-vs-IV card in a sibling
right column with h-100 d-flex flex-column so it stretches to the
full height of both stacked items — effectively a rowspan=2 layout.
On screens narrower than lg the right column drops below as a
single full-width card.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User wanted the comparison sitting alongside the Lookup / Expiry /
Load Surface controls instead of in the page header. Now it lives
on the right side of the toolbar row (ms-sm-auto), still hidden
until data is loaded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a compact card in the page header that shows ATM IV alongside
realized vol over 20/30/60-day windows, the IV-minus-HV spread in
vol points, and a RICH/FAIR/CHEAP verdict (driven by IV/HV30 ratio:
>=1.20x = RICH, <=0.80x = CHEAP, otherwise FAIR). Lets you eyeball
whether options are priced rich relative to recent realized vol the
moment the surface loads.
- datafetch.ts: extract annualizedVolWindow helper; new
fetchHistoricalVolWindows() returns hv20/hv30/hv60 from one
~90-day Yahoo historical pull
- options.ts: /api/analytics includes hvWindows in response
- surface.html: top-right hviv-card with per-window rows + footer
showing IV/HV ratio and sample size
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New /movers page surfaces Yahoo Finance's predefined screeners
(day_gainers, day_losers, most_actives, most_shorted_stocks)
filtered to common equities with market cap >= $2B, so every
listed name has a deep options chain. Per-row actions jump
straight into Chain / Vol Surface / IV Spike Scanner, or pin
the symbol to the Tracker watchlist.
- datafetch.ts: fetchMovers(category, count) using yf.screener,
post-filtered to quoteType=EQUITY and marketCap >= $2B
- options.ts: GET /api/movers?category=&count=
- movers.html: Tabler page with 4-tab segmented control, sortable
table, summary cards, volume-vs-avg ratio highlighting hot names
- All page sidebars: insert "Movers" link between Vol Surface
and Scanner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the IV/HV >= 1.5 heuristic with a baseline-relative jump:
a symbol is flagged SPIKE when current ATM IV is at least 30%
above its recent baseline (e.g., 10% -> 13%+). Baseline prefers
the 30-day average of saved snapshots; falls back to HV30 when
no scan history exists. Each scan persists a snapshot so the
baseline self-improves over time. BIG MOVE (|chg%| >= 3%) is now
shown as a separate badge instead of a spike requirement.
- snapshots.ts: add getAverageAtmIv(symbol, days)
- datafetch.ts: save snapshot per scan; compute baselineIv,
baselineSrc, baselineN, ivJumpPct; spike from jump threshold
- options.ts: /api/scan returns new baseline + jump fields
- scanner.html: header copy, table columns (Baseline IV, IV Δ%),
default sort by ivJumpPct desc, separate SPIKE/BIG MOVE badges
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: GET /api/scan?symbols=SYM1,SYM2,... — for each symbol fetches
the front-expiry options chain plus 30-day realized vol and returns
{ spot, change, changePct, atmIv, hv30, ivHv, spike, expiry }. Spike flag
is on when IV/HV ≥ 1.5 or |today's % change| ≥ 3. Defaults to ~15 popular
tickers when no list is given; cap of 30 symbols/scan.
Frontend: new scanner.html page — symbol input (with "Use defaults" / "Use
watchlist" shortcuts), summary cards (count · spikes · biggest mover ·
highest IV/HV), sortable results table with spike rows highlighted, and
shortcut buttons to open each symbol on Chain or Surface.
Scanner added to all sidebars between Vol Surface and Strategy P/L.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every page now uses the same brand logo (candle chart icon) and the same
seven nav items in the same order with identical tabler-style icons:
Dashboard · Options Chain · Vol Surface · Strategy P/L · Positions ·
Tracker · Settings. Only the active item differs per page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each watched symbol now appears as a small card showing live spot, ATM IV
(color-coded by level), and RR25 — fetched from /api/analytics. Click a
card to load that symbol's full IV/RR/Fly history charts; the active card
gets a blue border.
On page open: if there's a watchlist but no history loaded yet, auto-open
the first watched symbol so the page isn't empty. Refresh button re-pulls
all watchlist metrics. Empty state hints at adding symbols from the
Strategy page's "Save to Tracker" button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each row has a new Open button that copies the order's legs (locked, since
they're real fills) into the StrategyStore basket for that symbol — wiping
only that symbol's basket (with a confirm if it already has legs) — and
navigates to strategy.html so you can analyze, roll or adjust the position.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tracker is for symbol price/IV history, not positions. The "Save to
Tracker" button now adds the symbol to a watchlist (localStorage); the
Tracker page shows the watchlist as clickable chips.
New "Enter Position" button on the Strategy page posts the active legs
to /api/orders, then opens the new Positions page.
New Positions page (positions.html): lists entered positions with live
mid value, round-trip commission, gross & net P/L (Net = Gross − round-
trip commission), per-symbol filter, summary totals, close/reopen and
remove actions.
New Settings page (settings.html) configures the commission used on
Positions. Defaults to Interactive Brokers Fixed / IBKR Lite: $0.65
per contract, $1.00 minimum per order
(https://www.interactivebrokers.com/en/pricing/commissions-options.php).
Per-leg vs per-order toggle for complex orders.
Sidebar nav now: Dashboard · Options Chain · Vol Surface · Strategy P/L
· Positions · Tracker · Settings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New orders table (symbol, name, legs_json, entry_cost, created_at,
closed_at, status, note) sharing the snapshots.db
- Endpoints:
POST /api/orders save a position
GET /api/orders?symbol= list (most recent first)
GET /api/orders/:id single
PATCH /api/orders/:id { status:'open'|'closed', note? }
DELETE /api/orders/:id remove
Strategy page:
- "Save to Tracker" button posts the currently-active legs (with name,
net cost, side, qty, entry, IV, lock) as an order
Tracker page:
- New "Tracked Orders" section above the IV history charts: lists each
saved position for the current symbol with strategy name, leg summary,
entered cost, current value, P/L $ and %, opened date, status, and
Close/Reopen + Remove actions. Live P/L uses /api/chain mids for each
unique leg expiry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Expiry column header now has a master expiry selector with a 🔓/🔒
toggle. Locking it snaps every leg to the most common expiry, then any
change to the master propagates to all legs (each re-priced from its new
expiry's chain). Per-leg expiry selects are disabled while locked.
Lock resets when switching symbols or clearing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a blue "23 DTE" badge next to the strategy name in the header (or
"14 / 42 DTE" when active legs span multiple expiries — calendar/diagonal).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Legs table: small DTE chip next to each leg's expiry (e.g. "14d", "<1d",
"exp" — absolute days from today)
- Chart header: when active legs span multiple expiries (calendar/diagonal),
a "Remaining: 2026-05-15 (0d) · 2026-06-20 (36d)" line shows how many
days each unique expiry has left at the current slider position
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Expiry cell is now a dropdown of available expiries for that leg's
symbol (fetched from /api/expirations on page open / symbol switch /
Reload). Picking a different expiry pulls that expiry's chain on-demand
(cached), finds the same strike (or the closest available) for the leg's
type, and updates entry price, IV and mark. Lock clears since it's a new
contract.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed click-and-drag panning; the chart header now has ‹ and › buttons
that shift the price window left/right by ~40% of the current half-width
per click. Fit still re-centres and resets zoom.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hold the left mouse button on the chart and drag left/right to slide the
underlying-price window; the curve is re-sampled as you drag so it always
extends to the edges. Fit / switching symbols / clear-all re-centre and
reset zoom.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Strike cell becomes a dropdown of available strikes (for that leg's
symbol+expiry+type) drawn from the cached chain — seeded from chain.html's
last-loaded chain (localStorage) and refreshed by the strategy page's own
per-expiry chain fetch on open / Reload. Picking a new strike updates the
leg's entry price, IV and mark from that contract and clears the lock.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spot / break-even / zero-line labels now use solid high-contrast badges
(dark text on yellow, white text on blue, light text on dark) with padding
and a border instead of transparent/low-contrast backgrounds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
−/+ buttons (with a ±% readout) and a Fit button in the chart header widen
or narrow the underlying-price window the curve is sampled over, so you can
inspect a tight range around spot or zoom out to see the full payoff shape.
Bumped sampling density to 221 points.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The store now keeps one basket per symbol (v2 schema, auto-migrates v1).
Adding a leg from a different symbol no longer wipes the basket — it creates
that symbol's basket and makes it active. The Strategy page shows a symbol
picker at the top (when >1 symbol is saved) to switch between them; switching
auto-fetches that symbol's live marks. "Clear all" clears only the current
symbol's legs; new StrategyStore.clearAll() wipes everything.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On opening the Strategy page, automatically pull the current spot, each leg's
live mid (Mark column) and IV — without touching entry prices. The Reload
button still re-prices unlocked legs to the current mark. Clarified the legs
table hint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entry price, mark and strike are now rounded to 2dp on add, on edit, on
market reload, and on load (self-heals legacy entries) — fixes display of
float artifacts like "15.000000001". Also persist the leg's locked/currentMark
fields in the store.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Each leg has a 🔓/🔒 toggle next to its entry price; locked = entry stays
fixed (your fill), unlocked = entry re-prices to the current mark on Reload
- New "Mark" column shows each leg's current market mid (with delta vs entry)
- "Refresh spot" button replaced by "Reload": re-fetches spot, plus each leg's
current mark and IV from the live chain (per unique expiry), re-pricing
unlocked legs and refreshing IVs used by the T+0 curve
- reload() no longer resets the days-to-expiry slider on edits
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Legs table now sits below the P/L chart and stats
- Each leg row has a leftmost "Show" checkbox; unchecked legs are excluded
from the chart, stats, breakevens and net Greeks (and dimmed in the table),
so you can isolate or build up a strategy incrementally
- All derived values (chart, netCost, maxDTE/minDTE, strategy name, stats)
now operate on the active (checked) legs only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Evaluate the expiration P/L at the exact strike prices (and sample bounds),
not just on the coarse grid — so max profit / max loss are exact for
piecewise payoffs (butterfly peak now $800 not ~$679) and the chart renders
sharp vertices for butterflies, condors, etc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
Full-stack options analytics app: IV surface, Greeks, skew metrics,
vol term structure. Yahoo Finance data with Black-Scholes IV computation
and historical vol fallback for after-hours data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>