10 KiB
Market Screener
A personal stock screener and portfolio tracker. Scores stocks, ETFs, and bonds under two lenses — Market-Adjusted (what's acceptable in today's market) and Fundamental (strict Graham value-investing) — then compares them to produce an actionable signal. Comes with a live SvelteKit dashboard.
Table of Contents
Developer Setup
Prerequisites
- Node.js 20+
- npm 10+
Install
# Install server dependencies
npm install
# Install UI dependencies (first time only)
npm run ui:install
Start
npm run dev
This starts both the API server on port 3000 and the SvelteKit UI on port 5173 concurrently. Open http://localhost:5173.
To run the API server alone:
npm run server
Environment Variables
Create a .env file in the project root. None are required to run the app — it works with Yahoo Finance data out of the box. Optional keys unlock additional features.
ANTHROPIC_API_KEY — LLM news analysis (optional)
Powers the Analyze button on each screener section. Without this key the button is disabled.
- Go to console.anthropic.com
- Create an API key under API Keys
- Add to
.env:
ANTHROPIC_API_KEY=sk-ant-...
SIMPLEFIN_SETUP_TOKEN — Live bank/brokerage balances (optional)
Powers the personal finance section of the Portfolio page (net worth, account balances, spending breakdown).
- Go to beta-bridge.simplefin.org and create a Setup Token
- Add to
.env:
SIMPLEFIN_SETUP_TOKEN=aHR0cHM6Ly...
On first request the token is claimed automatically and the resulting Access URL is saved back to .env as SIMPLEFIN_ACCESS_URL. Subsequent restarts use the Access URL directly.
API_KEY — Bearer token auth (optional)
When set, every API route requires Authorization: Bearer <key>. Useful when the server is exposed to a network. /health and OPTIONS preflight are exempt.
API_KEY=your-secret-key
CLIENT_ORIGIN — CORS allowed origin (optional)
Defaults to http://localhost:5173. Change if the UI is served from a different origin.
CLIENT_ORIGIN=https://yourdomain.com
Complete .env example
ANTHROPIC_API_KEY=sk-ant-...
SIMPLEFIN_SETUP_TOKEN=aHR0cHM6Ly...
API_KEY=optional-secret
CLIENT_ORIGIN=http://localhost:5173
Commands
| Command | Description |
|---|---|
npm run dev |
Start API (port 3000) + UI (port 5173) together |
npm run server |
Start API server only |
npm run ui:install |
Install UI dependencies (first time / after git pull) |
npm test |
Run all unit + integration tests |
npm run test:watch |
Watch mode — re-run on file changes |
npm run typecheck |
TypeScript type check without emitting |
npm run format |
Format all source files with Prettier |
npm run format:check |
Check formatting without writing (used in CI) |
Running Tests
npm test
Uses Node's built-in node:test runner — no external framework. Tests cover:
- Scoring rules and gate values (
ScoringConfig,RuleMerger,MarketRegime) - Asset scorers (
StockScorer,EtfScorer,BondScorer) - Data mapping (
DataMapper) - Portfolio advice logic (
PortfolioAdvisor) - LLM response parsing (
LLMAnalyst) - Repository CRUD (
MarketCallRepository) - Controller integration tests for all API routes (Fastify
inject(), zero live network calls)
Pre-commit hook runs Prettier then tests. Pre-push hook runs tests.
Project Structure
bin/
server.ts API server entry point
server/
app.ts Fastify app factory — wires DI, rate limiting, auth hook
controllers/ HTTP layer: parse request → call service → return response
services/ Business logic (ScreenerEngine, BenchmarkProvider, PortfolioAdvisor…)
repositories/ JSON file persistence (MarketCallRepository, PortfolioRepository)
clients/ External API connectors (YahooFinanceClient, SimpleFINClient, AnthropicClient)
models/ Domain entities: Stock, Etf, Bond
scorers/ Stateless scoring functions: StockScorer, EtfScorer, BondScorer
config/ ScoringConfig (all gates/weights), constants
types/ TypeScript interfaces, one file per domain
ui/
src/
routes/ SvelteKit pages: /, /portfolio, /calls, /safe-buys
lib/
stores/ Svelte 5 reactive stores (screener.store, portfolio.store)
api/ Fetch wrappers for each API domain
portfolio/ Portfolio-specific components
calls/ Market calls components
styles/ Global SCSS design tokens and partials
tests/ Unit + integration tests
portfolio.json Your holdings (gitignored — create manually or via the UI)
market-calls.json Persisted market thesis calls (gitignored)
.benchmark-cache.json Benchmark data cache — survives server restart (gitignored)
User Guide
Screener tab
The main view. On load it automatically fetches today's financial news, extracts the most-mentioned tickers, and screens them.
Market context strip
The row of chips at the top shows live benchmark data fetched from Yahoo Finance:
| Chip | Meaning |
|---|---|
| 10Y | 10-year Treasury yield — the risk-free rate |
| VIX | Volatility index — market fear gauge |
| S&P | S&P 500 index price |
| S&P P/E | Trailing P/E of the S&P 500 (via SPY) |
| Tech P/E | Trailing P/E of the tech sector (via XLK) |
| REIT Yld | REIT dividend yield (via XLRE) |
| IG Sprd | Investment-grade bond spread above risk-free (LQD − TNX) |
| Rates | Rate regime: LOW / NORMAL / HIGH (based on 10Y yield) |
| Vol | Volatility regime: LOW / NORMAL / HIGH (based on VIX) |
The rate regime affects how strict the Market-Adjusted gates are — in a HIGH rate environment the P/E multiplier compresses and bond spreads tighten.
Signal Summary table
Quick overview of all screened tickers with their signal, market-adjusted verdict, fundamental verdict, market cap tier, and growth style.
Per-asset detail tables
Expand each section (STOCK / ETF / BOND) for full metrics: P/E, PEG, ROE, margins, FCF yield, D/E, analyst consensus, DCF intrinsic value, 52-week movement, and more.
Analyze button
Runs an Anthropic LLM over the latest Yahoo Finance news for assets in that section. Returns a sentiment summary, affected industries, and related tickers to watch. Requires ANTHROPIC_API_KEY.
Search tickers
Click Search tickers to screen any custom list — type tickers comma or space separated and press Enter or click Screen.
Signals explained
| Signal | What it means |
|---|---|
| ✅ Strong Buy | Passes both Market-Adjusted AND Fundamental gates — genuine value at current prices |
| ⚡ Momentum | Passes Market-Adjusted, holds fundamentally — good in the current market but not a bargain |
| ⚠️ Speculation | Passes Market-Adjusted, fails Fundamental — priced for perfection, high risk |
| 🔄 Neutral | Borderline in one or both lenses — hold, no clear edge |
| ❌ Avoid | Fails both lenses |
How scoring works
Every asset is scored twice:
Market-Adjusted gates move with the market. The stock P/E gate = SPY trailing P/E × 1.5 (compresses to × 1.2 in a HIGH rate regime). Tech P/E = XLK P/E × 1.3. This reflects what the market is currently willing to pay.
Fundamental gates are fixed Graham/value-investing standards that never change:
| Gate | Threshold | Rationale |
|---|---|---|
| Stock P/E | < 15× | Graham's actual rule |
| Stock PEG | < 1.0 | Lynch: PEG > 1.0 = paying full price |
| D/E ratio | < 1.5× | Distress typically starts above 2× |
| Quick ratio | > 0.8 | Below 0.8 = real liquidity stress |
Sector overrides apply in both modes — e.g. tech stocks allow P/E up to 35× and D/E up to 2.0, REITs are scored on yield rather than P/E.
Portfolio tab
Track your holdings and get hold/sell/add advice cross-referenced with screener signals.
Adding holdings — click + Add Holding and fill in ticker, shares, cost basis, asset type, and source broker. Holdings are saved to portfolio.json on disk.
Inline editing — click the ✎ pencil icon on any row to edit shares, cost basis, type, or source directly in the table.
Advice column — each holding is screened live and the signal is combined with your gain/loss position:
| Situation | Advice |
|---|---|
| ✅ Strong Buy signal | Hold & Add |
| ⚡ Momentum + > 30% gain | Consider partial profit-taking |
| ⚠️ Speculation + > 20% gain | Reduce position |
| ❌ Avoid signal + in profit | Sell (Take Profits) |
| ❌ Avoid signal + at a loss | Sell (Cut Loss) |
| Crypto | Hold / Review position (no fundamental scoring) |
Personal finance section (requires SimpleFIN) — when configured, the page also shows net worth, total assets vs liabilities, cash vs investments ratio, monthly income/spend, account balances, and a spending breakdown by category for the last 30 days.
Market Calls tab
Record and track quarterly investment theses from the day you make the call.
Creating a call — click + New Call and fill in:
- Title — e.g. "Q3 2025 — Rate pivot & tech rotation"
- Quarter — the quarter this thesis applies to
- Thesis — the macro reasoning behind the call (min 10 characters)
- Tickers — the assets you're watching for this thesis
When saved, the current price and signal for each ticker are snapshotted automatically.
Viewing performance — click any call card to see the current price and signal for each ticker alongside the original snapshot, so you can measure how the thesis played out.
Calendar — shows upcoming earnings dates, ex-dividend dates, and dividend payment dates for all tickers across your active calls.
Safe Buys tab
A filtered view showing only tickers with a ✅ Strong Buy signal across both lenses. A quick watchlist of assets passing the strictest criteria in the current market.
API rate limits
/api/screen, /api/screen/catalysts, and /api/analyze are capped at 10 requests per minute per IP. All other routes allow 60 per minute.