Financial2API / pipeline /resolver.py
Shivam4096's picture
Upload folder using huggingface_hub
2a29b09 verified
Raw History Blame Contribute Delete
14.9 kB
"""
pipeline/resolver.py -- Resolve a company name or ticker to structured metadata.
Source priority:
1. yahooquery (free, no key, excellent Indian stock support)
2. FMP API (if key available)
3. Mark as unlisted with clear message
"""
from __future__ import annotations
import sys
from pathlib import Path
from typing import Optional
sys.path.insert(0, str(Path(__file__).parent.parent))
from config import FMP_API_KEY, FMP_BASE, REQUEST_TIMEOUT
import requests
# -- CompanyMeta ---------------------------------------------------------------
class CompanyMeta:
def __init__(self, name, ticker, exchange, currency, sector, industry,
country, description, website, mkt_cap, share_price, is_listed,
source_note="", fiscal_year_end="March"):
self.name = name
self.ticker = ticker
self.exchange = exchange
self.currency = currency
self.sector = sector
self.industry = industry
self.country = country
self.description = description
self.website = website
self.mkt_cap = mkt_cap
self.share_price = share_price
self.is_listed = is_listed
self.source_note = source_note
self.fiscal_year_end = fiscal_year_end
def to_dict(self):
return self.__dict__.copy()
# -- Indian company name -> NSE ticker map -------------------------------------
INDIAN_TICKERS = {
# IT
"tcs": "TCS.NS", "tata consultancy": "TCS.NS", "tata consultancy services": "TCS.NS",
"infosys": "INFY.NS", "infy": "INFY.NS",
"wipro": "WIPRO.NS",
"hcl": "HCLTECH.NS", "hcl tech": "HCLTECH.NS", "hcl technologies": "HCLTECH.NS",
"tech mahindra": "TECHM.NS", "techm": "TECHM.NS",
"ltimindtree": "LTIM.NS", "lti mindtree": "LTIM.NS",
"mphasis": "MPHASIS.NS",
"persistent": "PERSISTENT.NS",
"coforge": "COFORGE.NS",
# Banking
"hdfc bank": "HDFCBANK.NS", "hdfcbank": "HDFCBANK.NS",
"icici bank": "ICICIBANK.NS", "icicibank": "ICICIBANK.NS",
"axis bank": "AXISBANK.NS", "axisbank": "AXISBANK.NS",
"sbi": "SBIN.NS", "state bank": "SBIN.NS", "state bank of india": "SBIN.NS",
"kotak": "KOTAKBANK.NS", "kotak bank": "KOTAKBANK.NS", "kotak mahindra bank": "KOTAKBANK.NS",
"yes bank": "YESBANK.NS",
"indusind bank": "INDUSINDBK.NS",
"bank of baroda": "BANKBARODA.NS",
"pnb": "PNB.NS", "punjab national bank": "PNB.NS",
"canara bank": "CANARABANK.NS",
# Conglomerates / Energy
"reliance": "RELIANCE.NS", "reliance industries": "RELIANCE.NS", "ril": "RELIANCE.NS",
"tata": "TATAMOTORS.NS",
"adani enterprises": "ADANIENT.NS", "adani ports": "ADANIPORTS.NS",
"adani green": "ADANIGREEN.NS", "adani total gas": "ATGL.NS",
"ongc": "ONGC.NS",
"oil and natural gas": "ONGC.NS",
"ioc": "IOC.NS", "indian oil": "IOC.NS",
"bpcl": "BPCL.NS", "bharat petroleum": "BPCL.NS",
"coal india": "COALINDIA.NS",
# FMCG
"hindustan unilever": "HINDUNILVR.NS", "hul": "HINDUNILVR.NS",
"itc": "ITC.NS",
"nestle india": "NESTLEIND.NS", "nestle": "NESTLEIND.NS",
"britannia": "BRITANNIA.NS",
"dabur": "DABUR.NS",
"marico": "MARICO.NS",
"godrej consumer": "GODREJCP.NS", "godrej": "GODREJCP.NS",
"emami": "EMAMILTD.NS",
# Pharma
"sun pharma": "SUNPHARMA.NS", "sun pharmaceutical": "SUNPHARMA.NS",
"cipla": "CIPLA.NS",
"dr reddy": "DRREDDY.NS", "dr reddys": "DRREDDY.NS",
"divi's labs": "DIVISLAB.NS", "divis": "DIVISLAB.NS",
"aurobindo": "AUROPHARMA.NS",
"lupin": "LUPIN.NS",
"torrent pharma": "TORNTPHARM.NS",
# Auto
"maruti": "MARUTI.NS", "maruti suzuki": "MARUTI.NS",
"tata motors": "TATAMOTORS.NS",
"m&m": "M&M.NS", "mahindra": "M&M.NS", "mahindra and mahindra": "M&M.NS",
"hero motocorp": "HEROMOTOCO.NS", "hero": "HEROMOTOCO.NS",
"bajaj auto": "BAJAJ-AUTO.NS",
"eicher": "EICHERMOT.NS", "royal enfield": "EICHERMOT.NS",
"tvs motor": "TVSMOTOR.NS",
# Finance (non-bank)
"bajaj finance": "BAJFINANCE.NS",
"bajaj finserv": "BAJAJFINSV.NS",
"hdfc life": "HDFCLIFE.NS",
"sbi life": "SBILIFE.NS",
"lic": "LICI.NS",
"icici prudential": "ICICIPRULI.NS",
"muthoot": "MUTHOOTFIN.NS",
# Infra / Utilities
"larsen": "LT.NS", "l&t": "LT.NS", "larsen and toubro": "LT.NS",
"ntpc": "NTPC.NS",
"power grid": "POWERGRID.NS",
"tata power": "TATAPOWER.NS",
"adani power": "ADANIPOWER.NS",
# Cement / Materials
"ultratech": "ULTRACEMCO.NS", "ultratech cement": "ULTRACEMCO.NS",
"shree cement": "SHREECEM.NS",
"ambuja": "AMBUJACEM.NS",
"acc": "ACC.NS",
"grasim": "GRASIM.NS",
"tata steel": "TATASTEEL.NS",
"jsw steel": "JSWSTEEL.NS",
"hindalco": "HINDALCO.NS",
"vedanta": "VEDL.NS",
"jspl": "JINDALSTEL.NS",
# Paints / Consumer
"asian paints": "ASIANPAINT.NS",
"berger paints": "BERGEPAINT.NS",
"pidilite": "PIDILITIND.NS",
"havells": "HAVELLS.NS",
"voltas": "VOLTAS.NS",
"titan": "TITAN.NS",
"tanishq": "TITAN.NS",
# Retail / Consumer
"dmart": "DMART.NS", "avenue supermarts": "DMART.NS",
"trent": "TRENT.NS",
"zomato": "ZOMATO.NS",
"nykaa": "NYKAA.NS",
"policybazaar": "POLICYBZR.NS",
# Telecom
"airtel": "BHARTIARTL.NS", "bharti airtel": "BHARTIARTL.NS",
"jio": "RELIANCE.NS", # Jio is part of Reliance
# IT Infrastructure
"hcl infosystems": "HCLISEC.NS",
"dixon": "DIXON.NS",
}
def _guess_ticker(query: str) -> Optional[str]:
"""Try to find an Indian NSE ticker for a given name/query."""
q = query.lower().strip()
# Clean up standard suffixes for safer matching
clean_q = q.replace(".ns", "").replace(".bo", "").replace(" ltd", "").replace(" limited", "").strip()
# Exact match
if clean_q in INDIAN_TICKERS:
return INDIAN_TICKERS[clean_q]
if q in INDIAN_TICKERS:
return INDIAN_TICKERS[q]
return None
# -- yahooquery resolution -------------------------------------------------------
def _yq_resolve(ticker: str, log_fn=None) -> Optional[CompanyMeta]:
"""Try to resolve a ticker via yahooquery. Returns CompanyMeta or None."""
def log(msg):
if log_fn: log_fn(msg)
try:
import yahooquery as yq
t = yq.Ticker(ticker)
# Yahooquery returns dicts with ticker as the key
price_data = t.price.get(ticker, {})
profile_data = t.summary_profile.get(ticker, {})
if isinstance(price_data, str) or isinstance(profile_data, str):
# Yahooquery sometimes returns strings like "No fundamentals data found"
raise ValueError(f"yahooquery returned string instead of dict: {price_data}")
name = price_data.get("longName") or price_data.get("shortName", "")
if not name:
raise ValueError(f"yahooquery returned empty name for {ticker}")
exchange_code = price_data.get("exchange", "")
currency = price_data.get("currency", "INR")
exchange_map = {
"NSI": "NSE (India)", "BSE": "BSE (India)",
"NMS": "NASDAQ (USA)", "NYQ": "NYSE (USA)",
"LSE": "LSE (UK)",
}
exchange_display = exchange_map.get(exchange_code, exchange_code or "Unknown")
country = profile_data.get("country", "India")
# Fiscal year end
fy_end = "March" if currency == "INR" else "December"
log(f" [OK] yahooquery: {name} | {ticker} | {exchange_display}")
return CompanyMeta(
name=name,
ticker=ticker,
exchange=exchange_display,
currency=currency,
sector=profile_data.get("sector", ""),
industry=profile_data.get("industry", ""),
country=country,
description=profile_data.get("longBusinessSummary", ""),
website=profile_data.get("website", ""),
mkt_cap=price_data.get("marketCap"),
share_price=price_data.get("regularMarketPrice"),
is_listed=True,
source_note="Data sourced from Yahoo Finance (yahooquery).",
fiscal_year_end=fy_end,
)
except Exception as e:
log(f" [ERR] yahooquery failed for {ticker}: {e}")
# FALLBACK: If yahooquery fails, try screener.in for Indian tickers
if ticker.endswith(".NS") or ticker.endswith(".BO"):
log(f" -> Trying screener.in fallback for {ticker}...")
try:
base = ticker.split(".")[0].upper()
import requests
from bs4 import BeautifulSoup
r = requests.get(f"https://www.screener.in/company/{base}/", headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
if r.ok:
soup = BeautifulSoup(r.text, "lxml")
h1 = soup.find("h1")
if h1:
name = h1.get_text(strip=True)
desc_div = soup.find("div", class_="company-profile")
desc = desc_div.get_text(strip=True) if desc_div else ""
log(f" [OK] screener.in resolved: {name}")
return CompanyMeta(
name=name, ticker=ticker, exchange="NSE/BSE (India)",
currency="INR", sector="Unknown", industry="Unknown",
country="India", description=desc[:500], website="",
mkt_cap=None, share_price=None, is_listed=True,
source_note="Data sourced from screener.in (yahooquery fallback).",
fiscal_year_end="March"
)
except Exception as e2:
log(f" [ERR] screener.in fallback failed: {e2}")
return None
# -- FMP resolution ------------------------------------------------------------
def _fmp_get(path: str, params: dict | None = None):
if not FMP_API_KEY:
return None
url = f"{FMP_BASE}{path}"
p = {"apikey": FMP_API_KEY}
if params: p.update(params)
try:
r = requests.get(url, params=p, timeout=REQUEST_TIMEOUT)
r.raise_for_status()
return r.json()
except Exception:
return None
def _fmp_resolve(query: str, log_fn=None) -> Optional[CompanyMeta]:
"""Try FMP search for global companies."""
def log(msg):
if log_fn: log_fn(msg)
if not FMP_API_KEY:
return None
log(" -> Searching FMP ...")
# Direct profile
profile = _fmp_get(f"/v3/profile/{query.upper()}")
if not (profile and isinstance(profile, list) and profile[0].get("symbol")):
# Search
results = _fmp_get("/v3/search", {"query": query, "limit": 5})
if results:
best = results[0]
profile = _fmp_get(f"/v3/profile/{best['symbol']}")
if not (profile and isinstance(profile, list)):
return None
p = profile[0]
if not p.get("companyName"):
return None
exchange = p.get("exchange", "") or p.get("exchangeShortName", "")
exchange_map = {"NSE":"NSE (India)","BSE":"BSE (India)","NASDAQ":"NASDAQ (USA)","NYSE":"NYSE (USA)"}
log(f" [OK] FMP: {p.get('companyName')} ({p.get('symbol')})")
return CompanyMeta(
name=p.get("companyName",""),
ticker=p.get("symbol",""),
exchange=exchange_map.get(exchange, exchange),
currency=p.get("currency","USD"),
sector=p.get("sector",""),
industry=p.get("industry",""),
country=p.get("country",""),
description=p.get("description",""),
website=p.get("website",""),
mkt_cap=p.get("mktCap"),
share_price=p.get("price"),
is_listed=True,
source_note="Data sourced from FMP.",
fiscal_year_end="December",
)
# -- yahooquery search by company name ------------------------------------------
def _yq_search(query: str, log_fn=None) -> Optional[CompanyMeta]:
"""Use yahooquery's search to find a ticker from a company name."""
def log(msg):
if log_fn: log_fn(msg)
try:
import yahooquery as yq
log(f" -> yahooquery search for '{query}' ...")
results = yq.search(query)
quotes = results.get("quotes", [])
if not quotes:
return None
# Take the first relevant result
for q in quotes:
symbol = q.get("symbol", "")
qtype = q.get("quoteType", "")
if qtype in ("EQUITY", "MUTUALFUND") and symbol:
log(f" -> Found: {q.get('longname', symbol)} ({symbol})")
return _yq_resolve(symbol, log_fn)
return None
except Exception as e:
log(f" [ERR] yahooquery search failed: {e}")
return None
# -- Public resolve() ----------------------------------------------------------
def resolve(query: str, log_fn=None) -> CompanyMeta:
"""
Resolve a company name or ticker to CompanyMeta.
Source priority: Indian map -> yahooquery direct -> yahooquery search -> FMP -> unlisted
"""
def log(msg):
if log_fn: log_fn(msg)
query = query.strip()
log(f"Resolving: '{query}' ...")
# 1. Check Indian name map -> resolve via yahooquery
indian_ticker = _guess_ticker(query)
if indian_ticker:
log(f" [OK] Matches known Indian map -> {indian_ticker}")
meta = _yq_resolve(indian_ticker, log_fn)
if meta: return meta
# 2. Try direct yahooquery (query as-is, and with .NS suffix)
for candidate in [query.upper(), f"{query.upper()}.NS", f"{query.upper()}.BO"]:
log(f" -> Trying direct ticker: {candidate}")
meta = _yq_resolve(candidate, log_fn)
if meta: return meta
# 3. yahooquery search
meta = _yq_search(query, log_fn)
if meta: return meta
# 4. FMP Search (for global companies)
meta = _fmp_resolve(query, log_fn)
if meta:
return meta
# 5. Unlisted fallback
log(f" ⚠ '{query}' not found in any public market database.")
log(" -> Treating as unlisted / private company.")
return CompanyMeta(
name=query, ticker="UNLISTED", exchange="Unlisted / Private",
currency="INR", sector="Unknown", industry="Unknown",
country="India", description="", website="",
mkt_cap=None, share_price=None, is_listed=False,
source_note=(
f"'{query}' could not be found. It may be unlisted, private, or use a different name. "
"Try using the exact BSE/NSE ticker (e.g. RELIANCE, TCS, HDFCBANK)."
),
fiscal_year_end="March",
)