// TRADERVIEW — WALKTHROUGH

Tutorial index Docs hub
Progress
06 / 18

06.Live scanner & Finnhub WS pipeline

The 6-panel intraday scanner, the 25-symbol chunked subscription layer, the in-process DashMap fan-out, TTS voice alerts, and the Webull read-only live snapshot. All live stores are bounded with oldest-first eviction so multi-day sessions don't grow without limit.

The six panels

The scanner view (frontend/js/views/live_scanner.js) is a six-panel grid where each panel is an independent leaderboard over the same Finnhub WebSocket tick stream. By default:

  • % Gainers — biggest % up vs prior close.
  • % Losers — biggest % down.
  • Volume leaders — highest cumulative session volume.
  • Volume vs avg-10 — biggest relative-volume spike (RVOL).
  • Range expansion — biggest intraday range relative to ATR(14).
  • Tape velocity — most prints per second (works best mid-session).

Each panel is independently filterable (price range, min volume, min market cap, exclude OTC, exclude ETFs) and independently sortable. The leaderboard re-renders at most twice per second to keep the WebView responsive even on a hot tape.

Finnhub WebSocket — chunked subs

The free Finnhub tier caps a single WebSocket at 25 simultaneous symbol subscriptions. The scanner needs hundreds. The pipeline:

  1. Universe — the set of symbols the panels collectively need. Re-computed every minute from the union of: panel filters (e.g. "all US equities > $5 with volume > 1M") + your watchlists + open positions.
  2. Chunking — split the universe into N chunks of 25. Open N WebSocket connections, each subscribed to one chunk.
  3. Fan-in — every chunk's ticks land in a single in-process DashMap<Symbol, Tick> + tokio::sync::broadcast channel.
  4. Fan-out — the browser opens WS /ws/ticks, picks a per-panel filter, and only receives ticks for symbols its filter matches. The server-side filter keeps WS bandwidth bounded even when the universe is large.
  5. Reconnect — chunks reconnect on disconnect with exponential backoff (start 1 s, cap 60 s, jitter ±20%). The in-memory tick store keeps the last value for every symbol so panels stay populated through a reconnect.

The free tier is honored with no premium key. Bring your own Finnhub key from Settings → API Keys → Finnhub if you want the paid tier (more symbols / connection, no rate-limit reconnects).

TTS voice alerts

The Voice alerts toggle in the scanner header reads matching leaderboard entries out loud via speechSynthesis. The phrasing is templated:

"Halt:  S H W Z, code T-One."
"Volume spike:  N V D A, R-VOL twelve point three."
"New gainer:  A M D, plus four point five percent."

Ticker symbols are spelled letter-by-letter (better than the synth's word-guess), volumes are quantized to a one-decimal multiplier, percentages round to one place. Throttled to one utterance per 3 s per panel to avoid talk-over.

Under Tauri's custom scheme speechSynthesis.speak can throw a cross-origin SecurityError on first call; it's wrapped in a try/catch so the panel keeps working — speech just silently fails. Falls back gracefully if no voices are available.

Bounded in-memory stores

Every live stream is sized so a multi-day session can't OOM the host:

  • Ticks — DashMap keyed by symbol, last-tick-only. Bounded by the universe size (rotates as the universe shifts).
  • Halts — VecDeque, cap 2 000 oldest-first eviction (step 07).
  • Catalysts — VecDeque, cap 10 000 oldest-first eviction (step 07).
  • Webull snapshot — single struct, last poll only.

The cap values are configurable in config.default.toml if you want to keep more history in memory; defaults are sized for a 16 GB host running everything else.

Webull live snapshot

Paste your Webull session tokens once in Settings → Webull. The server-side adapter polls tradeapi.webullbroker.com every 5 seconds and pushes a snapshot containing positions, day P&L, buying power, and open orders to WS /ws/webull. Tokens stay in memory only — never written to disk, never logged — and expire when you quit the app.

  • What it can do — read-only position + order display, live P&L on the Live Positions tile.
  • What it can't do — place orders. By design.
  • If the token expires — the adapter logs a warning, stops polling, and the Live Positions tile shows "session expired — re-paste tokens". The rest of the app is unaffected.
Treat tokens as credentials Don't paste Webull tokens on a shared machine. The in-memory-only guarantee covers disk and logs, but it doesn't cover your ~/.bash_history if you pasted them from a terminal you ran echo in.

Pre-market & markets snapshot

The Pre-market tile (GET /api/premarket/snapshot) shows futures + commodities + crypto + FX + economic events in one cached page. The Markets tile (GET /api/markets/snapshot) gives a 16-symbol world-markets grid (S&P, NASDAQ, DAX, FTSE, Nikkei, Hang Seng, etc.). Both routes use futures_util::future::join_all internally — the serial-loop version blocked for 150 s and starved the pool; the join_all version finishes in ~3 s.

Markets snapshot is cached for 60 s in-process so the grid doesn't re-fan-out on every browser refresh.