Skip to content

13F Holdings: Parse SEC Institutional Portfolio Filings with Python

See what the big funds are buying. SEC 13F filings disclose the equity holdings of institutional managers with over $100M in assets -- every quarter, publicly available. EdgarTools parses these filings into structured Python objects so you can analyze portfolios in a few lines of code.

from edgar import get_filings

filings = get_filings(form="13F-HR")
report = filings[0].obj()
report

13F holdings report parsed with Python edgartools

Three lines to get a fully parsed holdings report with management company, total portfolio value, and every position.


Access Holdings Data

The .holdings property returns a DataFrame with one row per security, aggregated across managers, sorted by value:

report.holdings
Column What it is
Issuer Company name ("APPLE INC")
Ticker Resolved ticker symbol ("AAPL")
Value Market value in thousands of dollars
SharesPrnAmount Share count or principal amount
Cusip 9-character CUSIP
Type "Shares" or "Principal"
PutCall "PUT", "CALL", or empty

Values are in thousands -- the SEC's reporting unit. Value of 135,364 means $135.4 million.


Compare 13F Holdings Quarter-over-Quarter

One call to see what changed:

report.compare_holdings()

Python 13F holdings quarter-over-quarter comparison

Every position gets a status: NEW, CLOSED, INCREASED, DECREASED, or UNCHANGED. Results are sorted by absolute value change so the biggest moves appear first.

comparison = report.compare_holdings()

# Dig into the data
df = comparison.data
new_buys = df[df['Status'] == 'NEW']
exits = df[df['Status'] == 'CLOSED']

The comparison DataFrame includes Shares, PrevShares, ShareChange, ShareChangePct, Value, PrevValue, ValueChange, ValueChangePct, and Status.


See how positions evolve across quarters with sparkline visualizations:

report.holding_history(periods=4)

13F holdings history with sparkline trends in Python

Each row shows share counts per quarter and a Unicode sparkline (▁▂▃▅▇) so you can spot trends at a glance.

history = report.holding_history(periods=4)
df = history.data  # Full DataFrame with one column per quarter

Using View Objects in Your Own App

holdings_view(), compare_holdings(), and holding_history() all return view objects that render in the terminal via Rich but also support iteration, indexing, and access to the underlying DataFrame. This makes them useful for building your own dashboards, reports, or exports.

All three views share the same interface:

view = report.holdings_view()
comparison = report.compare_holdings()
history = report.holding_history(periods=4)

# Iterate rows as dicts
for row in view:
    print(row['Ticker'], row['Value'])

# Index a single row (returns dict)
view[0]

# Slice (returns DataFrame)
view[:10]

# Length
len(view)

# Access the full DataFrame directly
view.data
comparison.data
history.data

Each view also carries metadata useful for rendering headers:

View Metadata
HoldingsView .display_limit
HoldingsComparison .current_period, .previous_period, .manager_name
HoldingsHistory .periods (list of quarter dates), .manager_name

Look Up a Specific Fund

from edgar import Company

berkshire = Company("BRK.A")
filing = berkshire.get_filings(form="13F-HR").latest(1)
report = filing.obj()

print(report.management_company_name)  # "Berkshire Hathaway Inc"
print(f"${report.total_value:,}K across {report.total_holdings} holdings")

Common Analysis Patterns

Portfolio concentration

h = report.holdings
total = h['Value'].sum()
h['Weight'] = (h['Value'] / total * 100).round(2)
h[['Ticker', 'Issuer', 'Value', 'Weight']].head(10)

Options positions

report.holdings.query("PutCall in ['PUT', 'CALL']")

Previous quarter's full report

previous = report.previous_holding_report()  # Returns a ThirteenF or None
previous.holdings

Multi-Manager Filings

Large institutions (Bank of America, State Street) file consolidated 13F reports. The holdings property automatically aggregates across all managers. If you need per-manager detail, use infotable instead:

report.infotable   # Disaggregated: one row per manager-security pair
report.holdings    # Aggregated: one row per security (recommended)

# Example: Berkshire Hathaway
# infotable: ~121 rows (3 managers x ~40 securities)
# holdings:   ~40 rows (aggregated by CUSIP)

# See who the other managers are
for mgr in report.other_managers:
    print(f"{mgr.name} (CIK: {mgr.cik})")

Metadata Quick Reference

Property Returns Example
management_company_name Company that filed "Berkshire Hathaway Inc"
report_period Quarter end date "2024-03-31"
filing_date Date filed "2024-05-15"
total_value Portfolio value ($000s) Decimal('313218000')
total_holdings Number of positions 40
filing_signer_name Who signed "Marc D. Hamburg"
filing_signer_title Signer's title "Senior Vice President"
form Form type "13F-HR"
accession_number SEC accession no. "0000950123-24-007092"
has_infotable() Has holdings data? True for 13F-HR, False for 13F-NT

Methods Quick Reference

Call Returns What it does
report.holdings DataFrame Aggregated holdings, one row per security
report.infotable DataFrame Raw holdings, disaggregated by manager
report.holdings_view() HoldingsView Rich-renderable, iterable holdings
report.compare_holdings() HoldingsComparison Quarter-over-quarter changes with status labels
report.holding_history(periods=4) HoldingsHistory Multi-quarter share trends with sparklines
report.previous_holding_report() ThirteenF Previous quarter's 13F object
report.other_managers list[OtherManager] Affiliated managers in consolidated filings
report.get_portfolio_managers() list[dict] Curated lookup of known portfolio managers

Things to Know

Values are in thousands. The SEC requires 13F values in $000s. A Value of 135,364 is $135.4 million.

holdings vs infotable. Use holdings (aggregated by CUSIP) for portfolio analysis. Use infotable only when you need per-manager detail in multi-manager filings.

Ticker resolution. Tickers are resolved from CUSIPs. Most resolve correctly, but delisted or obscure securities may show as blank.

Pre-2013 filings use TXT format. Before Q3 2013, 13F information tables were filed as fixed-width TXT rather than XML. EdgarTools parses both formats transparently -- filing.obj() returns the same ThirteenF object either way. The TXT parser handles spaced CUSIPs (025816 10 9), multi-line company names, multi-table paginated filings, and continuation rows for multi-manager assignments. Coverage across the 2005-2013 TXT era is ~93% of filings. The remaining ~7% are unusual formats (HTML tables, tab-delimited, non-standard layouts) that may return an empty infotable.

13F-NT means no holdings. Notice filings indicate the manager had nothing to report. has_infotable() returns False.

Report period vs filing date. The report_period is the quarter end. The filing_date can be up to 45 days later. Some managers file multiple historical periods on the same day.