How to Build an AI Trading Bot Using Congressional Insider Data
Congress members trade stocks they regulate. We built the database to track it. This guide shows you how to use that data to build a bot that gets in before the market does.
Why Congressional Trading Data is the Best Alternative Data Source
Most retail traders use the same data: price feeds, technical indicators, earnings calendars. Hedge funds use alternative data — credit card transactions, satellite imagery, shipping data — to find edges the market hasn't priced in yet.
Congressional trading data is arguably the best alternative data source available for free. Here's why:
- It's legally disclosed. Under the STOCK Act (2012), every member of Congress must publicly file stock trades within 45 days. No scraping required — it's mandated public disclosure.
- The information advantage is structural. Committee members don't just trade based on public information. They're in the room where legislation is drafted. A senator on the Banking Committee who buys bank stocks right before a favorable regulatory ruling has an informational edge you can measure — and follow.
- The signal has a long history. We have 14 years (2012–2026) and 188,695 trades to backtest against. That's enough data to validate a strategy.
- It's uncorrelated with technical signals. Political alpha doesn't care about RSI levels or moving averages. It's a fundamentally different signal source, which means it adds to any existing strategy.
GovGreed's ML model, trained on 3 congressional sessions, shows that bills scoring 70+ on investability are enacted at 9.1% vs 1.7% for medium-tier — a 5.4× lift over 37,143 bills. That's a real edge, not noise.
The Four Signals Your Bot Should Track
Not all congressional data is equal. Here's what actually moves markets, in order of conviction:
A Triple Signal fires when three conditions align on the same ticker: (1) a politician sits on the committee that controls a bill affecting that company, (2) they have traded that stock, and (3) they've received campaign contributions from the same industry. All three together suggest coordinated insider positioning. 752 active Triple Signals in the current 119th Congress.
When a congressional committee schedules a markup (a formal meeting to amend and vote on a bill), it's one of the clearest signals that legislation is about to advance. Insiders know before the markup is announced publicly. Stocks of affected companies often move in the 4–21 day window around a markup. Our API exposes the full markup calendar with linked bill signals.
Corporate officers and directors must disclose trades within 2 business days (SEC Form 4). When a CEO buys significant stock within 90 days before a congressional vote on legislation affecting their company, that's a confirmation signal — executives believe the bill will pass and be favorable. We cross-reference 22,731 executive trades against the congressional vote calendar.
42,143 bills across three congressional sessions, each scored 0–100 by a machine learning model trained on historical insider trading patterns. Bills scoring 70+ are enacted at 5.4× the rate of medium-scoring bills. Use this as a filter: only trade signals on bills with investability ≥ 50.
The Tech Stack
Before writing any code, pick your stack. Here's what most builders use:
| Layer | Tool | Cost | Notes |
|---|---|---|---|
| Signal Data | GovGreed API | Alpha / Waitlist | Congressional signals, bill scores, exec timing |
| Execution | Alpaca | Free (paper) | Commission-free, great Python SDK |
| Execution (Pro) | Interactive Brokers | $0–10/mo | Better for larger capital, options support |
| Price Data | FMP / Polygon | $0–$25/mo | For backtesting forward returns |
| Backtesting | Backtrader / QuantConnect | Free | Python-native, well documented |
| Scheduling | Cron / GitHub Actions | Free | Run your bot daily after market open |
If you're new to algorithmic trading, start with Alpaca paper trading. It's free, it's real market data, and it won't cost you anything when you make a mistake in your first strategy. Graduate to live trading only after a full paper trading cycle.
Step 1 — Get Your API Keys
GovGreed API Key
Join the GovGreed waitlist to request API access. We're in alpha, onboarding developers and trading teams selectively before our Summer 2026 launch. Everyone on the waitlist gets 30 days of full access free — more than enough to build and backtest your complete bot logic.
Alpaca API Keys
Sign up at alpaca.markets. Use the paper trading endpoint while developing (paper-api.alpaca.markets). Your keys will be in the dashboard under API Keys.
Install Dependencies
pip install requests alpaca-trade-api python-dotenv schedule
GOVGREED_API_KEY=your_govgreed_key_here ALPACA_API_KEY=your_alpaca_key_here ALPACA_SECRET_KEY=your_alpaca_secret_here ALPACA_BASE_URL=https://paper-api.alpaca.markets
Step 2 — Fetch Signals from GovGreed
The core of your bot is a signal fetcher. Here's a clean, production-ready module that pulls triple signals, filters for quality, and handles errors gracefully:
import requests import os from dotenv import load_dotenv from datetime import datetime, timedelta load_dotenv() GOVGREED_BASE = "https://api.govgreed.com/v1" HEADERS = {"Authorization": f"Bearer {os.getenv('GOVGREED_API_KEY')}"} def get_triple_signals(min_score=50, limit=20): """ Fetch triple signals: politician on committee + traded stock + took contributions. These are the highest-conviction insider signals. """ resp = requests.get( f"{GOVGREED_BASE}/signals/triple", headers=HEADERS, params={ "min_score": min_score, "limit": limit, "order": "opportunity_score.desc" } ) resp.raise_for_status() return resp.json().get("data", []) def get_upcoming_markups(days_ahead=14): """ Fetch committee markups scheduled in the next N days. Markups = bill is moving. This is the buy window. """ resp = requests.get( f"{GOVGREED_BASE}/signals/markup-calendar", headers=HEADERS, params={ "days_ahead": days_ahead, "has_signals": True } ) resp.raise_for_status() return resp.json().get("data", []) def get_exec_timing_signals(min_score=2.0): """ Corporate exec buys within 90 days before a congressional vote. Use as confirmation — exec agrees with the congressional signal. """ resp = requests.get( f"{GOVGREED_BASE}/signals/exec-timing", headers=HEADERS, params={ "category": "ahead_of_vote", "transaction_type": "purchase", "min_timing_score": min_score } ) resp.raise_for_status() return resp.json().get("data", []) def build_signal_universe(): """ Merge all signal types into a ranked list of actionable tickers. Returns: [{ticker, score, sources, days_to_markup}] """ triple = get_triple_signals(min_score=50) markups = get_upcoming_markups(days_ahead=14) exec_signals = get_exec_timing_signals() # Build ticker → score map ticker_scores = {} for signal in triple: ticker = signal["ticker"] if ticker not in ticker_scores: ticker_scores[ticker] = {"score": 0, "sources": [], "days_to_markup": None} ticker_scores[ticker]["score"] += signal.get("opportunity_score", 0) * 1.2 ticker_scores[ticker]["sources"].append("triple") for markup in markups: for ticker in markup.get("affected_tickers", []): if ticker not in ticker_scores: ticker_scores[ticker] = {"score": 0, "sources": [], "days_to_markup": None} ticker_scores[ticker]["score"] += 30 # markup bonus ticker_scores[ticker]["sources"].append("markup") ticker_scores[ticker]["days_to_markup"] = markup.get("days_away") for exec_sig in exec_signals: ticker = exec_sig["ticker"] if ticker not in ticker_scores: ticker_scores[ticker] = {"score": 0, "sources": [], "days_to_markup": None} ticker_scores[ticker]["score"] += exec_sig.get("timing_score", 0) * 0.8 ticker_scores[ticker]["sources"].append("exec") # Sort by composite score, return top candidates ranked = sorted( [{"ticker": k, **v} for k, v in ticker_scores.items()], key=lambda x: x["score"], reverse=True ) return [r for r in ranked if r["score"] > 40] # filter weak signals
Step 3 — Execution with Alpaca
Alpaca is the standard choice for retail algo traders. It's commission-free, has a clean Python SDK, and lets you paper trade with zero capital at risk while you test your strategy.
import alpaca_trade_api as tradeapi import os from dotenv import load_dotenv load_dotenv() alpaca = tradeapi.REST( os.getenv("ALPACA_API_KEY"), os.getenv("ALPACA_SECRET_KEY"), base_url=os.getenv("ALPACA_BASE_URL") ) def get_portfolio_value(): account = alpaca.get_account() return float(account.portfolio_value) def get_current_positions(): return {p.symbol: p for p in alpaca.list_positions()} def size_position(score, portfolio_value, max_position_pct=0.05): """ Kelly-inspired sizing: higher score = larger position. Never exceed 5% of portfolio on a single political signal. """ # Score 40-500+ → position 1%-5% of portfolio raw_pct = (score - 40) / (500 - 40) * max_position_pct pct = min(max(0.01, raw_pct), max_position_pct) return portfolio_value * pct def is_tradeable(ticker): """Check if ticker is tradeable on Alpaca""" try: asset = alpaca.get_asset(ticker) return asset.tradable and asset.status == "active" except: return False def execute_signal(ticker, score, reason=""): """ Execute a buy order for a given signal. Uses market order — signal is more important than price precision. """ if not is_tradeable(ticker): print(f"⚠️ {ticker} not tradeable, skipping") return None positions = get_current_positions() if ticker in positions: print(f"⏭️ Already holding {ticker}, skipping") return None portfolio_value = get_portfolio_value() position_value = size_position(score, portfolio_value) # Get current price for share calculation latest = alpaca.get_latest_trade(ticker) shares = int(position_value / latest.price) if shares < 1: print(f"⚠️ Position too small for {ticker} ({shares} shares)") return None order = alpaca.submit_order( symbol=ticker, qty=shares, side="buy", type="market", time_in_force="day" ) print(f"✅ Bought {shares}x {ticker} @ ~${latest.price:.2f}") print(f" Signal score: {score:.1f} | {reason}") return order
Step 4 — The Main Bot Loop
Wire the signal fetcher and executor together. This runs daily at market open, pulls fresh signals, and executes any new positions that meet your criteria.
import schedule import time from govgreed_signals import build_signal_universe from executor import execute_signal, get_current_positions # Configuration MAX_POSITIONS = 8 # never hold more than 8 political signals at once MIN_SIGNAL_SCORE = 60 # only act on strong signals REQUIRE_MARKUP = False # set True to only trade markup-linked signals def run_bot(): print(f"\n{'='*50}") print(f"🏛️ GovGreed Bot — {datetime.now().strftime('%Y-%m-%d %H:%M')}") print(f"{'='*50}") # Check position limits current_positions = get_current_positions() available_slots = MAX_POSITIONS - len(current_positions) if available_slots <= 0: print("📊 Portfolio full. No new positions today.") return # Fetch and rank all signals signals = build_signal_universe() print(f"📡 Found {len(signals)} signals above threshold") new_trades = 0 for signal in signals: if new_trades >= available_slots: break ticker = signal["ticker"] score = signal["score"] sources = signal["sources"] days_to_markup = signal.get("days_to_markup") # Quality filters if score < MIN_SIGNAL_SCORE: continue if REQUIRE_MARKUP and "markup" not in sources: continue if ticker in current_positions: continue # Build reason string for logging reason_parts = [] if "triple" in sources: reason_parts.append("TRIPLE SIGNAL") if "markup" in sources: reason_parts.append(f"MARKUP IN {days_to_markup}d") if "exec" in sources: reason_parts.append("EXEC BUY") reason = " + ".join(reason_parts) order = execute_signal(ticker, score, reason) if order: new_trades += 1 print(f"\n✅ Session complete. {new_trades} new positions.") # Schedule: run daily at 9:45am ET (after open volatility settles) schedule.every().day.at("09:45").do(run_bot) print("🤖 GovGreed Trading Bot started. Waiting for market open...") while True: schedule.run_pending() time.sleep(60)
Political signals have a longer time horizon (days to weeks) than technical signals. Set stop-losses (15–20% below entry) and hold time limits (30–90 days). Congressional trades are disclosed with a 45-day lag — you're not front-running in real-time, you're following a pattern.
Step 5 — Backtest Before Going Live
Don't deploy capital without validating the signal on historical data first. Here's the backtest methodology:
The Simple Backtest
For each triple signal in the historical dataset (2018–2024), record the signal date and ticker, then measure the stock's forward return at 5, 30, and 90 days. Compare to the S&P 500 return over the same window.
import requests import pandas as pd from datetime import timedelta # 1. Pull historical triple signals via GovGreed API historical = requests.get( "https://api.govgreed.com/v1/trades/historical", headers=HEADERS, params={"has_triple_signal": True, "date_from": "2018-01-01", "date_to": "2024-01-01"} ).json()["data"] # 2. For each signal, fetch forward price returns from FMP results = [] for signal in historical: ticker = signal["ticker"] signal_date = signal["trade_date"] price_history = requests.get( f"https://financialmodelingprep.com/stable/historical-price-eod/full?symbol={ticker}&apikey=YOUR_FMP_KEY" ).json()["historical"] df = pd.DataFrame(price_history).set_index("date").sort_index() try: entry_price = df.loc[signal_date]["close"] ret_30d = (df.iloc[df.index.get_loc(signal_date, method="nearest") + 30]["close"] - entry_price) / entry_price results.append({"ticker": ticker, "signal_date": signal_date, "ret_30d": ret_30d}) except: pass # 3. Analyze df_results = pd.DataFrame(results) print(f"Avg 30d return: {df_results['ret_30d'].mean():.1%}") print(f"Win rate: {(df_results['ret_30d'] > 0).mean():.1%}") print(f"Sharpe (annualized): {df_results['ret_30d'].mean() / df_results['ret_30d'].std() * (252/30)**0.5:.2f}")
Step 6 — Deploy and Monitor
Once you've validated on paper trading, here's the production deployment checklist:
- Switch to live Alpaca credentials — change
ALPACA_BASE_URLtohttps://api.alpaca.markets - Set up a server — a $5/month VPS on DigitalOcean or a free GitHub Actions scheduled workflow is enough to run daily
- Add alerting — send yourself a Slack or email notification on every trade and on any API errors
- Set position limits — never allocate more than 5% per signal, 40% total to political signals
- Log everything — write every signal, decision, and trade to a CSV. You'll need this to evaluate performance and tune the strategy.
- Review weekly — check which signals fired, which converted to gains, which didn't. Political timing varies — a bill can stall for months before passing.
The markup calendar is the highest-precision signal. Instead of running the bot daily, set up a webhook via the GovGreed Quant tier to ping your server the moment a committee schedules a markup on a bill with active triple signals. That's same-day execution, before most of the market processes the news.
Frequently Asked Questions
Is it legal to build a trading bot based on congressional trading data?
Yes. Congressional trades are public disclosures under the STOCK Act — every trade is filed publicly and searchable by anyone. Building a strategy around public disclosure is entirely legal. It is not insider trading because the information is public. The 45-day disclosure lag means you're following a pattern, not acting on non-public information.
What is the best data source for congressional trading signals?
GovGreed is the only source that cross-references STOCK Act trades with committee assignments, bill investability ML scores, executive insider timing signals, and campaign contributions in one API. Most alternative data providers offer raw trade data without the cross-reference layer. GovGreed's Triple Signal computation requires simultaneously matching across five separate federal disclosure systems — that's the moat.
How do I get access to the GovGreed API?
GovGreed is currently in alpha — join the waitlist to request access. Everyone on the waitlist gets 30 days of full access free when we launch (before Summer 2026). Alpaca paper trading is free. You can paper trade the complete bot stack at $0 cost during the alpha period.
How do I handle the 45-day disclosure lag?
The lag is actually less of a problem than it seems. You're not trying to front-run a single trade — you're identifying patterns: which politicians are consistently buying specific sectors they regulate. A pattern identified 45 days late still has predictive value for what's coming next. The markup calendar signal is near-real-time (markups are public as soon as they're scheduled).
Can this work for options?
Yes. Many of the high-investability bills create predictable catalyst windows (markup → committee vote → floor vote). These are defined risk plays. Use the markup calendar to identify option expiry windows. Alpaca supports options trading; Interactive Brokers is better for complex options strategies.
Ready to Build?
Everything in this guide is live and available now:
- Get your free GovGreed API key — 100 calls/day, top 20 triple signals, no credit card
- Sign up for Alpaca — paper trading is free and instant
- Explore the live dashboard — visualize all signals before building
- Read the full API docs — all endpoints, pricing, and schema
Building something with GovGreed signals? We're actively partnering with bot frameworks, brokers, and algo trading platforms. Get in touch to discuss data partnerships, co-marketing, and API revenue sharing.