From 966473f7c7d279f1a7047f45e1eec17bfb593f7b Mon Sep 17 00:00:00 2001 From: R5dan <167037995+R5dan@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:47:48 +0000 Subject: [PATCH] Add Market summary & status --- README.md | 1 + doc/source/reference/examples/market.py | 6 ++ doc/source/reference/index.rst | 2 + doc/source/reference/yfinance.market.rst | 16 ++++ yfinance/__init__.py | 3 +- yfinance/domain/market.py | 100 +++++++++++++++++++++++ 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 doc/source/reference/examples/market.py create mode 100644 doc/source/reference/yfinance.market.rst create mode 100644 yfinance/domain/market.py diff --git a/README.md b/README.md index 6f558c19..cbc656f4 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Yahoo! finance API is intended for personal use only.** - `Ticker`: single ticker data - `Tickers`: multiple tickers' data - `download`: download market data for multiple tickers +- `Market`: get infomation about a market - `Search`: quotes and news from search - `Sector` and `Industry`: sector and industry information - `EquityQuery` and `Screener`: build query to screen market diff --git a/doc/source/reference/examples/market.py b/doc/source/reference/examples/market.py new file mode 100644 index 00000000..07cec661 --- /dev/null +++ b/doc/source/reference/examples/market.py @@ -0,0 +1,6 @@ +import yfinance as yf + +EUROPE = yf.Market("EUROPE") + +status = EUROPE.status +summary = EUROPE.summary diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst index bbfa5455..5858a1a6 100644 --- a/doc/source/reference/index.rst +++ b/doc/source/reference/index.rst @@ -15,6 +15,7 @@ The following are the publicly available classes, and functions exposed by the ` - :attr:`Ticker `: Class for accessing single ticker data. - :attr:`Tickers `: Class for handling multiple tickers. +- :attr:`MarketSummary `: Class for accessing market summary. - :attr:`Search `: Class for accessing search results. - :attr:`Sector `: Domain class for accessing sector information. - :attr:`Industry `: Domain class for accessing industry information. @@ -33,6 +34,7 @@ The following are the publicly available classes, and functions exposed by the ` yfinance.stock yfinance.financials yfinance.analysis + yfinance.marketsummary yfinance.search yfinance.sector_industry yfinance.functions diff --git a/doc/source/reference/yfinance.market.rst b/doc/source/reference/yfinance.market.rst new file mode 100644 index 00000000..a11a8e82 --- /dev/null +++ b/doc/source/reference/yfinance.market.rst @@ -0,0 +1,16 @@ +===================== +Market Summary +===================== +.. currentmodule:: yfinance +Class +------------ +The `Market` class, allows you to access market data in a Pythonic way. +.. autosummary:: + :toctree: api/ + Market + +Market Sample Code +-------------------------- +The `Market` class, allows you to access market summary data in a Pythonic way. +.. literalinclude:: examples/market.py + :language: python \ No newline at end of file diff --git a/yfinance/__init__.py b/yfinance/__init__.py index bb79ca98..e2cefd55 100644 --- a/yfinance/__init__.py +++ b/yfinance/__init__.py @@ -30,6 +30,7 @@ from .domain.industry import Industry from .screener.screener import Screener from .screener.screener_query import EquityQuery +from .domain.market import Market __version__ = version.version __author__ = "Ran Aroussi" @@ -37,5 +38,5 @@ import warnings warnings.filterwarnings('default', category=DeprecationWarning, module='^yfinance') -__all__ = ['download', 'Search', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', +__all__ = ['download', 'Market', 'Search', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', 'Industry', 'EquityQuery', 'Screener'] diff --git a/yfinance/domain/market.py b/yfinance/domain/market.py new file mode 100644 index 00000000..e4c7d8b2 --- /dev/null +++ b/yfinance/domain/market.py @@ -0,0 +1,100 @@ +import datetime as dt + +from ..data import YfData +from ..data import utils +from ..const import _QUERY1_URL_ +import json as _json + +class Market(): + def __init__(self, market:'str', session=None, proxy=None, timeout=30): + self.market = market + self.session = session + self.proxy = proxy + 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, proxy=self.proxy, timeout=self.timeout) + if data is None or "Will be right back" in data.text: + raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n" + "Our engineers are working quickly to resolve " + "the issue. Thank you for your patience.") + try: + return data.json() + except _json.JSONDecodeError: + 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-GB", + "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: + 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 + try: + self._status.update( + open = dt.datetime.fromisoformat(self._status["open"]), + close = dt.datetime.fromisoformat(self._status["close"]), + tz = dt.timezone(self._status["timezone"]["gmtoffset"], self._status["timezone"]["short"]) + ) + except Exception as e: + self._logger.error(f"{self.market}: Failed to update market status") + self._logger.debug(f"{type(e)}: {e}") + except Exception as e: + self._logger.error(f"{self.market}: Failed to parse 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