A personal stock screener for one investor. It watches a handful of lists, shows fundamentals and signals for each ticker, and helps decide what to look at next. It never places orders, never shares your portfolio, and runs entirely on free data APIs.
Overview
The screener answers three questions, in order: what do I own, what is moving, and what is worth a closer look. Everything else is a detail of one of those three.
- Command is the home screen. A watchlist on the left, one ticker in detail on the right, and a market strip across the top.
- Portfolio is what you own: holdings, risk, sector mix, and plain-word rebalance suggestions.
- Brief is the morning read: earnings calendar, movers, fresh news, and what the Street is saying.
- Signals is discovery: scans of whole indexes for technical setups, plus a fundamental filter, saved screens, and a sector heatmap.
- Groups is the optional shared layer: small groups with per-ticker discussion and structured buy/sell calls. Your portfolio and watchlists are never visible there.
Personal, not social. The screener is built for one person. Shared features exist only inside Groups, and they are fenced off from private data by design: group endpoints require sign-in, check membership, and never return holdings or watchlists. There is no public profile, no feed, and no way for one user to see another user's lists.
Design principles
These rules were set while building the app and they still govern every change. A feature that breaks one of them does not ship.
- Free APIs only. Finnhub free tier, Yahoo chart data, and Alpha Vantage free tier. Anything that needs a paid plan is out of scope, including standardized statements, price targets, and options data.
- Calls only when needed, only for what is required. Finnhub allows 60 calls a minute on the shared key. The app caches aggressively, fetches per symbol only when a view needs it, and never polls for data nobody is looking at.
- No orders, ever. The screener finds candidates. It has no broker connection and no order flow. Signals are for targeting, not trading.
- Clean values, explanations on hover. Numbers sit on the card they belong to, with their 5-year average underneath. What a number means lives in a hover or tap, not in permanent text.
- Related numbers stay together. A ratio and its history are one card, not two sections.
- Think first, then design. Visual fixes follow design principles. No quick patches, no wasted space, nothing floating over numbers.
- Minimal and accessible. One slim top bar, one-line watchlist rows, real buttons with spoken labels, 24px touch targets, and all motion off under reduced-motion settings.
- Never lose the user's lists. A failed load never rewrites saved data. A failed symbol stays visible with a retry. Saving waits until a load has succeeded for the current identity.
Architecture
Three parts. A React single-page app served from Cloudflare Pages, a Cloudflare Worker that holds the API keys and talks to the data providers, and a Postgres database on Neon that stores lists and holdings. The browser never sees an API key, except for three Alpha Vantage keys used only for direct Congress-trades fetches from the browser.
| Part | What it is | What it owns |
|---|---|---|
| SPA | React 18 + Vite 6, served as static files from Cloudflare Pages (screener-raj.pages.dev) | Every screen, chart (lightweight-charts), scans, backtests, and the save guard |
| Worker | One Cloudflare Worker (screener-api), ~2,700 lines | API keys, Finnhub/Yahoo fan-out, 24-hour edge caches, JWT checks, group rules, Neon writes |
| Neon | Postgres 18, free plan, AWS US East 2 | screener_state, watchlist_lists, and five group tables |
| Clerk | Hosted sign-in, no phone number required | Identity only. The app keys cloud rows by clerk:<user id> |
| Finnhub | Free tier, 60 calls/min shared key, plus a second key for Portfolio and metrics fan-out | Quotes, fundamentals, news, earnings, analyst, insider, peers |
| Yahoo | Public chart API, no key | Price history, market strip, FX, extended-hours bars |
| Alpha Vantage | Free tier, three keys rotated from the browser | Congress trades only (25 calls/day per key) |
Why a Worker in the middle
Three reasons. First, keys stay server-side, so the shipped bundle contains no secret. Second, one Worker call can fan out to several provider calls and cache the merged result for a day, which is how the app stays inside the 60-calls-a-minute budget. Third, the Worker is where identity is checked: it verifies the Clerk token and refuses group writes from anyone who is not a member.
What runs in the browser, and why
Scans, backtests, diversification math, and the risk engine all run in the browser. They need price history, not Finnhub, so they cost zero Finnhub calls. A scan of the S&P 500 is hundreds of Yahoo chart fetches through the Worker, done six at a time, and the results live in local storage until the next scan. Congress trades also fetch from the browser, because the free tier blocks the same call from the Worker's shared egress.
How data flows
Quotes: one fetch, two sources
A quote is two facts from two places. The price and change come from Finnhub (/quote). For Toronto listings the price comes from the last Yahoo chart candle instead, because Finnhub's free quote for a TSX symbol is the US listing. The app asks the Worker for a whole list at once (/api/quotes), refreshes every 60 seconds, and shows a pulsing live dot while the market is open. A live tick stream (Finnhub websocket, proxied through the Worker, 50 symbols max) updates prices between polls during market hours.
Two guards keep quotes honest. First, a Toronto chart is only reused when its last bar is from today in Eastern time, or the chart was fetched within the last hour. That stops yesterday's close from masquerading as today's price. Second, the app resolves every symbol before it fetches: a bare TD always opens the Toronto listing TD.TO, never the New York one.
Saving: the guard that never loses a list
Lists and holdings save to the cloud, but only under strict rules. The rules exist because two real incidents wiped lists: a sign-in race and a failed load that deleted symbols it could not fetch. Both are fixed in code, and the rules below are the fix.
- Ready only after a successful load. The app flips its save flag only after the cloud load for the current identity returns. Until then it shows the local copy, saves nothing, and retries every 30 seconds.
- Identity is captured at both ends. A load and a save each record whose identity they belong to. If the identity changed while a request was in flight, the result is discarded or the write is aborted.
- Failed symbols stay. If quotes fail for a symbol, the row stays in the list as a failed row with Retry and a remove button. The old behaviour, deleting failed symbols, is gone.
- One stale client can still overwrite. A phone app that resumed from a suspended session can hold an old copy and save it. The footer version stamp (
app v<hash>) and the "new version available" banner exist to catch exactly that: reload before trusting a stale screen.
History and charts: Yahoo, cached a day
Charts, scans, backtests, and the strip all read Yahoo chart data through /api/history. The Worker caches each symbol for 24 hours, separately in each Cloudflare data centre. That means two data centres can briefly serve different snapshots of the same symbol, and a buy-and-hold figure can differ by a point or two between them. It is a cache vintage difference, not a calculation bug, and it heals itself as caches expire.
Fundamentals: one call per symbol, cached a day
/api/metrics returns P/E, market cap, yield, beta, ROE, net margin, debt/equity, revenue growth, and sector for up to 12 symbols per call. Each uncached symbol costs one Finnhub /stock/metric call, plus one /stock/profile2 call for the sector. Results are cached 24 hours per symbol; failed fetches are not cached. Toronto symbols short-circuit: there are no TSX fundamentals on the free tier, so they return empty rather than burning calls.
Live stream: one socket, shared
The Worker holds one Finnhub websocket and proxies trades to every connected browser. It subscribes to at most 50 symbols, only during market hours, skips Toronto symbols, and sends a ping to keep the socket alive. The app disconnects hidden tabs and reconnects with backoff, giving up after five fast failures. The 60-second poll stays as the fallback.
Sign-in & privacy
Sign-in is optional, through Clerk, and it never asks for a phone number. Signing in turns on cloud saving and unlocks the Brief, Portfolio, Signals, and Groups views. Signing out, or never signing in, shows a fixed five-stock preview (AAPL, MSFT, GOOG, TD.TO, NVDA) with the Fundamentals card only. No chart, no lists, no search, no saving.
- Identity is the key. Cloud rows are keyed by
clerk:<user id>with a verified token. Without a token the app uses a per-browser id that never leaves the device. - Anonymous data does not migrate. Lists made while signed out stay in that browser. The first sign-in copies the legacy watchlist into a list called Watchlist, and only that.
- Groups are Clerk-only. No anonymous access, no fallback. Every group endpoint checks the token and the membership, and a non-member gets a 404.
- Nothing is shared by default. A friend using the screener cannot see your portfolio or watchlist. Groups show only what you post inside them.
Command
The home screen. Watchlist on the left, one ticker in full detail in the middle, market strip on top.
Top bar
One slim sticky bar: the Screener wordmark, a pulsing live dot with the updated time, the view tabs on desktop, a search icon, and the profile avatar. The search panel hangs from the bar and opens any ticker by symbol or name, including tickers that are in no list.
Market strip
A one-row swipeable carousel of nine cards: S&P 500, Nasdaq, Nasdaq 100, TSX, VIX, US 10-year, WTI crude, gold, and silver. It advances one card about every 4.5 seconds and loops. The advance pauses on hover, touch, drag, wheel scroll, and keyboard focus, and it never runs while the strip is off screen or the tab is hidden. Gold and silver are futures (GC=F, SI=F), not spot, because true spot is not available free through Yahoo.
Watchlist
One flat list, in your order, no grouping. Each row is a single line: symbol on the left, price above the change on the right, for example AAPL $344.17, down $5.34, down 1.53%. Prices flash on tick. Rows are real buttons with spoken labels. Tapping a row opens that ticker in detail. The count sits in the tabs row ("3 names").
Adding is a button that explains itself: + Add to Watchlist. It opens a symbol input that stays open for multiple adds and closes on Escape. Removing is edit-gated: there is no remove button in normal browsing, not even on the selected row. Tap Edit next to the Add button and every row gets a remove button, the list tabs offer rename and delete, and rows stop opening tickers until you tap Done. A symbol whose quotes failed to load stays in the list as a failed row with Retry; its remove button follows the same edit rule.
Ticker detail
Selecting a ticker opens its detail page. Sections, in phone order: Chart, Fundamentals, Peers, Congress, News, Signal, Earnings, Quality, Analyst, Insider. On desktop the detail sits beside the watchlist with resizable columns. On phones a floating pill with the same section names appears once you have scrolled a little, and it stays up while you are scrolled down, so you can jump without scrolling back. It only shows sections that are actually on the page.
- Chart. Price bars with 6-month, 1-year, and 5-year ranges, weekly and monthly aggregation, SMA, EMA, Bollinger bands, support and resistance, and Fibonacci levels. After-hours prices come from Yahoo 5-minute bars, 04:00 to 20:00 Eastern.
- Fundamentals. The clean values view: each ratio with its 5-year average underneath, related ratios on the same card, explanations on hover. Toronto symbols show dashes where no free fundamental exists.
- Peers. Related companies from Finnhub, with the same one-tap open.
- Congress. Recent trades by members of Congress for that ticker, fetched from Alpha Vantage. Includes chamber, politician, party, state and district, transaction type and date, disclosure date, and amount range.
- News. Company news from Finnhub, newest first, linking out to the source.
- Signal. A six-factor verdict (trend, momentum, mean reversion, breakout, value, quality) scored 0 to 100, with a short "why this score". The fixed strategy ideas used to sit here; they were removed because they were too static. The card now shows only the live verdict.
- Earnings. Reported dates with actuals, plus upcoming dates for the next week.
- Quality. A Buffett and Munger style checklist for the business.
- Analyst. The current recommendation trend and the consensus direction.
- Insider. Recent insider transactions.
Currency
Values are shown in the listing currency. A dual-listed company is converted when the data source reports in another currency: Finnhub serves some fundamentals in the home currency, so the app detects it and converts absolute and per-share values through Yahoo FX. Ratios are never converted.
Portfolio
What you own, how risky it is, and where it is concentrated.
Holdings
Each holding is a symbol, a share count, an average cost, and an optional target weight. The total is shown in dollars, and each holding shows its current value, gain or loss, and weight. Holdings save to the cloud with the same guard as lists. Adding a holding is a small form on the Portfolio tab; removing one is a tap on its remove control.
Risk engine
The risk engine derives everything from the history the app already has, so it costs no extra API calls. It reports portfolio volatility, the largest drawdown, and value at risk in plain words, and it flags holdings that dominate the total.
Diversification
Your sector weights against the S&P 500 (a static GICS snapshot, currently October 30, 2026). Concentration is stated in plain words through the Herfindahl index, translated into an "effective number of positions". The dashboard flags sectors you are overweight in and sectors you have no exposure to, and it shows a pairwise correlation table between your holdings with plain labels instead of bare numbers. Finnhub industry labels are mapped to GICS sectors; a label with no mapping is shown as-is.
Problems and rebalance suggestions
A rule-based section that reads the portfolio and states what is wrong in plain language, for example a holding far above its target weight, and what rebalancing would look like. It is suggestions only. Nothing here trades, and nothing is submitted anywhere.
What is not here
There is no balance-add or cash-top-up flow in the interface. It was removed at the owner's request; the code is kept but not rendered. Candidate suggestions from the old balance feature are also off. ETFs are supported as holdings and are bucketed under "ETF" in the portfolio; they do not appear in watchlist signals or earnings.
Brief
The morning read. What reported, what is moving, what is new, and what the Street thinks.
- Earnings calendar. What reported, plus what is due today, tomorrow, and this week, for the symbols in your lists. Reported entries carry actuals where the provider has them.
- Movers. The session's biggest moves among your lists, labelled by session so an after-hours move is never mistaken for a regular-session one.
- Fresh news. News since the last 4:00 PM Eastern close, newest first.
- Street lines. The consensus trend and the insider net for your names, so a single headline does not carry more weight than the trend behind it.
The Brief is time-aware: it knows the last close in Eastern time and works across daylight saving changes, so "since the last close" always means the real last close. Signed-out visitors see a sign-in gate instead of the Brief.
Signals
Discovery. Scan whole indexes for technical setups, filter by fundamentals, and keep the screens you like.
The scanner
Pick a universe (S&P 500, Nasdaq-100, or My lists), pick one of six conditions, set the lookback window (last 5, 10, or 30 trading days), and tap Find signals. The app fetches one year of daily bars per name through the Worker, six at a time, evaluates the condition on the last N days, and returns the hits ranked by signal date, newest first. Tapping a hit opens it on Command. Nothing re-fetches until you tap Find again, and switching tabs or reloading restores the last scan exactly.
The six conditions
| Condition | What it looks for | Defaults |
|---|---|---|
| SMA crossover | Fast average crossing above the slow one | 6-day vs 30-day |
| EMA crossover | Fast exponential average crossing above the slow one | 20-day vs 50-day |
| RSI bounce | RSI dipping to oversold and turning back up | 14-day, 30 / 70 |
| MACD crossover | MACD line crossing above its signal line | 12 / 26 / 9 |
| Breakout | Price closing above the 20-day Donchian high | 20 days |
| Price + volume breakout | Breakout with volume above its average | 20 days, 1.5x volume |
Parameters are typed freely and clamped on blur or Enter. Every field shows its range and its cross rule underneath, and an invalid combination (fast above slow, oversold above overbought) disables Find with a message. The six conditions are the whole set: no new signals are added.
Preset backtests
Every signal row has a Backtest button. One click runs that exact preset on 5 years of daily bars, evaluated over 4 years, long side only. Entry is the bullish event, exit is the bearish event, no stop, no hold cap, costs on, next-bar fills, and the same gates as the main engine. A bearish row backtests the same setup's long side and the panel says so. The old Backtest tab and its fixed strategy ideas are gone; the engine and its 22 strategy definitions remain in code but have no interface.
Fundamentals mode
Flip the Signals tab to Fundamentals to filter an index by numbers instead of price action. Eight fields, each with a min and a max: P/E, market cap, yield, beta, ROE, net margin, debt/equity, and revenue growth. All set filters combine with AND. At least one filter must be set; a filter-free run is blocked by design. Results stream in progressively as the runner works through the universe in chunks, and a Stop button ends the run. When the weekday-morning warmer has run, the runner reads today's snapshot from Neon first: snapshot symbols are evaluated locally with zero Finnhub calls, and only the symbols missing from the snapshot go through the paced live path. A cold S&P 500 pass without a snapshot takes about 20 minutes; with a full snapshot it takes seconds. The runner paces uncached chunks 24 seconds apart to stay inside the Finnhub budget, so a cold S&P 500 pass takes about 20 minutes and a second run is fast because the metrics are cached.
Results table
Results can be shown as rows or as a sortable table. The table has the fixed columns (symbol, direction, date, price, signal, backtest) plus six columns you pick: P/E, yield, market cap, beta, distance from the 52-week high, and sector. Clicking a header sorts ascending, then descending, then back to scan order, with missing values last. Metric columns load per hit from /api/metrics. Distance from the 52-week high needs no fetch: it is computed from the scan's own one-year bars.
Saved screens
Name a scan setup and save it. Tapping a saved screen loads the setup and runs the scan in one tap. Deleting takes two taps. Twenty screens max, duplicate names rejected. Saved screens live in the browser's local storage, not in the cloud, by design.
Sector heatmap
Eleven S&P 500 sector tiles at the top of the Signals tab, sized by market-cap weight and coloured by the one-day or one-week change, with a toggle between the two. Full tint at 3 percent. Changes come from Yahoo chart data, eleven parallel calls when the tab opens, no key, no Finnhub. Tapping a tile shows its three largest holdings; tapping a holding opens it on Command. One failed tile renders grey and never blocks the rest.
Groups
The only shared part of the app. Small groups, per-ticker discussion, and structured calls with a paper scoreboard.
A group is a named room with up to 20 members, created by a signed-in user. Up to five groups per user. Inside a group there are per-ticker threads and a way to post a structured call: buy or sell, on a ticker, with a price and a thesis. Calls are not the Signal card's signals. A call is a person's stated position; a signal is the app's computed verdict.
- Calls and the scoreboard. Each call is tracked against the market as a paper outcome, and the group keeps a scoreboard of whose calls worked. Nothing here trades. A call is a note with a price attached, and the scoreboard is the record of it.
- Membership is checked on every write. Joining, posting, approving, and removing all go through Clerk-only endpoints that verify membership. A non-member gets a 404, and removing a member cuts their access.
- Rate limits. Ten group writes per minute per user, enforced in the Worker.
- Invite codes. Groups are joined by invite code. An old code stops working once it is replaced.
Privacy, restated. Groups never see your portfolio, your watchlist, or your holdings. The only things inside a group are the messages and calls you post there.
Watchlists & lists
There is one active list at a time, shown on Command. You can keep up to 20 named lists with up to 200 symbols each. The first list is called Watchlist. Switching lists switches the symbols the watchlist shows, the quotes it fetches, and the universe the Brief and Signals use when "My lists" is selected. Only the open list loads quotes.
- Creating. The + button in the tabs row opens a name input. Duplicate names are rejected.
- Renaming and deleting. Only in Edit mode: tap a tab to rename it inline, or tap its delete button twice to delete it. At least one list always remains.
- Adding. The + Add button opens a symbol input. A successful add keeps the input open so you can add several in a row. A symbol that is already in the list, or that cannot be resolved, shows a hint instead of failing silently.
- Removing. Only in Edit mode, as described on the Command page. There is no gesture, shortcut, or automatic path that removes a symbol.
- Symbols and listings. US symbols are bare (
AAPL). Toronto symbols carry.TO(TD.TO). A bare symbol that matches a Toronto listing resolves to the Toronto one. A symbol with no data anywhere (a delisted or bad ticker) can still sit in a list; it shows as a failed row, never as an empty slot.
Worker API
Every call goes to the Worker. The browser never calls Finnhub, Yahoo, or Neon directly, except for Congress trades. Base URL: https://screener-api.rajppatel-aiapp.workers.dev.
| Endpoint | What it returns | Cache |
|---|---|---|
GET /api/quotes?symbols=… | Price, change, and change percent for a list of symbols | None (live) |
GET /api/stock?symbol=… | Quote plus profile, metrics, news, and earnings for one symbol | Minutes, per symbol |
GET /api/history?symbol=…&range=… | Yahoo chart bars for a symbol and range | 24 h per symbol |
GET /api/metrics?symbols=… | Fundamentals for up to 12 symbols (P/E, cap, yield, beta, ROE, margin, debt/equity, growth, sector) | 24 h per symbol |
GET /api/metrics-universe?universe=… | Today's pre-warmed snapshot for sp500 or nasdaq100, one row per symbol, from Neon | 60 s edge cache; rows stamped with the Eastern date |
GET /api/warm | Runs one 20-symbol warming tick on demand (same code the cron calls). App origin only. | Never cached |
GET /api/market | The nine market-strip values (indexes, VIX, 10-year, oil, gold, silver) | Minutes |
GET /api/movers | Session movers for the Brief | Minutes |
GET /api/earnings?symbols=… | Earnings calendar and reported actuals | Hours |
GET /api/analyst?symbol=… | Recommendation trend for one symbol | Hours |
GET /api/peers?symbol=… | Peer companies for one symbol | 24 h |
GET /api/candidate?symbol=… | Portfolio candidate detail, on the second Finnhub key | Hours |
GET /api/congress?symbols=… | Congress trades. Currently returns a note: the Worker path is blocked on the free tier, so the browser fetches Alpha Vantage directly. | N/A |
GET /api/stream | Websocket proxy for live Finnhub trades (50 symbols max) | Live |
GET /api/state · PUT /api/state | Holdings (and the legacy watchlist field). Keyed by identity. | None |
GET /api/lists · PUT /api/lists | Named watchlists. PUT replaces the whole set. First GET migrates the legacy watchlist into a list called Watchlist. | None |
/api/groups… | Group rooms, members, threads, calls, and the scoreboard. Clerk JWT required on every call; membership checked. | None |
Two hard limits shape the Worker. One invocation fans out about four subrequests per uncached symbol against a cap of 50, so fan-out endpoints chunk small (earnings uses 6 symbols, metrics uses 12). And any provider error, including a rate limit, returns null for that symbol rather than an error page, so one bad symbol never breaks a whole list.
Storage & saving
Cloud (Neon)
| Table | What it holds |
|---|---|
screener_state | Holdings and the legacy watchlist, keyed by owner id |
watchlist_lists | Named watchlists, keyed by owner id |
universe_metrics | Morning-warmer snapshot: one row per symbol (symbol, universe, metrics JSON, Eastern snapshot date) |
| Five group tables | Groups, members, threads, calls, and scoreboard rows |
There is no daily snapshot storage. Nothing is versioned: a PUT replaces the whole list or state. That is why the save guard matters so much.
Browser (local storage)
| Key | What it holds |
|---|---|
screener.lists.v1 · screener.activeList.v1 | Local copy of the lists and which one is open |
screener.holdings.v1 | Local copy of the holdings |
screener.signals.v1 | Scan settings, last scan results, and Signals vs Fundamentals mode |
screener.filters.v1 | Fundamental filter results |
screener.screens.v1 | Saved screens |
screener.results-table.v1 | Rows vs Table view and picked columns |
screener.panels.v1 · screener.cols.v1 | Which detail sections are open, and desktop column widths |
screener.congress.v2 | Congress trades cache, 7 days per symbol |
screener.backtests.v5 | Dormant backtest cache from the removed Backtest tab |
Signed-in and signed-out data are kept in separate stores. The service worker (screener-v1) precaches the app shell and serves it offline, with network-first navigation, so the app opens even with no connection but never serves stale data as fresh.
Limits & known quirks
- Finnhub free tier: 60 calls a minute. One shared key for most calls, a second key for Portfolio candidates and metrics. The app paces, caches, and chunks to stay under it. A cold S&P 500 fundamentals pass takes about 20 minutes by design.
- No Toronto fundamentals. No free provider has them. Toronto symbols show dashes for P/E, market cap, yield, beta, and the rest. That is a data limit, not a bug.
- No standardized statements, price targets, or options data. All premium on the free tier. The app does not try to fetch them.
- History cache vintages differ by data centre. Two Cloudflare data centres can serve different 24-hour snapshots of the same symbol, so a buy-and-hold figure can differ by a point or two. Check the snapshot before suspecting the engine.
- Gold and silver are futures. True spot is not available free through Yahoo.
- Some tickers have no data. A delisted or bad symbol renders as a failed row with Retry. It is never silently dropped.
- A resumed phone app can be stale. A phone that wakes a suspended page may run an old bundle. The footer version stamp and the update banner exist for that: reload when the banner appears.
- Two-identity group checks are unverified. Join approve and deny, non-member 404s, member removal cutting access, and old invite rejection need a second identity to test, and there is no approved way to do that.
Versions & deploys
The app and the Worker deploy separately, and that split is deliberate.
- App (SPA). Built with Vite and deployed to Cloudflare Pages (
screener-raj). Every build is guarded: the bundle must reference the Worker URL and must not contain the string "Demo data", or the deploy does not go out. The footer shows the running bundle hash (app v<hash>), and the app checks for a newer bundle on load, every five minutes, and on foregrounding, showing a reload banner when one exists. - Worker. Updated by pasting into the Cloudflare Worker dashboard only. The stored API token cannot edit Worker scripts, and an API upload could wipe secret bindings whose values are unrecoverable. A copy page serves the current
worker.jswith a copy button for the paste. After a paste, the endpoints are verified with curl and the UI is verified in a real browser. The morning warmer's schedule is a cron trigger on the same Worker, added in the dashboard under Settings, Triggers:*/5 10-13 * * 1-5, Monday to Friday. Each fire warms the next 20 symbols; the whole universe (516 warmable names) takes about two and a half hours and finishes before the 9:30 ET open. - Data mode. The production environment must carry
VITE_DATA_MODE=liveandVITE_API_BASEpointing at the Worker. Losing those once shipped demo data to production; the build guard above exists because of it.
Changelog
| When | What changed |
|---|---|
| Oct 31, 2026 | Live Finnhub and Yahoo integration, Portfolio tab with risk engine, Neon persistence, Nasdaq-100 strip |
| Nov 1, 2026 | Clerk sign-in, ETF support (portfolio only), Paper & Ink theme, Command split layout, signals dashboard, overnight-price experiment reverted |
| Nov 2, 2026 | Named watchlists, live trade stream via Worker websocket, Morning Brief, flat watchlist, second Finnhub key |
| Nov 3, 2026 | Groups system (privacy enforced), NVO currency fix, social calls with paper scoreboard |
| Nov 4, 2026 | Congress trades via browser-side Alpha Vantage, signed-out gating, save guard, TD dual-listing fix, balance-add UI removed |
| Nov 5, 2026 | Signals tab (six conditions, preset backtests, persistence), fundamental filter, diversification dashboard, saved screens, sortable results table, sector heatmap, spinning market strip with gold and silver, floating section nav, list-wipe race and drop fixes, version stamp and update banner, minimalist redesign, edit-gated removal |
| Nov 5, 2026 | Morning warmer: weekday cron pre-fills today's S&P 500 and Nasdaq-100 fundamentals into Neon (universe_metrics), 20 symbols per tick; the filter reads the snapshot first and only live-fetches what's missing. TSX fundamentals confirmed out of scope. |