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:
- CIK resolution — EDGAR identifies companies by CIK number, not ticker. The API maps AAPL → 0000320193 automatically.
- Filing type filtering — EDGAR search returns 10-Ks, 8-Ks, proxies, and every other filing type. The API pre-filters to Form 4 only.
-
XML parsing — Form 4 filings use XBRL/XML with
<nonDerivativeTransaction>and<derivativeTransaction>elements nested in complex schemas. The API flattens this to simple JSON. - Amendment handling — Form 4/A filings amend previous filings. The API deduplicates and returns the latest version.
- 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)
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)