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

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    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.")







    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 and 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:


    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)







    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.




    More...
Working...