ETF holdings history API

View as Markdown What changed

How did an ETF's holdings change from one file to the next? GET /api/v1/history/{ticker}.json gives a fund's history from the first issuer file the archive holds: one row per file date with its position count and top 10 weight, and one step per new file with the lines that entered, the lines that exited and the weights that moved by 0.05 points or more.

Live example

The request: GET /api/v1/history/spy.json

SPY on this build: 28 files, from to . The latest step in which lines entered or exited, from the file of to that of :

Part of the answer, data through 2026-10-12
{
  "ticker": "SPY",
  "first": "2026-09-02",
  "last": "2026-10-12",
  "file_dates": 28,
  "cadence": "daily",
  "changes": [
    {
      "date": "2026-10-06",
      "previous": "2026-10-05",
      "entered": [["SKYD", 0.0077]],
      "exited": [["PSKY", 0.008]]
    }
  ]
}

Code

Each block below is a file that runs against each night's build of this site that has the files it reads, before it is published, through the same Worker that answers the API: the code shown is the code that ran. A program that calls the API on its own should name its project in its User-Agent; it is a request, not a condition.

cURL

Prints the whole answer as JSON; add | jq to pick fields.

Terminal
curl -s https://tickerinside.com/api/v1/history/spy.json

Python

Needs Python 3.9 or later and the tickerinside client, pip install tickerinside, which uses only the standard library.

holdings-history.py
import tickerinside as ti

spy = ti.history("SPY").data
print(spy["first"], spy["last"], spy["file_dates"])
for step in spy["changes"][-5:]:
    for ticker, weight in step.get("entered", []):
        print(step["date"], "entered", ticker, weight)
    for ticker, weight in step.get("exited", []):
        print(step["date"], "exited", ticker, weight)

JavaScript

Runs as a module in Node.js 20 or later (save it as a .mjs file), in Deno, or in a browser page: CORS is open.

holdings-history.mjs
const url = "https://tickerinside.com/api/v1/history/spy.json";
const spy = await (await fetch(url)).json();
console.log(spy.first, spy.last, spy.file_dates);
for (const step of spy.changes.slice(-5)) {
  for (const [t, w] of step.entered ?? []) {
    console.log(step.date, "entered", t, w);
  }
  for (const [t, w] of step.exited ?? []) {
    console.log(step.date, "exited", t, w);
  }
}

Fields to read

  • first: The date of the first file in the history.
  • last: The date of the latest file.
  • file_dates: How many files the history holds.
  • snapshots: One row per file date: positions, top 10 weight, key scheme.
  • changes: One step per file: date, previous (the file it is compared with), entered and exited with their weights, moved with the weight before and after.
  • overlap_history: How the fund's overlap with its ten closest funds moved over the same dates.
  • cite_as: A line to print under a figure, with the link to the file and its dates.

Limits

  • The history covers the 697 funds with an archived issuer file, each from its first archived file; the archive holds files dated from 2026-07-31.
  • Weights only: a step lists what entered, exited and moved, never share counts or market values.
  • A step across a change of keys or of how a file is read is marked restated and lists no trades.
  • The history is published under the TickerInside history terms, not CC BY 4.0: free per request, commercial use included, with a link to the file quoted. Republishing the archive as a whole, or fetching many funds' histories on a schedule to store them, is not granted. The terms: /legal/#history.

Reference