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) -> strOur 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) -> strA 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) -> strThe 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) -> strWhat 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 | NoneWhether 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) -> strThe 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).