Rotate to portrait

Vizual Stocks works best vertically on mobile.

VizualStocks API

PYTHON TUTORIAL · CONGRESS ANALYTICS

Analyze congressional portfolios with Python

Compare congressional portfolio holdings, concentration and benchmark returns with Python. Check your watchlist, find shared positions across members, and export a complete research report.

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.

CONGRESS RESEARCHThree portfolios. One comparison.
1Y
Sample API run · September 17, 2025–September 17, 2026. Model generated September 18, 2026. Priced coverage: Pelosi 53.3%, McCaul 72.5%, Khanna 68.7%; all three have complete-window coverage. Your report uses the latest returned snapshot.

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:

Terminal · install dependencies
python -m pip install requests

Log 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.

Windows PowerShell
$env:VIZUALSTOCKS_API_KEY = "YOUR_API_KEY"
macOS / Linux shell
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

Terminal · create the report
python congress_portfolios.py --output-dir congress-research

Open 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.

Terminal · choose members, window and watchlist
python congress_portfolios.py --members "Nancy Pelosi" "Michael McCaul" "Ro Khanna" --window 1Y --watchlist NVDA MSFT --output-dir congress-comparison

Use 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?

Terminal · discover member IDs
python congress_portfolios.py --list-members

The 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.

Python · retrieve holdings and weights
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:

TOP-FIVE CONCENTRATIONWeight 1 + Weight 2 + Weight 3 + Weight 4 + Weight 5For fewer than five holdings, use every available position.

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?

Python · retrieve a matching performance window
# 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.

Terminal · compare another window
python congress_portfolios.py --members "Nancy Pelosi" "Michael McCaul" --window YTD --watchlist NVDA --output-dir congress-ytd

07 · LEADERBOARD

Which modeled portfolios rank highest?

Python · inspect the scoped ranking
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?

Python · find modeled NVDA holders
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?

Python · compare shared and cross-party holdings
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 TICKER OVERLAPShared tickers ÷ distinct tickers across both portfolios × 100Example: 4 shared tickers out of 20 distinct tickers = 20% overlap.

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.