Ownership Intelligence
Map who owns a stock by combining institutional holders, beneficial owners, and insider trades into a single ownership report.
In this tutorial
What You'll Build
A command-line Python script that builds a comprehensive ownership map for any US public company. Instead of checking institutional holders, beneficial owners, and insider trades separately, this tool pulls all three in parallel and prints a unified report. One command gives you the full picture of who owns a stock and what they have been doing with their position.
The script will:
- Fetch 13F institutional holders, 13D/13G beneficial owners, and recent insider trades in parallel
- Print a three-section ownership report: institutions, major shareholders, and insider activity
- Flag activist investors (13D filers) vs passive holders (13G filers)
- Support an
--institution CIKmode to show a specific institution's full portfolio - Support a
--listmode to list all tracked institutions ranked by portfolio value
Prerequisites
- Python 3.8 or later
- The
requestslibrary (pip install requests) - A stockdata.dev API key — get one free
The free tier includes 1,000 API calls per month. A standard ownership report uses 3 API calls (one per data source). The --institution and --list modes each use 1 call.
The Code
Create a file called ownership.py and paste the following:
import sys import argparse from concurrent.futures import ThreadPoolExecutor import requests API_KEY = "your_api_key_here" BASE_URL = "https://api.stockdata.dev/v1" HEADERS = {"X-API-Key": API_KEY} def fmt_value(val): """Format a dollar value for display.""" if val >= 1e12: return f"${val / 1e12:.1f}T" if val >= 1e9: return f"${val / 1e9:.1f}B" if val >= 1e6: return f"${val / 1e6:.1f}M" return f"${val:,.0f}" def api_get(path, params=None): """Make a GET request to the API and return JSON.""" resp = requests.get(f"{BASE_URL}{path}", headers=HEADERS, params=params) if resp.status_code != 200: return None return resp.json() def fetch_ownership_data(ticker): """Fetch all three data sources in parallel.""" with ThreadPoolExecutor(max_workers=3) as pool: f_inst = pool.submit(api_get, f"/institutions/{ticker}") f_owners = pool.submit(api_get, f"/ownership/{ticker}", {"days": 365}) f_insider = pool.submit(api_get, f"/insider-trades/{ticker}", {"days": 90}) return f_inst.result(), f_owners.result(), f_insider.result() def print_institutions(data): """Print 13F institutional holders.""" print("\n[1] Institutional Holders (13F)") print("=" * 70) holders = data.get("holders", []) if data else [] if not holders: print(" No institutional holders found.") return holders.sort(key=lambda h: h.get("value", 0), reverse=True) print(f" {'Institution':<32}{'Shares':>14}{'Value':>14}{'Filed':>10}") print(" " + "-" * 68) for h in holders[:10]: name = h["institution_name"][:30] shares = f"{h['shares']:>,}" value = fmt_value(h["value"]) filed = h.get("filing_date", "")[:10] print(f" {name:<32}{shares:>14}{value:>14}{filed:>10}") print(f"\n Total institutions: {len(holders)}") def print_beneficial_owners(data): """Print 13D/13G beneficial owners.""" print("\n[2] Major Shareholders (13D/13G) - 5%+ Owners") print("=" * 70) owners = data.get("ownership", []) if data else [] if not owners: print(" No 13D/13G filings found in the last 365 days.") return print(f" {'Owner':<28}{'% Owned':>9}{'Shares':>14}{'Type':>7}{'Filed':>12}") print(" " + "-" * 68) for o in owners: name = o["reporting_person_name"][:26] pct = f"{o['percent_of_class']:.1f}%" shares = f"{o['aggregate_shares']:>,}" schedule = o.get("schedule_type", "") if schedule == "13D": tag = "ACTV" # Activist elif schedule == "13G": tag = "PASV" # Passive else: tag = schedule filed = o.get("filing_date", "")[:10] print(f" {name:<28}{pct:>9}{shares:>14}{tag:>7}{filed:>12}") def print_insider_trades(data): """Print recent insider trades.""" print("\n[3] Recent Insider Activity (Last 90 Days)") print("=" * 70) trades = data.get("trades", []) if data else [] if not trades: print(" No insider trades in the last 90 days.") return print(f" {'Date':<12}{'Insider':<22}{'Title':<16}{'Action':>8}{'Value':>12}") print(" " + "-" * 68) buys = 0 sells = 0 for t in trades: date = t.get("transaction_date", "")[:10] name = t["owner_name"].title()[:20] title = t.get("owner_title", "")[:15] action = t.get("action", "unknown") value = fmt_value(t.get("value", 0)) if action == "buy": buys += 1 label = "BUY" elif action == "sale": sells += 1 label = "SELL" else: label = action.upper() print(f" {date:<12}{name:<22}{title:<16}{label:>8}{value:>12}") print(f"\n Summary: {buys} buys, {sells} sells") def print_institution_portfolio(cik): """Show a specific institution's full portfolio.""" data = api_get(f"/institutions/portfolio/{cik}") if not data: print(f"Error: Could not fetch portfolio for CIK {cik}") return inst = data.get("institution", {}) holdings = data.get("holdings", []) print(f"\nPortfolio: {inst.get('name', cik)}") print(f"CIK: {inst.get('cik', cik)}") print(f"Total Value: {fmt_value(inst.get('total_value', 0))}") print("=" * 65) print(f" {'#':<5}{'Ticker':<10}{'Company':<25}{'Shares':>12}{'Value':>13}") print(" " + "-" * 63) for i, h in enumerate(holdings[:20], 1): ticker = h.get("ticker", "?") company = h.get("company_name", "")[:23] shares = f"{h.get('shares', 0):>,}" value = fmt_value(h.get("value", 0)) print(f" {i:<5}{ticker:<10}{company:<25}{shares:>12}{value:>13}") print(f"\n Showing top 20 of {data.get('count', len(holdings))} holdings") def print_institution_list(): """List all tracked institutions ranked by portfolio value.""" data = api_get("/institutions") if not data: print("Error: Could not fetch institution list.") return institutions = data.get("institutions", []) print(f"\nTracked Institutions ({len(institutions)} total)") print("=" * 70) print(f" {'#':<5}{'Institution':<32}{'CIK':<14}{'Portfolio':>14}") print(" " + "-" * 63) for i, inst in enumerate(institutions, 1): name = inst["institution_name"][:30] cik = inst["institution_cik"] value = fmt_value(inst["total_value"]) print(f" {i:<5}{name:<32}{cik:<14}{value:>14}") def main(): parser = argparse.ArgumentParser( description="Ownership intelligence: map who owns a stock" ) parser.add_argument("ticker", nargs="?", help="Stock ticker symbol") parser.add_argument( "--institution", metavar="CIK", help="Show full portfolio for an institution by CIK", ) parser.add_argument( "--list", action="store_true", help="List all tracked institutions", ) args = parser.parse_args() if args.list: print_institution_list() return if args.institution: print_institution_portfolio(args.institution) return if not args.ticker: parser.print_help() sys.exit(1) ticker = args.ticker.upper() print(f"Ownership Report: {ticker}") print("Fetching data from 3 sources...") institutions, owners, insiders = fetch_ownership_data(ticker) print_institutions(institutions) print_beneficial_owners(owners) print_insider_trades(insiders) print("\n" + "=" * 70) print("End of ownership report.") if __name__ == "__main__": main()
Replace "your_api_key_here" with your actual API key. For production scripts, use an environment variable: API_KEY = os.environ["STOCKDATA_API_KEY"]
How It Works
The script pulls from three distinct SEC filing types, each revealing a different layer of ownership. Understanding the differences is key to interpreting the report.
13F: Institutional Holdings
Every investment manager with $100 million or more in qualifying assets must file Form 13F quarterly. These filings show every equity position the institution holds, including share count and market value. The biggest names -- Vanguard, BlackRock, Berkshire Hathaway -- all file 13Fs. This is how you see what the largest pools of money are holding.
The /v1/institutions/{ticker} endpoint returns the 13F holders for a given stock, sorted by value.
13D vs 13G: The Critical Distinction
13D (Activist) is filed when someone acquires 5%+ of a company and intends to influence it -- pushing for board seats, M&A, restructuring, or strategic changes. A new 13D filing is often a major catalyst for the stock price.
13G (Passive) is filed when someone acquires 5%+ but has no intention of influencing the company. Index funds and large institutional managers typically file 13Gs. These are routine and rarely move the stock.
The difference matters enormously. A 13D from an activist hedge fund can mean a proxy fight is coming. A 13G from Vanguard just means the stock is in an index. The script labels these as ACTV and PASV so you can tell at a glance.
The /v1/ownership/{ticker} endpoint returns Schedule 13D and 13G filings, including the percent of shares owned and the schedule type.
Insider Trades: Officers and Directors
Corporate insiders -- CEOs, CFOs, board members -- must report their trades via Form 4. Insider buying is widely considered a bullish signal because it means someone with deep knowledge of the company is spending their own money on its stock. Insider selling is harder to interpret: executives sell for many reasons (taxes, diversification, personal expenses), so a sale alone is not necessarily bearish.
The /v1/insider-trades/{ticker} endpoint returns recent Form 4 filings with the insider's name, title, transaction date, and dollar value.
Parallel Fetching
The script uses Python's ThreadPoolExecutor to fetch all three endpoints at the same time. Since each API call is an independent network request, running them in parallel cuts the total wait time to roughly the duration of the slowest single call instead of the sum of all three.
Running It
Full ownership report
$ python ownership.py AAPL
Example output:
Ownership Report: AAPL Fetching data from 3 sources... [1] Institutional Holders (13F) ====================================================================== Institution Shares Value Filed -------------------------------------------------------------------- Vanguard Group Inc 1,400,000,000 $300.0B 2026-02-14 BlackRock Inc 1,050,000,000 $225.0B 2026-02-13 Berkshire Hathaway Inc 400,000,000 $85.8B 2026-02-14 State Street Corp 620,000,000 $133.0B 2026-02-12 FMR LLC 350,000,000 $75.1B 2026-02-10 Total institutions: 5 [2] Major Shareholders (13D/13G) - 5%+ Owners ====================================================================== Owner % Owned Shares Type Filed -------------------------------------------------------------------- Vanguard Group Inc 7.8% 1,400,000,000 PASV 2026-01-20 BlackRock Inc 5.9% 1,050,000,000 PASV 2026-01-18 [3] Recent Insider Activity (Last 90 Days) ====================================================================== Date Insider Title Action Value -------------------------------------------------------------------- 2026-01-15 Cook Timothy D Chief Executive SELL $11.4M 2026-01-10 Williams Jeffrey E Chief Financial SELL $4.5M 2025-12-20 O'Brien Deirdre SVP Retail BUY $1.2M Summary: 1 buys, 2 sells ====================================================================== End of ownership report.
View an institution's full portfolio
Use the CIK from the institutional holders section to see everything that institution holds:
$ python ownership.py --institution 0001067983
Example output:
Portfolio: Berkshire Hathaway Inc CIK: 0001067983 Total Value: $993.8B ================================================================= # Ticker Company Shares Value --------------------------------------------------------------- 1 AAPL Apple Inc. 300,000,000 $68.0B 2 BAC Bank of America Corp 1,032,852,006 $46.5B 3 AXP American Express Co 151,610,700 $45.2B 4 KO Coca-Cola Co 400,000,000 $28.7B 5 CVX Chevron Corp 118,610,534 $18.5B Showing top 20 of 117 holdings
List all tracked institutions
$ python ownership.py --list
Example output:
Tracked Institutions (35 total) ====================================================================== # Institution CIK Portfolio --------------------------------------------------------------- 1 Vanguard Group Inc 0000102909 $19.6T 2 BlackRock Inc 0001364742 $15.2T 3 State Street Corp 0000093751 $7.8T 4 FMR LLC 0000315066 $5.1T 5 Berkshire Hathaway Inc 0001067983 $993.8B
Reading the Signals
Each section of the report tells a different story. The real value comes from reading them together.
Institutions accumulating + insiders buying
This is the strongest bullish combination. When large institutions are increasing their 13F positions and corporate insiders are simultaneously buying with their own money, both groups with deep resources and knowledge are betting on the stock going up. Look for this pattern in the report: growing institutional share counts in Section 1 and BUY entries from C-suite executives in Section 3.
New 13D filing appears
A fresh 13D in Section 2 with the ACTV tag means an activist investor has taken a 5%+ stake and intends to push for changes. This is often the start of a significant price move. Activist campaigns can lead to board shakeups, strategic reviews, spin-offs, or buyouts. Pay attention to who the filer is -- well-known activists like Carl Icahn or Elliott Management have track records you can research.
Institutions holding while insiders sell
This is common and usually benign. Executives regularly sell shares for personal financial planning, tax obligations, or diversification. If institutional holders are steady or growing while insiders are selling modest amounts, the selling is likely routine. Worry only if insider selling is unusually large, comes from multiple executives at once, or coincides with institutions reducing their positions.
Institutions exiting + no insider buying
When major institutions are reducing or closing their positions and no insiders are stepping in to buy, that is a warning sign. The smart money is leaving and the people who know the company best are not buying the dip. This does not guarantee the stock will fall, but it removes two important sources of support.
Ownership data is always delayed. 13F filings are due 45 days after the quarter ends, and insider trades appear days after the transaction. Use these signals for research, not as real-time trading triggers.
Enhancements
Here are several ways to extend the ownership intelligence tool:
- Quarter-over-quarter tracking — Fetch the previous quarter's 13F data using the
period=previousparameter and compare share counts. Flag institutions that increased or decreased their positions by more than 10%. See the Institutional Holdings Tracker tutorial for a comparison pattern you can adapt. - 13D alert system — Run the script on a daily schedule and compare the list of 13D filers against the previous run. Send an email or Slack message when a new activist filing appears. New 13D filings are among the most tradeable SEC signals.
- Combine with financials — Use the
/v1/company/{ticker}/financialsendpoint to add revenue growth and profit margins to the report. Institutions accumulating a stock with improving fundamentals is a stronger signal than either data point alone. - Ownership concentration score — Calculate what percentage of outstanding shares the top 10 institutions hold. High concentration means a few large holders control the float, which can lead to sharp price moves if any of them sell.
- Multi-ticker comparison — Accept multiple tickers and build a matrix showing which institutions appear in multiple companies. Shared holders across a sector can reveal thematic bets by large funds.
- CSV export — Add a
--csvflag to write each section to a separate CSV file for analysis in a spreadsheet or data tool.
Ownership data is just one lens. Combine it with financial analysis (see the Financial Statement Analyzer tutorial) and corporate events (see the Corporate Event Monitor tutorial) for the most complete picture of a company.