Back to work
Beta2025–2026Full-Stack Engineer75% complete

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.

§ 01Stack
01Primary
Next.jsReactTypeScriptPostgreSQLPrisma
02Infrastructure
DigitalOcean managed PostgreSQLCustom Node.js HTTP + WebSocket servernode-cron + cron-parsertsxDockerVercel Analytics + Speed Insightsvercel.json
03Integrations
Zerodha KiteConnectUpstox SDKFyers APITwelveData REST + WebSocketExcel ingestion
04UI / Frontend
Tailwind CSSshadcn/uiRadix UI primitivesRechartsFramer Motion / motionTanStack TableSWRnext-themessonnerlucide-react
§ 02Key features
  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

§ 03Hardest problems
  1. 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.

  2. 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.

§ 04What I learned
  • 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.

§ 05By the numbers
lines of code
~25278
tracked instruments
200+ DHS stocks + 50+ Indian indices + 30+ global indices
broker integrations
4
db tables
9