When to Use
Use this skill when data from more than one vendor has to join on the same instrument. Apple Inc. is AAPL US Equity to Bloomberg (the composite exchange code) and also AAPL UW Equity (the primary exchange code, Nasdaq); AAPL.O as a Refinitiv RIC; US0378331005 as an ISIN; BBG000B9XRY4 as a FIGI. Every one of those is correct, none of them are interchangeable, and a join that assumes otherwise silently aggregates the wrong positions.
The engine holds one canonical internal symbol per instrument, maps each vendor's symbol to it over an effective-dated window, and resolves in both directions at a caller-supplied point in time.
| Property | Why the naive table gets it wrong |
|---|---|
| A vendor symbol is unique only within a window | Exchange tickers are recycled. NYSE S was Sprint until the NYSE removed the class from listing on 13 April 2020; SentinelOne listed under S on 30 June 2021. A table keyed on ("NYSE", "S") alone resolves a 2019 tick to SentinelOne. |
| One canonical symbol can have several symbols at one vendor | AAPL US Equity (composite) and AAPL UW Equity (Nasdaq primary) are both Bloomberg's. Last-write-wins hands a router whichever was loaded second. |
| A conflicting registration is a data defect | Pointing a live vendor key at a second canonical symbol repoints every join already keyed on it. It has to raise, not warn. |
When NOT to Use
- To validate an identifier. Nothing here checks a check digit;
US0378331005andUS0378331009are both accepted as opaque vendor strings. Useisin-cusip-sedol-cross-reference-serviceat the ingest boundary, before registering a mapping. - To decide which vendor is right about a field. Symbol mapping answers "which rows are the same instrument", not "whose tick size is correct". That is
reference-data-golden-source-designation. - As a corporate-action feed. The table is only as current as what was registered. A rename nobody entered is a mapping that stays wrong and never warns. Drive registration from
corporate-action-event-calendar-integrationandreference-data-change-notification-pipeline. - For intraday symbology changes. Windows are date-granular and half-open. They model listing-lifecycle events, which happen between sessions.
- As a substitute for a licensing review. RIC and Bloomberg ticker strings are licensed vendor symbology. Holding them in an internal table is a contractual question — see
data-vendor-contractual-usage-restriction-tracking.
Prerequisites
- A canonical internal symbol per instrument. Prefer one that does not move on a rename:
FBbecameMETAon 9 June 2022 while the listing and the CUSIP were unchanged. Key on the FIGI or an internal surrogate, carry the ticker as an attribute. VendorSymbolEntryrows:canonical_symbol,vendor_name,vendor_symbol(verbatim, as the vendor writes it),identifier_type, and optionallyeffective_from/effective_to/is_primary.- A
SymbolMappingConfigdecision:case_sensitive(default False) andallow_ambiguous(default False — conflicts raise). - For historical resolution, the observation's own date, to pass as
as_of.
Workflow
- Register mappings, verbatim and dated:
- Store the vendor's spelling exactly. Normalisation (trim, collapse internal whitespace runs, upper-case) applies to lookup keys only, because the value is what gets handed back to a vendor API or an order router — and
AAPL US EQUITYis not a Bloomberg ticker. - Date anything whose window you know. Undated means "currently effective, open-ended in the past", which is right for a symbol that has never moved and wrong for every one that has.
- Exactly one entry per (canonical, vendor, window) carries
is_primary=True— the symbol a reverse lookup returns. Register the alternates (AAPL UW Equity) withis_primary=False: they still resolve inbound, they are just not the routing answer.
- Store the vendor's spelling exactly. Normalisation (trim, collapse internal whitespace runs, upper-case) applies to lookup keys only, because the value is what gets handed back to a vendor API or an order router — and
- Let a conflict fail the load:
- A vendor key already resolving to a different canonical symbol over an overlapping window raises
AmbiguousMappingError. A secondis_primarysymbol for one (canonical, vendor) raises too. Re-registering an identical mapping is idempotent, so re-running an ingest is safe. - Only set
allow_ambiguous=Trueto bulk-load known-dirty legacy data you intend to audit rather than repair. The first registration still wins every lookup, the conflict is logged at ERROR each time it is read, and it appears inregistered_conflicts()and the coverage report. It is never resolved silently.
- A vendor key already resolving to a different canonical symbol over an overlapping window raises
- Resolve at a point in time:
forward_lookup(vendor, symbol, as_of=...)→ canonical.reverse_lookup(canonical, vendor, as_of=...)→ that vendor's primary symbol.translate(source_vendor, symbol, target_vendor, as_of=...)chains both and returnsNoneif either leg misses — never the canonical symbol as a consolation, which a caller would route on.- Omitting
as_ofmeans currently effective, never "whatever was registered last". A window that has closed does not answer: a miss you can alert on beats a confident answer naming the previous issuer.
- Handle the rename as two abutting windows:
retire_mapping(vendor, old_symbol, changeover_date)closes the open window; register the new symbol witheffective_from=changeover_date. Half-open windows mean one date, no gap and no overlap. Check the returned count — zero means nothing was retired, which is a failed retirement, not a no-op.
- Report coverage against the universe you actually trade:
get_coverage_report(expected_canonical=..., expected_vendors=..., as_of=...). Withoutexpected_canonicalthe report measures the table against itself and cannot show a gap.expected_vendorssurfaces per-vendor holes (AAPL@FACTSET) — the question that decides whether a strategy can run.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Returning a normalised symbol from a reverse lookup. Upper-casing the key is correct; upper-casing the value produces
AAPL US EQUITY, which no Bloomberg interface accepts. Normalise keys, store and return values verbatim. - Treating
(vendor, symbol)as a permanent identity. NYSESmeant Sprint through 13 April 2020 and SentinelOne from 30 June 2021, with fourteen months in between when it meant nothing. Resolving a historical tick withoutas_ofattributes Sprint's prints to SentinelOne — and the backtest will not crash, it will just be wrong. - Assuming one symbol per vendor per instrument. Bloomberg's composite and primary-exchange tickers are both live and both correct. Silently keeping the last one registered routes to the wrong venue's ticker roughly half the time.
- Resolving a conflict by overwriting. The second registration is evidence of an upstream merge error. Overwriting repoints every join already keyed on that symbol and destroys the evidence. Raise, or record the conflict and keep the first.
- Re-keying the canonical symbol on the ticker. A rename then looks like a delisting plus a new instrument, and position history splits in two.
FB→METAmoved the ticker; the CUSIP and the listing did not move. - Registering a blank symbol from a ragged CSV row. It creates the key
("", ""), which then answers every blank-string lookup an upstream feed produces. Blank fields are rejected outright. - Trusting
identifier_typeas a validation. It is a label the feed supplied.RICon a column full of ISINs is accepted; only a check-digit validator catches it. - Assuming a RIC root identifies a listing. A RIC is root +
.+ a one- or two-character exchange code.AAPL.Ois Nasdaq; the rootAAPLon its own is not a mapping key.
Verification
- Instantiate
SymbolMappingEngine()and register Apple at Bloomberg (AAPL US Equity, primary), Bloomberg (AAPL UW Equity,is_primary=False), Refinitiv (AAPL.O) and an ISIN source (US0378331005). Forward lookupAAPL.Oat Refinitiv $\implies$AAPL. Reverse lookupAAPLat Bloomberg $\implies$AAPL US Equity, byte-for-byte as registered.reverse_lookup_all$\implies$ both Bloomberg tickers. - Verify conflict rejection: register
SPRINT→ NYSES, thenSENTINELONE→ NYSESundated $\implies$AmbiguousMappingError, andforward_lookup("NYSE", "S")still returnsSPRINT. RegisterAAPL UW Equityas a second primary Bloomberg symbol $\implies$AmbiguousMappingError. - Verify point-in-time resolution: register
SPRINT_CORP→ NYSESwitheffective_to=2020-04-13andSENTINELONE→ NYSESwitheffective_from=2021-06-30$\implies$ no conflict;as_of=2019-06-01returnsSPRINT_CORP,as_of=2020-06-01returnsNone,as_of=2021-06-30returnsSENTINELONE, and omittingas_ofreturnsSENTINELONE. Confirm the window is half-open:2020-04-13misses,2020-04-12hits. - Verify the rename path: register
META_PLATFORMS→ NasdaqFB,retire_mapping("Nasdaq", "FB", 2022-06-09)$\implies$ returns 1, then registerMETAfrom2022-06-09$\implies$ no conflict;FBresolves at2022-06-08and misses today. - Verify validation: a blank
vendor_symbol, a non-string symbol, andeffective_from >= effective_to$\implies$ValueErrorin every case. - Verify coverage:
get_coverage_report(expected_canonical=["AAPL", "MSFT"], expected_vendors=["Bloomberg", "FactSet"])$\implies$PARTIAL_COVERAGE,unmapped_canonical == ["MSFT"],missing_vendor_coveragenamingAAPL@FACTSET. - Run
python -m unittest discover -s skills/reference-data-symbol-mapping-across-vendors/scripts.
Related Skills
isin-cusip-sedol-cross-reference-servicereference-data-golden-source-designationreference-data-change-notification-pipelinecorporate-action-event-calendar-integrationinstrument-universe-change-detection-and-alertingmulti-exchange-feed-normalizationdata-vendor-contractual-usage-restriction-tracking