Unveiling the Smart Money: Extracting and Analyzing Institutional Holdings from SEC EDGAR
Introduction to SEC EDGAR and Form 13F
Unveiling the Smart Money: Extracting and Analyzing Institutional Holdings from SEC EDGAR

Introduction to SEC EDGAR and Form 13F
The SEC EDGAR (Electronic Data Gathering, Analysis, and Retrieval) system is a treasure trove of financial data filed by publicly traded companies and institutional investors. Among these, Form 13F filings stand out as they require institutional investment managers with at least $100 million in assets under management to disclose their equity holdings every quarter. These filings offer unparalleled insights into the strategies of “smart money” investors like BlackRock, one of the world’s largest asset managers. By analyzing 13F filings, we can track BlackRock’s buying, holding, and selling decisions over time.
Why Analyzing Institutional Holdings Matters
Understanding the moves of institutional investors can provide retail investors and analysts with:
- Market Sentiment Indicators: Knowing what the largest players are buying or selling can highlight emerging trends.
- Risk Management Insights: Observing shifts in asset allocation by major funds.
- Opportunity Identification: Spotting new opportunities in under-followed securities that institutional investors are quietly accumulating.
Photo by Rostyslav Savchyn on Unsplash
Methodology for Extracting and Analyzing Holdings
In this article I will be presenting sample code on how to extract Form 13F filings of BlackRock.
import requests
import pandas as pd
from bs4 import BeautifulSoup
# SEC EDGAR Base URL
SEC_BASE_URL = "https://www.sec.gov/Archives"
# Fetch Form 13F Filings for a Given CIK
def fetch_13f_filings(cik, num_filings=2):
url = f"https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK={cik}&type=13F&count={num_filings}&output=atom"
headers = {"User-Agent": "Institutional Holdings Analysis (your_email@example.com)"}
response = requests.get(url, headers=headers)
response.raise_for_status()
soup = BeautifulSoup(response.content, "xml")
entries = soup.find_all("entry")
filings = [(entry.find("filing-date").text, entry.find("link")["href"]) for entry in entries]
return filings[:num_filings]
# Parse Holdings from XML Data
def parse_13f_holdings(filing_url):
headers = {"User-Agent": "Institutional Holdings Analysis (your_email@example.com)"}
response = requests.get(filing_url, headers=headers)
response.raise_for_status()
soup = BeautifulSoup(response.content, "xml")
rows = []
for info_table in soup.find_all("infoTable"):
name = info_table.find("nameOfIssuer").text
ticker = info_table.find("cusip").text
value = int(info_table.find("value").text) * 1000 # Value in dollars
shares = int(info_table.find("sshPrnamt").text)
rows.append({"Name": name, "Ticker": ticker, "Value": value, "Shares": shares})
return pd.DataFrame(rows)
# Compare Holdings Between Two Quarters
def compare_holdings(holdings_q1, holdings_q2):
merged = holdings_q1.merge(holdings_q2, on="Ticker", how="outer", suffixes=("_q1", "_q2"))
merged["Value_Change"] = merged["Value_q2"].fillna(0) - merged["Value_q1"].fillna(0)
merged["Shares_Change"] = merged["Shares_q2"].fillna(0) - merged["Shares_q1"].fillna(0)
return merged
# Main Function
def main():
blackrock_cik = "0001364742" # CIK for BlackRock
filings = fetch_13f_filings(blackrock_cik)
holdings = []
for date, filing_url in filings:
print(f"Processing filing from {date}...")
filing_content = filing_url.replace("-index.htm", ".xml")
holdings.append(parse_13f_holdings(filing_content))
if len(holdings) == 2:
comparison = compare_holdings(holdings[0], holdings[1])
comparison.to_csv("blackrock_holdings_comparison.csv", index=False)
print("Holdings comparison saved to 'blackrock_holdings_comparison.csv'")
if __name__ == "__main__":
main()
The step-by-step approach to extracting BlackRock’s holdings from the last two quarters is as follows:
- Identify BlackRock’s CIK: Each institutional investor is assigned a unique Central Index Key (CIK) by the SEC. BlackRock’s CIK serves as the identifier to fetch its filings.
- Retrieve Form 13F Filings: Access the SEC EDGAR system to fetch the last two Form 13F filings for BlackRock. These filings are updated quarterly.
- Parse and Clean Data: Extract relevant details such as the name of securities, ticker symbols, values, and share counts from the filing data.
- Compare Quarters: Analyze changes in holdings between the two quarters to identify:
- Increased Holdings: Positions where BlackRock has added shares.
- New Positions: Securities that were not held in the prior quarter.
- Reduced or Closed Positions: Securities where holdings were reduced or eliminated.
5. Present Insights: Visualize and summarize the findings in an easy-to-understand format.
Photo by Igor Savelev on Unsplash
Expanding Study to other institutions
Here is code block to include other institutions. Note that code does not yield data to some institutions which require more debugging. Nevertheless, I have presented the code (warts and all) as follows.
import os
import requests
from bs4 import BeautifulSoup
import pandas as pd
# Base URL for SEC EDGAR filings
HEADERS = {
"User-Agent": "Institutional Holdings Comparison Script (YourName, your_email@example.com)",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://www.sec.gov/",
}
# Directory to save CSV files
OUTPUT_DIR = "data/edgar/"
os.makedirs(OUTPUT_DIR, exist_ok=True)
# List of top institutions with their CIKs
INSTITUTIONS = [
{"name": "BlackRock, Inc.", "cik": "0001364742"},
{"name": "Vanguard Group, Inc.", "cik": "0000102909"},
{"name": "State Street Corporation", "cik": "0000093751"},
{"name": "Fidelity Investments", "cik": "0000322516"},
{"name": "T. Rowe Price Associates", "cik": "0000080255"},
{"name": "Capital Group Companies", "cik": "0000026058"},
{"name": "Geode Capital Management", "cik": "0001527751"},
{"name": "Northern Trust Corporation", "cik": "0000073124"},
{"name": "Dimensional Fund Advisors", "cik": "0000354204"},
{"name": "Invesco Ltd.", "cik": "0000914208"},
{"name": "JPMorgan Chase & Co.", "cik": "0000019617"},
{"name": "Goldman Sachs Group, Inc.", "cik": "0000886982"},
{"name": "Bank of America Corporation", "cik": "0000070858"},
{"name": "Morgan Stanley", "cik": "0000895421"},
{"name": "PIMCO", "cik": "0000816342"},
{"name": "Franklin Templeton Investments", "cik": "0000038777"},
{"name": "Wellington Management Company", "cik": "0000902219"},
{"name": "Ameriprise Financial, Inc.", "cik": "0000820027"},
{"name": "Charles Schwab Corporation", "cik": "0000316709"},
{"name": "Berkshire Hathaway Inc.", "cik": "0001067983"},
]
# Fetch the last two filings
def fetch_latest_filings(cik, count=2):
filing_url = f"https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK={cik}&type=13F-HR&output=atom&count={count}"
print(f"Fetching latest filings for CIK {cik}...")
response = requests.get(filing_url, headers=HEADERS)
if response.status_code != 200:
print("Failed to fetch filings. Check the URL or headers.")
return []
soup = BeautifulSoup(response.text, "xml")
entries = soup.find_all("entry")
filing_urls = []
for entry in entries:
link = entry.find("link")["href"]
filing_urls.append(link)
return filing_urls
# Parse XML holdings
def parse_holdings_xml(xml_content):
try:
from xml.etree import ElementTree as ET
root = ET.fromstring(xml_content)
namespace = {"ns": "http://www.sec.gov/edgar/document/thirteenf/informationtable"}
holdings = []
for info_table in root.findall("ns:infoTable", namespaces=namespace):
name = info_table.find("ns:nameOfIssuer", namespaces=namespace).text
shares = info_table.find("ns:shrsOrPrnAmt/ns:sshPrnamt", namespaces=namespace).text
value = info_table.find("ns:value", namespaces=namespace).text
holdings.append({
"Name": name,
"Shares": int(shares.replace(",", "")) if shares.isdigit() else 0,
"Value (000s)": int(value.replace(",", "")) if value.isdigit() else 0,
})
return pd.DataFrame(holdings)
except Exception as e:
print(f"Error parsing XML: {e}")
return None
# Fetch and parse holdings from a filing URL
def fetch_and_parse_filing(filing_url):
print(f"Fetching filing from: {filing_url}")
response = requests.get(filing_url, headers=HEADERS)
if response.status_code != 200:
print(f"Failed to fetch filing. Status code: {response.status_code}")
return None
# Parse filing page to find the XML file
soup = BeautifulSoup(response.text, "html.parser")
xml_link = soup.find("a", text="form13fInfoTable.xml")
if not xml_link:
print("Could not locate the holdings XML file.")
return None
xml_url = "https://www.sec.gov" + xml_link["href"]
print(f"Fetching XML file from: {xml_url}")
response = requests.get(xml_url, headers=HEADERS)
if response.status_code != 200:
print(f"Failed to fetch XML file. Status code: {response.status_code}")
return None
return parse_holdings_xml(response.content)
# Compare two quarters of holdings
def compare_holdings(current, previous):
# Merge current and previous holdings on "Name"
comparison = pd.merge(current, previous, on="Name", suffixes=("_cur", "_prev"))
# Calculate changes
comparison["Sh_Δ"] = comparison["Shares_cur"] - comparison["Shares_prev"]
comparison["Val_Δ (M)"] = comparison["Value (000s)_cur"] - comparison["Value (000s)_prev"]
# Convert shares and values to millions
for column in ["Shares_cur", "Shares_prev", "Sh_Δ"]:
comparison[column] = (comparison[column] / 1_000_000).round(2) # Convert to millions and round
for column in ["Value (000s)_cur", "Value (000s)_prev", "Val_Δ (M)"]:
comparison[column] = (comparison[column] / 1_000_000).round(2) # Convert to millions and round
# Add Price/Share columns for current and previous quarters
comparison["P/S_cur"] = (comparison["Value (000s)_cur"] / comparison["Shares_cur"]).round(2)
comparison["P/S_prev"] = (comparison["Value (000s)_prev"] / comparison["Shares_prev"]).round(2)
return comparison
# Main workflow
def main():
for institution in INSTITUTIONS:
print(f"\nProcessing {institution['name']} ({institution['cik']})...")
# Fetch the latest two filings
filing_urls = fetch_latest_filings(institution["cik"])
if len(filing_urls) < 2:
print(f"Not enough filings found for {institution['name']}.")
continue
# Fetch and parse holdings for the latest two quarters
current_holdings = fetch_and_parse_filing(filing_urls[0])
previous_holdings = fetch_and_parse_filing(filing_urls[1])
if current_holdings is None or previous_holdings is None:
print(f"Failed to fetch or parse holdings for {institution['name']}.")
continue
# Compare holdings
comparison = compare_holdings(current_holdings, previous_holdings)
# Save results to CSV
institution_dir = os.path.join(OUTPUT_DIR, institution["name"].replace(",", "").replace(" ", "_"))
os.makedirs(institution_dir, exist_ok=True)
comparison.to_csv(os.path.join(institution_dir, "holdings_comparison.csv"), index=False)
print(f"Saved comparison results for {institution['name']} to {institution_dir}")
if __name__ == "__main__":
main()
Additional Topics to Explore
- Data Visualization:
- Create charts and graphs to highlight trends in BlackRock’s portfolio, such as sector allocation changes or top increases/decreases.
2. Cross-Quarter Trend Analysis:
- Extend the analysis beyond two quarters to identify longer-term strategies.
- Expanding to Other Institutions:
- Generalize the code to analyze holdings for other major institutional investors.
- Exploring Sector Allocation:
- Aggregate holdings by sector to observe macro-level strategies.
메타데이터
- post_id
- 5ebcc195dbb2
- slug
- unveiling-the-smart-money-extracting-and-analyzing-institutional-holdings-from-sec-edgar-5ebcc195dbb2
- url
- https://medium.com/@larry.prestosa/unveiling-the-smart-money-extracting-and-analyzing-institutional-holdings-from-sec-edgar-5ebcc195dbb2
- canonical_url
- https://medium.com/@larry.prestosa/unveiling-the-smart-money-extracting-and-analyzing-institutional-holdings-from-sec-edgar-5ebcc195dbb2
- author_url
- https://medium.com/@larry.prestosa
- status
- ok
- fetched_at
- 2026-06-26 21:52:29