LSEG Support: EikonGetSymbology Fails to Resolve Bare-Ticker RICs
Reproducible notebook for the LSEG Data Quality team.
Uses the LSEG Data Library for Python (lseg-data).
Each issue section is preceded by a code cell that reproduces it.
Date: March 2026
Products affected: Eikon Data API — EikonGetSymbology endpoint
Secondary issue: Case inconsistency in delisted RIC codes returned by EikonGetSymbology
Setup
import lseg.data as ld
import pandas as pd
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
pd.set_option('display.max_colwidth', 80)
pd.set_option('display.max_rows', 100)
ld.open_session()
Summary
EikonGetSymbology (exposed in the LSEG Data Library as ld.convert.symbol() with symbol_type mapping) does not reliably resolve bare-ticker RICs (e.g., HES, A) to their exchange-suffixed equivalents (e.g., HES.N, A.N). ld.get_data() resolves both forms to the same company, but ld.convert.symbol() treats them as different instrument universes. This makes it impossible to programmatically track RIC lifecycle events (delistings, ticker changes, corporate actions) for any instrument stored under its bare ticker.
A separate issue: ld.convert.symbol() returns delisted RIC codes with inconsistent casing relative to the input (e.g., 1COV.DE returns 1COv.DE^L25).
Issue 1: Bare-Ticker Symbology Resolution
The Inconsistency
ld.get_data() treats bare tickers and exchange-suffixed RICs as equivalent. Both return identical company data. However, ld.convert.symbol() does not recognise this equivalence.
Step 1: Confirm both forms resolve to the same company in ld.get_data()
-- Issue 1, Step 1: Both bare and suffixed RICs resolve to the same company --
step1_df = ld.get_data(
universe=['HES', 'HES.N'],
fields=['TR.InstrumentIsActive', 'TR.CommonName', 'TR.ExchangeName']
)
print('Both HES and HES.N resolve to Hess Corp:')
step1_df
Both forms return identical company data: Hess Corp, InstrumentIsActive = False, listed on NYSE.
The Eikon data model treats HES and HES.N as the same instrument.
Step 2: Query symbology with the exchange-suffixed form (works)
-- Issue 1, Step 2: Symbology lookup with exchange-suffixed RIC --This correctly returns the delisted successor code.
step2_df = ld.convert.symbol(
symbols=['HES.N'],
from_symbol_type='RIC',
to_symbol_types=['RIC'],
best_match=False
)
print('Exchange-suffixed form correctly returns delisted code:')
step2_df
The exchange-suffixed form HES.N correctly resolves to the delisted successor HES.N^G25.
This is the expected and correct behaviour.
Step 3: Query symbology with the bare ticker (fails)
-- Issue 1, Step 3: Symbology lookup with bare ticker --This returns ~96 unrelated instruments (derivatives, warrants, cross-listed products).The NYSE equity HES.N is NOT among the results.
step3_df = ld.convert.symbol(
symbols=['HES'],
from_symbol_type='RIC',
to_symbol_types=['RIC'],
best_match=False
)
print(f'Bare ticker returns {len(step3_df)} results:')
print(f'Does HES.N appear? {any("HES.N" in str(v) for v in step3_df.values.flatten() if v is not None)}')
step3_df
The bare ticker HES returns ~96 unrelated instruments -- derivatives, warrants, HK options, and cross-listed products -- with zero NYSE equity results and zero delisted equity results.
There is no path from HES to HES.N or HES.N^G25 through the symbology service.
The Inconsistency Also Exists for Active Tickers
This is not limited to delisted stocks. Active bare tickers produce inconsistent results.
-- Issue 1: Active bare ticker test --Test multiple active bare tickers to see if their .N / .O forms are returned.
active_tests = [
('A', 'A.N', 'Agilent Technologies'),
('GE', 'GE.N', 'GE Aerospace'),
('BA', 'BA.N', 'Boeing'),
('AAPL', 'AAPL.O', 'Apple'),
]
results = []
for bare, suffixed, name in active_tests:
df = ld.convert.symbol(
symbols=[bare],
from_symbol_type='RIC',
to_symbol_types=['RIC'],
best_match=False
)
n_results = len(df)
# Check if the suffixed form appears anywhere in the results
flat = [str(v) for v in df.values.flatten() if v is not None]
found = any(suffixed in v for v in flat)
results.append({
'Bare Ticker': bare,
'Expected Suffixed': suffixed,
'Company': name,
'Results Returned': n_results,
'Suffixed Form Found?': found
})
active_test_df = pd.DataFrame(results)
print('Active bare ticker resolution test:')
active_test_df
Bare ticker Company ld.convert.symbol() results Includes suffixed form?
A Agilent Technologies (NYSE) ~64 results No -- A.N not returned
GE GE Aerospace (NYSE) ~97 results Yes -- GE.N at position 5
BA Boeing (NYSE) ~97 results Yes
AAPL Apple (NASDAQ) ~99 results Yes -- AAPL.O returned
For A, there is no programmatic way to discover that A.N is the exchange-suffixed equivalent using ld.convert.symbol() alone.
For GE, the .N form is present but buried among ~97 alternatives with no ranking or filtering to distinguish the primary equity from derivatives and cross-listings.
Why This Matters
LSEG's own data model encourages storing bare tickers. TR.PrimaryRIC for A.N returns A:
-- Issue 1: TR.PrimaryRIC returns the bare ticker --LSEG designates the bare ticker as the "primary" form.
primary_df = ld.get_data(
universe=['A.N'],
fields=['TR.PrimaryRIC']
)
print('TR.PrimaryRIC for A.N:')
primary_df
LSEG designates the bare ticker as the TR.PrimaryRIC value. A system that follows LSEG's data model and stores TR.PrimaryRIC will accumulate bare tickers.
When those stocks eventually delist, the system cannot use ld.convert.symbol() to find the successor code -- the bare ticker resolves to a different instrument universe than the exchange-suffixed form.
Confirmed Delisted Cases
The table below confirms that the exchange-suffixed form correctly resolves while the bare form does not.
-- Issue 1: Confirmed delisted cases --Compare symbology results for bare vs suffixed forms.
delisted_cases = [
('HES', 'HES.N', 'Hess Corp', 'Chevron acquisition'),
('SNV', 'SNV.N', 'Synovus Financial', 'Columbia Banking acquisition'),
]
for bare, suffixed, name, event in delisted_cases:
print(f'\n=== {name} ({event}) ===')
# Suffixed form
df_suf = ld.convert.symbol(
symbols=[suffixed],
from_symbol_type='RIC',
to_symbol_types=['RIC'],
best_match=False
)
print(f' {suffixed} -> {len(df_suf)} result(s):')
print(f' {df_suf.to_string()}')
# Bare form
df_bare = ld.convert.symbol(
symbols=[bare],
from_symbol_type='RIC',
to_symbol_types=['RIC'],
best_match=False
)
flat = [str(v) for v in df_bare.values.flatten() if v is not None]
has_equity = any(suffixed in v for v in flat)
print(f' {bare} -> {len(df_bare)} results, contains {suffixed}? {has_equity}')
Bare Ticker Company Event ld.convert.symbol(bare) ld.convert.symbol(suffixed)
HES Hess Corp Chevron acquisition ~96 results, 0 equities HES.N -> HES.N^G25
SNV Synovus Financial Columbia Banking acquisition ~60+ results, 0 equities SNV.N -> SNV.N^A26
In both cases, the correct delisted code exists and is returned when queried with the exchange-suffixed RIC. The symbology service simply cannot bridge from the bare form to the suffixed form.
Issue 2: Case Inconsistency in Delisted RIC Codes
The Problem
ld.convert.symbol() returns delisted RIC codes with altered casing relative to the original.
-- Issue 2: Case inconsistency in delisted RIC codes --The input '1COV.DE' should produce '1COV.DE^L25', but the API returns '1COv.DE^L25'.
case_df = ld.convert.symbol(
symbols=['1COV.DE'],
from_symbol_type='RIC',
to_symbol_types=['RIC'],
best_match=False
)
print('Symbology result for 1COV.DE:')
case_df
-- Issue 2: Verify the casing difference programmatically --
original_ric = '1COV.DE'
Extract the returned delisted RIC from the results
returned_values = [str(v) for v in case_df.values.flatten() if v is not None and '^' in str(v)]
if returned_values:
delisted_ric = returned_values[0]
base_of_delisted = delisted_ric.split('^')[0]
print(f'Original RIC: {original_ric}')
print(f'Delisted RIC: {delisted_ric}')
print(f'Base of delisted: {base_of_delisted}')
print(f'Exact match? {original_ric == base_of_delisted}')
print(f'Case-insensitive? {original_ric.upper() == base_of_delisted.upper()}')
# Show the exact character difference
for i, (a, b) in enumerate(zip(original_ric, base_of_delisted)):
if a != b:
print(f' Character {i}: input={repr(a)} vs returned={repr(b)}')
else:
print('No delisted RIC found in results -- inspect case_df above.')
The original RIC 1COV.DE (Covestro, Xetra) is returned in the RIC column with correct casing, but the delisted successor in the RICs column has 1COv.DE^L25 -- a lowercase v that was not present in the original.
Why This Matters
RIC codes are treated as exact identifiers in financial systems. Standard string matching (old_ric + "^" prefix check) fails because the casing changed. This forces every consumer of ld.convert.symbol() to implement case-insensitive matching for delisted RIC detection -- a workaround for what should be case-stable data.
Additional Evidence: No Existing Field Bridges Bare to Suffixed
There is currently no Refinitiv field that reliably returns the exchange-suffixed form when given a bare ticker.
-- Additional evidence: TR.RICCode and TR.PrimaryRIC both fail to bridge --
bridge_df = ld.get_data(
universe=['A', 'A.N', 'HES', 'HES.N'],
fields=['TR.RICCode', 'TR.PrimaryRIC', 'TR.CommonName']
)
print('TR.RICCode echoes back the input; TR.PrimaryRIC returns the bare form:')
bridge_df
Input TR.RICCode TR.PrimaryRIC Provides .N form?
A A A No
A.N A.N A Only if you already know it
HES HES HES No
HES.N HES.N HES Only if you already know it
TR.RICCode simply echoes back the input form.
TR.PrimaryRIC always returns the bare form, even when queried with the suffixed version.
Neither provides a reliable path from bare ticker to exchange-suffixed RIC.
What We Are Asking For
Request 1
ld.convert.symbol() / EikonGetSymbology should include the exchange-suffixed equity RIC (e.g., HES.N) among the returned alternatives when queried with a bare ticker (HES) -- at minimum for US equities, where the bare form is designated as TR.PrimaryRIC.
Request 2
Expose a field (e.g., TR.ExchangeRIC) in ld.get_data() that returns the exchange-suffixed form for any valid input RIC. Currently, TR.RICCode echoes back the input form, and TR.PrimaryRIC returns the bare form even when queried with the suffixed version. Neither provides a reliable path from bare ticker to exchange-suffixed RIC.
Request 3
Delisted RIC codes should preserve the casing of the original RIC. If the active RIC is 1COV.DE, the delisted form should be 1COV.DE^L25, not 1COv.DE^L25.
Summary
Issue Severity API Function Status
1 Bare-ticker RICs not resolved to exchange-suffixed equivalents Critical ld.convert.symbol() Unresolved
2 Delisted RIC codes returned with altered casing Medium ld.convert.symbol() Unresolved
Impact
Issue 1 blocks programmatic tracking of RIC lifecycle events for instruments stored under their LSEG-designated TR.PrimaryRIC (bare ticker). This affects every system that follows LSEG's own data model.
Issue 2 forces consumers to implement case-insensitive matching as a workaround for what should be case-stable identifier data.