DEV Community

lulzasaur
lulzasaur

Posted on

Build a Form 4 Insider-Buy Alert Without Parsing SEC XML

Build a Form 4 Insider-Buy Alert Without Parsing SEC XML

When a public company CEO buys $500K of their own stock on the open market, that's one of the strongest conviction signals in finance. The data is public — SEC Form 4 filings — but getting it into a usable format is the hard part.

EDGAR returns XML with nested schemas, accession number lookups, and CIK-to-ticker mapping. Most people spend 3+ days just on the parser before writing any alert logic.

I needed this for trading research, so I built an API that does the EDGAR parsing and returns clean JSON. Here's how to build an insider-buy alert system in 40 lines of Python.

The Full Script

import requests
from datetime import datetime, timedelta

API_URL = "https://marketplace-price-api-production.up.railway.app"
API_KEY = "your-key"  # Free tier: 50 calls/month
HEADERS = {"X-Api-Key": API_KEY}

# Check yesterday's filings
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")

# Track these tickers (or leave empty to scan all filings)
WATCHLIST = ["AAPL", "MSFT", "GOOGL", "NVDA", "AMZN", "META", "TSLA"]

alerts = []

for ticker in WATCHLIST:
    resp = requests.get(
        f"{API_URL}/sec/insider-trades",
        params={"ticker": ticker, "startDate": yesterday, "limit": 20},
        headers=HEADERS,
    )

    if resp.status_code != 200:
        continue

    for filing in resp.json().get("filings", []):
        # Get transaction details
        detail = requests.get(
            f"{API_URL}/sec/insider-trades/filing/{filing['accession']}",
            headers=HEADERS,
        ).json()

        for tx in detail.get("transactions", []):
            # Only care about open market purchases
            if tx.get("code") != "P":
                continue

            value = tx["shares"] * tx["pricePerShare"]
            if value >= 50_000:  # $50K minimum
                alerts.append({
                    "ticker": ticker,
                    "insider": detail["owner"]["name"],
                    "title": detail["owner"].get("title", ""),
                    "shares": tx["shares"],
                    "price": tx["pricePerShare"],
                    "value": value,
                    "date": tx["date"],
                })

# Print alerts
for a in sorted(alerts, key=lambda x: -x["value"]):
    print(f"🚨 {a['insider']} ({a['title']})")
    print(f"   Bought {a['shares']:,} shares of {a['ticker']}")
    print(f"   at ${a['price']:.2f} — ${a['value']:,.0f} total")
    print(f"   Filed: {a['date']}")
    print()

if not alerts:
    print("No significant insider purchases yesterday.")
Enter fullscreen mode Exit fullscreen mode

Save this as insider_alerts.py and run it daily. That's it.

What the API Handles For You

Here's the work you're not doing:

  1. CIK resolution — EDGAR identifies companies by CIK number, not ticker. The API maps AAPL → 0000320193 automatically.
  2. Filing type filtering — EDGAR search returns 10-Ks, 8-Ks, proxies, and every other filing type. The API pre-filters to Form 4 only.
  3. XML parsing — Form 4 filings use XBRL/XML with <nonDerivativeTransaction> and <derivativeTransaction> elements nested in complex schemas. The API flattens this to simple JSON.
  4. Amendment handling — Form 4/A filings amend previous filings. The API deduplicates and returns the latest version.
  5. Rate limiting — SEC throttles at 10 req/sec and requires a User-Agent header with contact info. The API handles the SEC-side rate limits.

Filtering by Transaction Type

Form 4 transaction codes tell you what happened:

Code Meaning Why It Matters
P Open market purchase The signal — insider buying with own money
S Open market sale Could be a 10b5-1 plan or discretionary
A Award/grant Compensation, ignore for trading signals
M Option exercise Converting options, often paired with S
F Tax withholding Automatic, not a decision

Focus on P codes. When a CFO buys $200K of stock on the open market — not an option exercise, not a grant — they're making a bet with personal money.

Adding Slack/Discord Alerts

Wire it to your team channel:

import json
from urllib.request import Request, urlopen

WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

if alerts:
    blocks = []
    for a in alerts[:5]:
        blocks.append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": (
                    f"*{a['insider']}* ({a['title']})\n"
                    f"Bought {a['shares']:,} shares of *{a['ticker']}* "
                    f"at ${a['price']:.2f} — *${a['value']:,.0f}*"
                )
            }
        })

    payload = {"text": "Insider Buy Alerts", "blocks": blocks}
    req = Request(WEBHOOK_URL, json.dumps(payload).encode(),
                  {"Content-Type": "application/json"})
    urlopen(req)
Enter fullscreen mode Exit fullscreen mode

What the Research Says

Academic studies (Lakonishok & Lee 2001, Jeng et al. 2003) consistently show insider purchases outperform the market by 7-10% annualized. The strongest signals:

  • Cluster buys — multiple insiders buying within a week
  • Large purchases — $100K+ relative to the insider's compensation
  • CEO/CFO buys — officers have better information than board members

This script catches all three patterns. You can extend the filtering to flag cluster buys by grouping alerts by ticker.

I vibe coded this API for my own portfolio research. It's on RapidAPI — free tier to evaluate, paid tiers when you're running it in production daily.

Top comments (0)