297 lines
10 KiB
Markdown
297 lines
10 KiB
Markdown
# 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](#developer-setup)
|
||
- [Environment Variables](#environment-variables)
|
||
- [Commands](#commands)
|
||
- [Running Tests](#running-tests)
|
||
- [Project Structure](#project-structure)
|
||
- [User Guide](#user-guide)
|
||
|
||
---
|
||
|
||
## Developer Setup
|
||
|
||
### Prerequisites
|
||
|
||
- Node.js 20+
|
||
- npm 10+
|
||
|
||
### Install
|
||
|
||
```bash
|
||
# Install server dependencies
|
||
npm install
|
||
|
||
# Install UI dependencies (first time only)
|
||
npm run ui:install
|
||
```
|
||
|
||
### Start
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
This starts both the API server on **port 3000** and the SvelteKit UI on **port 5173** concurrently. Open [http://localhost:5173](http://localhost:5173).
|
||
|
||
To run the API server alone:
|
||
|
||
```bash
|
||
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.
|
||
|
||
1. Go to [console.anthropic.com](https://console.anthropic.com)
|
||
2. Create an API key under **API Keys**
|
||
3. Add to `.env`:
|
||
|
||
```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).
|
||
|
||
1. Go to [beta-bridge.simplefin.org](https://beta-bridge.simplefin.org) and create a Setup Token
|
||
2. Add to `.env`:
|
||
|
||
```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.
|
||
|
||
```env
|
||
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.
|
||
|
||
```env
|
||
CLIENT_ORIGIN=https://yourdomain.com
|
||
```
|
||
|
||
### Complete `.env` example
|
||
|
||
```env
|
||
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
|
||
|
||
```bash
|
||
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.
|