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:

Validated Signal

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:

🔺
Triple Signal
Highest 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.

📅
Markup Calendar Alert
Event Catalyst

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.

🏢
Exec Pre-Vote Buy
Confirmation Signal

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.

📊
Bill ML Score (Investability)
Structural Signal

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:

Congressional Trading Bot Tech Stack: Tools, Costs, and Components
LayerToolCostNotes
Signal DataGovGreed APIAlpha / WaitlistCongressional signals, bill scores, exec timing
ExecutionAlpacaFree (paper)Commission-free, great Python SDK
Execution (Pro)Interactive Brokers$0–10/moBetter for larger capital, options support
Price DataFMP / Polygon$0–$25/moFor backtesting forward returns
BacktestingBacktrader / QuantConnectFreePython-native, well documented
SchedulingCron / GitHub ActionsFreeRun your bot daily after market open
Start Here

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

1

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.

2

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.

3

Install Dependencies

bash terminal
pip install requests alpaca-trade-api python-dotenv schedule
dotenv .env
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:

python govgreed_signals.py
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.

python executor.py
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.

python bot.py
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)
Risk Management

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.

python backtest.py
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:

  1. Switch to live Alpaca credentials — change ALPACA_BASE_URL to https://api.alpaca.markets
  2. Set up a server — a $5/month VPS on DigitalOcean or a free GitHub Actions scheduled workflow is enough to run daily
  3. Add alerting — send yourself a Slack or email notification on every trade and on any API errors
  4. Set position limits — never allocate more than 5% per signal, 40% total to political signals
  5. Log everything — write every signal, decision, and trade to a CSV. You'll need this to evaluate performance and tune the strategy.
  6. Review weekly — check which signals fired, which converted to gains, which didn't. Political timing varies — a bill can stall for months before passing.
Pro Tip

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:

Partnership Program

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.