Skip to content

data_updater_v2.identity

identity.py — our own ids: one per security (global_id) and one per ticker era of it on its market (id), minted exactly as nc-feeds’ Symbol mints them (nc_feeds/contracts/symbols.py: the same namespace, the same seed order, the same listing key), so the two systems name a security alike.

global_id uuid5(NAMESPACE, the first identifier present — the composite FIGI, the ISIN,
the CUSIP, the share-class FIGI, the record FIGI — else the listing itself,
``listing:{market}|{symbol}|{kind}``). Minted ONCE, when a security is first
seen, and stored: every later build passes the stored one back (identifiers
learned later never move it), and a renamed listing carries its old ticker's.
id uuid5(NAMESPACE, ["listing", global_id, market, symbol]): one per ticker era —
a rename opens a new id under the same global_id; a ticker another security
takes later is another id under another global_id.

The market is the venue’s (US for every ETF and equity here: a ticker is unique per market, so a move between US venues keeps the id); an index, a coin or a commodity trades on no venue, and its kind stands in. The kind in the ticker fallback is what the listing IS (asset_class: a mutual fund in the ETF table keys as mutual_fund), else its family.

The ticker fallback cannot tell a ticker reused by another security when neither carries an identifier (nc-feeds: “that case still needs identity resolution”). Here the catalog resolves it: a security found under a ticker another security held before is minted with the day the ticker was freed in its fallback key (reused_on) — the one addition to nc-feeds’ rule, and it touches only a security with no identifier at all, on a reused ticker.

Functions

assign

assign(family: str, today: Mapping[str, Mapping[str, Any]], stored: Sequence[Any], changes: Sequence[Any] = (), since: dt.date | None = None, on: dt.date) -> dict[str, Any]

Which security each of today’s tickers is, and its ids.

today is our symbol -> what is known of the listing now: its identifier columns (SEEDS) and asset_class. stored is the family’s catalog rows (every era); changes the symbol changes (old_symbol, new_symbol, date, conflict), of which only those dated after since and by on are applied — the ones the stored catalog has not seen (None: none, as on a first build).

Per ticker, in this order: renamed a change moved a stored security onto it: the new era’s id under the stored global_id (the old era’s row, if the rename goes back, is reused). Not applied when the identifiers say the two are different securities, nor when the change is already applied (the two tickers share a security), nor when no stored row is the one renamed (two renames in one window, AAA -> BBB -> CCC, start from AAA’s row: BBB was never ours); a ticker held today by another security (a swap: Corgi’s QAU -> SCAU -> IDAU -> QAU on 2026-08-03) ends that era. held today’s row of the ticker: kept, unless its security moved away by a change today — then today’s line is the same security still listed under the old ticker (a twin: its identifiers say so, or none tell and the change is at most TWIN_DAYS old) or another security on a reused ticker; and unless its identifiers, read again, are another security’s (reused). returning a stored past era of the ticker that is the same security (its identifiers, or the only past era when none tell): reactivated. untold a stored security under another ticker, not listed today, that its identifiers say it is (same_security, or the same identifier-seeded global_id): a rename no source told, its security carried (renamed, dated None). new minted (global_id); a fallback key a stored past era already took is minted with the day the ticker was taken again (reused_on).

Returns ids (symbol -> id, global_id, the stored row carried: its fetch and columns; None for a new security), ended (stored rows whose era ends though their ticker is listed today), and what was decided, named: renamed, twins, reused, refused (a change not applied, and why), ambiguous.

canonical

canonical(symbol: str) -> str

Our spelling of a ticker, as nc-feeds keys it: trimmed, upper case, a dash for a dot.

global_id

global_id(family: str, symbol: str, ids: Mapping[str, Any] | None = None, kind: str | None = None, reused_on: dt.date | None = None) -> str

A security id minted from what is known of it now (seed). Stored once minted.

listing_id

listing_id(family: str, symbol: str, global_id: str) -> str

The id of one ticker era of a security (nc-feeds’ Symbol.listing_id): reproduced from the market, the ticker and the security, so a past era’s id needs no stored row.

market_of

market_of(family: str) -> str

What keys a family’s tickers: the US market for listed ETFs and equities, the family for an index, a coin or a commodity (nothing trades on a venue).

resolve_changes

resolve_changes(changes: Iterable[Any], catalog: Mapping[str, Sequence[Any]]) -> tuple[dict[tuple[str, str, dt.date], tuple[str, str, str]], list[dict[str, Any]]]

The ids of each symbol change our catalog holds the security of: (old_symbol, new_symbol, date) -> (global_id, id of the new era, old_id of the era before), the eras reproduced (listing_id), so a past era needs no row.

Walked back from every catalog row (catalog: family -> its rows, every era): its era ends at the change that last moved its security to another ticker — unless a change brought the same security back to it (a rename back: its catalog holds both tickers), then it runs on; the latest change into its ticker before that end began it — unless the ticker left another security in between (that change was an earlier holder’s) — and the walk goes on from the old ticker, ending there. Today’s eras are walked first: a past era that only left the pull ended before whoever holds its ticker now, so it never claims their renames; then the past eras told a rename into their ticker from a ticker their own security holds. Such an era (one that only left the pull) never claims a change from a ticker our catalog holds under other securities only (one of them could have made it: not one first trading after its day, nor one on the old ticker today that never held the new one — a ticker has one holder at a time), and gives way to a change proven another’s (today’s era, a walked chain, or its own old ticker held): when two such eras could each have it, or none of its own, it is left unresolved and named (clashes), never guessed — as is a change two securities would claim. A change marked conflict is never walked.

same_security

same_security(a: Any, b: Any) -> bool | None

Whether two catalog rows (or identifier mappings) are one security.

Yes when any identifier present on both is equal: the composite FIGI, the share-class FIGI, the ISIN or the CUSIP. A FIGI can change while the security does not — a reorganisation or redomicile reissues it, the ISIN kept (RKLB, SIRI, VNOM on 2026-10-09: EODHD still holds the old FIGI, OpenFIGI the new) — and a reverse split reissues the ISIN and CUSIP, the FIGI kept. So no: only when the identifiers present on both are all unequal, a FIGI among them. None: nothing on both sides tells.

seed

seed(family: str, symbol: str, ids: Mapping[str, Any] | None = None, kind: str | None = None, reused_on: dt.date | None = None) -> str

The key a security id is minted from: its first identifier present (SEEDS, read from ids: a catalog row’s columns), else the listing — kind what it IS (None: the family), reused_on the day another security left the ticker (see the module).