01 · REPORT
What can you learn from a congressional portfolio?
Start with three questions: what does the portfolio hold, how concentrated is it, and how did its modeled return compare with the market? Then connect those results to your watchlist and look for positions shared by several members.
You’ll build a local HTML report with bar charts and seven CSV tables. The example compares Nancy Pelosi, Michael McCaul and Ro Khanna—three members available in the Free Sandbox—and checks NVDA and MSFT. Change the names or tickers to reuse the same workflow.
These are modeled portfolios, not official account balances or a feed of recent buys and sells. The report keeps the model’s dates and coverage beside the results.
02 · SETUP
Set up your research workspace
You’ll need Python 3.10 or newer. Save congress_portfolios.py in your working folder, then install the libraries:
python -m pip install requestsLog in to get your API key. In the dashboard, click Create API key → Create key → Copy, then return here and replace YOUR_API_KEY below.
$env:VIZUALSTOCKS_API_KEY = "YOUR_API_KEY"export VIZUALSTOCKS_API_KEY="YOUR_API_KEY"Choose the command for your terminal. The key is sent in the X-API-Key header and is not saved in your research export.
03 · BUILD
Run the complete analysis
python congress_portfolios.py --output-dir congress-researchOpen congress-research/report.html in your browser. The script retrieves the member directory, three portfolios and their performance, a leaderboard, and holders of two watchlist stocks: 10 GET requests before any retries. Charts render locally; opening the report makes no further API requests.
The report includes all six parts of this tutorial. You can use it immediately, then explore the calculations below. The notebook examples import functions from the same downloaded file.
python congress_portfolios.py --members "Nancy Pelosi" "Michael McCaul" "Ro Khanna" --window 1Y --watchlist NVDA MSFT --output-dir congress-comparisonUse a new output folder for each run so previous research stays intact. To skip the watchlist requests, add --watchlist with no tickers.
04 · MEMBERS
What stocks does a member’s portfolio hold?
python congress_portfolios.py --list-membersThe member directory lists stable IDs, modeled availability and freeSandboxAvailable. The directory is broader than your plan’s portfolio access. Use an exact name in quotes or copy a returned member ID.
from congress_portfolios import (
make_session, api_get, select_members,
normalized_holdings, concentration,
)
with make_session() as session:
directory = api_get(session, "/congress/members")
member = select_members(directory, ["Nancy Pelosi"])[0]
portfolio = api_get(
session,
f"/congress/members/{member['memberId']}/portfolio",
)
holdings = normalized_holdings(portfolio)
print(concentration(holdings))
for row in holdings:
print(row["ticker"], row["portfolioWeightPercent"])The portfolio endpoint returns tickers, estimated values, shares and portfolio weights. The script groups repeated ticker rows before analysis. In holdings.csv, each row represents one ticker in one selected member’s modeled portfolio.
portfolioWeightPercent is already a percentage: 12.5 means 12.5%, not 1,250%. A listed holding answers “what is in the current model?” It does not establish that the member bought it recently.
05 · CONCENTRATION
How much sits in the largest positions?
Two portfolios can share several tickers and still have very different exposure. Add the five largest ticker weights to see how much of each modeled portfolio is concentrated in its biggest positions:
concentration(holdings) returns top5WeightPercent, holdingCount and holdingsWithoutWeight. The report charts the largest available weights and stores the summary in members.csv.
If any holding has an unavailable weight, the top-five total stays blank rather than treating the missing position as zero. The weights describe the current modeled holdings; they do not tell you which stocks caused the portfolio’s historical return.
06 · PERFORMANCE
How did the portfolios perform against the benchmark?
# Continue in the same notebook, with member from above.
with make_session() as session:
performance = api_get(
session,
f"/congress/members/{member['memberId']}/performance",
window="1Y",
)
print(performance["periodStartDate"], performance["periodEndDate"])
print("Portfolio %:", performance["portfolioReturnPercent"])
print("Benchmark %:", performance["benchmarkReturnPercent"])
print("Excess, percentage points:", performance["excessReturnPercent"])
print("Coverage:", performance["coverage"])The performance endpoint supports YTD, 1Y, 2Y, 3Y and MAX. It returns portfolio and benchmark returns, excess return and dated chart points. The current model uses the S&P 500 price index (^GSPC) as its benchmark.
Compare like with like. The script only draws the combined comparison chart when every selected member has numeric returns, matching returned dates and hasCompleteWindow=true. Otherwise it keeps the individual results in the table and explains why the combined chart is omitted. MAX means available modeled history, not necessarily the same period for every member.
- Excess return is a percentage-point difference
- A modeled return of 12% versus a benchmark return of 9% is a 3-percentage-point difference. The report labels it “Excess (pp).” It is not a risk-adjusted alpha estimate.
- Coverage belongs beside the comparison
- Complete-window coverage concerns the available history. Priced coverage concerns the share of eligible records that the model could price. One can be complete while the other is partial, as in the sample above.
Is this the return from copying disclosed trades?
No. The model estimates positions using reported transaction amounts and prices, and calculates performance using transaction-date events. It is not a strategy that waits for public disclosure and then trades. The script uses the API’s calculated returns; it does not reconstruct or expose the underlying transactions.
python congress_portfolios.py --members "Nancy Pelosi" "Michael McCaul" --window YTD --watchlist NVDA --output-dir congress-ytd07 · LEADERBOARD
Which modeled portfolios rank highest?
with make_session() as session:
ranking = api_get(session, "/congress/leaderboard", window="1Y")
print("Universe:", ranking["scope"])
for row in ranking["members"][:10]:
print(row["rank"], row["member"]["name"],
row["portfolioReturnPercent"], row["coverage"])Use the leaderboard to find other members worth examining. Optional chamber, party and state filters narrow the comparison. The current implementation ranks by the selected window’s modeled return and requires at least three priced eligible records.
leaderboard.csv preserves every returned rank and coverage field; the HTML report previews the first ten. Its scope can differ from the three portfolios you selected. A Free Sandbox ranking covers its curated members, not all Congress.
A leading rank is a starting point for research. Open that member’s holdings and compare more than one window before interpreting the result.
08 · WATCHLIST
Who has exposure to the stocks you follow?
with make_session() as session:
holders = api_get(session, "/congress/stocks/NVDA/holders")
print("Universe:", holders["scope"])
for row in holders["holders"]:
print(row["member"]["name"], row["member"].get("party"),
row["portfolioWeightPercent"])The holders-by-ticker endpoint starts with a stock rather than a member. It returns the modeled members holding that ticker and each position’s portfolio weight.
watchlist_summary.csv counts distinct member IDs for every requested ticker, including a zero-holder result. watchlist.csv gives the member-level detail. These searches use your API’s returned universe, not just the members selected for the comparison.
Read the position weight alongside the holder count: a stock appearing in several portfolios is a different observation from one member concentrating heavily in it. Neither is a count of recent purchases. On Free Sandbox, use eligible tickers from the company directory.
09 · OVERLAP
Which positions do the portfolios share?
from congress_portfolios import overlap_rows, pairwise_rows
with make_session() as session:
directory = api_get(session, "/congress/members")
members = select_members(
directory, ["Nancy Pelosi", "Michael McCaul", "Ro Khanna"]
)
portfolios = [api_get(
session, f"/congress/members/{m['memberId']}/portfolio"
) for m in members]
shared = [row for row in overlap_rows(portfolios)
if row["selectedHolderCount"] >= 2]
both_parties = [row for row in shared if row["democratAndRepublican"]]
print("Shared holdings:", shared)
print("Both parties:", both_parties)
print("Pairwise overlap:", pairwise_rows(portfolios))overlap.csv counts how many selected members hold each ticker. Filter to two or more for shared positions. democratAndRepublican identifies tickers present in at least one selected Democratic and one selected Republican portfolio. It does not claim that both parties broadly agree on the stock.
pairwise.csv stores that calculation for each pair. It compares the names held, not the amount invested: two portfolios can have identical ticker lists but very different weights. The report keeps shared holdings and member weights available as separate views.
10 · EXPORTS
Take the results into your own workflow
- report.html
- A self-contained visual report with holdings charts, performance, leaderboard, watchlist and overlap sections. No API key or external chart service is needed to open it.
- members.csv · holdings.csv
- Member comparisons, top-five concentration, weights, dates and coverage for your research model.
- leaderboard.csv · watchlist.csv · watchlist_summary.csv
- Returned rankings, per-member stock exposure and distinct holder counts with their scopes.
- overlap.csv · pairwise.csv · metadata.json
- Shared tickers, pairwise comparisons, retrieval time and model-generation dates. A retrieval timestamp is not a trade or disclosure date.
Open the CSV files in Excel or load them into pandas. Numeric fields stay numeric and unavailable values stay blank. Save each new run to a different folder; a change between modeled snapshots is not automatically a newly disclosed trade.
For the operating performance of companies you find, continue with the company KPI research workbook or revenue segmentation analysis.
11 · TROUBLESHOOTING
Resolve missing data or access errors
- No matching member
- Run --list-members and use an exact name or stable ID. An available directory entry does not necessarily grant access to its portfolio.
- 401 or 403
- Check your key or select sandbox-eligible members and tickers. The default example uses three curated members and two sandbox tickers.
- No holders, no shared positions, or no ranks
- These can be valid empty results. The report retains table headers and explains empty sections rather than inventing a row.
- Missing comparison chart or concentration total
- Inspect returned dates, complete-window coverage and missing weights. Individual results remain available in the exports.
- Rate limit or output folder already exists
- The script makes bounded retries for temporary failures. For access and quota details, see the API error reference. Choose a new output folder to preserve an earlier report.
