Matcher
matcher ¶
match SecurityProviderRecords.
SecurityMatchSource: TypeAlias = SecurityProviderRecord | Security module-attribute ¶
MatchResult(matched: bool, match_type: MatchType | None = None, warning: str | None = None, name_similarity: float | None = None) dataclass ¶
MatchType ¶
Security matching result.
ISIN_NAME = 'isin_name' class-attribute instance-attribute ¶
ISIN_TICKER = 'isin_ticker' class-attribute instance-attribute ¶
ISIN_TICKER_US = 'isin_ticker_us' class-attribute instance-attribute ¶
TICKER_COUNTRY_NAME = 'ticker_country_name' class-attribute instance-attribute ¶
TICKER_NAME = 'ticker_name' class-attribute instance-attribute ¶
TICKER_US_NAME = 'ticker_us_name' class-attribute instance-attribute ¶
SecurityMatcher(*, name_similarity_threshold: float = 85.0) ¶
Match provider records representing the same security.
Source code in src/equities_classifier/matching/matcher.py
harmonize_ticker(ticker: str | None) -> str | None staticmethod ¶
match(left: SecurityMatchSource, right: SecurityProviderRecord) -> MatchResult ¶
Match SecurityProviderRecords according to matching hierarchy.
Source code in src/equities_classifier/matching/matcher.py
def match(
self,
left: SecurityMatchSource,
right: SecurityProviderRecord,
) -> MatchResult:
"""Match SecurityProviderRecords according to matching hierarchy."""
left_ticker = self.harmonize_ticker(left.identifier_value(SecurityIdentifierType.TICKER))
left_ticker_cleaned = self.harmonize_ticker(left.identifier_value_cleaned(SecurityIdentifierType.TICKER))
left_ticker_country = left.identifier_country(SecurityIdentifierType.TICKER)
right_ticker = self.harmonize_ticker(right.identifier_value(SecurityIdentifierType.TICKER))
right_ticker_cleaned = self.harmonize_ticker(right.identifier_value_cleaned(SecurityIdentifierType.TICKER))
right_ticker_country = right.identifier_country(SecurityIdentifierType.TICKER)
left_ticker_us = self.harmonize_ticker(left.identifier_value(SecurityIdentifierType.TICKER_US))
right_ticker_us = self.harmonize_ticker(right.identifier_value(SecurityIdentifierType.TICKER_US))
left_isin = left.identifier_value(SecurityIdentifierType.ISIN)
right_isin = right.identifier_value(SecurityIdentifierType.ISIN)
# 1a. Ticker + ISIN
if (
left_ticker_cleaned
and right_ticker_cleaned
and left_isin
and right_isin
and left_ticker_cleaned == right_ticker_cleaned
and left_isin == right_isin
):
return MatchResult(matched=True, match_type=MatchType.ISIN_TICKER)
# 1b. Ticker-US + ISIN
if (
left_ticker_us
and right_ticker_us
and left_isin
and right_isin
and left_ticker_us == right_ticker_us
and left_isin == right_isin
):
return MatchResult(matched=True, match_type=MatchType.ISIN_TICKER_US)
# 2. ISIN + similar name
if (
left_isin
and right_isin
and left_isin == right_isin
and left_ticker_cleaned != right_ticker_cleaned
):
similarity = name_similarity(left.name, right.name)
if similarity >= self._name_similarity_threshold or name_contains_other_name(left.name, right.name):
return MatchResult(
matched=True,
match_type=MatchType.ISIN_NAME,
warning=(f"ISIN matches but ticker differs: {left_ticker!r} != {right_ticker!r}"),
name_similarity=similarity,
)
# 3. Ticker incl. country postfix + similar name
if (
not (left_isin and right_isin)
and left_ticker
and left_ticker_country
and right_ticker
and right_ticker_country
and left_ticker == right_ticker
):
similarity = name_similarity(left.name, right.name)
if similarity >= self._name_similarity_threshold or name_contains_other_name(left.name, right.name):
return MatchResult(
matched=True,
match_type=MatchType.TICKER_COUNTRY_NAME,
name_similarity=similarity,
)
# 4. Ticker + similar name
if (
not (left_isin and right_isin)
and left_ticker_cleaned
and right_ticker_cleaned
and left_ticker_cleaned == right_ticker_cleaned
):
similarity = name_similarity(left.name, right.name)
if similarity >= self._name_similarity_threshold or name_contains_other_name(left.name, right.name):
return MatchResult(
matched=True,
match_type=MatchType.TICKER_NAME,
name_similarity=similarity,
)
# 5. Ticker-US + similar name
if (
not (left_isin and right_isin)
and left_ticker_us
and right_ticker_us
and left_ticker_us == right_ticker_us
):
similarity = name_similarity(left.name, right.name)
if similarity >= self._name_similarity_threshold or name_contains_other_name(left.name, right.name):
return MatchResult(
matched=True,
match_type=MatchType.TICKER_US_NAME,
name_similarity=similarity,
)
return MatchResult(matched=False)