2026-06-04 16:28:21 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 11:24:08 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 11:24:08 -04:00
2026-06-04 11:24:08 -04:00
2026-06-04 16:28:21 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 16:28:21 -04:00
2026-06-04 01:36:28 -04:00
2026-06-04 01:36:28 -04:00
2026-06-02 00:34:42 -04:00
2026-06-04 01:36:28 -04:00

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

# 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 (25%)
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):

{
  "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
  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

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

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

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.

S
Description
No description provided
Readme 1.7 MiB
Languages
TypeScript 49%
Svelte 30.2%
HTML 11.7%
SCSS 8.7%
JavaScript 0.2%
Other 0.2%