DEV Community

SmartMoneyData
SmartMoneyData

Posted on

Who Owns a Stock? Get Every Hedge Fund and Institutional Holder with Python (SEC 13F)

"Who owns this stock?" sounds like a simple question. Most finance sites answer it with the top 10 holders, a quarter late, and put the rest behind a paywall.

The full answer is public. Every investment manager with $100M or more in US stocks files a Form 13F with the SEC each quarter, listing every position. The SEC even publishes all of them as one bulk data set. The problem is size: one quarter is a 100 MB zip holding a 400 MB table of about 3 million positions, keyed by CUSIP rather than ticker.

This tutorial gets you from a ticker to the full holder list, with quarter-over-quarter changes, without downloading any of that yourself.

What you will build

  • The full list of institutions holding a stock, largest first
  • Who added, reduced, opened (NEW) or EXITED a position versus the previous quarter
  • The top buyers and sellers of a stock in one call
  • A cross-check of several stocks to see which funds hold all of them

The data source

We will use the Institutional Ownership by Ticker actor. It reads the SEC's quarterly Form 13F data sets, maps your ticker to its CUSIP, and compares the latest quarter with the one before. For each ticker it returns one summary row, then one row per holder:

Field Meaning
filer, filerCik the fund or institution
action NEW, ADDED, REDUCED, UNCHANGED or EXITED vs last quarter
shares, valueUsd position at quarter end
changeShares, changePct how much they bought or sold
pctOfInstitutionalShares their slice of all 13F-reported shares
putShares, callShares options, kept apart from the share count
filingUrl the original filing on EDGAR

It costs $2 per 1,000 rows.

Step 1: Setup

pip install apify-client pandas
export APIFY_TOKEN="your-token-here"   # Apify Console → Settings → API & Integrations
Enter fullscreen mode Exit fullscreen mode

Step 2: Get the holders of a stock

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

run = client.actor("smartmoney-data/institutional-ownership-by-ticker").call(
    run_input={"tickers": ["AAPL", "PLTR"], "maxHoldersPerTicker": 200}
)
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())

summaries = [r for r in rows if r["type"] == "ticker-summary"]
holders = [r for r in rows if r["type"] == "holder"]

for s in summaries:
    print(s["ticker"], s["reportPeriod"], s["holders"], "holders,",
          s["newPositions"], "new,", s["exited"], "exited")
Enter fullscreen mode Exit fullscreen mode

In a test run on October 13, 2026 (quarter ending June 30, 2026), this found 6,139 institutions holding AAPL and 2,880 holding PLTR. For AAPL, 295 opened new positions, 2,547 added, 2,892 trimmed and 185 sold out.

Step 3: See who is buying and who is selling

import pandas as pd

df = pd.DataFrame(holders)
aapl = df[df.ticker == "AAPL"]

print(aapl[["filer", "action", "shares", "changeShares", "pctOfInstitutionalShares"]].head(10))
Enter fullscreen mode Exit fullscreen mode

The largest holders come first. In the same run BlackRock held 1.16 billion AAPL shares (11.9% of all 13F-reported shares, up 1.6% on the quarter), followed by Vanguard and State Street.

The summary row already ranks the biggest movers, so you do not have to sort 6,000 rows yourself:

s = next(s for s in summaries if s["ticker"] == "AAPL")
for b in s["topBuyers"][:5]:
    print("BUY ", b["filer"], f'{b["changeShares"]:+,}', b["action"])
for b in s["topSellers"][:5]:
    print("SELL", b["filer"], f'{b["changeShares"]:+,}', b["action"])
Enter fullscreen mode Exit fullscreen mode

Step 4: Find new money

New positions are often the most interesting rows: a fund that did not own the stock at all last quarter now does.

new = df[df.action == "NEW"].sort_values("valueUsd", ascending=False)
print(new[["ticker", "filer", "shares", "valueUsd", "filedOn"]].head(20))
Enter fullscreen mode Exit fullscreen mode

Step 5: Which funds hold all of my stocks?

Run several tickers at once and intersect the holder lists:

held = df[df.action != "EXITED"]
counts = held.groupby("filer").ticker.nunique()
print(counts[counts == held.ticker.nunique()].index.tolist()[:20])
Enter fullscreen mode Exit fullscreen mode

Going further

Use it from an AI assistant

https://mcp.apify.com/?tools=smartmoney-data/institutional-ownership-by-ticker
Enter fullscreen mode Exit fullscreen mode

Add it as a remote MCP server in Claude, ChatGPT or Cursor, then ask: "Which funds opened new positions in NVDA last quarter?"

FAQ

Why is the data a quarter behind? Managers have 45 days after each quarter to file, and the SEC then publishes the bulk data set. Every 13F site has the same delay, because it is the same data.

Are short positions included? No. 13F covers long positions, plus puts and calls, which are reported separately here.

What about amended filings? They are applied: a restatement replaces the original report, and a "new holdings" amendment adds to it.

Is this legal to use? Yes. Form 13F filings are public SEC records.


Data is for informational purposes only and is not investment advice.

Top comments (1)

Collapse
 
arhancanli profile image
Arhan Canli •

One thing worth adding to the NEW / EXITED columns: a 13F only shows long positions at quarter end, so "EXITED" can also mean the manager restructured. A fund that starts filing under a new CIK after a reorganisation or merger shows up as a NEW holder in one entity and an EXITED holder in another, with the same shares. Late 13F-HR/A amendments can flip a row the same way. Matching on filer name or a manager-level roll-up before counting 295 "new positions" would tell you how many are real.

Small check on the AAPL summary: 295 + 2,547 + 2,892 + 185 = 5,919 against 6,139 holders. Is the 220 difference the UNCHANGED rows, and are the 185 exits counted in the 6,139? That decides whether "holders" means this quarter or either quarter.

For the other direction (5%+ holders from 13D/13G, plus 13F and Form 4 as MCP tools for an assistant), canli-mcp is free, MIT, no key, runs locally: npx -y canli-mcp, repos at github.com/arhancanli/canlicapital. I built it.