Congressional trading intelligence + CI://TERMINAL market desk
Track House and Senate periodic transaction reports (PTRs), score them, estimate post-trade performance with yfinance, and explore the book in a Next.js app plus CI://TERMINAL — a dense Bloomberg-style ASCII desk (no Bloomberg marks or logos).
Product name: CongressInvests / CI.
Open-source repository. Do not commit .env, API keys, or the SQLite database.
| Area | Capability |
|---|---|
| Disclosures | House Clerk FD (zip/PDF) + Senate eFD with fallbacks |
| Scoring | 0–100 suspicion score and tags (routine → high_alert) |
| Market | Price since trade date, estimated shares/PnL, charts (yfinance + cache) |
| Web UI | Dashboard, politicians, monthly trades, stocks, signals, leaderboard, analyze |
| CI://TERMINAL | Monthly wire, member photos, co-holders, sectors, candles, returns board, CI://COPILOT (English) |
| Copilot | Tool-calling analyst (Groq / OpenAI / local). Always answers in English. |
┌───────────────────────┐ ┌───────────────────────────┐
│ web_fused (Next.js) ├─────────►│ FastAPI :8000 │
│ :3000 │ │ SQLite + pipelines │
└───────────────────────┘ │ yfinance market data │
│ /terminal/ (static) │
┌───────────────────────┐ │ /politicians/*.jpg │
│ CI://TERMINAL ├─────────►│ /api/terminal/* │
│ ci_terminal/ │ └───────────────────────────┘
└───────────────────────┘
| Component | Path | Port |
|---|---|---|
| API | server/api_server.py |
8000 |
| Web UI | web_fused/ |
3000 |
| Terminal | ci_terminal/ served at /terminal/ |
via 8000 |
| Domain | src/congress_quant_tracker/ |
— |
| Photos | data/politicians/{bioguide}.jpg |
/politicians/{bioguide}.jpg |
- Python 3.12+ and uv (or standard
pip) - Node.js 20+ (for
web_fused) - Windows / macOS / Linux
git clone https://github.com/CostaJr007/congress-quant-tracker.git
cd congress-quant-tracker
# Using uv (recommended):
uv sync
# Or using standard pip:
pip install -e .
# Install frontend dependencies:
cd web_fused && npm install && cd ..copy .env.example .env # Windows
# cp .env.example .env # Unix| Variable | Default | Role |
|---|---|---|
MARKET_DATA_ENABLED |
1 |
yfinance (charts, returns, enrich) |
NO_YF |
0 |
skip yfinance in the scorer only |
GROQ_API_KEY |
— | optional PDF extraction + Copilot |
OPENAI_API_KEY |
— | optional Copilot provider |
TAVILY_API_KEY |
— | optional search / ticker resolve |
DISCORD_WEBHOOK_URL |
— | optional alerts on new / flagged trades |
HTTP_PROXY |
— | HTTPS proxy if Senate eFD is blocked |
DATABASE_URL |
local SQLite | database URL |
API_HOST |
127.0.0.1 |
API bind address (0.0.0.0 exposes on LAN) |
API_PORT |
8000 |
API port |
Windows shortcuts:
run_ui.bat(API + Next.js + terminal)start.bat(API + terminal only)
Manual launch:
# Terminal 1: API (market data ON)
set MARKET_DATA_ENABLED=1
python server/api_server.py
# (or with uv: uv run python server/api_server.py)
# Terminal 2: Next.js Web UI
cd web_fused
echo NEXT_PUBLIC_API_URL=http://localhost:8000 > .env.local
npm run dev -- -p 3000| URL | App |
|---|---|
| http://localhost:3000 | CongressInvests UI |
| http://localhost:8000/terminal/ | CI://TERMINAL |
| http://localhost:8000/docs | OpenAPI / Swagger Docs |
python scripts/update_official.py # House Clerk (incremental by default)
python scripts/update_senate.py # Senate (direct, then proxy)
python scripts/enrich_all.py # sectors + photos + options + rescore
python scripts/rescore.py # rescore only
python scripts/reparse_ptr_pdfs.py # re-read already-downloaded PTR PDFs(You can also prefix commands with uv run if uv is installed).
Pipelines are incremental: each completed run saves a screener checkpoint
(pipeline_state) and the next run only fetches filings newer than the
watermark minus a 7-day overlap for late/amended disclosures — never further
back than DATA_WINDOW_DAYS (2 years). Pass --days N to force a window.
Repair jobs run in dry-run mode unless --apply is passed:
scripts/merge_duplicate_politicians.py (same person registered under
several name spellings), scripts/purge_sample_data.py (demo rows, and
--trades-only to leave company market caps alone),
scripts/repair_data_quality.py (absurd dates, zero→NULL amounts, bogus
options) and scripts/prune_old_trades.py (rows older than the window).
HTTP equivalents live under POST /api/pipeline/*.
House PTR filings come in two shapes: born-digital PDFs with a text layer, and
scanned/hand-filled pages that are images only. The parser handles both:
parsers/pdf_text.py walks pdfplumber → pypdf → pypdfium2 → OCR per page, and
parsers/ptr_form.py reads the printed table of scanned pages, where the side
is an X under "Purchase"/"Sale"/"Exch" and the amount is an X in one of the ten
lettered amount columns. Scanned filings are read through OCR, which needs an
extra dependency:
uv pip install -e ".[ocr]" # or: pip install -e ".[ocr]"Without an OCR engine, image-only filings are reported as unread (method=empty)
instead of yielding a silent zero. scripts/reparse_ptr_pdfs.py --dry-run
shows what each cached PDF parses to before anything is written.
All sources funnel through one ingestion path
(src/congress_quant_tracker/services/ingest.py):
- Validation & normalization before anything touches the DB (ticker, side, dates via the option-expiration sanitizer, value ranges, bounded strings).
- Provenance on every row:
Trade.notesstores the source tag (house_official,senate, ...). - Same-day/same-side duplicates are merged (widened ranges, backfilled fields), never silently dropped.
- A disclosed trade is never dropped for lack of a ticket symbol: the symbol is
resolved from the asset name when the company directory knows it, and
otherwise stored under a stable
XX……pseudo-symbol (notescarries;unlisted) so the disclosure stays visible. - Demo/sample payloads are rejected at the door.
scripts/purge_sample_data.pyremoves legacy seeded rows (dry-run by default;--applyto execute). The oldscripts/seed_database.pyrefuses to touch the production DB unlessSEED_ALLOW=1and a disposableDATABASE_URLare set.
Monospace black canvas, orange bars. Brand is CI://TERMINAL.
| Widget | Role |
|---|---|
| MEMBERS · BY MONTH | Browse by filed/traded month; photo cards; collapse heavy filers |
| RETURNS LEADERBOARD | Rank by Δ% / ADJ% (BUY: +Δ%, SELL: −Δ%) and estimated PnL$ |
| FOCUSED ASSET | Daily candles (or line) + TX marker on the trade date |
| ASSET HOLDERS | Who else traded the same ticker |
| POLITICIAN BOOK | Member tickers + TX/FILED dates |
| SECTOR DESK | House & Senate overlap by sector |
| CI://COPILOT | F2 — English-only desk analyst |
Presets: CGS (default) · GLB · EQ · MET · NWS
Editable layout (EDIT / drag) stored in localStorage (layout.v3).
First load: if an old layout hides RETURNS, click RESET.
| Endpoint | Description |
|---|---|
GET /api/terminal/dataset?dataset=tape|stocks|... |
yfinance market payloads |
GET /api/terminal/congress/wire |
Disclosures (month, chamber, party, side, q) |
GET /api/terminal/congress/months |
Months with trades |
GET /api/terminal/congress/holders/{ticker} |
Co-holders |
GET /api/terminal/congress/sector |
Sector desk |
GET /api/terminal/congress/politician?name= |
Member book |
GET /api/terminal/congress/returns |
Returns leaderboard |
GET /api/terminal/market/{ticker}?from_date= |
Daily OHLCV |
POST /api/terminal/chat |
Copilot (English) |
GET /api/analyze/overview |
Party / sector / options / suspicious |
POST /api/pipeline/enrich |
Sectors + photos + rescore |
LIVE config: ci_terminal/js/live.config.js (same-origin).
- Disclosures use value ranges, not exact shares → shares/PnL are midpoint estimates.
change_pct= asset move since trade date (yfinance,auto_adjust).return_side_adj= BUY: +Δ% / SELL: −Δ% (side-adjusted outcome).- Metals on the tape use Yahoo futures (
GC=F, etc.) as spot proxies. - News wire is DEMO unless a live RSS feed succeeds. Do not invent live headlines.
See ci_terminal/DATA_DEFINITIONS.md and ci_terminal/README.md.
- Dashboard, Trades (month filter + grouping), Politicians, Stocks, Signals, Leaderboard, Analyze
- Sidebar search + CI Terminal link
NEXT_PUBLIC_API_URL=http://localhost:8000
congress-quant-tracker/
├── server/
│ ├── api_server.py # FastAPI app factory + static mounts
│ ├── deps.py # shared DB session, serializers, aggregates
│ └── routers/ # one module per resource area
│ ├── core.py # health, dashboard, search, meta
│ ├── trades.py # trade list/detail, months, market
│ ├── politicians.py # list/detail, leaderboard
│ ├── stocks.py # stock aggregation
│ ├── analyze.py # party / sector / options analyzers
│ ├── pipeline.py # update / rescore / enrich triggers
│ └── terminal.py # CI://TERMINAL live feed + copilot
├── src/congress_quant_tracker/ # fetchers, parsers, scoring, analyzers
│ ├── enrichers/ # market, terminal, sectors
│ └── agent/ # CI://COPILOT
├── ci_terminal/ # CI://TERMINAL static desk
├── web_fused/ # Next.js UI
├── scripts/ # update, enrich, rescore, purge
├── tests/ # pytest (unit + API via TestClient)
├── pyproject.toml
└── README.md
Do not commit: .env, *.db, data/, API keys, proxies with credentials.
.gitignore already covers secrets, price cache, node_modules, and PDFs.
Photos are served from data/politicians/ at /politicians/{bioguide}.jpg.
| Command | Action |
|---|---|
python server/api_server.py |
API :8000 + /terminal/ |
start.bat |
API + open terminal |
run_ui.bat |
API + web UI + terminal |
cd web_fused && npm run dev |
UI :3000 |
python scripts/update_official.py |
House |
python scripts/update_senate.py |
Senate |
python scripts/enrich_all.py |
Sectors + photos + options + rescore |
python -m pytest |
Unit tests + API tests (temp SQLite, no network) |
- Yahoo/yfinance: delay and rate limits; first returns-leaderboard load can be slow.
- Senate eFD: Akamai/403 — try a working HTTPS proxy; the pipeline falls back to CongressInvests and retries direct if the proxy is dead.
- Some obscure tickers still have no sector; the desk uses a static ticker→sector map plus yfinance when enabled.
- Exchange holiday calendar is not applied; the session clock may show OPEN on holidays.
- Share and PnL figures are not official positions.
Internal research. Official House/Senate disclosures and unofficial Yahoo quotes.
Not investment advice. Do not copy Bloomberg trademarks or logos.