Allow region scoping for Sector and Industry
Yahoo's `/v1/finance/sectors/{key}` and `/industries/{key}` endpoints accept
an ISO 3166-1 alpha-2 `region` query parameter and return regional top
companies/ETFs/funds accordingly. The Domain base class hard-coded `region=US`,
so `Sector("technology").top_companies` always returned U.S. names regardless
of the user's interest.
- Add `region: str = "US"` to `Domain.__init__` and propagate through
`Sector` and `Industry`.
- Normalize input via `.strip().upper()` so "us", " GB ", and "Fr" all work.
- Use `self._region` in the request params.
- Document and test the behavior.
Closes #2601
This commit is contained in:
@@ -30,3 +30,13 @@ The modules can be chained with Ticker as below.
|
||||
|
||||
.. literalinclude:: examples/sector_industry_ticker.py
|
||||
:language: python
|
||||
|
||||
Region scoping
|
||||
--------------
|
||||
By default ``Sector`` and ``Industry`` return U.S. data. Pass a Yahoo region
|
||||
(ISO 3166-1 alpha-2 country code, case-insensitive) to scope ``top_companies``,
|
||||
``top_etfs``, ``top_mutual_funds`` and the industry top performing/growth lists::
|
||||
|
||||
yf.Sector("technology", region="GB").top_companies # UK
|
||||
yf.Sector("technology", region="DE").top_companies # Germany
|
||||
yf.Industry("software-infrastructure", region="JP") # Japan
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import unittest
|
||||
|
||||
from tests.context import yfinance as yf
|
||||
|
||||
|
||||
class TestSectorRegion(unittest.TestCase):
|
||||
def test_default_region_is_us(self):
|
||||
s = yf.Sector("technology")
|
||||
self.assertEqual(s._region, "US")
|
||||
|
||||
def test_region_is_normalized(self):
|
||||
for raw, expected in [("us", "US"), (" GB ", "GB"), ("Fr", "FR")]:
|
||||
s = yf.Sector("technology", region=raw)
|
||||
self.assertEqual(s._region, expected)
|
||||
|
||||
def test_us_and_gb_top_companies_differ(self):
|
||||
us = yf.Sector("technology").top_companies
|
||||
gb = yf.Sector("technology", region="GB").top_companies
|
||||
self.assertIsNotNone(us)
|
||||
self.assertIsNotNone(gb)
|
||||
# UK-listed symbols carry the .L suffix, U.S. symbols do not.
|
||||
self.assertTrue(any(sym.endswith(".L") for sym in gb.index))
|
||||
self.assertFalse(any(sym.endswith(".L") for sym in us.index))
|
||||
|
||||
def test_industry_region_propagates(self):
|
||||
ind = yf.Industry("software-infrastructure", region="DE")
|
||||
self.assertEqual(ind._region, "DE")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -14,16 +14,20 @@ class Domain(ABC):
|
||||
and methods for fetching and parsing data. Derived classes must implement the `_fetch_and_parse()` method.
|
||||
"""
|
||||
|
||||
def __init__(self, key: str, session=None):
|
||||
def __init__(self, key: str, session=None, region: str = "US"):
|
||||
"""
|
||||
Initializes the Domain object with a key, session.
|
||||
Initializes the Domain object with a key, session, and region.
|
||||
|
||||
Args:
|
||||
key (str): Unique key identifying the domain entity.
|
||||
session (Optional[requests.Session]): Session object for HTTP requests. Defaults to None.
|
||||
region (str): Yahoo region (ISO 3166-1 alpha-2 country code, e.g.
|
||||
"US", "GB", "FR", "DE", "JP"). Determines the regional scope
|
||||
of returned data such as ``top_companies``. Defaults to "US".
|
||||
"""
|
||||
self._key: str = key
|
||||
self.session = session
|
||||
self._region: str = region.strip().upper()
|
||||
self._data: YfData = YfData(session=session)
|
||||
|
||||
self._name: Optional[str] = None
|
||||
@@ -118,7 +122,7 @@ class Domain(ABC):
|
||||
Returns:
|
||||
Dict: The JSON response data from the request.
|
||||
"""
|
||||
params_dict = {"formatted": "true", "withReturns": "true", "lang": "en-US", "region": "US"}
|
||||
params_dict = {"formatted": "true", "withReturns": "true", "lang": "en-US", "region": self._region}
|
||||
result = self._data.get_raw_json(query_url, params=params_dict)
|
||||
return result
|
||||
|
||||
|
||||
@@ -14,14 +14,17 @@ class Industry(Domain):
|
||||
Represents an industry within a sector.
|
||||
"""
|
||||
|
||||
def __init__(self, key, session=None):
|
||||
def __init__(self, key, session=None, region: str = "US"):
|
||||
"""
|
||||
Args:
|
||||
key (str): The key identifier for the industry.
|
||||
session (optional): The session to use for requests.
|
||||
region (str): Yahoo region (ISO 3166-1 alpha-2 country code, e.g.
|
||||
"US", "GB", "FR", "DE", "JP"). Scopes top performing/growth
|
||||
company listings. Defaults to "US".
|
||||
"""
|
||||
YfData(session=session)
|
||||
super(Industry, self).__init__(key, session)
|
||||
super(Industry, self).__init__(key, session, region)
|
||||
self._query_url = f'{_QUERY_URL_}/industries/{self._key}'
|
||||
|
||||
self._sector_key = None
|
||||
|
||||
@@ -15,18 +15,21 @@ class Sector(Domain):
|
||||
such as top ETFs, top mutual funds, and industry data.
|
||||
"""
|
||||
|
||||
def __init__(self, key, session=None):
|
||||
def __init__(self, key, session=None, region: str = "US"):
|
||||
"""
|
||||
Args:
|
||||
key (str): The key representing the sector.
|
||||
session (requests.Session, optional): A session for making requests. Defaults to None.
|
||||
|
||||
region (str): Yahoo region (ISO 3166-1 alpha-2 country code, e.g.
|
||||
"US", "GB", "FR", "DE", "JP"). Scopes ``top_companies``,
|
||||
``top_etfs`` and ``top_mutual_funds``. Defaults to "US".
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
||||
:attr:`Sector.industries <yfinance.Sector.industries>`
|
||||
Map of sector and industry
|
||||
"""
|
||||
super(Sector, self).__init__(key, session)
|
||||
super(Sector, self).__init__(key, session, region)
|
||||
self._query_url: str = f'{_QUERY_URL_}/sectors/{self._key}'
|
||||
self._top_etfs: Optional[Dict] = None
|
||||
self._top_mutual_funds: Optional[Dict] = None
|
||||
|
||||
Reference in New Issue
Block a user