From 65cb6cc5f2fc068f4d51f61e4d15a56b289f7581 Mon Sep 17 00:00:00 2001 From: ojy Date: Wed, 13 May 2026 07:14:06 +0000 Subject: [PATCH] Tracker watchlist: summary cards + auto-load first symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/tracker.html | 95 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/frontend/tracker.html b/frontend/tracker.html index 9e859ee..0143f91 100644 --- a/frontend/tracker.html +++ b/frontend/tracker.html @@ -278,15 +278,41 @@
- -
- Watchlist: - + +
+
+

Watchlist

+ +
+
+ +
@@ -297,7 +323,11 @@ -

No snapshot data. Enter a symbol and click Load History.

+

+ No snapshot data yet. Click a watchlist card above, or enter a symbol and click Load History. +
+ Tip: add symbols to the watchlist from the Strategy page's Save to Tracker button. +

@@ -647,6 +677,8 @@ error: '', watchlist: [], + watchlistData: {}, + watchlistLoading: false, _charts: { atmIv: null, rr25: null, fly25: null }, @@ -661,7 +693,13 @@ if (this.snapshots.length) this.$nextTick(() => this.renderCharts()); } this._loadWatchlist(); - window.addEventListener('storage', (e) => { if (e.key === 'optionsPricer:watchlist') this._loadWatchlist(); }); + window.addEventListener('storage', (e) => { if (e.key === 'optionsPricer:watchlist') { this._loadWatchlist(); this._loadWatchlistData(); } }); + // populate the watchlist cards + if (this.watchlist.length) this._loadWatchlistData(); + // if we have nothing loaded yet but a watchlist exists, auto-open the first + if (this.snapshots.length === 0 && this.watchlist.length > 0) { + this.loadSymbol(this.watchlist[0]); + } }, _loadWatchlist() { @@ -669,6 +707,38 @@ catch { this.watchlist = []; } }, + async _loadWatchlistData() { + if (this.watchlist.length === 0) return; + this.watchlistLoading = true; + try { + const data = { ...this.watchlistData }; + // fetch each symbol's nearest analytics in parallel + await Promise.all(this.watchlist.map(async (sym) => { + try { + const r = await fetch('/api/analytics?symbol=' + encodeURIComponent(sym)); + if (!r.ok) return; + const e = await r.json(); + const d = e.data ?? e; + // skewMetrics is keyed by expiry — pick the nearest + const expiries = (d.volSurface?.expiries || Object.keys(d.skewMetrics || {})).sort(); + const front = expiries[0]; + const m = (d.skewMetrics || {})[front] || {}; + data[sym] = { + spot: d.spot ?? null, + atmIv: m.atmIv ?? d.atmIv ?? null, + rr25: m.rr25 ?? null, + fly25: m.fly25 ?? null, + expiry: front || null, + ts: new Date().toISOString(), + }; + } catch {} + })); + this.watchlistData = data; + } finally { + this.watchlistLoading = false; + } + }, + loadSymbol(s) { this.symbol = s; this.fetchExpirations(); @@ -678,6 +748,9 @@ removeWatch(s) { this.watchlist = this.watchlist.filter(x => x !== s); try { localStorage.setItem('optionsPricer:watchlist', JSON.stringify(this.watchlist)); } catch {} + delete this.watchlistData[s]; + // keep reactivity by reassigning + this.watchlistData = { ...this.watchlistData }; }, _persist() {