Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Proposal: Support Sector and Industry data from Markets Tab #2058

Merged
merged 1 commit into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,55 @@ data = yf.download("SPY AAPL", period="1mo")

#### `yf.download()` and `Ticker.history()` have many options for configuring fetching and processing. [Review the Wiki](https://github.com/ranaroussi/yfinance/wiki) for more options and detail.

### Sector and Industry

The `Sector` and `Industry` modules allow you to access the US market information.

To initialize, use the relevant sector or industry key as below. (Complete mapping of the keys is available in `const.py`.)

```python
import yfinance as yf

tech = yf.Sector('technology')
software = yf.Industry('software-infrastructure')

# Common information
tech.key
tech.name
tech.symbol
tech.ticker
tech.overview
tech.top_companies
tech.research_reports

# Sector information
tech.top_etfs
tech.top_mutual_funds
tech.industries

# Industry information
software.sector_key
software.sector_name
software.top_performing_companies
software.top_growth_companies
```

The modules can be chained with Ticker as below.
```python
import yfinance as yf

# Ticker to Sector and Industry
msft = yf.Ticker('MSFT')
tech = yf.Sector(msft.info.get('sectorKey'))
software = yf.Industry(msft.info.get('industryKey'))

# Sector and Industry to Ticker
tech_ticker = tech.ticker
tech_ticker.info
software_ticker = software.ticker
software_ticker.history()
```

### Logging

`yfinance` now uses the `logging` module to handle messages, default behaviour is only print errors. If debugging, use `yf.enable_debug_mode()` to switch logging to debug with custom formatting.
Expand Down
4 changes: 3 additions & 1 deletion yfinance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
from .multi import download
from .utils import enable_debug_mode
from .cache import set_tz_cache_location
from .domain.sector import Sector
from .domain.industry import Industry

__version__ = version.version
__author__ = "Ran Aroussi"

import warnings
warnings.filterwarnings('default', category=DeprecationWarning, module='^yfinance')

__all__ = ['download', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location']
__all__ = ['download', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', 'Industry']
150 changes: 150 additions & 0 deletions yfinance/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
_QUERY1_URL_ = 'https://query1.finance.yahoo.com'
_BASE_URL_ = 'https://query2.finance.yahoo.com'
_ROOT_URL_ = 'https://finance.yahoo.com'

Expand Down Expand Up @@ -155,3 +156,152 @@
"recommendationTrend",
"futuresChain",
)

# map last updated as of 2024.09.18
SECTOR_INDUSTY_MAPPING = {
'basic-materials': {'specialty-chemicals',
'gold',
'building-materials',
'copper',
'steel',
'agricultural-inputs',
'chemicals',
'other-industrial-metals-mining',
'lumber-wood-production',
'aluminum',
'other-precious-metals-mining',
'coking-coal',
'paper-paper-products',
'silver'},
'communication-services': {'internet-content-information',
'telecom-services',
'entertainment',
'electronic-gaming-multimedia',
'advertising-agencies',
'broadcasting',
'publishing'},
'consumer-cyclical': {'internet-retail',
'auto-manufacturers',
'restaurants',
'home-improvement-retail',
'travel-services',
'specialty-retail',
'apparel-retail',
'residential-construction',
'footwear-accessories',
'packaging-containers',
'lodging',
'auto-parts',
'auto-truck-dealerships',
'gambling',
'resorts-casinos',
'leisure',
'apparel-manufacturing',
'personal-services',
'furnishings-fixtures-appliances',
'recreational-vehicles',
'luxury-goods',
'department-stores',
'textile-manufacturing'},
'consumer-defensive': {'discount-stores',
'beverages-non-alcoholic',
'household-personal-products',
'packaged-foods',
'tobacco',
'confectioners',
'farm-products',
'food-distribution',
'grocery-stores',
'beverages-brewers',
'education-training-services',
'beverages-wineries-distilleries'},
'energy': {'oil-gas-integrated',
'oil-gas-midstream',
'oil-gas-e-p',
'oil-gas-equipment-services',
'oil-gas-refining-marketing',
'uranium',
'oil-gas-drilling',
'thermal-coal'},
'financial-services': {'banks-diversified',
'credit-services',
'asset-management',
'insurance-diversified',
'banks-regional',
'capital-markets',
'financial-data-stock-exchanges',
'insurance-property-casualty',
'insurance-brokers',
'insurance-life',
'insurance-specialty',
'mortgage-finance',
'insurance-reinsurance',
'shell-companies',
'financial-conglomerates'},
'healthcare': {'drug-manufacturers-general',
'healthcare-plans',
'biotechnology',
'medical-devices',
'diagnostics-research',
'medical-instruments-supplies',
'medical-care-facilities',
'drug-manufacturers-specialty-generic',
'health-information-services',
'medical-distribution',
'pharmaceutical-retailers'},
'industrials': {'aerospace-defense',
'specialty-industrial-machinery',
'railroads',
'building-products-equipment',
'farm-heavy-construction-machinery',
'specialty-business-services',
'integrated-freight-logistics',
'waste-management',
'conglomerates',
'industrial-distribution',
'engineering-construction',
'rental-leasing-services',
'consulting-services',
'trucking',
'electrical-equipment-parts',
'airlines',
'tools-accessories',
'pollution-treatment-controls',
'security-protection-services',
'marine-shipping',
'metal-fabrication',
'infrastructure-operations',
'staffing-employment-services',
'airports-air-services',
'business-equipment-supplies'},
'real-estate': {'reit-specialty',
'reit-industrial',
'reit-retail',
'reit-residential',
'reit-healthcare-facilities',
'real-estate-services',
'reit-office',
'reit-diversified',
'reit-mortgage',
'reit-hotel-motel',
'real-estate-development',
'real-estate-diversified'},
'technology': {'software-infrastructure',
'semiconductors',
'consumer-electronics',
'software-application',
'information-technology-services',
'semiconductor-equipment-materials',
'communication-equipment',
'computer-hardware',
'electronic-components',
'scientific-technical-instruments',
'solar',
'electronics-computer-distribution'},
'utilities': {'utilities-regulated-electric',
'utilities-renewable',
'utilities-diversified',
'utilities-regulated-gas',
'utilities-independent-power-producers',
'utilities-regulated-water'}
}
5 changes: 5 additions & 0 deletions yfinance/domain/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# domain/__init__.py
from .sector import Sector
from .industry import Industry

__all__ = ['Sector', 'Industry']
97 changes: 97 additions & 0 deletions yfinance/domain/domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from ..ticker import Ticker
from ..const import _QUERY1_URL_
from ..data import YfData
from typing import Dict, List, Optional

import pandas as _pd

_QUERY_URL_ = f'{_QUERY1_URL_}/v1/finance'

class Domain:
def __init__(self, key: str, session=None, proxy=None):
self._key: str = key
self.proxy = proxy
self.session = session
self._data: YfData = YfData(session=session)

self._name: Optional[str] = None
self._symbol: Optional[str] = None
self._overview: Optional[Dict] = None
self._top_companies: Optional[_pd.DataFrame] = None
self._research_reports: Optional[List[Dict[str, str]]] = None

@property
def key(self) -> str:
return self._key

@property
def name(self) -> str:
self._ensure_fetched(self._name)
return self._name

@property
def symbol(self) -> str:
self._ensure_fetched(self._symbol)
return self._symbol

@property
def ticker(self) -> Ticker:
self._ensure_fetched(self._symbol)
return Ticker(self._symbol)

@property
def overview(self) -> Dict:
self._ensure_fetched(self._overview)
return self._overview

@property
def top_companies(self) -> Optional[_pd.DataFrame]:
self._ensure_fetched(self._top_companies)
return self._top_companies

@property
def research_reports(self) -> List[Dict[str, str]]:
self._ensure_fetched(self._research_reports)
return self._research_reports

def _fetch(self, query_url, proxy) -> Dict:
params_dict = {"formatted": "true", "withReturns": "true", "lang": "en-US", "region": "US"}
result = self._data.get_raw_json(query_url, user_agent_headers=self._data.user_agent_headers, params=params_dict, proxy=proxy)
return result

def _parse_and_assign_common(self, data) -> None:
self._name = data.get('name')
self._symbol = data.get('symbol')
self._overview = self._parse_overview(data.get('overview', {}))
self._top_companies = self._parse_top_companies(data.get('topCompanies', {}))
self._research_reports = data.get('researchReports')

def _parse_overview(self, overview) -> Dict:
return {
"companies_count": overview.get('companiesCount', None),
"market_cap": overview.get('marketCap', {}).get('raw', None),
"message_board_id": overview.get('messageBoardId', None),
"description": overview.get('description', None),
"industries_count": overview.get('industriesCount', None),
"market_weight": overview.get('marketWeight', {}).get('raw', None),
"employee_count": overview.get('employeeCount', {}).get('raw', None)
}

def _parse_top_companies(self, top_companies) -> Optional[_pd.DataFrame]:
top_companies_column = ['symbol', 'name', 'rating', 'market weight']
top_companies_values = [(c.get('symbol'),
c.get('name'),
c.get('rating'),
c.get('marketWeight',{}).get('raw',None)) for c in top_companies]

if not top_companies_values:
return None

return _pd.DataFrame(top_companies_values, columns = top_companies_column).set_index('symbol')

def _fetch_and_parse(self) -> None:
raise NotImplementedError("_fetch_and_parse() needs to be implemented by children classes")

def _ensure_fetched(self, attribute) -> None:
if attribute is None:
self._fetch_and_parse()
Loading
Loading