Stock-Tool/CLAUDE.md

5.2 KiB
Raw Blame History

Stock Tool — Claude Context

Project Overview

A multi-factor stock screener that evaluates ~6,500 US-listed equities using a composite score across 8 dimensions: Value, Growth, Momentum, Quality, Profitability, Sentiment, Analyst, and Risk. Sold as a licensed desktop app (PySide6 GUI).

File Map

File Purpose
dist/stock_screener.py All scoring logic, data fetching, index/ticker collection. Pure Python + pandas.
dist/screener_gui.py PySide6 GUI. Imports from stock_screener. Main class: ScreenerApp(QMainWindow).
cache_builder.py Nightly job that populates PostgreSQL cache from EDGAR + yfinance. Runs on VPS via cron.
api/main.py FastAPI server on VPS. Handles license auth, cache serving, version management.
updater.py Client-side auto-updater. Checks /update endpoint, downloads new build.
license_check.py HMAC + JWT license validation. Baked into app via PyArmor.
push_update.py Dev tool: SCP new build to VPS + register version via /admin/register-version.
launcher.py Thin launcher that checks license before starting GUI.
health_monitor.py VPS health monitoring.
discord_bot/bot.py Discord bot integration (separate process).

Architecture (3-tier)

[cache_builder.py]  →  [PostgreSQL on VPS]  →  [API]  →  [Client app]
     nightly                6 tables            FastAPI     screener_gui + stock_screener
  • cache_builder.py runs nightly (2am stable / 3am beta) via cron on the VPS
  • Data flows: EDGAR XBRL Frames API + yfinance → PostgreSQL tables → served by FastAPI
  • Client fetches cached data at startup via /cache/analyst, /cache/fundamentals, /cache/sector
  • Scoring runs entirely client-side in stock_screener.py — no server-side scoring

PostgreSQL Tables (database: verimund, user: verimund_user)

Table Contents
analyst_cache Price history, returns, sentiment, analyst ratings, short interest (~6,975 rows)
fundamentals_cache EDGAR-sourced financials: revenue, margins, D/E, ROA/ROE, Piotroski, accruals
sector_stats Per-sector median/MAD for each metric. Used by _z_score() for normalization.
app_versions Version registry per channel (stable/beta). Read by /admin/versions.
licenses License keys + HWIDs + expiry. Validated at auth time.
auth_log Auth attempt log.

Scoring Formula

Composite = 0.18*Value + 0.18*Growth + 0.14*Momentum + 0.11*Quality
          + 0.08*Profitability + 0.13*Sentiment + 0.10*Analyst + 0.08*Risk

Each score is 0100. Z-scores use sector median/MAD from sector_stats table. If sector stats are missing for a metric, _z_score() returns neutral 50.

Profile System (important)

_classify_profile(row) classifies each stock into one of: mature, high_growth, financial, capital_intensive, early_stage, turnaround

Profiles define sub-weights per scoring dimension in _PROFILE_WEIGHTS. Weights are normalized in _profile_score_row() — they don't need to sum to 1.0 in the dict.

D/E handling: yfinance returns debtToEquity as percentage (e.g. 150 = 1.5x). Code divides by 100 at ingest (stock_screener.py line ~1356). Negative D/E means negative book equity — replaced with 999.0 before z-scoring.

Key Patterns

  • _z_series(series, metric, sectors, sector_stats) — vectorized z-score using sector stats
  • _score_series(series, breakpoints) — absolute breakpoint scoring (no sector normalization)
  • _profile_score_row(row, dimension, metric_scores) — blend metric scores using profile weights
  • All scoring functions signature: compute_X_score(df, sector_stats=None) → pd.Series

VPS Infrastructure

  • Provider: Hetzner CPX21, Ubuntu 22.04
  • Domains: api.verimundsolutions.com (API), git.verimundsolutions.com (Gitea)
  • API: FastAPI via uvicorn, managed by PM2
  • Secrets: /srv/api/.env on VPS (DB_PASS, JWT_SECRET, HMAC_SECRET, ADMIN_KEY)
  • Backups: nightly rclone → Cloudflare R2 bucket verimund-backups
  • Git: Gitea at git.verimundsolutions.com, repo: ssz223/Stock-Tool, branches: main (stable) / beta
  • Website: Taken down intentionally (2026-04-03), will be re-done under different brand

Running Tests

# Fast tests (run automatically via hook on every edit):
python -m pytest tests/test_scoring.py tests/test_gui.py -q

# Full suite including network (run before pushing):
python -m pytest tests/ -v

# Network tests only:
python -m pytest tests/test_network.py -v

# Skip network tests:
python -m pytest tests/ -m "not network" -v

Known Pitfalls

  • screener_gui.py uses PySide6 (not PyQt5). Offscreen: QT_QPA_PLATFORM=offscreen
  • screener_gui.py imports from stock_screener (same dir) — tests must cd dist/ or add dist to sys.path
  • EDGAR XBRL Frames API: one call per concept returns all US filers. Foreign/IFRS filers can have data 18 months old with no user-facing flag.
  • Sector stats built from analyst_cache in Phase 5 of cache_builder. If Phase 1 partially fails, sector medians are biased.
  • _sum_frames requires >= 4 quarters for valid TTM — returns None otherwise.
  • Forward PE not in sector stats — _z_score returns neutral 50 for forward PE z-scoring.