Insider Trade Scanner
Scan SEC Form 4 filings to find notable insider buys and sells across the market.
In this tutorial
What You'll Build
A command-line Python script that scans recent SEC Form 4 filings to find insider trades. Form 4 is the filing that corporate insiders (officers, directors, and large shareholders) must submit when they buy or sell company stock. Open market purchases by insiders are one of the most-watched signals in the market -- when a CEO spends their own money buying shares, it often means they believe the stock is undervalued.
The script will:
- Fetch recent insider trades from the stockdata.dev API
- Filter to open market buys (the most interesting signal)
- Display a formatted table with date, ticker, insider name, title, shares, and total value
- Support filtering by ticker or time range
Prerequisites
- Python 3.7 or newer
- The
requestslibrary (pip install requests) - A stockdata.dev API key (get one free)
The Code
Create a file called insider_scanner.py and paste the following:
import argparse import requests API_KEY = "your_api_key_here" BASE_URL = "https://api.stockdata.dev/v1" def fetch_trades(ticker=None, days=7, action="buy"): """Fetch insider trades from the API.""" headers = {"X-API-Key": API_KEY} if ticker: url = f"{BASE_URL}/insider-trades/{ticker}" params = {"days": days, "limit": 100} else: url = f"{BASE_URL}/insider-trades" params = {"days": days, "action": action, "limit": 100} resp = requests.get(url, headers=headers, params=params) resp.raise_for_status() return resp.json()["trades"] def format_value(value): """Format a dollar value with commas.""" if value >= 1_000_000: return f"${value / 1_000_000:.1f}M" return f"${value:,.0f}" def print_trades(trades, title): """Print trades as a formatted table.""" if not trades: print("No trades found.") return print(f"\n{title}") print("─" * 90) print(f"{'Date':12}{'Ticker':8}{'Insider':22}{'Title':18}{'Shares':>10}{'Value':>14}") print("─" * 90) for t in trades: name = t["owner_name"].title() # Convert "SMITH JOHN" to "Smith John" title_str = t.get("owner_title", "")[:17] shares = f"{t['shares']:>,}" value = format_value(t["value"]) print(f"{t['transaction_date']:12}{t['ticker']:8}{name[:21]:22}{title_str:18}{shares:>10}{value:>14}") print(f"\nTotal: {len(trades)} trades") def main(): parser = argparse.ArgumentParser(description="Scan SEC insider trades") parser.add_argument("--ticker", help="Filter to a specific company") parser.add_argument("--days", type=int, default=7, help="Look back N days (default: 7)") args = parser.parse_args() if args.ticker: trades = fetch_trades(ticker=args.ticker, days=args.days, action=None) title = f"Insider Trades for {args.ticker.upper()} (last {args.days} days)" else: trades = fetch_trades(days=args.days) title = f"Recent Insider Buys (last {args.days} days)" print_trades(trades, title) if __name__ == "__main__": main()
Replace "your_api_key_here" with your actual API key. For production scripts, consider using an environment variable instead of hardcoding the key.
How It Works
When corporate insiders trade their company's stock, they must disclose it to the SEC via Form 4. Each trade includes a transaction code that tells you what kind of trade it was:
- P (Purchase) -- Open market buy. The insider spent their own money to buy shares on the open market. This is the strongest bullish signal because it's entirely voluntary.
- S (Sale) -- Open market sale. The insider sold shares. Sales can be for many reasons (diversification, taxes, personal expenses), so they're a weaker signal on their own.
- M (Exercise) -- Option exercise. The insider exercised stock options. Often followed by a sale, this is usually part of a compensation plan and less informative.
The script filters to open market buys by default because they are the most actionable signal. When a CEO or CFO buys shares with their own money, it means they believe the stock is undervalued at the current price.
The API maps these codes to readable action values: "buy" for purchases, "sale" for sales. You can filter on either using the action query parameter.
Running It
Scan for recent insider buys across all companies:
python insider_scanner.py
Example output:
Recent Insider Buys (last 7 days) ────────────────────────────────────────────────────────────────────────────────────────── Date Ticker Insider Title Shares Value ────────────────────────────────────────────────────────────────────────────────────────── 2026-02-14 XYZ John Smith CEO 10,000 $245,000 2026-02-13 ABC Jane Doe Director 5,000 $178,500 2026-02-12 DEF Robert Chen CFO 8,500 $312,750 2026-02-11 GHI Maria Garcia VP Operations 2,000 $67,400 2026-02-10 JKL David Park Director 15,000 $495,000 Total: 5 trades
Look up all insider trades for a specific company:
python insider_scanner.py --ticker AAPL --days 30
Example output:
Insider Trades for AAPL (last 30 days) ────────────────────────────────────────────────────────────────────────────────────────── Date Ticker Insider Title Shares Value ────────────────────────────────────────────────────────────────────────────────────────── 2026-02-01 AAPL Cook Timothy D Chief Executive 50,000 $11.4M 2026-01-28 AAPL Williams Jeffrey E Chief Financial 20,000 $4.5M 2026-01-22 AAPL O'Brien Deirdre SVP Retail 10,000 $2.3M Total: 3 trades
Filtering and Sorting
You can extend the script with a few small additions to find the most interesting trades.
Find the largest buys
Sort trades by value to find the biggest purchases:
# Sort by total value, largest first trades = fetch_trades(days=30) trades.sort(key=lambda t: t["value"], reverse=True) print_trades(trades[:10], "Top 10 Largest Insider Buys (last 30 days)")
Filter by insider title
Focus on C-suite executives or board directors:
# Only show trades by CEOs, CFOs, and Directors trades = fetch_trades(days=30) key_titles = ["ceo", "cfo", "chief executive", "chief financial", "director"] filtered = [ t for t in trades if any(kw in t.get("owner_title", "").lower() for kw in key_titles) ] print_trades(filtered, "C-Suite & Director Buys (last 30 days)")
Combine both
Find the largest C-suite purchases -- these are the strongest insider signals:
trades = fetch_trades(days=30) key_titles = ["ceo", "cfo", "chief executive", "chief financial"] csuite = [t for t in trades if any(kw in t.get("owner_title", "").lower() for kw in key_titles)] csuite.sort(key=lambda t: t["value"], reverse=True) print_trades(csuite[:10], "Top C-Suite Buys (last 30 days)")
Enhancements
Here are some ways to extend the scanner:
- Save to CSV -- Add
--csv output.csvflag to export trades for further analysis in Excel or Google Sheets. Use Python's built-incsvmodule. - Track specific insiders -- Keep a list of notable insiders (e.g., well-known activist investors or successful CEO-buyers) and alert when they make new purchases.
- Combine with company profile -- Use the
/v1/company/{ticker}endpoint to add market cap and sector to each trade, helping you assess whether the buy is significant relative to the company's size. - Historical analysis -- Track insider buys over time and compare against subsequent stock performance. Use
--days 90to gather a larger dataset. - Email alerts -- Run the script on a schedule with a cron job and send an email when new large buys appear. See the SEC Filing Alert Email tutorial for an email notification pattern you can adapt.
Insider buying is just one data point. Always do your own research before making investment decisions. Combine insider activity with financial analysis (see the Financial Statement Analyzer tutorial) for a more complete picture.