Import python venv for stability
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# domain/__init__.py
|
||||
from .sector import Sector
|
||||
from .industry import Industry
|
||||
|
||||
__all__ = ['Sector', 'Industry']
|
||||
@@ -0,0 +1,195 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import pandas as _pd
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ..const import _QUERY1_URL_
|
||||
from ..data import YfData
|
||||
from ..ticker import Ticker
|
||||
|
||||
_QUERY_URL_ = f'{_QUERY1_URL_}/v1/finance'
|
||||
|
||||
class Domain(ABC):
|
||||
"""
|
||||
Abstract base class representing a domain entity in financial data, with key attributes
|
||||
and methods for fetching and parsing data. Derived classes must implement the `_fetch_and_parse()` method.
|
||||
"""
|
||||
|
||||
def __init__(self, key: str, session=None):
|
||||
"""
|
||||
Initializes the Domain object with a key, session.
|
||||
|
||||
Args:
|
||||
key (str): Unique key identifying the domain entity.
|
||||
session (Optional[requests.Session]): Session object for HTTP requests. Defaults to None.
|
||||
"""
|
||||
self._key: str = key
|
||||
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:
|
||||
"""
|
||||
Retrieves the key of the domain entity.
|
||||
|
||||
Returns:
|
||||
str: The unique key of the domain entity.
|
||||
"""
|
||||
return self._key
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""
|
||||
Retrieves the name of the domain entity.
|
||||
|
||||
Returns:
|
||||
str: The name of the domain entity.
|
||||
"""
|
||||
self._ensure_fetched(self._name)
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def symbol(self) -> str:
|
||||
"""
|
||||
Retrieves the symbol of the domain entity.
|
||||
|
||||
Returns:
|
||||
str: The symbol representing the domain entity.
|
||||
"""
|
||||
self._ensure_fetched(self._symbol)
|
||||
return self._symbol
|
||||
|
||||
@property
|
||||
def ticker(self) -> Ticker:
|
||||
"""
|
||||
Retrieves a Ticker object based on the domain entity's symbol.
|
||||
|
||||
Returns:
|
||||
Ticker: A Ticker object associated with the domain entity.
|
||||
"""
|
||||
self._ensure_fetched(self._symbol)
|
||||
return Ticker(self._symbol)
|
||||
|
||||
@property
|
||||
def overview(self) -> Dict:
|
||||
"""
|
||||
Retrieves the overview information of the domain entity.
|
||||
|
||||
Returns:
|
||||
Dict: A dictionary containing an overview of the domain entity.
|
||||
"""
|
||||
self._ensure_fetched(self._overview)
|
||||
return self._overview
|
||||
|
||||
@property
|
||||
def top_companies(self) -> Optional[_pd.DataFrame]:
|
||||
"""
|
||||
Retrieves the top companies within the domain entity.
|
||||
|
||||
Returns:
|
||||
pandas.DataFrame: A DataFrame containing the top companies in the domain.
|
||||
"""
|
||||
self._ensure_fetched(self._top_companies)
|
||||
return self._top_companies
|
||||
|
||||
@property
|
||||
def research_reports(self) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Retrieves research reports related to the domain entity.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, str]]: A list of research reports, where each report is a dictionary with metadata.
|
||||
"""
|
||||
self._ensure_fetched(self._research_reports)
|
||||
return self._research_reports
|
||||
|
||||
def _fetch(self, query_url) -> Dict:
|
||||
"""
|
||||
Fetches data from the given query URL.
|
||||
|
||||
Args:
|
||||
query_url (str): The URL used for the data query.
|
||||
|
||||
Returns:
|
||||
Dict: The JSON response data from the request.
|
||||
"""
|
||||
params_dict = {"formatted": "true", "withReturns": "true", "lang": "en-US", "region": "US"}
|
||||
result = self._data.get_raw_json(query_url, params=params_dict)
|
||||
return result
|
||||
|
||||
def _parse_and_assign_common(self, data) -> None:
|
||||
"""
|
||||
Parses and assigns common data fields such as name, symbol, overview, and top companies.
|
||||
|
||||
Args:
|
||||
data (Dict): The raw data received from the API.
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
Parses the overview data for the domain entity.
|
||||
|
||||
Args:
|
||||
overview (Dict): The raw overview data.
|
||||
|
||||
Returns:
|
||||
Dict: A dictionary containing parsed overview information.
|
||||
"""
|
||||
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]:
|
||||
"""
|
||||
Parses the top companies data and converts it into a pandas DataFrame.
|
||||
|
||||
Args:
|
||||
top_companies (Dict): The raw top companies data.
|
||||
|
||||
Returns:
|
||||
Optional[pandas.DataFrame]: A DataFrame containing top company data, or None if no data is available.
|
||||
"""
|
||||
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')
|
||||
|
||||
@abstractmethod
|
||||
def _fetch_and_parse(self) -> None:
|
||||
"""
|
||||
Abstract method for fetching and parsing domain-specific data.
|
||||
Must be implemented by derived classes.
|
||||
"""
|
||||
raise NotImplementedError("_fetch_and_parse() needs to be implemented by children classes")
|
||||
|
||||
def _ensure_fetched(self, attribute) -> None:
|
||||
"""
|
||||
Ensures that the given attribute is fetched by calling `_fetch_and_parse()` if the attribute is None.
|
||||
|
||||
Args:
|
||||
attribute: The attribute to check and potentially fetch.
|
||||
"""
|
||||
if attribute is None:
|
||||
self._fetch_and_parse()
|
||||
@@ -0,0 +1,153 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import pandas as _pd
|
||||
from typing import Dict, Optional
|
||||
|
||||
from .. import utils
|
||||
from ..config import YfConfig
|
||||
from ..data import YfData
|
||||
|
||||
from .domain import Domain, _QUERY_URL_
|
||||
|
||||
class Industry(Domain):
|
||||
"""
|
||||
Represents an industry within a sector.
|
||||
"""
|
||||
|
||||
def __init__(self, key, session=None):
|
||||
"""
|
||||
Args:
|
||||
key (str): The key identifier for the industry.
|
||||
session (optional): The session to use for requests.
|
||||
"""
|
||||
YfData(session=session)
|
||||
super(Industry, self).__init__(key, session)
|
||||
self._query_url = f'{_QUERY_URL_}/industries/{self._key}'
|
||||
|
||||
self._sector_key = None
|
||||
self._sector_name = None
|
||||
self._top_performing_companies = None
|
||||
self._top_growth_companies = None
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns a string representation of the Industry instance.
|
||||
|
||||
Returns:
|
||||
str: String representation of the Industry instance.
|
||||
"""
|
||||
return f'yfinance.Industry object <{self._key}>'
|
||||
|
||||
@property
|
||||
def sector_key(self) -> str:
|
||||
"""
|
||||
Returns the sector key of the industry.
|
||||
|
||||
Returns:
|
||||
str: The sector key.
|
||||
"""
|
||||
self._ensure_fetched(self._sector_key)
|
||||
return self._sector_key
|
||||
|
||||
@property
|
||||
def sector_name(self) -> str:
|
||||
"""
|
||||
Returns the sector name of the industry.
|
||||
|
||||
Returns:
|
||||
str: The sector name.
|
||||
"""
|
||||
self._ensure_fetched(self._sector_name)
|
||||
return self._sector_name
|
||||
|
||||
@property
|
||||
def top_performing_companies(self) -> Optional[_pd.DataFrame]:
|
||||
"""
|
||||
Returns the top performing companies in the industry.
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: DataFrame containing top performing companies.
|
||||
"""
|
||||
self._ensure_fetched(self._top_performing_companies)
|
||||
return self._top_performing_companies
|
||||
|
||||
@property
|
||||
def top_growth_companies(self) -> Optional[_pd.DataFrame]:
|
||||
"""
|
||||
Returns the top growth companies in the industry.
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: DataFrame containing top growth companies.
|
||||
"""
|
||||
self._ensure_fetched(self._top_growth_companies)
|
||||
return self._top_growth_companies
|
||||
|
||||
def _parse_top_performing_companies(self, top_performing_companies: Dict) -> Optional[_pd.DataFrame]:
|
||||
"""
|
||||
Parses the top performing companies data.
|
||||
|
||||
Args:
|
||||
top_performing_companies (Dict): Dictionary containing top performing companies data.
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: DataFrame containing parsed top performing companies data.
|
||||
"""
|
||||
compnaies_column = ['symbol','name','ytd return','last price','target price']
|
||||
compnaies_values = [(c.get('symbol', None),
|
||||
c.get('name', None),
|
||||
c.get('ytdReturn',{}).get('raw', None),
|
||||
c.get('lastPrice',{}).get('raw', None),
|
||||
c.get('targetPrice',{}).get('raw', None),) for c in top_performing_companies]
|
||||
|
||||
if not compnaies_values:
|
||||
return None
|
||||
|
||||
return _pd.DataFrame(compnaies_values, columns = compnaies_column).set_index('symbol')
|
||||
|
||||
def _parse_top_growth_companies(self, top_growth_companies: Dict) -> Optional[_pd.DataFrame]:
|
||||
"""
|
||||
Parses the top growth companies data.
|
||||
|
||||
Args:
|
||||
top_growth_companies (Dict): Dictionary containing top growth companies data.
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: DataFrame containing parsed top growth companies data.
|
||||
"""
|
||||
compnaies_column = ['symbol','name','ytd return','growth estimate']
|
||||
compnaies_values = [(c.get('symbol', None),
|
||||
c.get('name', None),
|
||||
c.get('ytdReturn',{}).get('raw', None),
|
||||
c.get('growthEstimate',{}).get('raw', None),) for c in top_growth_companies]
|
||||
|
||||
if not compnaies_values:
|
||||
return None
|
||||
|
||||
return _pd.DataFrame(compnaies_values, columns = compnaies_column).set_index('symbol')
|
||||
|
||||
def _fetch_and_parse(self) -> None:
|
||||
"""
|
||||
Fetches and parses the industry data.
|
||||
"""
|
||||
result = None
|
||||
|
||||
try:
|
||||
result = self._fetch(self._query_url)
|
||||
data = result['data']
|
||||
self._parse_and_assign_common(data)
|
||||
|
||||
self._sector_key = data.get('sectorKey')
|
||||
self._sector_name = data.get('sectorName')
|
||||
self._top_performing_companies = self._parse_top_performing_companies(data.get('topPerformingCompanies'))
|
||||
self._top_growth_companies = self._parse_top_growth_companies(data.get('topGrowthCompanies'))
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
logger = utils.get_yf_logger()
|
||||
logger.error(f"Failed to get industry data for '{self._key}' reason: {e}")
|
||||
logger.debug("Got response: ")
|
||||
logger.debug("-------------")
|
||||
logger.debug(f" {result}")
|
||||
logger.debug("-------------")
|
||||
@@ -0,0 +1,107 @@
|
||||
import datetime as dt
|
||||
import json as _json
|
||||
|
||||
from ..config import YfConfig
|
||||
from ..const import _QUERY1_URL_
|
||||
from ..data import utils, YfData
|
||||
from ..exceptions import YFDataException
|
||||
|
||||
class Market:
|
||||
def __init__(self, market:'str', session=None, timeout=30):
|
||||
self.market = market
|
||||
self.session = session
|
||||
self.timeout = timeout
|
||||
|
||||
self._data = YfData(session=self.session)
|
||||
|
||||
self._logger = utils.get_yf_logger()
|
||||
|
||||
self._status = None
|
||||
self._summary = None
|
||||
|
||||
def _fetch_json(self, url, params):
|
||||
data = self._data.cache_get(url=url, params=params, timeout=self.timeout)
|
||||
if data is None or "Will be right back" in data.text:
|
||||
raise YFDataException("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***")
|
||||
try:
|
||||
return data.json()
|
||||
except _json.JSONDecodeError:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
self._logger.error(f"{self.market}: Failed to retrieve market data and recieved faulty data.")
|
||||
return {}
|
||||
|
||||
def _parse_data(self):
|
||||
# Fetch both to ensure they are at the same time
|
||||
if (self._status is not None) and (self._summary is not None):
|
||||
return
|
||||
|
||||
self._logger.debug(f"{self.market}: Parsing market data")
|
||||
|
||||
# Summary
|
||||
|
||||
summary_url = f"{_QUERY1_URL_}/v6/finance/quote/marketSummary"
|
||||
summary_fields = ["shortName", "regularMarketPrice", "regularMarketChange", "regularMarketChangePercent"]
|
||||
summary_params = {
|
||||
"fields": ",".join(summary_fields),
|
||||
"formatted": False,
|
||||
"lang": "en-US",
|
||||
"market": self.market
|
||||
}
|
||||
|
||||
status_url = f"{_QUERY1_URL_}/v6/finance/markettime"
|
||||
status_params = {
|
||||
"formatted": True,
|
||||
"key": "finance",
|
||||
"lang": "en-US",
|
||||
"market": self.market
|
||||
}
|
||||
|
||||
self._summary = self._fetch_json(summary_url, summary_params)
|
||||
self._status = self._fetch_json(status_url, status_params)
|
||||
|
||||
try:
|
||||
self._summary = self._summary['marketSummaryResponse']['result']
|
||||
self._summary = {x['exchange']:x for x in self._summary}
|
||||
except Exception as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
self._logger.error(f"{self.market}: Failed to parse market summary")
|
||||
self._logger.debug(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
try:
|
||||
# Unpack
|
||||
self._status = self._status['finance']['marketTimes'][0]['marketTime'][0]
|
||||
self._status['timezone'] = self._status['timezone'][0]
|
||||
del self._status['time'] # redundant
|
||||
except Exception as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
self._logger.error(f"{self.market}: Failed to parse market status")
|
||||
self._logger.debug(f"{type(e)}: {e}")
|
||||
try:
|
||||
self._status.update({
|
||||
"open": dt.datetime.fromisoformat(self._status["open"]),
|
||||
"close": dt.datetime.fromisoformat(self._status["close"]),
|
||||
"tz": dt.timezone(dt.timedelta(hours=int(self._status["timezone"]["gmtoffset"]))/1000, self._status["timezone"]["short"])
|
||||
})
|
||||
except Exception as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
self._logger.error(f"{self.market}: Failed to update market status")
|
||||
self._logger.debug(f"{type(e)}: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
self._parse_data()
|
||||
return self._status
|
||||
|
||||
|
||||
@property
|
||||
def summary(self):
|
||||
self._parse_data()
|
||||
return self._summary
|
||||
@@ -0,0 +1,152 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import pandas as _pd
|
||||
from typing import Dict, Optional
|
||||
|
||||
from ..config import YfConfig
|
||||
from ..const import SECTOR_INDUSTY_MAPPING_LC
|
||||
from ..utils import dynamic_docstring, generate_list_table_from_dict, get_yf_logger
|
||||
|
||||
from .domain import Domain, _QUERY_URL_
|
||||
|
||||
class Sector(Domain):
|
||||
"""
|
||||
Represents a financial market sector and allows retrieval of sector-related data
|
||||
such as top ETFs, top mutual funds, and industry data.
|
||||
"""
|
||||
|
||||
def __init__(self, key, session=None):
|
||||
"""
|
||||
Args:
|
||||
key (str): The key representing the sector.
|
||||
session (requests.Session, optional): A session for making requests. Defaults to None.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`Sector.industries <yfinance.Sector.industries>`
|
||||
Map of sector and industry
|
||||
"""
|
||||
super(Sector, self).__init__(key, session)
|
||||
self._query_url: str = f'{_QUERY_URL_}/sectors/{self._key}'
|
||||
self._top_etfs: Optional[Dict] = None
|
||||
self._top_mutual_funds: Optional[Dict] = None
|
||||
self._industries: Optional[_pd.DataFrame] = None
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Returns the string representation of the Sector object.
|
||||
|
||||
Returns:
|
||||
str: A string representation of the object.
|
||||
"""
|
||||
return f'yfinance.Sector object <{self._key}>'
|
||||
|
||||
@property
|
||||
def top_etfs(self) -> Dict[str, str]:
|
||||
"""
|
||||
Gets the top ETFs for the sector.
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: A dictionary of ETF symbols and names.
|
||||
"""
|
||||
self._ensure_fetched(self._top_etfs)
|
||||
return self._top_etfs
|
||||
|
||||
@property
|
||||
def top_mutual_funds(self) -> Dict[str, str]:
|
||||
"""
|
||||
Gets the top mutual funds for the sector.
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: A dictionary of mutual fund symbols and names.
|
||||
"""
|
||||
self._ensure_fetched(self._top_mutual_funds)
|
||||
return self._top_mutual_funds
|
||||
|
||||
@dynamic_docstring({"sector_industry": generate_list_table_from_dict(SECTOR_INDUSTY_MAPPING_LC,bullets=True)})
|
||||
@property
|
||||
def industries(self) -> _pd.DataFrame:
|
||||
"""
|
||||
Gets the industries within the sector.
|
||||
|
||||
Returns:
|
||||
pandas.DataFrame: A DataFrame with industries' key, name, symbol, and market weight.
|
||||
|
||||
{sector_industry}
|
||||
"""
|
||||
self._ensure_fetched(self._industries)
|
||||
return self._industries
|
||||
|
||||
def _parse_top_etfs(self, top_etfs: Dict) -> Dict[str, str]:
|
||||
"""
|
||||
Parses top ETF data from the API response.
|
||||
|
||||
Args:
|
||||
top_etfs (Dict): The raw ETF data from the API response.
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: A dictionary of ETF symbols and names.
|
||||
"""
|
||||
return {e.get('symbol'): e.get('name') for e in top_etfs}
|
||||
|
||||
def _parse_top_mutual_funds(self, top_mutual_funds: Dict) -> Dict[str, str]:
|
||||
"""
|
||||
Parses top mutual funds data from the API response.
|
||||
|
||||
Args:
|
||||
top_mutual_funds (Dict): The raw mutual fund data from the API response.
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: A dictionary of mutual fund symbols and names.
|
||||
"""
|
||||
return {e.get('symbol'): e.get('name') for e in top_mutual_funds}
|
||||
|
||||
def _parse_industries(self, industries: Dict) -> _pd.DataFrame:
|
||||
"""
|
||||
Parses industry data from the API response into a DataFrame.
|
||||
|
||||
Args:
|
||||
industries (Dict): The raw industry data from the API response.
|
||||
|
||||
Returns:
|
||||
pandas.DataFrame: A DataFrame containing industry key, name, symbol, and market weight.
|
||||
"""
|
||||
industries_column = ['key','name','symbol','market weight']
|
||||
industries_values = [(i.get('key'),
|
||||
i.get('name'),
|
||||
i.get('symbol'),
|
||||
i.get('marketWeight',{}).get('raw', None)
|
||||
) for i in industries if i.get('name') != 'All Industries']
|
||||
return _pd.DataFrame(industries_values, columns=industries_column).set_index('key')
|
||||
|
||||
def _fetch_and_parse(self) -> None:
|
||||
"""
|
||||
Fetches and parses sector data from the API.
|
||||
|
||||
Fetches data for the sector and parses the top ETFs, top mutual funds,
|
||||
and industries within the sector. Stores the parsed data in the corresponding
|
||||
attributes `_top_etfs`, `_top_mutual_funds`, and `_industries`.
|
||||
|
||||
Raises:
|
||||
Exception: If fetching or parsing the sector data fails.
|
||||
"""
|
||||
result = None
|
||||
|
||||
try:
|
||||
result = self._fetch(self._query_url)
|
||||
data = result['data']
|
||||
self._parse_and_assign_common(data)
|
||||
|
||||
self._top_etfs = self._parse_top_etfs(data.get('topETFs', {}))
|
||||
self._top_mutual_funds = self._parse_top_mutual_funds(data.get('topMutualFunds', {}))
|
||||
self._industries = self._parse_industries(data.get('industries', {}))
|
||||
|
||||
except Exception as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
logger = get_yf_logger()
|
||||
logger.error(f"Failed to get sector data for '{self._key}' reason: {e}")
|
||||
logger.debug("Got response: ")
|
||||
logger.debug("-------------")
|
||||
logger.debug(f" {result}")
|
||||
logger.debug("-------------")
|
||||
Reference in New Issue
Block a user