Python Intermediate

Ownership Intelligence

Map who owns a stock by combining institutional holders, beneficial owners, and insider trades into a single ownership report.

Note: The code examples in this tutorial have not yet been verified against the live API. If you encounter issues, please let us know.

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 CIK mode to show a specific institution's full portfolio
  • Support a --list mode to list all tracked institutions ranked by portfolio value

Prerequisites

  • Python 3.8 or later
  • The requests library (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:

Python
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

Shell
$ python ownership.py AAPL

Example output:

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:

Shell
$ python ownership.py --institution 0001067983

Example output:

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

Shell
$ python ownership.py --list

Example output:

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=previous parameter 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}/financials endpoint 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 --csv flag 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.

Ready to build?

Get your free API key and start coding in minutes.

Get Free API Key