Skip to content

Instantly share code, notes, and snippets.

@nrempel
Created December 15, 2025 01:08
Show Gist options
  • Select an option

  • Save nrempel/200d7e21ebee7fdd64b50002c10d1006 to your computer and use it in GitHub Desktop.

Select an option

Save nrempel/200d7e21ebee7fdd64b50002c10d1006 to your computer and use it in GitHub Desktop.
Monitor hedge fund 13F holdings with the Earnings Feed API - Python & JavaScript examples

Track Hedge Fund Holdings with the Earnings Feed API

A practical guide to monitoring institutional investor portfolios using SEC 13F data.

What You'll Learn

  • How to access 13F institutional holdings data
  • Track specific fund managers (Berkshire Hathaway, Bridgewater, etc.)
  • Find all institutional holders of a stock
  • Detect position changes between quarters
  • Build a portfolio tracking system

Background: What is Form 13F?

Institutional investment managers with $100M+ in assets must file Form 13F quarterly, disclosing their U.S. equity holdings. This reveals what hedge funds, mutual funds, pension funds, and other large investors own.

Why it matters: Following "smart money" is a time-tested strategy. When Berkshire Hathaway builds a position, the market pays attention.

The catch: 13F filings are due 45 days after quarter end, so the data is delayed. Use it for research and position building, not day trading.

Getting Started

1. Get an API Key

Sign up at earningsfeed.com and generate an API key. The free tier works for this tutorial.

2. Your First Request

curl -s "https://earningsfeed.com/api/v1/institutional/holdings?limit=5" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .

Understanding the Data

Each holding includes:

Field Description
managerCik SEC identifier for the fund manager
managerName Name of the fund/manager
cusip 9-character security identifier
issuerName Company name
ticker Stock symbol (when available)
value Market value in USD
shares Number of shares held
putCall "Put", "Call", or null (for common stock)
reportPeriodDate Quarter end date (e.g., "2024-09-30")
filedAt When the 13F was filed

Practical Examples

Track a Famous Investor

Find all of Warren Buffett's current holdings:

import requests

API_KEY = "your_api_key_here"

def get_berkshire_holdings():
    """Get Berkshire Hathaway's latest 13F holdings."""

    holdings = []
    cursor = None

    while True:
        params = {
            "managerCik": 1067983,  # Berkshire Hathaway
            "limit": 100
        }
        if cursor:
            params["cursor"] = cursor

        response = requests.get(
            "https://earningsfeed.com/api/v1/institutional/holdings",
            headers={"Authorization": f"Bearer {API_KEY}"},
            params=params
        )

        data = response.json()
        holdings.extend(data["items"])

        if not data["hasMore"]:
            break
        cursor = data["nextCursor"]

    return holdings

holdings = get_berkshire_holdings()

# Sort by value and display
holdings.sort(key=lambda x: x["value"], reverse=True)

print("Berkshire Hathaway Portfolio (Top 20):\n")
total_value = sum(h["value"] for h in holdings)

for h in holdings[:20]:
    pct = (h["value"] / total_value) * 100
    print(f"{h['ticker'] or h['cusip']:6} | {h['issuerName'][:30]:30} | "
          f"${h['value']:>15,} | {pct:5.1f}%")

print(f"\nTotal portfolio value: ${total_value:,}")

Sample output:

Berkshire Hathaway Portfolio (Top 20):

AAPL   | APPLE INC                      |  $91,234,567,000 |  45.2%
BAC    | BANK OF AMERICA CORP           |  $29,876,543,000 |  14.8%
AXP    | AMERICAN EXPRESS CO            |  $21,234,567,000 |  10.5%
KO     | COCA-COLA CO                   |  $18,765,432,000 |   9.3%
...

Find All Holders of a Stock

Who owns Apple?

def get_institutional_holders(ticker, min_value=1_000_000_000):
    """Find all institutional holders of a stock with position > min_value."""

    holders = []
    cursor = None

    while True:
        params = {
            "ticker": ticker,
            "minValue": min_value,
            "limit": 100
        }
        if cursor:
            params["cursor"] = cursor

        response = requests.get(
            "https://earningsfeed.com/api/v1/institutional/holdings",
            headers={"Authorization": f"Bearer {API_KEY}"},
            params=params
        )

        data = response.json()
        holders.extend(data["items"])

        if not data["hasMore"]:
            break
        cursor = data["nextCursor"]

    return holders

holders = get_institutional_holders("AAPL", min_value=5_000_000_000)

# Group by manager and get most recent filing per manager
from collections import defaultdict

latest_by_manager = {}
for h in holders:
    key = h["managerCik"]
    if key not in latest_by_manager or h["reportPeriodDate"] > latest_by_manager[key]["reportPeriodDate"]:
        latest_by_manager[key] = h

# Sort by position size
sorted_holders = sorted(latest_by_manager.values(), key=lambda x: x["value"], reverse=True)

print("Largest Institutional Holders of AAPL:\n")
for h in sorted_holders[:15]:
    print(f"{h['managerName'][:40]:40} | ${h['value']:>15,}")

JavaScript/Node.js Version

const API_KEY = 'your_api_key_here';

async function getBerkshirePortfolio() {
  const params = new URLSearchParams({
    managerCik: '1067983',  // Berkshire Hathaway
    limit: '100'
  });

  const response = await fetch(
    `https://earningsfeed.com/api/v1/institutional/holdings?${params}`,
    {
      headers: { 'Authorization': `Bearer ${API_KEY}` }
    }
  );

  const { items } = await response.json();

  // Sort by value
  items.sort((a, b) => b.value - a.value);

  console.log('Berkshire Hathaway Top Holdings:\n');
  for (const h of items.slice(0, 10)) {
    console.log(`${h.ticker || h.cusip}: $${h.value.toLocaleString()}`);
  }
}

getBerkshirePortfolio();

Detecting Position Changes

The real alpha is in tracking what funds are buying and selling. Compare holdings across quarters:

def compare_quarters(manager_cik, current_quarter, previous_quarter):
    """Compare a fund's holdings between two quarters."""

    def get_holdings_for_quarter(quarter):
        holdings = []
        cursor = None

        while True:
            params = {
                "managerCik": manager_cik,
                "reportPeriod": quarter,
                "limit": 100
            }
            if cursor:
                params["cursor"] = cursor

            response = requests.get(
                "https://earningsfeed.com/api/v1/institutional/holdings",
                headers={"Authorization": f"Bearer {API_KEY}"},
                params=params
            )

            data = response.json()
            holdings.extend(data["items"])

            if not data["hasMore"]:
                break
            cursor = data["nextCursor"]

        return {h["cusip"]: h for h in holdings}

    current = get_holdings_for_quarter(current_quarter)
    previous = get_holdings_for_quarter(previous_quarter)

    # Find changes
    new_positions = []
    increased = []
    decreased = []
    closed = []

    for cusip, holding in current.items():
        if cusip not in previous:
            new_positions.append(holding)
        else:
            prev_shares = previous[cusip]["shares"]
            curr_shares = holding["shares"]
            if curr_shares > prev_shares * 1.1:  # >10% increase
                increased.append({**holding, "prev_shares": prev_shares})
            elif curr_shares < prev_shares * 0.9:  # >10% decrease
                decreased.append({**holding, "prev_shares": prev_shares})

    for cusip, holding in previous.items():
        if cusip not in current:
            closed.append(holding)

    return {
        "new": new_positions,
        "increased": increased,
        "decreased": decreased,
        "closed": closed
    }

# Example: Track Bridgewater's changes
changes = compare_quarters(
    manager_cik=1350694,  # Bridgewater Associates
    current_quarter="2024-09-30",
    previous_quarter="2024-06-30"
)

print("NEW POSITIONS:")
for h in changes["new"][:5]:
    print(f"  {h['ticker'] or h['cusip']}: ${h['value']:,}")

print("\nINCREASED POSITIONS:")
for h in changes["increased"][:5]:
    pct_change = ((h["shares"] - h["prev_shares"]) / h["prev_shares"]) * 100
    print(f"  {h['ticker'] or h['cusip']}: +{pct_change:.1f}% (${h['value']:,})")

print("\nCLOSED POSITIONS:")
for h in changes["closed"][:5]:
    print(f"  {h['ticker'] or h['cusip']}: ${h['value']:,}")

Options Activity

13F filings also include put and call options. Track bearish bets:

def get_put_positions(min_value=10_000_000):
    """Find large put option positions (bearish bets)."""

    response = requests.get(
        "https://earningsfeed.com/api/v1/institutional/holdings",
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={
            "putCall": "put",
            "minValue": min_value,
            "limit": 100
        }
    )

    holdings = response.json()["items"]

    print(f"Large Put Positions (>${min_value/1_000_000:.0f}M):\n")
    for h in holdings:
        print(f"{h['managerName'][:30]:30} | PUT {h['ticker'] or h['cusip']:6} | ${h['value']:>12,}")

get_put_positions()

Building a Watchlist Tracker

Monitor multiple funds and get alerts on changes:

import json
from datetime import datetime
from pathlib import Path

WATCHLIST = {
    1067983: "Berkshire Hathaway",
    1350694: "Bridgewater Associates",
    1336528: "Citadel Advisors",
    1649339: "Tiger Global Management",
    1037389: "Renaissance Technologies",
}

CACHE_FILE = Path("holdings_cache.json")

def load_cache():
    if CACHE_FILE.exists():
        return json.loads(CACHE_FILE.read_text())
    return {}

def save_cache(cache):
    CACHE_FILE.write_text(json.dumps(cache, indent=2))

def get_fund_holdings(cik):
    """Fetch all holdings for a fund."""
    holdings = []
    cursor = None

    while True:
        params = {"managerCik": cik, "limit": 100}
        if cursor:
            params["cursor"] = cursor

        response = requests.get(
            "https://earningsfeed.com/api/v1/institutional/holdings",
            headers={"Authorization": f"Bearer {API_KEY}"},
            params=params
        )

        data = response.json()
        holdings.extend(data["items"])

        if not data["hasMore"]:
            break
        cursor = data["nextCursor"]

    return holdings

def check_for_changes():
    """Check watchlist funds for portfolio changes."""

    cache = load_cache()

    for cik, name in WATCHLIST.items():
        print(f"\nChecking {name}...")

        current = get_fund_holdings(cik)
        current_by_cusip = {h["cusip"]: h for h in current}

        cached_key = str(cik)
        if cached_key in cache:
            previous = cache[cached_key]

            # Find new positions
            for cusip, holding in current_by_cusip.items():
                if cusip not in previous:
                    print(f"  NEW: {holding['ticker'] or cusip} - ${holding['value']:,}")

            # Find closed positions
            for cusip in previous:
                if cusip not in current_by_cusip:
                    print(f"  CLOSED: {cusip}")

        # Update cache
        cache[cached_key] = {h["cusip"]: h["shares"] for h in current}

    save_cache(cache)
    print(f"\nCache updated at {datetime.now()}")

if __name__ == "__main__":
    check_for_changes()

Run this weekly or after each 13F filing deadline (Feb 14, May 15, Aug 14, Nov 14).

Famous Fund CIKs

Quick reference for popular managers:

Manager CIK
Berkshire Hathaway 1067983
Bridgewater Associates 1350694
Citadel Advisors 1336528
Renaissance Technologies 1037389
Two Sigma 1179392
Tiger Global 1649339
Pershing Square 1336528
Appaloosa Management 929855
Greenlight Capital 1079114
Third Point 1040273

Find any manager by searching:

def find_manager(name_query):
    """Search for a fund manager by name."""
    response = requests.get(
        "https://earningsfeed.com/api/v1/companies/search",
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={
            "q": name_query,
            "limit": 10
        }
    )

    for company in response.json()["items"]:
        print(f"{company['name']}: CIK {company['cik']}")

find_manager("Soros")

Filing Schedule

13F filings are due 45 days after quarter end:

Quarter Period End Filing Deadline Data Available
Q1 March 31 May 15 Mid-May
Q2 June 30 August 14 Mid-August
Q3 September 30 November 14 Mid-November
Q4 December 31 February 14 Mid-February

Pro tip: Most funds file close to the deadline. Set up monitoring to catch filings as they come in.

Rate Limits

Tier Rate Limit Monthly Cost
Free 15 req/min $0
Pro 60 req/min $15
Trader 300 req/min $75

For bulk historical analysis, use the maximum limit=100 and implement pagination to minimize requests.

API Reference

Full documentation: earningsfeed.com/api/docs

Endpoints used in this tutorial:

  • GET /api/v1/institutional/holdings - List 13F holdings
  • GET /api/v1/companies/search - Find fund manager CIK
  • GET /api/v1/filings?forms=13F-HR - Track when new 13Fs are filed

What's Next?

  • Combine with insider data: See if insiders are buying when institutions are selling
  • Build a dashboard: Visualize portfolio changes over time
  • Backtest strategies: Test if following specific managers generates alpha
  • Track sector concentration: Monitor how funds rotate between sectors

Built with the Earnings Feed API. Questions? support@earningsfeed.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment