Methodology
Capitol_Signals is an open-source correlation engine for congressional financial activity. Each week it ingests:
- Stock trade disclosures filed under the STOCK Act (via QuiverQuant)
- Federal contract awards (USAspending.gov)
- Congressional bills and committee assignments (Congress.gov + unitedstates/congress-legislators)
- Lobbying disclosures filed under the Lobbying Disclosure Act (lda.gov)
- Market price data (yfinance) and news headlines (GDELT, on-demand)
The engine joins these across indices to produce correlations (trade ↔ contract, trade ↔ bill, trade ↔ lobbying) and aggregates them into rollups (one rollup per member-ticker-trade) with a 0-100 composite suspiciousness score. The score weights six factors: jurisdiction alignment (member’s committee vs. ticker’s sector), event timing, trade dollar amount, disclosure lag (days between transaction and STOCK Act disclosure), count of distinct correlated signals, and lobbying intensity in the relevant sector during the trade quarter. (Abnormal price movement vs. SPY was removed from the composite in v1.2 to eliminate data leakage; it is reported separately as an informational field.)
A second layer runs two ML heads in annotation mode alongside the composite:
- Composite-distillation head, an XGBoost model trained to replicate the composite scoring rule, using temporal train/test splits to prevent lookahead. It surfaces SHAP feature attributions so readers can see which rule factors drove the score. Features cited in each brief are SHAP attributions from this head.
- Forward-CAR head, a second XGBoost model trained to predict cumulative abnormal return (vs. SPY) at 30, 60, and 90 days after the trade date. This head captures whether the market actually rewarded the trade, independent of rule alignment. Both heads use purged k-fold cross-validation on the training set and are evaluated by precision-at-k (not ROC-AUC) to match the top-of-list publication use case.
ml_agreement enum. Each rollup carries an agreement label summarizing how the two heads relate:
| Value | Meaning |
|---|---|
high-confidence | Both heads flag suspicious; composite and distillation align, forward-CAR also elevated. |
agreed | Heads concur within normal tolerance. |
diverged | Composite says suspicious-by-correlation; forward-CAR says the trade did not pay off. The disagreement is often the most interesting editorial signal. |
partial | Distillation head scored; forward-CAR window (30d) not yet elapsed. |
unscored | Rollup predates dual-head deployment or lacked sufficient features. |
The composite score remains the primary ranking signal. ML heads are annotations that add editorial context, not replacements for the rule-based score.
What correlation does and does not mean. A high composite score means multiple independent signals align in time and topic. It does not prove the member used non-public information, breached fiduciary duty, or violated the STOCK Act. The signals are public, the explanations are public, and the inferences are circumstantial. We publish them so the public, journalists, and policymakers can decide what is worth following up on.
Source code, data sources, and licensing.
- Pipeline source: github.com/brianchaplow/Capitol_Signals
- STOCK Act trade data: Quiver Quantitative Hobbyist API ($10/mo, public re-use permitted)
- Bills + member data: Congress.gov API (free, public domain)
- Contracts: USAspending.gov (free, public domain)
- Lobbying: Senate LDA API (free, public domain)
- Market data: yfinance (Yahoo Finance, terms apply)
Capitol_Signals is built and maintained by Brian Chaplow. No subscription, no copy-trading, no affiliate links. Reach out via the GitHub repo for corrections or methodology questions.
Per-brief data sidecar schema
Every editorial brief at /briefs/{slug}/ ships a co-located data/ directory
with the trades, correlations, and scores it cites. Files:
| File | Columns | Notes |
|---|---|---|
| trades.csv / trades.jsonl | trade_id, bioguide_id, member_name, ticker, transaction_date, amount_range, amount_min, amount_max, sector, industry | The headline trade plus any sibling cited |
| correlations.csv / correlations.jsonl | correlation_id, trade_id, bioguide_id, ticker, transaction_date, composite_score, severity_tier, scored_at | Supporting rollups for same member + ticker |
| scores.csv / scores.jsonl | correlation_id, trade_id, composite_score, ml_score_distill, ml_score_car, ml_agreement, ml_shap_top3, abnormal_return_30d, abnormal_return_60d, abnormal_return_90d | Composite + ML head outputs |
License: CC0 1.0. Reproducibility: every file is keyed by the rollup’s correlation_id; cross-reference with the weekly snapshot for the same ISO-year-week to confirm the brief’s claims.
Weekly snapshot schema
Every Monday at 02:30 UTC the engine writes a full snapshot to
/data/{YYYY}-W{WW}/. The snapshot contains:
| Table | Source index | Row count (approx) | Why included |
|---|---|---|---|
| trades | cs-trades | 1,300+ | Primary signal |
| correlations | cs-correlations (rollups only) | 500+ | Pair-level + rollup correlations |
| profiles | cs-member-profiles | 170+ | Aggregated member-level metrics |
File formats per table:
{table}.csv(Excel-friendly, scalar columns){table}.jsonl(one JSON object per line){table}.sqlite(single SQLite file with primary keys + indexes)
What we exclude
The weekly snapshot does NOT include:
- cs-bills (~80,000 docs): reference data, not signal data; commits to the Astro repo would balloon git history past 5 GB within a year.
- cs-lobbying (~57,000 docs): same scope-trim reasoning; lobbying linkage
is preserved in the correlation rollups themselves (
supporting_lobbyingfield) without shipping the raw filings.
These constraints are enforced at the script level in
analytics/publish_weekly_snapshot.py. A future Phase 20.5 may publish trimmed
projections (bill id + sector + sponsor only, ~5 MB) or a Datasette-style live
query endpoint on a separate object store if the data community requests.
Reproducibility
Each weekly snapshot directory is committed to
github.com/brianchaplow/capitolsignals-astro under
public/data/{YYYY}-W{WW}/. The directory contents are deterministic for a
given OpenSearch state: columns are sorted, rows are sorted by primary key,
SQLite indexes are deterministic, and floats are rounded to 4 decimals before
serialization. Rerunning the script for the same year-week against the same
OpenSearch state produces byte-identical CSV output.
To reproduce a specific brief’s claims:
- Find the brief at
/briefs/{slug}/. - Read the sidecar at
/briefs/{slug}/data/. - Cross-reference with the matching weekly snapshot at
/data/{iso_year_week}/. - The brief’s
iso_year_weekfrontmatter pins the snapshot to use.
Member opt-out posture
Capitol Signals correlates public data. Member trades are disclosed under the STOCK Act; contracts are public via USAspending; bills are public via Congress.gov; lobbying is public via lda.gov. The project does not invite member opt-out requests because the underlying disclosures are mandated by law. If a current member of Congress identifies a factual error in a brief, the editorial process accepts corrections; corrections are noted in the brief body and the brief’s git history.
Slug and iso_year_week convention
Brief slugs follow the pattern {YYYY-WW}-{lastname_lower}-{ticker_lower},
where iso_year_week refers to the ISO week the underlying trade or alert
event occurred (transaction_date), NOT the brief publication week. For
example, the Moskowitz GD archetype brief at /briefs/2026-W13-moskowitz-gd/
derives W13 from the underlying trade on 2026-03-23 (ISO week 13), even
though the brief was published in 2026-W21. This keeps the slug pinned to
the editorial subject so the same correlation never gets a different slug
across republications, and so auto-generated slugs from
generate_weekly_brief.py:emit_astro_brief are deterministic. The
derivation order is transaction_date first, scored_at fallback (per the
Plan 05 implementation and the test in tests/test_brief_content_schema.py).