Files
edgartools/venv/lib/python3.10/site-packages/edgar/funds/series_resolution.py
2025-12-09 12:13:01 +01:00

111 lines
3.9 KiB
Python

"""
Series resolution service for ETF/Fund ticker-to-series mapping.
This module provides services for resolving ticker symbols to series IDs,
addressing GitHub issue #417.
"""
import logging
from dataclasses import dataclass
from functools import lru_cache
from typing import List, Optional
from edgar.core import log
__all__ = ['SeriesInfo', 'TickerSeriesResolver']
@dataclass
class SeriesInfo:
"""Information about a fund series"""
series_id: str
series_name: Optional[str]
ticker: str
class_id: Optional[str] = None
class_name: Optional[str] = None
class TickerSeriesResolver:
"""Handles ticker to series ID resolution with caching."""
@staticmethod
@lru_cache(maxsize=1000)
def resolve_ticker_to_series(ticker: str) -> List[SeriesInfo]:
"""Resolve ticker to all associated series with ETF fallback."""
if not ticker:
return []
try:
# First try mutual fund data (original behavior)
from edgar.reference.tickers import get_mutual_fund_tickers
mf_data = get_mutual_fund_tickers()
# Find all matches for this ticker
matches = mf_data[mf_data['ticker'].str.upper() == ticker.upper()]
series_list = []
for _, row in matches.iterrows():
series_info = SeriesInfo(
series_id=row['seriesId'],
series_name=None, # Not available in the ticker data
ticker=row['ticker'],
class_id=row['classId']
)
series_list.append(series_info)
# If found in mutual fund data, return those results
if series_list:
return series_list
# NEW: Fallback to company ticker data for ETFs
log.debug(f"Ticker {ticker} not found in mutual fund data, trying company data...")
from edgar.reference.tickers import find_cik, get_company_tickers
cik = find_cik(ticker)
if cik:
# Found as company ticker - likely an ETF
company_data = get_company_tickers()
company_matches = company_data[
(company_data['ticker'].str.upper() == ticker.upper()) &
(company_data['cik'] == cik)
]
if len(company_matches) > 0:
company_match = company_matches.iloc[0]
# Create synthetic series info for ETF
etf_series = SeriesInfo(
series_id=f"ETF_{cik}", # Synthetic series ID for ETFs
series_name=company_match['company'], # Company name as series name
ticker=company_match['ticker'],
class_id=f"ETF_CLASS_{cik}" # Synthetic class ID
)
log.debug(f"Resolved {ticker} as ETF company with CIK {cik}")
return [etf_series]
log.debug(f"Ticker {ticker} not found in either mutual fund or company data")
return []
except Exception as e:
log.warning(f"Error resolving ticker {ticker} to series: {e}")
return []
@staticmethod
def get_primary_series(ticker: str) -> Optional[str]:
"""Get the primary/most relevant series for a ticker."""
series_list = TickerSeriesResolver.resolve_ticker_to_series(ticker)
if not series_list:
return None
# If only one series, return it
if len(series_list) == 1:
return series_list[0].series_id
# If multiple series, return the first one (could be enhanced with better logic)
return series_list[0].series_id
@staticmethod
def has_multiple_series(ticker: str) -> bool:
"""Check if a ticker maps to multiple series."""
series_list = TickerSeriesResolver.resolve_ticker_to_series(ticker)
return len(series_list) > 1