Build Your Own 13F Tracker: Free Automation with Python + SEC EDGAR
Want to know what Buffett added last quarter, or what Burry loaded up on? Most people reach for Whalewisdom (USD 300 per year, roughly USD 25 per month) or the free but slower-to-update Dataroma. But these sites all draw on the same source: SEC EDGAR, which is public, free, and has an API. This piece shows you how to pull straight from the source, and along the way spells out how to read 13F data correctly and where its limits lie.
What a 13F Is
- Who has to file: institutional investment managers with at least USD 100M in US equity positions (measured by market value on the last trading day of any month; crossing the threshold triggers the filing duty)
- What gets reported: long positions, including purchased put/call options (the table marks each PUT or CALL); short positions are invisible
- Frequency: quarterly, filed within 45 days after quarter end
- Format: XML, machine-readable, which is exactly where the automation opportunity lies
Limits First, Method Second
A 13F has three structural limits you have to understand up front:
- 45-day lag: what you see is the position as of quarter end; the manager may already have reversed course by now.
- No shorts: hedge funds often run paired long/short trades, and the short leg never shows up on a 13F, so reading the long side alone can point you in the wrong direction. Purchased puts, by contrast, do show up (marked PUT): Burry's famous bearish bets were read straight out of the PUT column of his 13F.
- Position size carries different meaning: USD 10M is pocket change for Berkshire but a heavy bet for a small fund. Changes tell you more than absolute holdings, and portfolio weight tells you more than dollar amounts.
The Core Workflow: Four Steps
Step 1: Pull the filer's filing list by CIK
Every filer has a CIK number (Berkshire's is 0001067983). SEC's submissions API returns all of that filer's filings in a single call:
import requests
HEADERS = {"User-Agent": "YourName your@email.com"} # SEC requires this
CIK = "0001067983" # Berkshire Hathaway
url = f"https://data.sec.gov/submissions/CIK{CIK}.json"
data = requests.get(url, headers=HEADERS).json()
filings = data["filings"]["recent"]
idx = [i for i, f in enumerate(filings["form"]) if f == "13F-HR"]
Step 2: Parse the 13F XML holdings table
import xml.etree.ElementTree as ET
ns = {"ns": "http://www.sec.gov/edgar/document/thirteenf/informationtable"}
root = ET.fromstring(xml_text)
positions = []
for it in root.findall(".//ns:infoTable", ns):
positions.append({
"name": it.find("ns:nameOfIssuer", ns).text,
"cusip": it.find("ns:cusip", ns).text,
"value": int(it.find("ns:value", ns).text),
"shares": int(it.find("ns:shrsOrPrnAmt/ns:sshPrnamt", ns).text),
})
Step 3: Map CUSIP to ticker
A 13F identifies securities by CUSIP, not ticker. Maintain a lookup table (or lean on SEC's company_tickers.json), and once you have built out the common large caps you can reuse it.
Step 4: Quarter-over-quarter diff
Align this quarter's and last quarter's holdings tables by CUSIP, compute the change in share counts, and you get four kinds of signal (added, trimmed, new position, exited), output sorted by the dollar value of the change.
The Four Pitfalls Beginners Hit Most
| Pitfall | Consequence | Fix |
|---|---|---|
| No User-Agent set | SEC rejects the request outright | Put "name + email" in the header |
| Requests too fast | Exceeding 10 req/sec gets you temporarily blocked (SEC's official wording is a "brief period"; access resumes once you drop back below the limit) | Add rate limiting: sleep 0.15 seconds before fetching the next one |
| value unit mix-up | Filings before 2023 are denominated in thousands of USD, USD thereafter. Mixing them across years throws your numbers off by 1000x | Determine the unit from the filing period, then normalize |
| Comparing only the latest two quarters | You miss the accumulation trend (three consecutive quarters of adding is a stronger signal than a single quarter) | Keep at least four to eight quarters of history |
Scheduling the Automation
13F filings cluster in a flood between day 40 and day 45 after quarter end. My setup: Windows Task Scheduler runs the script once a week, switching to daily during the filing peak; output writes to Excel, and when a change clears a threshold it emails a notice to me. The variable cost of the whole system is zero; the only investment is the few hours it takes to build it the first time.
The next level up is wiring 13F signals into an AI research pipeline: when two or more of your tracked managers add the same stock in the same quarter, automatically trigger a deep read of that company's filings. I wrote up how that part works in the AI research workflow article (in Chinese).
Thoughts after reading?
Questions about this piece, or a different take? Email hello@taxcodeusstocks.com.
Sources
- SEC EDGAR full-text search and submissions API (data.sec.gov)
- SEC Form 13F official instructions and XML technical specification
- The code is a simplified version of what the author actually runs, tested on Python 3.13