When to Use
Use this skill in reference data management, index constituent tracking, and trading bot universe maintenance. Trading algorithms relying solely on ephemeral ticker symbols fail during ticker renames (FB $\to$ META), corporate delistings, or index additions. Keying on an immutable permanent identifier — "A FIGI is never reused and remains with the instrument in perpetuity. A FIGI does not change as a result of any corporate action" (FIGI Allocation Rules, Sec. 1.2.1) — the engine cross-matches snapshots ($U_{t-1}$ vs $U_t$) into Additions, Deletions, Ticker Renames, Venue Migrations and Trading-Status transitions, and maps each to a downstream action.
The ticker case is not hypothetical: Meta's FB was retired on 9 June 2022, and FB today resolves to the ProShares S&P Dynamic Buffer ETF (FIGI BBG01VRMNFB1). A ticker-keyed universe would see FB "still present" and keep trading an unrelated instrument.
When NOT to Use
- As a delisting feed. A diff sees only what two files disagree about. A delisted instrument keeps its FIGI and normally stays in the security master (FIGI Allocation Rules, Sec. 3.2.4), so a delisting usually appears as a
statustransition, not as a set difference — and only if your vendor maintains the status field. Corporate-action confirmation belongs tocorporate-action-event-calendar-integration. - As an execution or liquidation component. The engine emits recommendations. Position exit, order cancellation, and unsubscription are the caller's, under its own risk controls.
- On snapshots keyed at different granularity levels. FIGIs are allocated at share class, country composite and trading-venue level. Comparing a composite-keyed file against an exchange-level-keyed file makes every identifier differ — Apple is
BBG000B9XRY4composite butBBG000B9Y5X2on Nasdaq — and produces a full-universe delete-and-re-add. Same for mixing FIGI with ISIN. - On an ISIN-keyed multi-venue universe without a composite key. An ISIN "represents the identification of a financial instrument rather than the market a financial instrument trades on" (ANNA ISIN Uniform Guidelines 2025, Sec. 1.1): Meta's
US30303M1027maps to 258 FIGI rows across venues. Key on(ISIN, MIC)or on exchange-level FIGIs; the engine rejects duplicate keys rather than silently keeping one listing. - For point-in-time backtest universes. Reconstructing what was investable on a past date is
point-in-time-index-constituent-trackingandsurvivorship-bias-free-universe-construction.
Prerequisites
- Previous snapshot ($U_{t-1}$) and current snapshot ($U_t$), from the same vendor, same extract, same identifier scheme and same granularity level.
- One permanent identifier per row, unique within each snapshot.
- A
statusfield maintained by the vendor if delistings/halts are to be detected at all. - A calibrated
max_deletion_ratio— the fraction of the universe allowed to disappear in one comparison before the snapshot is treated as broken rather than believed.
Workflow
-
Ingest and key by permanent identifier:
- Set
id_scheme="FIGI"or"ISIN"so malformed identifiers are rejected at the door."OPAQUE"(the default, for in-house keys) checks only non-blankness and uniqueness — under it, a snapshot accidentally keyed on tickers is accepted and every rename becomes a delete-plus-add. - Decision point — duplicate identifier. The engine raises rather than last-wins-collapsing. A duplicate is a broken extract or an ISIN spanning venues; both need fixing upstream, not silently reducing the row count.
- Pass
previous_as_of/current_as_ofwhen you have them. Swapping the two snapshots inverts additions and deletions, turning every new listing into a liquidation instruction; supplying the dates makes that fail loudly.
- Set
-
Sanity-check the snapshot before believing the delta (churn guard):
- Decision point — before any action is emitted. If deletions exceed
max_deletion_ratioof the previous universe, or the current snapshot is empty while the previous was not, the file is treated as truncated:statusbecomesUNIVERSE_SNAPSHOT_SUSPECT, everyrecommended_actionis downgraded toHOLD_FOR_MANUAL_REVIEW, and the intended action is preserved insuppressed_actionfor the audit trail. Holding is the fail-safe direction — a bad file's liquidations are irreversible, a delayed exit is not — but it is not free: a genuine mass-delisting event is also held, so a suspect report must page a human, not sit in a queue. - The default 10% is a library default, not an industry standard, and is calibrated for a universe of hundreds of names. On a 3-name universe one deletion is 33% and trips it every time.
- Decision point — before any action is emitted. If deletions exceed
-
Classify the delta:
- Addition (id in $U_t$ only):
INITIATE_COVERAGEwhen the new row isACTIVE; a row added in a non-tradable status getsREVIEW_STATUS_CHANGEinstead. A new FIGI is also how a spin-off arrives (Allocation Rules, Sec. 3.2.7) — confirm the reason before sizing. - Deletion (id in $U_{t-1}$ only):
LIQUIDATE_POSITION_AND_UNSUBSCRIBE. Absence from a file is not proof of a delisting — index removal, a vendor scope change and a truncated file look identical here. - Ticker rename (id in both, ticker differs):
UPDATE_SYMBOL_MAPPER. Comparison is case- and whitespace-insensitive so vendor re-casing is not alerted as a rename. - Venue migration (id in both, exchange differs):
UPDATE_ROUTING_TABLE. Exchange codes attached to composite-level FIGIs do change (Sec. 3.2.2 / 3.2.3) and orders routed to the old venue fail. - Status transition (id in both, status differs) — decision point, the transition target decides the action, not the fact of a change: $\to$
DELISTEDisLIQUIDATE_POSITION_AND_UNSUBSCRIBE; $\to$HALTED/SUSPENDEDisFREEZE_TRADING_ALERTS; non-tradable $\to$ACTIVEisRESUME_TRADING_ELIGIBILITY; anything outside the recognised vocabulary isREVIEW_STATUS_CHANGEand logged, never guessed at. - A change in
asset_namealone raises nothing: a company renaming itself changes neither the FIGI nor tradability (Sec. 3.2.5).
- Addition (id in $U_t$ only):
-
Dispatch alerts in the emitted order: deletions, status changes, venue migrations, renames, additions — risk-reducing first, so a consumer that dies part-way through has already applied the protective actions. One instrument can emit several alerts (rename and halt); apply all of them.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Keying the diff on ticker symbols: a rename becomes a deletion of the old ticker plus an addition of the new one — liquidating a position that never changed and losing every historical join. Worse, retired tickers get reassigned:
FBnow belongs to an unrelated ETF, so the diff sees no change at all while the bot trades the wrong instrument. - Actioning a truncated vendor file: a half-written or failed FTP transfer looks exactly like a mass delisting, and a naive diff answers with
LIQUIDATEfor the entire book. Never let a diff liquidate more than a calibrated fraction of the universe without a human. - Treating every status change as a freeze:
ACTIVE$\to$DELISTEDneeds the position closed out, not a frozen alert;HALTED$\to$ACTIVEmust not freeze anything. A single blanket action for all transitions is wrong in both directions. - Assuming a delisting shows up as a deletion: FIGIs persist through delisting and vendors keep the row (Sec. 3.2.4). If the vendor maintains
status, the delisting is a status transition; if it does not, this engine cannot see the delisting at all. - Liquidating on a merger completion: when the acquired company delists, its holding may already have converted to cash or acquirer shares (Sec. 3.2.6 — the FIGI does not change for either party). An exit order into a dead symbol will be rejected; reconcile against the corporate-action feed.
- Mixing FIGI granularity levels or identifier schemes between snapshots: composite vs exchange-level vs share class FIGIs are different identifiers for the same company, so mixing them delete-and-re-adds the whole universe. Apple:
BBG000B9XRY4(US composite),BBG000B9Y5X2(Nasdaq),BBG001S5N8V8(share class). - Trusting an ISIN as a per-listing key: one ISIN covers every fungible listing, and ISINs are not universally permanent — a merger by amalgamation retires both ISINs and allocates a new one (ANNA Guidelines, Sec. 4.2), while a pure name change keeps it (Sec. 4.9). FIGI has no such exception.
- Updating the symbol mapper without notifying live execution daemons: the mapper and the running subscriptions must be updated in the same operation, or orders route on a symbol the venue has retired.
- Bootstrapping against an empty previous snapshot: every instrument becomes an
INITIATE_COVERAGEaddition. Checktotal_previous_count == 0and treat that run as a baseline, not as a signal.
Verification
- Instantiate
UniverseChangeDetectionEngine(id_scheme="FIGI"). Compare $U_{t-1}$ (BBG000MM2P62:FB,BBG000B9XRY4:AAPL,BBG000H6HNW3:TWTR, plus 20 unchanged names) with $U_t$ (BBG000MM2P62:META,BBG000B9XRY4:AAPL,BBG000N7QR55:PLTR, same 20) $\implies$ exactly 3 alerts: 1 addition (PLTR,INITIATE_COVERAGE), 1 deletion (TWTR,LIQUIDATE_POSITION_AND_UNSUBSCRIBE), 1 rename (FB$\to$META,UPDATE_SYMBOL_MAPPER) — never a deletion ofFBplus an addition ofMETA. - Churn guard: compare a 50-name universe against an empty snapshot $\implies$
status == "UNIVERSE_SNAPSHOT_SUSPECT", all 50 alerts carryHOLD_FOR_MANUAL_REVIEWwithsuppressed_action == "LIQUIDATE_POSITION_AND_UNSUBSCRIBE", and no alert recommends liquidation. Atmax_deletion_ratio=0.10, 1 deletion out of 10 must not be suspect and 2 out of 10 must be. - Status transitions:
ACTIVE$\to$DELISTED$\implies$LIQUIDATE_POSITION_AND_UNSUBSCRIBE;ACTIVE$\to$HALTED$\implies$FREEZE_TRADING_ALERTS;HALTED$\to$ACTIVE$\implies$RESUME_TRADING_ELIGIBILITY; an unrecognised status $\implies$REVIEW_STATUS_CHANGE. - Negative checks: a blank
permanent_id, a duplicate identifier within one snapshot, a ticker passed as a FIGI underid_scheme="FIGI", an ISIN with a broken check digit underid_scheme="ISIN", and reversedas_ofdates must each raise. - Run
python -m unittest discover -s skills/instrument-universe-change-detection-and-alerting/scriptsand confirm a 100% pass rate.