# SEC 13F Holdings Diff — Quarterly Fund Position Changes Monitor (`datadeltas/sec-13f-diff`) Actor

Quarter-over-quarter position changes for any institutional fund, straight from SEC 13F-HR information tables: new positions, exits, buys and sells with share counts and USD values before and after.

- **URL**: https://apify.com/datadeltas/sec-13f-diff.md
- **Developed by:** [Daniel Matthee](https://apify.com/datadeltas) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 position change detecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## SEC 13F Holdings Diff — Quarterly Fund Position Changes

**What changed in a fund's portfolio last quarter, as a clean feed of rows.**
Point it at any institutional manager — Berkshire Hathaway, Bridgewater,
Citadel, Duquesne, a boutique you follow — and it diffs their two most recent
Form 13F-HR information tables straight from SEC EDGAR, emitting one row per
portfolio action: **new position, exited, increased, decreased**, with share
counts and USD values on both sides and a ticker attached.

No API key. No third-party data vendor. Just EDGAR, parsed properly.

> **Read this first: 13F is quarterly, and late.** US managers file Form 13F
> within **45 days of quarter end**, so a "new position" you see here may
> already be up to four months old. This is a portfolio-change feed, not a
> trade tape, and nothing can make 13F timely — the delay is the regulation.
> Run this on a **weekly schedule**: it costs almost nothing on the ~51 weeks a
> fund has nothing new, and fires the moment their filing lands (mid-February,
> mid-May, mid-August, mid-November).

### Example output

One real row, from Berkshire Hathaway's Q2 2026 filing:

```json
{
  "cik": "0001067983",
  "fundName": "BERKSHIRE HATHAWAY INC",
  "accessionNumber": "0001193125-26-352200",
  "reportPeriod": "2026-06-30",
  "previousAccessionNumber": "0001193125-26-226661",
  "previousReportPeriod": "2026-03-31",
  "changeType": "increased",
  "issuerName": "ALPHABET INC",
  "titleOfClass": "CAP STK CL A",
  "cusip": "02079K305",
  "ticker": "GOOGL",
  "sharesBefore": 54249798,
  "sharesAfter": 78791167,
  "sharesChange": 24541369,
  "amountType": "SH",
  "valueBefore": 15600071913,
  "valueAfter": 28157599351,
  "valueChange": 12557527438,
  "putCall": null,
  "filedAt": "2026-08-14",
  "filingUrl": "https://www.sec.gov/Archives/edgar/data/1067983/000119312526352200/0001193125-26-352200-index.htm",
  "detectedAt": "2026-08-31T10:59:19Z"
}
```

Every row names both filings it was derived from, so any number in it can be
checked against the source in two clicks.

#### Fields

| Field | Meaning |
|---|---|
| `cik` | The manager's SEC CIK, zero-padded to 10 digits |
| `fundName` | Manager name as EDGAR publishes it |
| `accessionNumber` / `reportPeriod` | The newer filing and the quarter it covers |
| `previousAccessionNumber` / `previousReportPeriod` | The filing it was diffed against (null on an opening snapshot) |
| `changeType` | `new-position`, `exited`, `increased` or `decreased` |
| `issuerName` / `titleOfClass` / `cusip` | The security, as the filer reported it |
| `ticker` | Resolved from SEC's public data; `null` when unresolvable, never guessed |
| `sharesBefore` / `sharesAfter` / `sharesChange` | Position size on each side (null on the absent side) |
| `amountType` | `SH` for a share count, `PRN` for a principal amount |
| `valueBefore` / `valueAfter` / `valueChange` | Position value in **plain USD** |
| `putCall` | `Put`, `Call`, or null for the underlying security |
| `filedAt` / `filingUrl` | Filing date and EDGAR filing-index page |
| `detectedAt` | When this monitor first saw the change |

### Input options

| Input | Default | What it does |
|---|---|---|
| `ciks` | — | CIK numbers of the managers to watch, any zero-padding. The exact, unambiguous way to name a fund. |
| `fundNames` | — | Manager names resolved via EDGAR company search. Every match is logged with its CIK and real name, and all matches are processed. |
| `changeTypes` | all four | `new-position`, `exited`, `increased`, `decreased`. |
| `minPositionValue` | 0 | USD floor on the larger of the two values — cuts a fund's long tail. |
| `maxItems` | 1000 | Cap per run; biggest positions first, the rest carry over to the next run. |
| `resolveTickers` | true | Adds tickers from SEC's public data. Off = faster run, all tickers null. |
| `emitInitialSnapshot` | false | Only affects a fund with exactly one 13F ever filed (nothing to diff against). |
| `userAgent` | placeholder | **Set this to your own contact.** SEC's fair-access policy requires it. |

At least one of `ciks` / `fundNames` is required. There is no whole-market
default: a 13F diff is per-manager, and thousands of managers file.

### Three ways buyers use it

**"Tell me what Berkshire bought and sold this quarter, the day it's public."**
Put a handful of famous CIKs in `ciks`, schedule it weekly, and pipe the
dataset into Slack or a spreadsheet. You get the story before the write-ups do,
with the filing URL to cite.

**"Track 40 managers and show me only the moves that matter."** Set
`minPositionValue` to your threshold and `changeTypes` to
`new-position, exited` for a conviction feed, or leave all four on for a full
delta table. Nothing repeats between runs, so the dataset appends cleanly into
a warehouse keyed on `accessionNumber` + `cusip`.

**"Build a 13F product without paying a data vendor."** Every row is derived
from the primary source with the traps handled (see below), so you can join it
to prices on `ticker` or to any other EDGAR dataset on `accessionNumber` /
`cik` without a reconciliation project.

### What makes this correct (and why that is not automatic)

These are all real behaviours of real filings, verified live, that a
straightforward 13F parser gets wrong:

- **One position is reported on many rows.** Filers split a holding across
  their sub-managers. Berkshire reports its Ally Financial stake on **six**
  separate rows; one large quant fund filed **16,127 rows for 7,166 unique
  CUSIPs**. Positions here are aggregated per `(cusip, amountType, putCall)`
  before diffing, so you see the whole holding, not a fragment of it.
- **A value change is not a portfolio change.** If a fund holds exactly the
  same share count and the price moved, that is the market, not the manager —
  and emitting it would bury every real trade. Change types are decided on
  **share counts**. Berkshire's Apple stake was flat at 227,917,808 shares
  across Q1 and Q2 2026 while its reported value rose $8bn: no row.
  `valueBefore`/`valueAfter` are still on every row you do get, so you can
  compute price effects yourself.
- **The value column is sometimes thousands, sometimes dollars.** SEC required
  whole dollars from 3 January 2023, but **filers still report thousands** —
  measured on a real 2026 filing showing 336,300 Alphabet shares at a value of
  120,184. The scale is detected per filing, so `valueBefore`/`valueAfter` are
  always plain USD.
- **The holdings document has no fixed filename.** Real filings call it
  `infotable.xml`, `form13fInfoTable.xml`, `form13f_20260630.xml`, `56757.xml`
  or `renaissance13Fq22026_holding.xml`. It is discovered from the filing
  directory, not guessed.
- **Puts, calls and the underlying are separate positions**, and `PRN`
  principal amounts never get mixed into `SH` share counts.
- **Same-quarter double filings** are collapsed before the diff, so a fund is
  never diffed against itself and handed you an all-zero quarter.

### Update frequency & how the monitor behaves

- Run it **weekly**. On a week with no new filing, the run does one small JSON
  fetch per fund, emits nothing, and exits successfully — a zero-row run is a
  **successful** run, and the normal state of things outside filing season.
- State is the last-diffed accession number **per fund**, so adding a fund to
  the watch-list never re-emits another fund's history, and a fund that fails
  one run retries on the next.
- The **first run** on a fund with two or more filings gives you a real
  quarter-over-quarter diff immediately. A fund with exactly one filing ever
  has nothing to diff — off by default, opt in with `emitInitialSnapshot`.
- `maxItems` overflow carries over losslessly: a 1,314-change three-fund
  backfill split 1,000 / 314 across two runs with zero duplicates.

### Honest limitations

- **Quarterly and up to 45 days late.** Restated above because it matters.
- **13F covers only 13(f)-listed long positions.** No short positions, no cash,
  no bonds, no non-US-listed holdings, no derivatives beyond the listed puts
  and calls. A 13F is not a fund's whole book and never was.
- **Amendments (13F-HR/A) are ignored** by default, so a restated quarter does
  not arrive looking like fresh trading.
- **`ticker` is best-effort.** It comes from SEC's own public data at roughly
  98% coverage on listed US equities; unlisted issuers, foreign lines and
  obscure classes come back `null`. It is never inferred from the issuer name.
- **Numbers are the filer's own.** Share counts and values are reported as
  filed, aggregated but never recomputed or price-adjusted. If a manager filed
  a mistake, you get the mistake — and the `filingUrl` to prove it.
- **Fund names can be ambiguous.** A name matching several 13F filers is
  logged with every candidate CIK and real name, and all of them are processed,
  because fund families genuinely file under more than one CIK. Use `ciks` when
  you need certainty.

### Reliability

- Talks only to `data.sec.gov` and `www.sec.gov`. No proxies, no third-party
  data source, no login.
- Identifies itself to SEC with the `userAgent` you set, paces requests well
  under SEC's limit, and backs off on rate limits. A rejected User-Agent fails
  the run with a message telling you exactly what to set.
- A malformed row logs a clear warning naming the missing field and is skipped;
  the rest of the filing still parses. A filing that parses to zero rows fails
  that fund rather than reporting the whole portfolio as sold — the run still
  completes for your other funds.
- If every watched fund fails, the run **fails** rather than reporting a quiet
  quarter.
- Schema checked against live filings before each release.

### Pricing (pay per event)

| Event | Price (USD) | Charged |
|---|---|---|
| Actor start | $0.005 | Flat, per run |
| Position change detected | $0.005 / $0.003 / $0.002 | Per emitted change — first 500 in a run, then 501-2000, then beyond |
| Fund checked | $0.002 | Per watched fund whose latest filing was checked, emitted or not |

A 20-fund weekly schedule costs about **$0.045 per quiet run** — roughly
$2.30 a year of quiet weeks — and pays out four real quarterly diffs. The
volume tiering exists so that one quant fund's 1,200-change quarter does not
cost more than your subscription.

### Changelog

**0.2** — `maxItems` is now shared fairly between the funds in a run, so a
small cap no longer fills up with the first fund you listed and leaves the
others silent until next run. Anything over the cap still carries over and is
emitted next run, once, in value order.

**0.1** — First release. Per-fund quarter-over-quarter diff of 13F-HR
information tables; four change types on share-count semantics; multi-row
position aggregation; per-filing value-scale detection; keyless CUSIP→ticker
resolution; fund lookup by CIK or name; `changeTypes` / `minPositionValue` /
`maxItems` filters; per-CIK accession state for weekly monitoring.

# Actor input Schema

## `ciks` (type: `array`):

SEC CIK numbers of the managers you want, any zero-padding (e.g. 0001067983 for Berkshire Hathaway, 1350694 for Bridgewater). This is the exact, unambiguous way to name a fund. Combine with fundNames if you like.

## `fundNames` (type: `array`):

Manager names, resolved against EDGAR's company search (e.g. 'Bridgewater Associates'). A name that matches several 13F filers is ambiguous — every match is logged with its CIK and real name, and ALL of them are processed, because fund families genuinely file under more than one CIK. Prefer ciks when you know them.

## `changeTypes` (type: `array`):

Which portfolio actions count. Default: all four. A position whose value moved but whose share count did not is a price move, not a portfolio action, and is never emitted.

## `minPositionValue` (type: `integer`):

Optional floor applied to the larger of the position's before/after USD value — use it to cut a fund's long tail and keep only meaningful moves. 0 = no floor.

## `maxItems` (type: `integer`):

Cap on changes emitted in a single run (cost control). Biggest positions come first; anything over the cap carries over to the next run.

## `resolveTickers` (type: `boolean`):

Adds a ticker to each row using SEC's own public fails-to-deliver data (~98% coverage on listed US equities, no API key). Costs one small download per run. Turn off for the fastest possible run; unresolved CUSIPs are always null, never guessed.

## `emitInitialSnapshot` (type: `boolean`):

Only applies when a fund has exactly ONE 13F-HR on file and there is nothing to diff against. Off (default): the filing is recorded and nothing is emitted. On: every holding is emitted as new-position with null before-values. Funds with two or more filings always produce a real quarter-over-quarter diff on the first run.

## `userAgent` (type: `string`):

SEC's fair-access policy requires a User-Agent identifying who you are, e.g. 'my-company research you@example.com'. Please set your own contact here.

## Actor input object example

```json
{
  "ciks": [
    "0001067983"
  ],
  "changeTypes": [
    "new-position",
    "exited",
    "increased",
    "decreased"
  ],
  "minPositionValue": 0,
  "maxItems": 1000,
  "resolveTickers": true,
  "emitInitialSnapshot": false,
  "userAgent": "apify-actor-sec-13f-diff daniel@apexwebstudio.co.nz"
}
```

# Actor output Schema

## `records` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "ciks": [
        "0001067983"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datadeltas/sec-13f-diff").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "ciks": ["0001067983"] }

# Run the Actor and wait for it to finish
run = client.actor("datadeltas/sec-13f-diff").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "ciks": [
    "0001067983"
  ]
}' |
apify call datadeltas/sec-13f-diff --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datadeltas/sec-13f-diff"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/tMgpaGWmu1pBZjv3t/builds/qmwhHlSmhbeFJih7S/openapi.json
