ZipLime/security-master
US Security Master Which company a ticker belonged to, on a date. Which filer a CUSIP points at. What a company used to be called. 23 553 filers · 144 054 identifier spans · 10 061 784 dated observations · 74 814 CUSIP–ticker pairs · 1960 to 2026 This is the glue for the rest of the family. Every other ZipLime dataset keys on the SEC's CIK, which never changes — and every price series, broker feed and research note keys on a ticker, which changes all the time. Joining the two… See the full description on the dataset page: https://huggingface.co/datasets/ZipLime/security-master.
US Security Master
Which company a ticker belonged to, on a date. Which filer a CUSIP points at. What a company used to be called.
23 553 filers · 144 054 identifier spans · 10 061 784 dated observations · 74 814 CUSIP–ticker pairs · 1960 to 2026
This is the glue for the rest of the family. Every other ZipLime dataset keys on the SEC's CIK, which never changes — and every price series, broker feed and research note keys on a ticker, which changes all the time. Joining the two with today's mapping is the quiet way to put the wrong company's history into a backtest.
The pipeline lives in `recipe/` at the same revision as the data. See PIPELINE.md for the method and its limits.
The problem this exists for
A ticker is not an identity. PLUM can be one company until 2014 and another from 2019, and a strategy that resolves it by today's map reads the second company's fundamentals against the first company's prices for the earlier years. This dataset says which was which, and when.
import polars as pl
spans = pl.read_parquet("data/identifiers/*.parquet")
def cik_for(ticker: str, on: str) -> str | None:
hit = spans.filter(
(pl.col("kind") == "ticker")
& (pl.col("value") == ticker)
& (pl.col("valid_from") <= pl.lit(on).str.to_date())
& (pl.col("valid_to") >= pl.lit(on).str.to_date())
).sort("observations", descending=True)
return hit["cik"][0] if hit.height else None
cik_for("FB", "2015-06-30") # Meta, under its old symbol
cik_for("META", "2015-06-30") # None — the symbol did not exist yetWhat a span means, and what it does not
valid_from and valid_to are the first and last filing that stated the identifier, not registry dates. Nobody publishes the day a ticker changed hands. A gap inside a span means nobody filed, not that the identifier lapsed, and observations says how much evidence stands behind it.
Two flags answer two different questions:
is_latest_for_filer— the newest value this company gave. Use it to ask what did this company last call itself, including companies that stopped filing in 2011.is_current— that, and the company still files. Use it to ask who owns this ticker now. Defining current without the second condition left 1 218 tickers current for two companies at once; with it, 54 remain, and those are genuine dual filers.
The CUSIP bridge
13F and N-PORT identify a holding by CUSIP. Everything else identifies a company by CIK. No free source maps between them, so this one is built: settlement records tie a CUSIP to a ticker on a date, filings tie a ticker to a filer over a span, and the two compose.
53.9% of CUSIP-ticker pairs resolve to a filer; 74.8% weighted by how often the CUSIP appears in settlement records, counting only the pairs that are not funds — which is what the build gates on. Measured against real 13F holdings, the matched set covers 83.1% of holding rows and 87.5% of position value (82.2% and 86.7% keeping only high and medium).
Every one of those was higher a release ago — 68.8%, 78.7%, 93.9% and 95.0% — because the issuer-prefix rule was lending fund families a stranger's CIK and a coverage metric counts a wrong filer as a hit. Dropping the wrong matches reads as lost coverage. PIPELINE.md has what happened.
Counting raw CUSIPs understates it badly, and the reason is worth knowing: 10 679 of these rows are funds. A fund settles like a share, and most file nothing under a ticker of their own — IVV, IWM and VOO are lines inside a registrant trust's filings, not filers. They carry looks_like_fund = true and are excluded from the coverage the build gates on. Some funds are their own trust and do file: SPY, GLD, DIA and MDY each resolve to the trust that issues them, and 749 fund rows carry a CIK on that basis. A fund that matched a company only by sharing its ticker is refused instead: MDY pointed at Midway Gold until the name check was added.
A further 8 527 rows carry a settlement symbol that nobody trades under — clearing placeholders (XCEMZZZZ), preferred lines (BACPRS), CUSIP fragments. They are real evidence about the instrument and are marked standard_symbol = false rather than dropped.
bridge = pl.read_parquet("data/cusip_map/*.parquet")
holdings = holdings.join(
bridge.filter(pl.col("match_confidence").is_in(["high", "medium"]))
.select("cusip", "cik"),
on="cusip", how="left",
) # now 13F joins to fundamentals and insider trades on `cik`Configs
observations is published so that anyone who disagrees with how the spans were derived can derive their own. It is the only honest way to ship a judgement.
Where the evidence comes from
company_tickers.json alone is what most projects use, and it is a snapshot: it states today and carries no past, so a company that changed symbol appears under the new one for its entire life. The history here comes from four million filings that each stated a symbol on a known date.
Coverage and honest gaps
- *81% of filers have a ticker; 93% of active filers do.* The rest are funds, trusts and private filers that never printed one.
- Exchange is sparse — 8 228 spans. Filers rarely name the venue, and SEC's current map only covers live listings.
- Coverage starts in 1960 only because a filing header stated a name change from then. Ticker evidence effectively begins in 2006 with Form 4, and identifiers before that are not in EDGAR to be found.
- A span is evidence, not a registry. See above.
- Most fund CUSIPs do not resolve, by construction. See above.
- 54 tickers are current for two filers. Dual filers and reorganisations mid-transfer; the raw observations let you decide.
Provenance and updates
Sources: SEC EDGAR (Form 4 and XBRL filings, both via sibling ZipLime datasets), company_tickers_exchange.json, and the fails-to-deliver files — all US Government works in the public domain.
Rebuilt weekly, Monday 07:40 UTC, by a Hugging Face Job (`jobs/run.py`) that lints, tests, rebuilds, verifies and only then publishes.
