Methodology
How Capitol Markets builds its rankings, conflict flags, and metrics — in plain language.
0. How we use the public record
Capitol Markets is a news publication operating under the news-media exception in 5 U.S.C. app. § 105(c)(B) of the Ethics in Government Act. We obtain and use Periodic Transaction Reports filed by Members of Congress and senior federal officials for editorial dissemination to the general public.
We do not sell raw disclosure data, do not provide bulk data feeds for resale, do not compile or sell consumer credit information, and do not use disclosure data to solicit political or charitable contributions. Subscribers receive a curated daily digest of significant filings and personalized alert routing — both editorial products, not raw data licenses.
The disclosures themselves are published by the U.S. House Clerk (disclosures-clerk.house.gov), the U.S. Senate Select Committee on Ethics (efdsearch.senate.gov), and the U.S. Office of Government Ethics (oge.gov). We encourage readers to view the original filings.
1. Where the data comes from
Capitol Markets pulls every Periodic Transaction Report (PTR) directly from the official U.S. government disclosure portals. No third-party scrapers, no paid datasets, no surveys.
- U.S. House: annual filing index XML and per-PTR PDFs from
disclosures-clerk.house.gov. - U.S. Senate: HTML PTRs from
efdsearch.senate.govafter accepting the office's prohibition agreement form. - Rosters & committees:
clerk.house.gov/xml/lists/MemberData.xmlfor the House (members, committees, and assignments) andsenate.gov/general/contact_information/senators_cfm.xml+ the official assignments page for the Senate. - Photos & bios:
bioguide.congress.gov(Library of Congress). - Prices: Yahoo Finance daily closes, with Finnhub as a fallback.
2. How we estimate trade volume
The STOCK Act requires politicians to disclose trades in dollar ranges($1,001–$15,000, $15,001–$50,000, etc.) — never an exact amount. We use the midpoint of each range as a position-size estimate. For "Over $X" entries we treat the value as 1.5× X.
A politician's "Volume" in any window is the sum of these midpoints across all disclosed trades in that window. Volume rankings always work — they don't require price data.
3. How we estimate return + P&L
For each BUY trade with a resolvable ticker:
- Take the closing price on (or just after) the transaction date as the entry.
- Mark to the most recent close as the exit.
- Estimated P&L =
(exit − entry) / entry × disclosed-midpoint.
For SELL trades we attribute zeroP&L to the politician — they exited the position, so subsequent price moves are not theirs. We don't penalize a savvy seller-at-the-top by treating future upside as a "missed gain". (Matched FIFO realized P&L would be cleaner but we don't have share counts to do it accurately, especially for option contracts that trade under the same ticker as the underlying stock.)
The aggregate Estimated Return %is the disclosed-amount-weighted average of BUYs' mark-to-market % return. Stocks-only— options, bonds, and crypto tickers are excluded from this aggregate because they need different pricing models. Per-trade P&L is still shown for those instruments in the trade row, but flagged as an estimate.
Skill score (current production formula, audited 2026-05-08):
score = min(alphaUsd, estGainUsd)
× √(min(1, pricedCount / 5)) // sample-size credibility
× log10(1 + scorableVolume / $100K) // size credibilityThree guard rails working together:
- Skill basis =
min(alphaUsd, estGainUsd). Skill is capped at the lesser of (alpha vs S&P 500) and (absolute net profit). A trader who lost money in absolute dollars but "beat" the S&P because the S&P fell more cannot rank above traders who actually made money. Negative skill basis → negative score → ranked at the bottom. - Sample-size credibility =
√(min(1, pricedCount / 5)). Caps at 1.0 once you have 5+ priced trades. Below 5 trades, your score is shrunk by √(N/5). The UI also surfaces a small "early sample" badge wheneverpricedCount < 10so readers can see when a headline number is computed off a thin slice (1M and YTD are short windows; many serious traders legitimately have <10 priced trades early in the year). - Size credibility =
log10(1 + scorableVolume / $100K). Smooth ramp: $100K volume → 0.3, $1M → 1.0, $10M → 2.0, $100M → 3.0. Important: scorable volume includes only trades that actually contributed to the return (resolved tickers, stock-classified). Bond and untickered trades inflate gross flow but don't earn credibility weight, so you can't game the score with treasury holdings.
The Skill tier badge (Elite / Strong / Solid / Below avg / Weak / Underwater) is your percentile rank within the displayed cohort.
This is a return estimate, not a portfolio IRR. It cannot capture position sizing precisely (no share counts disclosed), partial sales between disclosures, options delta/theta dynamics, or off-market positions. Treat as directional, not exact.
4. Conflict flags
Each trade with an identifiable ticker is checked against the politician's committee assignments. If the ticker's industry sector matches a sector the committee has jurisdiction over (e.g., Banking → bank stocks, Armed Services → defense primes), the trade earns a conflict flag.
Flags are not accusations of wrongdoing — STOCK Act trades are legal — but structural overlaps worth noting. The committee→sector mapping is conservative and published in our codebase.
4b. Sector-aware benchmarks (alpha)
Each trade's Alpha is computed against the SPDR sector ETF that most closely matches the company's industry — not against SPY directly. A healthcare trade is benchmarked against XLV; a defense trade againstITA; a bank trade against XLF; an energy trade againstXLE; and so on. This measures “did the politician beat their sector,” which is a tighter measure of skill than “did healthcare beat the broad market this year.”
When the ticker has no curated sector or the sector ETF has no price coverage yet, alpha falls back to SPY so the calculation always returns a number. The aggregate Estimated Return % at the politician level is the dollar-weighted average of trade-level alphas plus benchmark returns.
4c. Window-edge smoothing
For rolling windows (1M / 6M / 1Y / 5Y), trades within the oldest 10% of the window receive a linearly ramped weight from 0 → 1.0. This prevents a single trade from flipping a politician's rank overnight as it ages out of the window. YTD has a fixed start (Jan 1) and uses no smoothing.
4d. "Most newsworthy" composite score
The Most Newsworthy hero card and the editor's-pick list on the homepage rank trades by a composite score, not by raw dollar volume. Volume rewards prolific filers (Trump files large trades almost every week, dominating any amount-only ranking). Newsworthiness rewards trades that are actually a story.
The score combines eight signals, each normalized so its contribution is readable:
- Size — log-scaled to about 1.0 for a $1 million trade. A $15 million trade only outweighs a $1 million trade by about 17%.
- Committee conflict — flagged trades earn 0.5–1.0 depending on the flag intensity score.
- Options bump — a flat 0.25 when the trade is an options contract, because options are a leveraged-conviction signal.
- Novelty — linear decay from 0.8 today to 0 at day 14. Older trades fade out of the editor's pick.
- Politician fame — up to 0.6 for the top 10 most-trafficked profiles (Pelosi, Tuberville, Trump and so on).
- Filing-window position — trades filed past the 45-day STOCK Act window earn up to 0.6 more, depending on how far past the window.
- Cluster context — up to 0.5 when other politicians traded the same ticker in the same direction in the last 14 days.
- Size in context — up to 0.4 when the trade is large relative to that politician's lifetime average. A $50,000 trade is unremarkable for a senator who averages $3 million per filing, but a headline for a freshman whose average is $5,000.
Each "reason chip" you see on a Most Newsworthy row is the specific signal that fired for that trade. Curious about the live formula or the weights? It lives at src/lib/newsworthy.ts in our public source tree.
5. Limitations to be honest about
- Disclosure ranges are wide. A "$15,001–$50,000" trade could be 4× off in either direction. We use the midpoint and surface it as Est. P&L / Est. Return, not exact dollars.
- No share counts. The STOCK Act requires only dollar ranges — never share counts or basis. So even a perfectly-priced BUY can't produce a true realized P&L; we can only mark the disclosed midpoint to market.
- Options under the same ticker. A politician's "BUY NVDA Call $200 strike" and a later "SELL NVDA stock" share the ticker NVDA but are different instruments. We use a dedicated classifier (assetType + assetName parsing) to exclude listed options from the headline stocks-only aggregate; per-trade rows still surface an estimate for transparency.
- Managed-account rebalances. A small number of politicians disclose what looks like an institutional brokerage doing periodic portfolio rebalances rather than active trades — multi-ticker round-trip days at low average ticket size. Profile pages surface a "Likely managed account" chip when the pattern is detected so readers know the headline % is computed off largely passive activity.
- Time-weighting. We use money-weighting, not a true time-weighted return (TWR) or modified Dietz. Capital deployed for 5 years and capital deployed for 3 weeks are treated equally on dollar volume (subject to the window-edge smoothing in §4c).
- We do not currently ingest paper PTRs filed by a small number of senators (only HTML PTRs).
- OGE Form 278-T (executive-branch officials including Cabinet) is not yet ingested.
- Bonds, real estate, mutual funds, and assets without a clean ticker are excluded from return math.
- Net worth fields are placeholders pending Annual Financial Disclosure ingestion.
6. Update cadence
The ingestion cron runs every 6 hours. House PTRs typically appear within hours of filing during business hours. Senate PTRs update throughout the day. New filings flow into the leaderboard, conflicts page, and email alerts on the next cron tick.
Have a question or spot a methodology bug? Get in touch.