This guide shows you how to analyze trading data from both US House Representatives and Senators, and build systematic trading signals based on congressional activity.
FinBrain provides two separate endpoints for congressional trading data:
- House Trades - US House Representatives (
fb.house_trades) - Senate Trades - US Senators (
fb.senate_trades)
Why Track Congressional Trades?
Members of Congress have access to non-public information through their committee work, briefings, and legislative activities. While trading on material non-public information is illegal, their disclosed trades can still provide insights:
- Committee knowledge - Members on finance committees may have early insights
- Legislative impact - Understanding how laws affect specific companies
- Historical accuracy - Some members have notable track records
Prerequisites
pip install finbrain-python pandasStep 1: Basic Congressional Data
Let’s start by fetching congressional trading data using as_dataframe=True:
import pandas as pdfrom finbrain import FinBrainClient
fb = FinBrainClient(api_key="YOUR_API_KEY")
# Get House trades as a DataFramehouse_df = fb.house_trades.ticker("NVDA", as_dataframe=True)print("=== House Trades ===")print(house_df.head())# politician transactionType amount disclosureDate# date# 2024-01-08 Nancy Pelosi Purchase $1,000,001 - $5,000,000 2024-02-02
# Get Senate trades as a DataFramesenate_df = fb.senate_trades.ticker("META", as_dataframe=True)print("\n=== Senate Trades ===")print(senate_df.head())# politician transactionType amount disclosureDate# date# 2025-11-13 Shelley Moore Capito Purchase $1,001 - $15,000 2025-12-04
# Filter for purchases onlypurchases = house_df[house_df["transactionType"] == "Purchase"]print(f"\nHouse Purchases: {len(purchases)}")
# Every row carries two dates: the index is the transaction date, and# disclosureDate is when the filing became public. The gap is the reporting lag.lag = (pd.to_datetime(house_df["disclosureDate"]) - house_df.index).dt.daysprint(f"Median reporting lag: {lag.median():.0f} days")Note the two dates. date (the index) is when the member traded; disclosureDate is when the periodic transaction report became public. Only the second one was knowable at the time, which matters as soon as you start backtesting. Filings collected before this field was captured return null, so guard for it.
Step 2: Scan Multiple Tickers
Scan your watchlist for congressional activity using DataFrames:
from finbrain import FinBrainClient
fb = FinBrainClient(api_key="YOUR_API_KEY")
def scan_congressional_activity(tickers): """Scan multiple tickers for congressional trades""" results = []
for ticker in tickers: try: df = fb.house_trades.ticker(ticker, as_dataframe=True)
if not df.empty: purchases = df[df["transactionType"] == "Purchase"] sales = df[df["transactionType"] == "Sale"]
results.append({ "ticker": ticker, "total_trades": len(df), "purchases": len(purchases), "sales": len(sales), "net_signal": "bullish" if len(purchases) > len(sales) else "bearish" }) except Exception: continue
return sorted(results, key=lambda x: x["total_trades"], reverse=True)
# Scan tech stockstickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA"]results = scan_congressional_activity(tickers)
print("\n=== Congressional Activity Scan ===")for r in results: print(f"{r['ticker']}: {r['purchases']} buys, {r['sales']} sells ({r['net_signal']})")Step 3: Track Specific Representatives
Some representatives have notable trading track records. Use DataFrames for easy filtering:
from finbrain import FinBrainClientimport pandas as pd
fb = FinBrainClient(api_key="YOUR_API_KEY")
def track_representative(representative_name, tickers): """Track all trades by a specific representative""" all_trades = []
for ticker in tickers: try: df = fb.house_trades.ticker(ticker, as_dataframe=True)
if not df.empty: # Filter for specific representative rep_trades = df[df["politician"].str.contains(representative_name, case=False, na=False)].copy() rep_trades["ticker"] = ticker all_trades.append(rep_trades) except Exception: continue
if not all_trades: return pd.DataFrame()
# Combine and sort by date combined = pd.concat(all_trades) return combined.sort_index(ascending=False)
# Track Nancy Pelosi's tradestickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMZN", "CRM", "RBLX", "DIS", "TSLA"]pelosi_trades = track_representative("Pelosi", tickers)
print("\n=== Nancy Pelosi Recent Trades ===")print(pelosi_trades[["ticker", "transactionType", "amount", "disclosureDate"]].head(10))Step 4: Large Trade Alerts
Focus on significant trades using DataFrame filtering:
from finbrain import FinBrainClientimport pandas as pd
fb = FinBrainClient(api_key="YOUR_API_KEY")
LARGE_AMOUNTS = [ "$500,001 - $1,000,000", "$1,000,001 - $5,000,000", "Over $5,000,000"]
def get_large_trades(tickers): """Find large congressional trades""" all_trades = []
for ticker in tickers: try: df = fb.house_trades.ticker(ticker, as_dataframe=True)
if not df.empty: # Filter for large amounts large = df[df["amount"].isin(LARGE_AMOUNTS)].copy() large["ticker"] = ticker all_trades.append(large) except Exception: continue
if not all_trades: return pd.DataFrame()
combined = pd.concat(all_trades)
# Add estimated value column for sorting def estimate_value(amount): if "Over $5,000,000" in str(amount): return 7500000 elif "$1,000,001 - $5,000,000" in str(amount): return 3000000 elif "$500,001 - $1,000,000" in str(amount): return 750000 return 0
combined["estimated_value"] = combined["amount"].apply(estimate_value) return combined.sort_values("estimated_value", ascending=False)
# Find large tradestickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMZN", "META"]large = get_large_trades(tickers)
print("\n=== Large Congressional Trades ===")print(large[["ticker", "politician", "transactionType", "amount"]].head(10))Step 5: Bipartisan Consensus
Find stocks both parties are buying using DataFrame aggregation:
from finbrain import FinBrainClient
fb = FinBrainClient(api_key="YOUR_API_KEY")
def find_bipartisan_buys(tickers): """Find stocks where multiple representatives are buying""" results = []
for ticker in tickers: try: df = fb.house_trades.ticker(ticker, as_dataframe=True)
if df.empty: continue
purchases = df[df["transactionType"] == "Purchase"] unique_buyers = purchases["politician"].nunique()
if unique_buyers >= 2: results.append({ "ticker": ticker, "unique_buyers": unique_buyers, "total_purchases": len(purchases) }) except Exception: continue
return sorted(results, key=lambda x: x["unique_buyers"], reverse=True)
# Find stocks with multiple buyerstickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMZN", "META"]multi_buyers = find_bipartisan_buys(tickers)
print("\n=== Multiple Representatives Buying ===")for b in multi_buyers: print(f"{b['ticker']}: {b['unique_buyers']} unique buyers, {b['total_purchases']} purchases")Step 6: Trade Analysis by Representative
Analyze trading patterns by representative using DataFrame operations:
from finbrain import FinBrainClientimport pandas as pd
fb = FinBrainClient(api_key="YOUR_API_KEY")
def analyze_representative_activity(tickers): """Analyze trading activity by representative""" all_trades = []
for ticker in tickers: try: df = fb.house_trades.ticker(ticker, as_dataframe=True)
if not df.empty: df = df.copy() df["ticker"] = ticker all_trades.append(df) except Exception: continue
if not all_trades: return pd.DataFrame()
combined = pd.concat(all_trades)
# Group by representative and analyze analysis = combined.groupby("politician").agg( total_trades=("transactionType", "count"), purchases=("transactionType", lambda x: (x == "Purchase").sum()), sales=("transactionType", lambda x: (x == "Sale").sum()), tickers=("ticker", lambda x: list(x.unique())) ).sort_values("total_trades", ascending=False)
return analysis
# Analyze activitytickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMZN", "META"]activity = analyze_representative_activity(tickers)
print("\n=== Representative Activity Analysis ===")print(activity[["total_trades", "purchases", "sales"]].head(10))Step 7: Complete Signal Generator
Here’s a complete congressional signal generator using as_dataframe=True:
from finbrain import FinBrainClientfrom datetime import datetime, timedeltaimport pandas as pd
class CongressionalSignals: def __init__(self, api_key): self.fb = FinBrainClient(api_key=api_key) self.large_amounts = [ "$500,001 - $1,000,000", "$1,000,001 - $5,000,000", "Over $5,000,000" ]
def generate_signal(self, ticker): """Generate trading signal from congressional activity""" try: df = self.fb.house_trades.ticker(ticker, as_dataframe=True) except Exception: return {"signal": "neutral", "reason": "no_data"}
if df.empty: return {"signal": "neutral", "reason": "no_trades"}
# Recent trades (last 60 days), measured from the DISCLOSURE date — # that is the day the filing became public and the trade became # actionable. Filtering on the transaction date instead would treat # information as available up to 45 days before anyone could see it. cutoff = datetime.now() - timedelta(days=60) disclosed = pd.to_datetime(df["disclosureDate"], errors="coerce") recent = df[disclosed > cutoff]
# Rows with no disclosure date become NaT above and drop out, which is # the behaviour you want: an undated filing cannot be anchored in time. if recent.empty: return {"signal": "neutral", "reason": "no_recent_trades"}
# Analyze trades using DataFrame operations purchases = recent[recent["transactionType"] == "Purchase"] sales = recent[recent["transactionType"] == "Sale"]
large_purchases = purchases[purchases["amount"].isin(self.large_amounts)] large_sales = sales[sales["amount"].isin(self.large_amounts)]
# Multiple buyers check unique_buyers = purchases["politician"].nunique() has_multiple_buyers = unique_buyers >= 2
# Generate signal score = 0 reasons = []
# Purchase/sale ratio if len(purchases) > len(sales) * 2: score += 2 reasons.append(f"{len(purchases)} purchases vs {len(sales)} sales") elif len(sales) > len(purchases) * 2: score -= 2 reasons.append(f"More sales than purchases")
# Large trades if len(large_purchases) > 0: score += 2 reasons.append(f"{len(large_purchases)} large purchases") if len(large_sales) > 0: score -= 1 reasons.append(f"{len(large_sales)} large sales")
# Multiple buyers if has_multiple_buyers: score += 1 reasons.append(f"{unique_buyers} unique buyers")
# Determine signal if score >= 3: signal = "strong_buy" elif score >= 1: signal = "buy" elif score <= -3: signal = "strong_sell" elif score <= -1: signal = "sell" else: signal = "neutral"
return { "ticker": ticker, "signal": signal, "score": score, "reasons": reasons, "purchases": len(purchases), "sales": len(sales), "unique_buyers": unique_buyers }
def scan_universe(self, tickers): """Scan multiple tickers for signals""" signals = []
for ticker in tickers: result = self.generate_signal(ticker) if result["signal"] != "neutral": signals.append(result)
# Sort by score return sorted(signals, key=lambda x: x["score"], reverse=True)
# Use the signal generatorsignals = CongressionalSignals(api_key="YOUR_API_KEY")
universe = [ "NVDA", "AAPL", "MSFT", "GOOGL", "AMZN", "META", "TSLA", "JPM", "BAC", "GS", "V", "MA", "JNJ", "PFE", "UNH", "ABBV"]
results = signals.scan_universe(universe)
print("\n=== Congressional Signals ===")for r in results: print(f"\n{r['ticker']}: {r['signal']} (score: {r['score']})") print(f" Purchases: {r['purchases']}, Sales: {r['sales']}") print(f" Unique Buyers: {r['unique_buyers']}") for reason in r["reasons"]: print(f" - {reason}")Important Considerations
- Disclosure delays - Trades are reported up to 45 days after execution. Use
disclosureDaterather than the transaction date to anchor any backtest; entering on the transaction date assumes knowledge that was not public yet and will flatter your results - Not investment advice - This data is for research purposes only
- Past performance - Historical results don’t guarantee future returns
- Legal boundaries - Only use publicly available information
Related Resources
- Congressional Trading Dataset - US House and Senate trading data
- Congressional Trading API Reference
- Building an Insider Trading Scanner

