DHS Dashboard
Real-time Indian-markets research terminal — 200+ stocks, 4 broker feeds, sub-second WebSocket fan-out with multi-source failover.
Summary
DHS Dashboard is a real-time stock research terminal for the Indian market that unifies DarkHorseStocks' 200+ NSE recommendations with global benchmark, sectoral, and thematic indices on a single live surface. A custom Next.js server runs a WebSocket fan-out tier with a four-tier broker routing chain — Zerodha (primary) → Upstox → Fyers → TwelveData (backup) — so the dashboard keeps streaming even if a broker session dies or an upstream API throttles. A Prisma + PostgreSQL data layer with polymorphic instrument mapping powers historical OHLCV charts, pre-computed multi-window sparklines, gainer/loser leaderboards, and DHS-return calculations broadcast once on the server and consumed identically by every connected client.
Target user
DarkHorseStocks subscribers and Indian retail investors who want a single dashboard for DHS-recommended NSE stocks, sectoral and global indices, and live broker-grade tick prices — without keeping Kite, Upstox, and Fyers tabs open side-by-side.
- 01
Built a multi-broker live-streaming engine with deterministic failover: Zerodha primary, Upstox/Fyers secondary, TwelveData backup, database last — so a single broker auth lapse never blacks out the dashboard.
- 02
Designed a singleton `LivePriceManager` + reference-counted `WebSocketServerManager` that subscribes upstream only to symbols at least one client is watching, and unsubscribes when the last viewer leaves — preventing wasted broker quota on inactive instruments.
- 03
Shipped a `.broker-tokens.json` hot-reload pipeline (fs.watch-based) that swaps Zerodha/Upstox/Fyers tokens at runtime without dropping connected clients — daily broker re-auth no longer requires a server restart.
- 04
Modeled a 9-table Prisma schema with polymorphic `symbol`/`instrumentType` references (no FK constraints) so stocks and indices share the same historical-prices, live-prices, sparkline-cache, and broker-instrument tables — and one screener query covers both.
- 05
Pre-computed seven sparkline windows (7d / 30d / 90d / 6m / 1y / 3y / 5y) plus highs/lows per window into a single `sparkline_cache` row per symbol, so the dashboard loads 200+ stocks with instant historical context and zero on-demand price fetches.
- 06
Built a smart cron layer (`cron_job_config` + `cron_job_logs` tables) that skips redundant fetches: it checks the last stored date per symbol, the day of week, and market hours before hitting TwelveData — so re-running the daily pipeline mid-day is free.
- 07
Wired a 4-broker instrument-mapping table (`broker_instruments`) with Zerodha tokens, Upstox instrument keys, Fyers segment codes, and TwelveData symbols indexed per provider — so the routing layer never has to translate symbols at request time.
- 08
Migrated the production database from SQL Server to DigitalOcean managed PostgreSQL and upgraded the app from Next 14 / React 18 to Next 16 / React 19 / Prisma 6 across one branch without downtime.
Naively, every connected browser triggers a fresh subscription to its 50-symbol watchlist, and Zerodha's plan caps total live tokens hard. Solved with a reference-counted subscription map (`Map<symbol, count>`) inside `WebSocketServerManager`: client-driven subscribe messages bump the count, the upstream subscription only fires when count goes 0→1, unsubscribe drops the count, and the upstream unsubscribe only fires on the 1→0 transition. This keeps the broker stream tight to actual viewer demand and survives client churn without thrashing the upstream session.
Zerodha / Upstox / Fyers all issue short-lived access tokens that expire daily; restarting the WS process to reload them would drop every connected user. Solved with a `TokenFileWatcher` (`fs.watch` on `.broker-tokens.json`) that, on change, calls `wsServerService.updateZerodhaToken / updateUpstoxToken / updateFyersToken` — each broker service reconnects its single upstream WS with the new token while the client-facing fan-out tier and the per-client subscriptions stay alive. Operators re-auth via the `/brokers` page; clients see at most a one-tick gap.
- L01
Polymorphic columns (`symbol` + `instrumentType`, no FK) beat union types when you have two instrument families that share 90% of their downstream tables.
One sparkline cache, one historical-prices table, one live-prices table — all reachable by symbol — meant the screener, ticker bar, and gainers/losers code paths didn't need to branch on instrument kind. The price is application-level integrity, which is fine because writes are all serverside and well-typed.
- L02
Custom Next.js servers stop being scary the second you realize the framework hands you `app.getRequestHandler()`. The hard part isn't running Next next to other Node code — it's the upgrade-event interception, and that's a five-line patch. Don't reach for Socket.IO if all you need is `ws` and a single path.
- L03
Smart cron — checking 'do I already have today's data?' before each fetch — saved more upstream quota than any caching layer. The cheapest API call is the one you never make. Storing job runs in `cron_job_logs` made debugging skipped runs trivial later.
- tracked instruments
- 200+ DHS stocks + 50+ Indian indices + 30+ global indices
- broker integrations
- 4
- db tables
- 9