"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
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")
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))
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"])
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))
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])
Going further
- One fund's whole portfolio instead of one stock's holders: Hedge Fund 13F Tracker.
- What the best-known funds agree on: Hedge Fund 13F Consensus.
- Faster signals than 13F: insiders file within two days (Insider Trades), and 5%+ activist stakes within ten (13D/13G).
Use it from an AI assistant
https://mcp.apify.com/?tools=smartmoney-data/institutional-ownership-by-ticker
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)
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.