# Market Screener A Node.js stock screener and personal finance assistant. Screens stocks, ETFs, and bonds using live Yahoo Finance data and scores each asset under two lenses — **Market-Adjusted** (what's acceptable in today's market) and **Fundamental** (strict Graham value-investing) — then compares both to give you an honest signal. Comes with a **Fastify API server** and a companion **SvelteKit dashboard** (`../market-screener-ui`) for an interactive UI, or use it as a CLI to generate HTML reports. --- ## Quick Start ```bash # API + Dashboard (recommended) npm install cd ../market-screener-ui && npm install && cd ../market_screener npm run dev # starts API on :3000 + UI on :5173 # open http://localhost:5173 # CLI only npm start # screen today's news catalyst tickers → screener-report.html ``` --- ## Commands | Command | What it does | |---|---| | `npm run dev` | Start API server (port 3000) + SvelteKit UI (port 5173) together | | `npm run server` | Start API server only | | `npm start` | CLI: fetch today's market news, extract tickers, screen them | | `npm start -- watch` | CLI: screen the default watchlist | | `npm start -- AAPL MSFT VOO` | CLI: screen specific tickers | | `npm run finance` | CLI: portfolio advice + SimpleFIN → `finance-report.html` | | `npm run import-portfolio -- file.csv` | Import Robinhood/Vanguard/Fidelity CSV into `portfolio.json` | | `npm test` | Run all 61 unit tests | | `npm run test:watch` | Re-run tests on file changes | | `npm run format` | Format all source files with Prettier | --- ## How the Screener Works Every asset is scored **twice** under different rule sets: ### Market-Adjusted mode Gates derived from live Yahoo Finance benchmarks — reflects what is acceptable in today's market: | Gate | Formula | |---|---| | Stock P/E | S&P 500 P/E (via SPY) × 1.5× (or × 1.2× in HIGH rate regime) | | Tech P/E | XLK sector P/E × 1.3× | | REIT min yield | XLRE dividend yield × 0.85× | | Bond min spread | LQD − TNX spread × 0.80× | ### Fundamental mode Strict Graham/value-investing gates — reflects genuine value regardless of market conditions: | Gate | Value | Rationale | |---|---|---| | Stock P/E | < 15× | Graham's actual rule (trailing earnings) | | Stock PEG | < 1.0 | Lynch standard: 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 | | Bond spread | > 1.5% | Must clear risk-free rate meaningfully | | Bond duration | < 7 years | Rate sensitivity management | ### Signals | Signal | Meaning | |---|---| | ✅ Strong Buy | Passes both lenses — genuinely good value | | ⚡ Momentum | Passes market-adjusted, holds fundamentally | | ⚠️ Speculation | Passes market-adjusted, fails fundamental — priced for perfection | | 🔄 Neutral | Hold territory in one or both lenses | | ❌ Avoid | Fails both | --- ## Sector Overrides Sector-specific rules apply in both modes (not just inflated): | Sector | Key adjustments | |---|---| | **Technology** | P/E up to 35×, D/E up to 2.0 (buybacks), FCF weight raised | | **REIT** | P/E/PEG disabled, scored on dividend yield + P/FFO proxy | | **Financial** | D/E disabled, scored on ROE (≥12%) + P/B (< 1.5×) | | **Energy** | FCF primary signal (weight 4), dividend yield scored | | **Healthcare** | Revenue growth primary, P/E up to 25× (R&D burn) | | **Communication** | FCF primary (META/GOOGL platforms), P/E up to 25× | | **Consumer Staples** | Margin/ROE focus, low revenue growth expectations (2–5%) | | **Consumer Discretionary** | Revenue growth primary, P/E up to 25× | --- ## API Server ``` GET /health → { status: "ok" } POST /api/screen → screen tickers body: { tickers: string[] } GET /api/screen/catalysts → Yahoo news → { tickers, stories } GET /api/finance/portfolio → portfolio advice + net worth GET /api/finance/market-context → live benchmark data ``` Set `CLIENT_ORIGIN` env var to allow a different CORS origin (default: `http://localhost:5173`). --- ## Personal Finance Edit `portfolio.json` with your holdings (or import from a broker CSV): ```json { "holdings": [ { "ticker": "AAPL", "shares": 10, "costBasis": 150.00, "source": "Robinhood", "type": "stock" }, { "ticker": "VOO", "shares": 8, "costBasis": 380.00, "source": "Vanguard", "type": "etf" }, { "ticker": "BTC-USD", "shares": 0.25, "costBasis": 45000, "source": "Coinbase", "type": "crypto" } ] } ``` `npm run finance` (CLI) or `GET /api/finance/portfolio` (API) screens your holdings and cross-references the screener signal with your gain/loss position to give hold/sell/add advice. ### SimpleFIN (optional — live bank/brokerage balances) 1. Get your setup token from [beta-bridge.simplefin.org](https://beta-bridge.simplefin.org) 2. Add to `.env`: `SIMPLEFIN_SETUP_TOKEN=aHR0cHM6Ly...` 3. On first run the Access URL is claimed and saved to `.env` automatically ### Importing broker holdings ```bash npm run import-portfolio -- ~/Downloads/robinhood_holdings.csv npm run import-portfolio -- ~/Downloads/vanguard_holdings.csv ``` Broker is auto-detected from CSV headers. Multiple imports merge into `portfolio.json`. --- ## Project Structure ``` bin/ screen.js CLI screener entry point finance.js CLI personal finance entry point import-portfolio.js Broker CSV importer server.js Fastify API server entry point scripts/ summary-reporter.js Custom node:test reporter (silent pass, shows failures + summary) src/ config/ ScoringConfig.js All gates, weights, thresholds (single source of truth) constants.js Shared enums: SIGNAL, ASSET_TYPE, SECTOR, SCORE_MODE, REGIME market/ YahooClient.js Wraps yahoo-finance2 v3 with retry + backoff BenchmarkProvider.js Fetches live benchmarks → marketContext (1-hour cache) MarketRegime.js Derives inflated gate overrides from live data + rate regime screener/ ScreenerEngine.js Orchestrates fetch → score × 2. screenTickers() → pure data (server/CLI) screenWithProgress() → with stdout progress (CLI only) DataMapper.js Normalises Yahoo payload → flat asset objects Uses trailingPE (not forwardPE). Preserves negative FCF. RuleMerger.js Merges base rules + sector overrides + MarketRegime assets/ Stock, Etf, Bond data containers scorers/ StockScorer, EtfScorer, BondScorer (stateless) analyst/ CatalystAnalyst.js Extracts tickers from Yahoo Finance news headlines finance/ clients/ SimpleFINClient.js Auth + account fetching via Basic Auth header PersonalFinanceAnalyzer.js Net worth, cash vs investments, spending PortfolioAdvisor.js Hold/sell/add advice per holding PortfolioImporter.js Parses Robinhood/Vanguard/Fidelity CSV reporters/ HtmlReporter.js render() → string | generate() → file (CLI) FinanceReporter.js render() → string | generate() → file (CLI) server/ app.js Fastify app factory (buildApp) routes/ screener.js POST /api/screen, GET /api/screen/catalysts finance.js GET /api/finance/portfolio, GET /api/finance/market-context ``` --- ## Environment Variables ```bash # .env SIMPLEFIN_ACCESS_URL=https://user:pass@beta-bridge.simplefin.org/simplefin # or on first run: SIMPLEFIN_SETUP_TOKEN=aHR0cHM6Ly... # Optional server config PORT=3000 HOST=0.0.0.0 CLIENT_ORIGIN=http://localhost:5173 # CORS allowed origin for SvelteKit UI ``` --- ## Testing 61 unit tests, no external test framework: ```bash npm test # summary output: "✅ 61 tests: 61 passed (0.02s)" npm run test:watch # verbose spec output for development ``` Pre-commit: Prettier (auto-format staged files) + full test suite. Pre-push: full test suite.