A complete deep-dive into the React + Vite frontend, dual-watchlist system, 5 quantitative scatter models, automated data pipeline, and JAMstack deployment powering a professional-grade Indian stock market terminal.
Table of Contents
This is a fully serverless, zero-cost professional trading terminal for Indian markets. It tracks two separate watchlists β a curated Alpha 132 investment watchlist and an MTF swing-trading watchlist of 216 margin-eligible stocks β and synthesizes institutional-grade analytics for each.
The system answers two core questions: "How do my sector bets look as equal-weighted index charts?" and "Which stocks are showing quantitative signals worth acting on right now?"
Everything is computed by a Python script, committed as static JSON to GitHub, auto-deployed via Cloudflare Pages, and rendered in a React + Vite browser app β no backend server, no database, no cost.
JAMstack architecture β Python computes data offline, commits static JSON to GitHub, and the React frontend fetches it directly. No runtime server. The "backend" is a scheduled GitHub Action + Python script running twice daily.
β’ Dual watchlist β Investment + MTF mode, switchable at runtime
β’ Delta cache β only fetches new trading days each run
β’ Equal-weighting β no stock dominates by market cap
β’ React + Vite β full TypeScript component tree, zero global state libraries
The dashboard supports two completely independent watchlists that can be switched in real-time using the navbar toggle. Each watchlist has its own set of computed JSON files and its own data pipeline processing path. The React app uses a WatchlistId type ('investment' | 'mtf') as the top-level state that drives all data fetching and view availability.
Purpose: Long-term portfolio management and sector analysis
β’ 132 hand-picked Indian small- and mid-cap stocks
β’ Synthesizes 14 equal-weighted indices (1 master + 13 sector)
β’ All 3 views available: Index Chart Heatmap Quant
β’ Data source files: indices_summary.json, heatmap_data.json, quant_models.json
Purpose: Swing trade candidate selection using 3.5x+ margin stocks
β’ 216 stocks eligible for MTF (Margin Trade Funding) at β₯3.5x margin
β’ No index chart view (not tracked as index constituents)
β’ Available views: Heatmap Quant
β’ Data source files: mtf_heatmap_data.json, mtf_quant_models.json
β’ Clicking a heatmap tile navigates directly to Quant Models with that stock selected
When the user clicks the Alpha 132 / MTF Stocks toggle in the navbar:
1. App.tsx calls handleWatchlistChange() which updates the watchlist state
2. If switching to MTF and current view is 'chart', the view auto-redirects to 'heatmap'
3. Both useIndexData and useQuantData hooks re-fire their refresh() callbacks, fetching the watchlist-specific JSON files
4. The selected quant ticker is reset to null to clear cross-watchlist state
5. The navbar center tab group dynamically shows/hides the "Index Chart" button based on watchlist
Rather than re-downloading full history every run, the pipeline uses a delta cache approach:
1. On first run β download 1 year of daily OHLCV data for every stock and save to data/history.json
2. On subsequent runs β check the last date in cache per ticker, fetch only new trading days
3. Merge new rows into existing cache and re-save
4. The history.json file is excluded from the public/data/ sync (it can be very large and is not consumed by the frontend)
This reduces typical update time from ~15 minutes down to under 2 minutes.
Yahoo Finance throttles aggressive scrapers. The ingestion loop adds a random 1.5β3.0 second delay between each ticker fetch to avoid 429 errors.
Some stocks have mismatches between NSE symbol and Yahoo Finance ticker. A SYMBOL_OVERRIDES dict handles these edge cases:
Stocks List.json (Investment) and MTF Stocks.json (Swing) both share this schema:
The exchange field determines the suffix: NSE β .NS BSE β .BO
The --watchlist CLI flag controls which file is processed: --watchlist all (default in CI), --watchlist investment, or --watchlist mtf.
Every stock's price history is normalized to a base of 1000 on the first day it appears. This removes price bias β a βΉ5 stock and a βΉ5000 stock contribute equally.
The Index Value on any day is the arithmetic mean of all normalized prices in that sector:
Since all stocks start at 1000 on Day 0, the index also starts at 1000 β exactly like how Nifty started at 1000 on November 3, 1995.
Each day's candle is synthesized:
β’ Open = mean of all stocks' normalized open
β’ High = mean of all stocks' normalized high
β’ Low = mean of all stocks' normalized low
β’ Close = mean of all stocks' normalized close
This gives a realistic candle with wicks and bodies, just like a real index candle.
6 return windows computed per stock & index:
1D 1 trading day back
1W 5 trading days back
1M 21 trading days back
3M 63 trading days back
6M 126 trading days back
1Y full year (first record)
A sector only gets its own sub-index if it has 4 or more stocks. Smaller sectors are included in the Master Index but don't get a standalone chart.
Result: 1 Master Index + 13 Sector Sub-Indices = 14 total indices across all 132 investment stocks.
The heatmap uses D3's treemap() with the squarify tiling algorithm β the same technique used by Finviz and Bloomberg. It recursively subdivides a rectangle into child rectangles with aspect ratios as close to 1:1 as possible.
Tile size = absolute value of % return (or equal in Equal-Sizing mode). Bigger movers = bigger tiles.
Tile color = diverging redβgreen scale:
β’ Timeframe toggle β 1D / 1W / 1M / 3M / 6M / 1Y
β’ Sector filter β show one sector at a time
β’ Search β live ticker/name search
β’ Return filter β click a stat chip to filter by return bucket
β’ Hierarchy mode β Sector grouping vs Flat layout
β’ Sizing mode β Equal tiles vs Magnitude-proportional tiles
β’ Tile click β in MTF mode, navigates to Quant Models with that stock selected
heatmap_data.json / mtf_heatmap_data.json follow D3's hierarchical format:
The Quant view renders 5 interactive scatter plots computed by build_indices.py using numpy + pandas. Each model takes the full price history of every stock and outputs per-stock (x, y) coordinates plus statistical metadata. The frontend (QuantScatterCanvas.tsx) renders these with a custom Canvas API renderer β no charting library β with OLS regression overlay, interactive tooltip, and click-to-lock selection.
The same 5 models are computed independently for both watchlists, outputting quant_models.json and mtf_quant_models.json.
β’ QuantDashboard.tsx β top-level orchestrator; loads data, manages model selection state
β’ QuantModelTabs.tsx β 5-tab selector with live signal count badges
β’ QuantModelHUD.tsx β statistical summary panel (regression stats, signal counts, model metadata)
β’ QuantScatterCanvas.tsx β custom Canvas2D renderer; handles OLS line, quadrant crosshairs, point hover/click, sector filter, search filter
β’ QuantWatchlistTable.tsx β sortable table of all stocks in the active model with flags, sector filter, and search; clicking a row syncs the scatter canvas selection
30 7 * * 1-5 (07:30 UTC)10 10 * * 1-5 (10:10 UTC)actions/checkout@v4 to clone the full repo onto the runner.actions/setup-python@v5 with cache: 'pip' to accelerate dependency installs on repeat runs.yfinance, pandas, numpy, and requests. numpy is required for quant model computation (Hurst exponent, OLS regression, Mahalanobis distance).data/.public/data/ so Vite's build can serve them. history.json is excluded to keep the bundle small.data/ and public/data/ folders. Uses git diff --staged --quiet to skip commit if nothing changed (market holiday). Commit message includes [skip ci] to prevent infinite CI loops.The frontend has been fully migrated from a vanilla HTML/JS single-file to a Vite + React 18 + TypeScript application. This enables:
β’ Type-safe data contracts β all JSON shapes modelled in src/types/index.ts
β’ React hooks for data fetching (useIndexData, useQuantData)
β’ Hot Module Replacement during development
β’ Production build via npm run build β Cloudflare Pages auto-deploys from the dist/ output
β’ Tailwind CSS utility classes for styling
useIndexData(watchlist) β fetches either:
β’ Investment: indices_summary.json + heatmap_data.json in parallel
β’ MTF: mtf_heatmap_data.json only (no summary needed)
useQuantData(watchlist) β fetches either:
β’ Investment: quant_models.json
β’ MTF: mtf_quant_models.json
Both hooks auto-refresh when the watchlist prop changes via useEffect dependency on the refresh callback.
TradingView made a breaking change in v5. Old API:
Correct v5 API used in this project:
The entire system is built on free, open-source, and zero-cost components.
build_indices.py writes to data/ (your local cache). The React app reads from public/data/ in development and /data/ at runtime in production (served from Cloudflare Pages). Always sync after running the pipeline locally.