diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000..9b786ce52
Binary files /dev/null and b/.DS_Store differ
diff --git a/.github/workflows/deploy_doc.yml b/.github/workflows/deploy_doc.yml
new file mode 100644
index 000000000..3afaa75f2
--- /dev/null
+++ b/.github/workflows/deploy_doc.yml
@@ -0,0 +1,43 @@
+name: Build and Deploy Sphinx Docs
+
+on:
+ push:
+ branches:
+ - dev-documented
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out the repository
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ pip install Sphinx==8.0.2 pydata-sphinx-theme==0.15.4 Jinja2==3.1.4 sphinx-copybutton==0.5.2
+
+ - name: Build Sphinx documentation
+ run: |
+ sphinx-build -b html doc/source doc/_build/html -v
+
+ - name: List generated HTML files
+ run: |
+ ls -l -R doc/_build/html
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_branch: documentation
+ publish_dir: doc/_build/html
+ destination_dir: docs
+ enable_jekyll: false
diff --git a/.gitignore b/.gitignore
index a2b988c12..b5d9905c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,10 @@ test.ipynb
env/
venv/
ENV/
+
+# Documentation
+/doc/build/
+/doc/_build/
+/doc/source/reference/api
+!yfinance.css
+!/doc/source/development/assets/branches.png
\ No newline at end of file
diff --git a/README.md b/README.md
index d631bdf53..bd47e8f39 100644
--- a/README.md
+++ b/README.md
@@ -32,328 +32,31 @@ Yahoo! finance API is intended for personal use only.**
+**yfinance** offers a Pythonic way to fetch financial & market data from [Yahoo!Ⓡ finance](https://finance.yahoo.com).
-**yfinance** offers a threaded and Pythonic way to download market data from [Yahoo!Ⓡ finance](https://finance.yahoo.com).
+## Main components
-→ Check out this [Blog post](https://aroussi.com/#post/python-yahoo-finance) for a detailed tutorial with code examples.
+- `Ticker`: single ticker data
+- `Tickers`: multiple tickers' data
+- `download`: download market data for multiple tickers
+- `Sector` and `Industry`: sector and industry information
+- `EquityQuery` and `Screener`: build query to screen market
-[Changelog »](https://github.com/ranaroussi/yfinance/blob/main/CHANGELOG.rst)
-
----
-
-- [Installation](#installation)
-- [Quick start](#quick-start)
-- [Advanced](#logging)
-- [Wiki](https://github.com/ranaroussi/yfinance/wiki)
-- [Contribute](#developers-want-to-contribute)
-
----
+## **NEW DOCUMENTATION WEBSITE**: [ranaroussi.github.io/yfinance](https://ranaroussi.github.io/yfinance/index.html)
## Installation
-Install `yfinance` using `pip`:
-
-``` {.sourceCode .bash}
-$ pip install yfinance --upgrade --no-cache-dir
-```
-
-[With Conda](https://anaconda.org/ranaroussi/yfinance).
-
-To install with optional dependencies, replace `optional` with: `nospam` for [caching-requests](#smarter-scraping), `repair` for [price repair](https://github.com/ranaroussi/yfinance/wiki/Price-repair), or `nospam,repair` for both:
+Install `yfinance` from PYPI using `pip`:
``` {.sourceCode .bash}
-$ pip install "yfinance[optional]"
-```
-
-[Required dependencies](./requirements.txt) , [all dependencies](./setup.py#L62).
-
----
-
-## Quick Start
-
-### The Ticker module
-
-The `Ticker` module, which allows you to access ticker data in a more Pythonic way:
-
-```python
-import yfinance as yf
-
-msft = yf.Ticker("MSFT")
-
-# get all stock info
-msft.info
-
-# get historical market data
-hist = msft.history(period="1mo")
-
-# show meta information about the history (requires history() to be called first)
-msft.history_metadata
-
-# show actions (dividends, splits, capital gains)
-msft.actions
-msft.dividends
-msft.splits
-msft.capital_gains # only for mutual funds & etfs
-
-# show share count
-msft.get_shares_full(start="2022-01-01", end=None)
-
-# show financials:
-msft.calendar
-msft.sec_filings
-# - income statement
-msft.income_stmt
-msft.quarterly_income_stmt
-# - balance sheet
-msft.balance_sheet
-msft.quarterly_balance_sheet
-# - cash flow statement
-msft.cashflow
-msft.quarterly_cashflow
-# see `Ticker.get_income_stmt()` for more options
-
-# show holders
-msft.major_holders
-msft.institutional_holders
-msft.mutualfund_holders
-msft.insider_transactions
-msft.insider_purchases
-msft.insider_roster_holders
-
-msft.sustainability
-
-# show recommendations
-msft.recommendations
-msft.recommendations_summary
-msft.upgrades_downgrades
-
-# show analysts data
-msft.analyst_price_targets
-msft.earnings_estimate
-msft.revenue_estimate
-msft.earnings_history
-msft.eps_trend
-msft.eps_revisions
-msft.growth_estimates
-
-# Show future and historic earnings dates, returns at most next 4 quarters and last 8 quarters by default.
-# Note: If more are needed use msft.get_earnings_dates(limit=XX) with increased limit argument.
-msft.earnings_dates
-
-# show ISIN code - *experimental*
-# ISIN = International Securities Identification Number
-msft.isin
-
-# show options expirations
-msft.options
-
-# show news
-msft.news
-
-# get option chain for specific expiration
-opt = msft.option_chain('YYYY-MM-DD')
-# data available via: opt.calls, opt.puts
-```
-
-For tickers that are ETFs/Mutual Funds, `Ticker.funds_data` provides access to fund related data.
-
-Funds' Top Holdings and other data with category average is returned as `pd.DataFrame`.
-
-```python
-import yfinance as yf
-spy = yf.Ticker('SPY')
-data = spy.funds_data
-
-# show fund description
-data.description
-
-# show operational information
-data.fund_overview
-data.fund_operations
-
-# show holdings related information
-data.asset_classes
-data.top_holdings
-data.equity_holdings
-data.bond_holdings
-data.bond_ratings
-data.sector_weightings
-```
-
-If you want to use a proxy server for downloading data, use:
-
-```python
-import yfinance as yf
-
-msft = yf.Ticker("MSFT")
-
-msft.history(..., proxy="PROXY_SERVER")
-msft.get_actions(proxy="PROXY_SERVER")
-msft.get_dividends(proxy="PROXY_SERVER")
-msft.get_splits(proxy="PROXY_SERVER")
-msft.get_capital_gains(proxy="PROXY_SERVER")
-msft.get_balance_sheet(proxy="PROXY_SERVER")
-msft.get_cashflow(proxy="PROXY_SERVER")
-msft.option_chain(..., proxy="PROXY_SERVER")
-...
+$ pip install yfinance
```
-### Multiple tickers
-
-To initialize multiple `Ticker` objects, use
-
-```python
-import yfinance as yf
-
-tickers = yf.Tickers('msft aapl goog')
-
-# access each ticker using (example)
-tickers.tickers['MSFT'].info
-tickers.tickers['AAPL'].history(period="1mo")
-tickers.tickers['GOOG'].actions
-```
-
-To download price history into one table:
-
-```python
-import yfinance as yf
-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()
-```
-
-### Market Screener
-The `Screener` module allows you to screen the market based on specified queries.
-
-#### Query Construction
-To create a query, you can use the `EquityQuery` class to construct your filters step by step. The queries support operators: `GT` (greater than), `LT` (less than), `BTWN` (between), `EQ` (equals), and logical operators `AND` and `OR` for combining multiple conditions.
-
-#### Screener
-The `Screener` class is used to execute the queries and return the filtered results. You can set a custom body for the screener or use predefined configurations.
-
-
-
-### 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.
-
-### Smarter scraping
-
-Install the `nospam` packages for smarter scraping using `pip` (see [Installation](#installation)). These packages help cache calls such that Yahoo is not spammed with requests.
-
-To use a custom `requests` session, pass a `session=` argument to
-the Ticker constructor. This allows for caching calls to the API as well as a custom way to modify requests via the `User-agent` header.
-
-```python
-import requests_cache
-session = requests_cache.CachedSession('yfinance.cache')
-session.headers['User-agent'] = 'my-program/1.0'
-ticker = yf.Ticker('msft', session=session)
-# The scraped response will be stored in the cache
-ticker.actions
-```
-
-Combine `requests_cache` with rate-limiting to avoid triggering Yahoo's rate-limiter/blocker that can corrupt data.
-```python
-from requests import Session
-from requests_cache import CacheMixin, SQLiteCache
-from requests_ratelimiter import LimiterMixin, MemoryQueueBucket
-from pyrate_limiter import Duration, RequestRate, Limiter
-class CachedLimiterSession(CacheMixin, LimiterMixin, Session):
- pass
-
-session = CachedLimiterSession(
- limiter=Limiter(RequestRate(2, Duration.SECOND*5)), # max 2 requests per 5 seconds
- bucket_class=MemoryQueueBucket,
- backend=SQLiteCache("yfinance.cache"),
-)
-```
-
-### Managing Multi-Level Columns
-
-The following answer on Stack Overflow is for [How to deal with
-multi-level column names downloaded with
-yfinance?](https://stackoverflow.com/questions/63107801)
-
-- `yfinance` returns a `pandas.DataFrame` with multi-level column
- names, with a level for the ticker and a level for the stock price
- data
- - The answer discusses:
- - How to correctly read the the multi-level columns after
- saving the dataframe to a csv with `pandas.DataFrame.to_csv`
- - How to download single or multiple tickers into a single
- dataframe with single level column names and a ticker column
-
-### Persistent cache store
-
-To reduce Yahoo, yfinance store some data locally: timezones to localize dates, and cookie. Cache location is:
-
-- Windows = C:/Users/\/AppData/Local/py-yfinance
-- Linux = /home/\/.cache/py-yfinance
-- MacOS = /Users/\/Library/Caches/py-yfinance
-
-You can direct cache to use a different location with `set_tz_cache_location()`:
-
-```python
-import yfinance as yf
-yf.set_tz_cache_location("custom/cache/location")
-...
-```
-
----
+The list of changes can be found in the [Changelog](https://github.com/ranaroussi/yfinance/blob/main/CHANGELOG.rst)
## Developers: want to contribute?
-`yfinance` relies on community to investigate bugs and contribute code. Developer guide: https://github.com/ranaroussi/yfinance/discussions/1084
+`yfinance` relies on community to investigate bugs, review code, and contribute code. Developer guide: https://github.com/ranaroussi/yfinance/discussions/1084
---
@@ -362,7 +65,6 @@ yf.set_tz_cache_location("custom/cache/location")
**yfinance** is distributed under the **Apache Software License**. See
the [LICENSE.txt](./LICENSE.txt) file in the release for details.
-
AGAIN - yfinance is **not** affiliated, endorsed, or vetted by Yahoo, Inc. It's
an open-source tool that uses Yahoo's publicly available APIs, and is
intended for research and educational purposes. You should refer to Yahoo!'s terms of use
@@ -378,3 +80,4 @@ details on your rights to use the actual data downloaded.
Please drop me a note with any feedback you have.
**Ran Aroussi**
+
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 000000000..d0c3cbf10
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644
index 000000000..747ffb7b3
--- /dev/null
+++ b/doc/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/doc/source/_static/yfinance.css b/doc/source/_static/yfinance.css
new file mode 100644
index 000000000..9fd5c1d33
--- /dev/null
+++ b/doc/source/_static/yfinance.css
@@ -0,0 +1,4 @@
+/* Hide the "Section Navigation" title */
+p.bd-links__title {
+ display: none;
+}
\ No newline at end of file
diff --git a/doc/source/_templates/autosummary/class.rst b/doc/source/_templates/autosummary/class.rst
new file mode 100644
index 000000000..12b03d75d
--- /dev/null
+++ b/doc/source/_templates/autosummary/class.rst
@@ -0,0 +1,29 @@
+:orphan:
+
+{{ objname | escape | underline }}
+
+.. currentmodule:: {{ module }}
+
+.. autoclass:: {{ objname }}
+
+ {% block attributes %}
+ {% if attributes %}
+ .. rubric:: Attributes
+
+ {% for item in attributes %}
+ .. autoattribute:: {{ item }}
+ :noindex:
+ {%- endfor %}
+ {% endif %}
+ {% endblock attributes %}
+
+ {% block methods %}
+ {% if methods %}
+ .. rubric:: Methods
+
+ {% for item in methods %}
+ .. automethod:: {{ item }}
+ :noindex:
+ {%- endfor %}
+ {% endif %}
+ {% endblock methods %}
diff --git a/doc/source/advanced/caching.rst b/doc/source/advanced/caching.rst
new file mode 100644
index 000000000..744377241
--- /dev/null
+++ b/doc/source/advanced/caching.rst
@@ -0,0 +1,59 @@
+Caching
+=======
+
+Smarter Scraping
+----------------
+
+Install the `nospam` package to cache API calls and reduce spam to Yahoo:
+
+.. code-block:: bash
+
+ pip install yfinance[nospam]
+
+To use a custom `requests` session, pass a `session=` argument to
+the Ticker constructor. This allows for caching calls to the API as well as a custom way to modify requests via the `User-agent` header.
+
+.. code-block:: python
+
+ import requests_cache
+ session = requests_cache.CachedSession('yfinance.cache')
+ session.headers['User-agent'] = 'my-program/1.0'
+ ticker = yf.Ticker('MSFT', session=session)
+
+ # The scraped response will be stored in the cache
+ ticker.actions
+
+
+Combine `requests_cache` with rate-limiting to avoid triggering Yahoo's rate-limiter/blocker that can corrupt data.
+
+.. code-block:: python
+
+ from requests import Session
+ from requests_cache import CacheMixin, SQLiteCache
+ from requests_ratelimiter import LimiterMixin, MemoryQueueBucket
+ from pyrate_limiter import Duration, RequestRate, Limiter
+ class CachedLimiterSession(CacheMixin, LimiterMixin, Session):
+ pass
+
+ session = CachedLimiterSession(
+ limiter=Limiter(RequestRate(2, Duration.SECOND*5)), # max 2 requests per 5 seconds
+ bucket_class=MemoryQueueBucket,
+ backend=SQLiteCache("yfinance.cache"),
+ )
+
+
+Persistent Cache
+----------------
+
+To reduce Yahoo, yfinance store some data locally: timezones to localize dates, and cookie. Cache location is:
+
+- Windows = C:/Users/\/AppData/Local/py-yfinance
+- Linux = /home/\/.cache/py-yfinance
+- MacOS = /Users/\/Library/Caches/py-yfinance
+
+You can direct cache to use a different location with :attr:`set_tz_cache_location `:
+
+.. code-block:: python
+
+ import yfinance as yf
+ yf.set_tz_cache_location("custom/cache/location")
\ No newline at end of file
diff --git a/doc/source/advanced/index.rst b/doc/source/advanced/index.rst
new file mode 100644
index 000000000..e1612ccf8
--- /dev/null
+++ b/doc/source/advanced/index.rst
@@ -0,0 +1,11 @@
+========
+Advanced
+========
+
+.. toctree::
+ :maxdepth: 2
+
+ logging
+ proxy
+ caching
+ multi_level_columns
\ No newline at end of file
diff --git a/doc/source/advanced/logging.rst b/doc/source/advanced/logging.rst
new file mode 100644
index 000000000..89464871d
--- /dev/null
+++ b/doc/source/advanced/logging.rst
@@ -0,0 +1,11 @@
+Logging
+=======
+
+`yfinance` uses the `logging` module to handle messages. By default, only errors are logged.
+
+If debugging, you can switch to debug mode with custom formatting using:
+
+.. code-block:: python
+
+ import yfinance as yf
+ yf.enable_debug_mode()
diff --git a/doc/source/advanced/multi_level_columns.rst b/doc/source/advanced/multi_level_columns.rst
new file mode 100644
index 000000000..434396aea
--- /dev/null
+++ b/doc/source/advanced/multi_level_columns.rst
@@ -0,0 +1,13 @@
+************************
+Multi-Level Column Index
+************************
+
+The following answer on Stack Overflow is for `How to deal with
+multi-level column names downloaded with yfinance? `_
+
+- `yfinance` returns a `pandas.DataFrame` with multi-level column names, with a level for the ticker and a level for the stock price data
+
+The answer discusses:
+
+- How to correctly read the the multi-level columns after saving the dataframe to a csv with `pandas.DataFrame.to_csv`
+- How to download single or multiple tickers into a singledataframe with single level column names and a ticker column
\ No newline at end of file
diff --git a/doc/source/advanced/proxy.rst b/doc/source/advanced/proxy.rst
new file mode 100644
index 000000000..a1868a3bb
--- /dev/null
+++ b/doc/source/advanced/proxy.rst
@@ -0,0 +1,11 @@
+************
+Proxy Server
+************
+
+You can download data via a proxy:
+
+.. code-block:: python
+
+ msft = yf.Ticker("MSFT")
+ msft.history(..., proxy="PROXY_SERVER")
+
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 000000000..e83733b3a
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,45 @@
+import os
+import sys
+sys.path.insert(0, os.path.abspath('../..'))
+
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'yfinance - market data downloader'
+copyright = '2017-2019 Ran Aroussi'
+author = 'Ran Aroussi'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',
+ "sphinx.ext.githubpages",
+ "sphinx.ext.autosectionlabel",
+ "sphinx.ext.autosummary",
+ "sphinx_copybutton"]
+
+templates_path = ['_templates']
+exclude_patterns = []
+autoclass_content = 'both'
+autosummary_generate = True
+autodoc_default_options = {
+ 'exclude-members': '__init__'
+}
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_title = 'yfinance'
+html_theme = 'pydata_sphinx_theme'
+html_theme_options = {
+ "github_url": "https://github.com/ranaroussi/yfinance",
+ "navbar_align": "left"
+}
+html_static_path = ['_static']
+html_css_files = ['yfinance.css']
diff --git a/doc/source/development/assets/branches.png b/doc/source/development/assets/branches.png
new file mode 100644
index 000000000..3f620d532
Binary files /dev/null and b/doc/source/development/assets/branches.png differ
diff --git a/doc/source/development/contributing.rst b/doc/source/development/contributing.rst
new file mode 100644
index 000000000..0464ccfe9
--- /dev/null
+++ b/doc/source/development/contributing.rst
@@ -0,0 +1,109 @@
+********************************
+Contributiong to yfinance
+********************************
+
+`yfinance` relies on the community to investigate bugs and contribute code. Here’s how you can help:
+
+Contributing
+------------
+
+1. Fork the repository on GitHub.
+2. Clone your forked repository:
+
+ .. code-block:: bash
+
+ git clone https://github.com/your-username/yfinance.git
+
+3. Create a new branch for your feature or bug fix:
+
+ .. code-block:: bash
+
+ git checkout -b feature-branch-name
+
+4. Make your changes, commit them, and push your branch to GitHub. To keep the commit history and `network graph `_ compact:
+
+ Use short summaries for commits
+
+ .. code-block:: shell
+
+ git commit -m "short summary" -m "full commit message"
+
+ **Squash** tiny or negligible commits with meaningful ones.
+
+ .. code-block:: shell
+
+ git rebase -i HEAD~2
+ git push --force-with-lease origin
+
+5. Open a pull request on the `yfinance` GitHub page.
+
+For more information, see the `Developer Guide `_.
+
+Branches
+---------
+
+To support rapid development without breaking stable versions, this project uses a two-layer branch model:
+
+.. image:: assets/branches.png
+ :alt: Branching Model
+
+`Inspiration `_
+
+- **dev**: New features and some bug fixes are merged here. This branch allows collective testing, conflict resolution, and further stabilization before merging into the stable branch.
+- **main**: Stable branch where PIP releases are created.
+
+By default, branches target **main**, but most contributions should target **dev**.
+
+**Exceptions**:
+Direct merges to **main** are allowed if:
+
+- `yfinance` is massively broken
+- Part of `yfinance` is broken, and the fix is simple and isolated
+
+Unit Tests
+----------
+
+Tests are written using Python’s `unittest` module. Here are some ways to run tests:
+
+- **Run all price tests**:
+
+ .. code-block:: shell
+
+ python -m unittest tests.test_prices
+
+- **Run a subset of price tests**:
+
+ .. code-block:: shell
+
+ python -m unittest tests.test_prices.TestPriceRepair
+
+- **Run a specific test**:
+
+ .. code-block:: shell
+
+ python -m unittest tests.test_prices.TestPriceRepair.test_ticker_missing
+
+- **Run all tests**:
+
+ .. code-block:: shell
+
+ python -m unittest discover -s tests
+
+Rebasing
+--------------
+
+If asked to move your branch from **main** to **dev**:
+
+1. Ensure all relevant branches are pulled.
+2. Run:
+
+ .. code-block:: shell
+
+ git checkout
+ git rebase --onto dev main
+ git push --force-with-lease origin
+
+Running the GitHub Version of yfinance
+--------------------------------------
+
+To download and run a GitHub version of `yfinance`, refer to `GitHub discussion `_
\ No newline at end of file
diff --git a/doc/source/development/documentation.rst b/doc/source/development/documentation.rst
new file mode 100644
index 000000000..7ec2c8301
--- /dev/null
+++ b/doc/source/development/documentation.rst
@@ -0,0 +1,46 @@
+*************************************
+Contribution to the documentation
+*************************************
+
+.. contents:: Documentation:
+ :local:
+
+About documentation
+------------------------
+* yfinance documentation is written in reStructuredText (rst) and built using Sphinx.
+* The documentation file is in `doc/source/..`.
+* Most of the notes under API References read from class and methods docstrings. These documentations, found in `doc/source/reference/api` is autogenerated by Sphinx and not included in git.
+
+Building documentation locally
+-------------------------------
+To build the documentation locally, follow these steps:
+
+1. **Install Required Dependencies**:
+
+ * Make sure `Sphinx` and any other dependencies are installed. If a `requirements.txt` file is available, you can install dependencies by running:
+
+ .. code-block:: console
+
+ pip install -r requirements.txt
+
+
+2. **Build with Sphinx**:
+
+ * After dependencies are installed, use the sphinx-build command to generate HTML documentation.
+ * Go to `doc/` directory Run:
+
+ .. code-block:: console
+
+ make clean && make html
+
+3. **View Documentation Locally**:
+
+ * Open `doc/build/html/index.html` in the browser to view the generated documentation.
+
+Building documentation on main
+-------------------------------
+The documentation updates are built on merge to `main` branch. This is done via GitHub Actions workflow based on `/yfinance/.github/workflows/deploy_doc.yml`.
+
+1. Reivew the changes locally and push to `dev`.
+
+2. When `dev` gets merged to `main`, GitHub Actions workflow is automated to build documentation.
\ No newline at end of file
diff --git a/doc/source/development/index.rst b/doc/source/development/index.rst
new file mode 100644
index 000000000..d6eb1d984
--- /dev/null
+++ b/doc/source/development/index.rst
@@ -0,0 +1,10 @@
+===========
+Development
+===========
+
+.. toctree::
+ :maxdepth: 1
+
+ contributing
+ documentation
+ reporting_bug
\ No newline at end of file
diff --git a/doc/source/development/reporting_bug.rst b/doc/source/development/reporting_bug.rst
new file mode 100644
index 000000000..12a6f62ea
--- /dev/null
+++ b/doc/source/development/reporting_bug.rst
@@ -0,0 +1,5 @@
+********************************
+Reporting a Bug
+********************************
+
+Open a new issue on our `GitHub `_.
\ No newline at end of file
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 000000000..0f7a94722
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,74 @@
+yfinance documentation
+======================
+
+Download Market Data from Yahoo! Finance's API
+----------------------------------------------
+
+.. admonition:: IMPORTANT LEGAL DISCLAIMER
+
+ **Yahoo!, Y!Finance, and Yahoo! finance are registered trademarks of Yahoo, Inc.**
+
+ yfinance is **not** affiliated, endorsed, or vetted by Yahoo, Inc. It's
+ an open-source tool that uses Yahoo's publicly available APIs, and is
+ intended for research and educational purposes.
+
+ **You should refer to Yahoo!'s terms of use**
+ (`here `__),
+ (`here `__),
+ and (`here `__)
+ for details on your rights to use the actual data downloaded.
+ Remember - the Yahoo! finance API is intended for personal use only.
+
+Install
+-------
+
+.. code-block:: bash
+
+ $ pip install yfinance
+
+Quick start
+-----------
+
+Showing a small sample of yfinance API, the full API is much bigger and covered in :doc:`reference/index`.
+
+.. code-block:: python
+
+ import yfinance as yf
+ dat = yf.Ticker("MSFT")
+
+
+One ticker symbol
+
+.. code-block:: python
+
+ dat = yf.Ticker("MSFT")
+ dat.info
+ dat.calendar
+ dat.analyst_price_targets
+ dat.quarterly_income_stmt
+ dat.history(period='1mo')
+ dat.option_chain(dat.options[0]).calls
+
+Multiple ticker symbols
+
+.. code-block:: python
+
+ tickers = yf.Tickers('MSFT AAPL GOOG')
+ tickers.tickers['MSFT'].info
+ yf.download(['MSFT', 'AAPL', 'GOOG'], period='1mo')
+
+Funds
+
+.. code-block:: python
+
+ spy = yf.Ticker('SPY').funds_data
+ spy.description
+ spy.top_holdings
+
+.. toctree::
+ :maxdepth: 1
+ :titlesonly:
+
+ advanced/index
+ reference/index
+ development/index
diff --git a/doc/source/reference/examples/download.py b/doc/source/reference/examples/download.py
new file mode 100644
index 000000000..430f20007
--- /dev/null
+++ b/doc/source/reference/examples/download.py
@@ -0,0 +1,2 @@
+import yfinance as yf
+data = yf.download("SPY AAPL", period="1mo")
\ No newline at end of file
diff --git a/doc/source/reference/examples/funds_data.py b/doc/source/reference/examples/funds_data.py
new file mode 100644
index 000000000..7eda35d58
--- /dev/null
+++ b/doc/source/reference/examples/funds_data.py
@@ -0,0 +1,18 @@
+import yfinance as yf
+spy = yf.Ticker('SPY')
+data = spy.funds_data
+
+# show fund description
+data.description
+
+# show operational information
+data.fund_overview
+data.fund_operations
+
+# show holdings related information
+data.asset_classes
+data.top_holdings
+data.equity_holdings
+data.bond_holdings
+data.bond_ratings
+data.sector_weightings
\ No newline at end of file
diff --git a/doc/source/reference/examples/proxy.py b/doc/source/reference/examples/proxy.py
new file mode 100644
index 000000000..5aaeb6132
--- /dev/null
+++ b/doc/source/reference/examples/proxy.py
@@ -0,0 +1,13 @@
+import yfinance as yf
+
+msft = yf.Ticker("MSFT")
+
+msft.history(..., proxy="PROXY_SERVER")
+msft.get_actions(proxy="PROXY_SERVER")
+msft.get_dividends(proxy="PROXY_SERVER")
+msft.get_splits(proxy="PROXY_SERVER")
+msft.get_capital_gains(proxy="PROXY_SERVER")
+msft.get_balance_sheet(proxy="PROXY_SERVER")
+msft.get_cashflow(proxy="PROXY_SERVER")
+msft.option_chain(..., proxy="PROXY_SERVER")
+...
\ No newline at end of file
diff --git a/doc/source/reference/examples/sector_industry.py b/doc/source/reference/examples/sector_industry.py
new file mode 100644
index 000000000..7db3fd3a2
--- /dev/null
+++ b/doc/source/reference/examples/sector_industry.py
@@ -0,0 +1,25 @@
+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
+
diff --git a/doc/source/reference/examples/sector_industry_ticker.py b/doc/source/reference/examples/sector_industry_ticker.py
new file mode 100644
index 000000000..6e9c97266
--- /dev/null
+++ b/doc/source/reference/examples/sector_industry_ticker.py
@@ -0,0 +1,11 @@
+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()
\ No newline at end of file
diff --git a/doc/source/reference/examples/ticker.py b/doc/source/reference/examples/ticker.py
new file mode 100644
index 000000000..00a4f7c06
--- /dev/null
+++ b/doc/source/reference/examples/ticker.py
@@ -0,0 +1,22 @@
+import yfinance as yf
+
+dat = yf.Ticker("MSFT")
+
+# get historical market data
+dat.history(period='1mo')
+
+# options
+dat.option_chain(dat.options[0]).calls
+
+# get financials
+dat.balance_sheet
+dat.quarterly_income_stmt
+
+# dates
+dat.calendar
+
+# general info
+dat.info
+
+# analysis
+dat.analyst_price_targets
diff --git a/doc/source/reference/examples/tickers.py b/doc/source/reference/examples/tickers.py
new file mode 100644
index 000000000..241813019
--- /dev/null
+++ b/doc/source/reference/examples/tickers.py
@@ -0,0 +1,8 @@
+import yfinance as yf
+
+tickers = yf.Tickers('msft aapl goog')
+
+# access each ticker using (example)
+tickers.tickers['MSFT'].info
+tickers.tickers['AAPL'].history(period="1mo")
+tickers.tickers['GOOG'].actions
\ No newline at end of file
diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst
new file mode 100644
index 000000000..f1807da27
--- /dev/null
+++ b/doc/source/reference/index.rst
@@ -0,0 +1,39 @@
+=============
+API Reference
+=============
+
+Overview
+--------
+
+The `yfinance` package provides easy access to Yahoo! Finance's API to retrieve market data. It includes classes and functions for downloading historical market data, accessing ticker information, managing cache, and more.
+
+
+Public API
+==========
+
+The following are the publicly available classes, and functions exposed by the `yfinance` package:
+
+- :attr:`Ticker `: Class for accessing single ticker data.
+- :attr:`Tickers `: Class for handling multiple tickers.
+- :attr:`Sector `: Domain class for accessing sector information.
+- :attr:`Industry `: Domain class for accessing industry information.
+- :attr:`download `: Function to download market data for multiple tickers.
+- :attr:`EquityQuery `: Class to build equity market query.
+- :attr:`Screener `: Class to screen the market using defined query.
+- :attr:`enable_debug_mode `: Function to enable debug mode for logging.
+- :attr:`set_tz_cache_location `: Function to set the timezone cache location.
+
+.. toctree::
+ :maxdepth: 1
+ :hidden:
+
+
+ yfinance.ticker_tickers
+ yfinance.stock
+ yfinance.financials
+ yfinance.analysis
+ yfinance.sector_industry
+ yfinance.functions
+
+ yfinance.funds_data
+ yfinance.price_history
diff --git a/doc/source/reference/yfinance.analysis.rst b/doc/source/reference/yfinance.analysis.rst
new file mode 100644
index 000000000..27cea866c
--- /dev/null
+++ b/doc/source/reference/yfinance.analysis.rst
@@ -0,0 +1,81 @@
+===================
+Analysis & Holdings
+===================
+
+.. currentmodule:: yfinance.Ticker
+
+Analysis
+--------
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ get_recommendations
+ recommendations
+
+ get_recommendations_summary
+ recommendations_summary
+
+ get_upgrades_downgrades
+ upgrades_downgrades
+
+ get_sustainability
+ sustainability
+
+ get_analyst_price_targets
+ analyst_price_targets
+
+ get_earnings_estimate
+ earnings_estimate
+
+ get_revenue_estimate
+ revenue_estimate
+
+ get_earnings_history
+ earnings_history
+
+ get_eps_trend
+ eps_trend
+
+ get_eps_revisions
+ eps_revisions
+
+ get_growth_estimates
+ growth_estimates
+
+
+Holdings
+--------
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ get_funds_data
+ funds_data
+
+.. seealso::
+ :meth:`yfinance.scrapers.funds.FundsData`
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ get_insider_purchases
+ insider_purchases
+
+ get_insider_transactions
+ insider_transactions
+
+ get_insider_roster_holders
+ insider_roster_holders
+
+ get_major_holders
+ major_holders
+
+ get_institutional_holders
+ institutional_holders
+
+ get_mutualfund_holders
+ mutualfund_holders
diff --git a/doc/source/reference/yfinance.financials.rst b/doc/source/reference/yfinance.financials.rst
new file mode 100644
index 000000000..8a7c3ac4e
--- /dev/null
+++ b/doc/source/reference/yfinance.financials.rst
@@ -0,0 +1,29 @@
+==========
+Financials
+==========
+
+.. currentmodule:: yfinance.Ticker
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ get_income_stmt
+ income_stmt
+
+ get_balance_sheet
+ balance_sheet
+
+ get_cashflow
+ cashflow
+
+ get_earnings
+ earnings
+
+ calendar
+
+ get_earnings_dates
+ earnings_dates
+
+ get_sec_filings
+ sec_filings
diff --git a/doc/source/reference/yfinance.functions.rst b/doc/source/reference/yfinance.functions.rst
new file mode 100644
index 000000000..ef83454b7
--- /dev/null
+++ b/doc/source/reference/yfinance.functions.rst
@@ -0,0 +1,51 @@
+=========================
+Functions and Utilities
+=========================
+
+.. currentmodule:: yfinance
+
+Download Market Data
+~~~~~~~~~~~~~~~~~~~~~
+The `download` function allows you to retrieve market data for multiple tickers at once.
+
+.. autosummary::
+ :toctree: api/
+
+ download
+
+Query Market Data
+~~~~~~~~~~~~~~~~~~~~~
+The `Sector` and `Industry` modules allow you to access the sector and industry information.
+
+.. autosummary::
+ :toctree: api/
+
+ EquityQuery
+ Screener
+
+.. seealso::
+ :attr:`EquityQuery.valid_operand_fields `
+ supported operand values for query
+ :attr:`EquityQuery.valid_eq_operand_map `
+ supported `EQ query operand parameters`
+ :attr:`Screener.predefined_bodies `
+ supported predefined screens
+
+
+Enable Debug Mode
+~~~~~~~~~~~~~~~~~
+Enables logging of debug information for the `yfinance` package.
+
+.. autosummary::
+ :toctree: api/
+
+ enable_debug_mode
+
+Set Timezone Cache Location
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Sets the cache location for timezone data.
+
+.. autosummary::
+ :toctree: api/
+
+ set_tz_cache_location
diff --git a/doc/source/reference/yfinance.funds_data.rst b/doc/source/reference/yfinance.funds_data.rst
new file mode 100644
index 000000000..ec0d44c8f
--- /dev/null
+++ b/doc/source/reference/yfinance.funds_data.rst
@@ -0,0 +1,11 @@
+====================
+`FundsData` class
+====================
+
+.. currentmodule:: yfinance.scrapers.funds
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ FundsData
diff --git a/doc/source/reference/yfinance.price_history.rst b/doc/source/reference/yfinance.price_history.rst
new file mode 100644
index 000000000..888870c4d
--- /dev/null
+++ b/doc/source/reference/yfinance.price_history.rst
@@ -0,0 +1,9 @@
+====================
+`PriceHistory` class
+====================
+
+.. currentmodule:: yfinance.scrapers.history
+
+.. autoclass:: PriceHistory
+ :members:
+ :undoc-members:
\ No newline at end of file
diff --git a/doc/source/reference/yfinance.sector_industry.rst b/doc/source/reference/yfinance.sector_industry.rst
new file mode 100644
index 000000000..14f2f1f2b
--- /dev/null
+++ b/doc/source/reference/yfinance.sector_industry.rst
@@ -0,0 +1,32 @@
+=======================
+Sector and Industry
+=======================
+
+.. currentmodule:: yfinance
+
+Sector class
+--------------
+The `Sector` and `Industry` modules provide access to the Sector and Industry information.
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ Sector
+ Industry
+
+.. seealso::
+ :attr:`Sector.industries `
+ Map of sector and industry
+
+Sample Code
+---------------------
+To initialize, use the relevant sector or industry key as below.
+
+.. literalinclude:: examples/sector_industry.py
+ :language: python
+
+The modules can be chained with Ticker as below.
+
+.. literalinclude:: examples/sector_industry_ticker.py
+ :language: python
diff --git a/doc/source/reference/yfinance.stock.rst b/doc/source/reference/yfinance.stock.rst
new file mode 100644
index 000000000..785d5e30a
--- /dev/null
+++ b/doc/source/reference/yfinance.stock.rst
@@ -0,0 +1,50 @@
+=====
+Stock
+=====
+
+.. currentmodule:: yfinance.Ticker
+
+Ticker stock methods
+--------------------
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ get_isin
+ isin
+
+ history
+
+.. seealso::
+ :meth:`yfinance.scrapers.history.PriceHistory.history`
+ Documentation for history
+
+.. autosummary::
+ :toctree: api/
+ :recursive:
+
+ get_history_metadata
+
+ get_dividends
+ dividends
+
+ get_splits
+ splits
+
+ get_actions
+ actions
+
+ get_capital_gains
+ capital_gains
+
+ get_shares_full
+
+ get_info
+ info
+
+ get_fast_info
+ fast_info
+
+ get_news
+ news
diff --git a/doc/source/reference/yfinance.ticker_tickers.rst b/doc/source/reference/yfinance.ticker_tickers.rst
new file mode 100644
index 000000000..a3ff602e2
--- /dev/null
+++ b/doc/source/reference/yfinance.ticker_tickers.rst
@@ -0,0 +1,46 @@
+=====================
+Ticker and Tickers
+=====================
+
+.. currentmodule:: yfinance
+
+
+Class
+------------
+The `Ticker` module, allows you to access ticker data in a Pythonic way.
+
+.. autosummary::
+ :toctree: api/
+
+ Ticker
+ Tickers
+
+
+Ticker Sample Code
+------------------
+The `Ticker` module, allows you to access ticker data in a Pythonic way.
+
+.. literalinclude:: examples/ticker.py
+ :language: python
+
+To initialize multiple `Ticker` objects, use
+
+.. literalinclude:: examples/tickers.py
+ :language: python
+
+For tickers that are ETFs/Mutual Funds, `Ticker.funds_data` provides access to fund related data.
+
+Funds' Top Holdings and other data with category average is returned as `pd.DataFrame`.
+
+.. literalinclude:: examples/funds_data.py
+ :language: python
+
+If you want to use a proxy server for downloading data, use:
+
+.. literalinclude:: examples/proxy.py
+ :language: python
+
+To initialize multiple `Ticker` objects, use `Tickers` module
+
+.. literalinclude:: examples/tickers.py
+ :language: python
diff --git a/requirements.txt b/requirements.txt
index f19ca36b1..55fea8d84 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,4 +8,4 @@ pytz>=2022.5
frozendict>=2.3.4
beautifulsoup4>=4.11.1
html5lib>=1.1
-peewee>=3.16.2
+peewee>=3.16.2
\ No newline at end of file
diff --git a/tests/test_ticker.py b/tests/test_ticker.py
index 257e3ed06..ea95f89d8 100644
--- a/tests/test_ticker.py
+++ b/tests/test_ticker.py
@@ -216,11 +216,24 @@ def test_history(self):
self.assertFalse(data.empty, "data is empty")
def test_download(self):
+ tomorrow = pd.Timestamp.now().date() + pd.Timedelta(days=1) # helps with caching
for t in [False, True]:
for i in [False, True]:
- data = yf.download(self.symbols, threads=t, ignore_tz=i)
- self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
- self.assertFalse(data.empty, "data is empty")
+ for m in [False, True]:
+ for n in [1, 'all']:
+ symbols = self.symbols[0] if n == 1 else self.symbols
+ data = yf.download(symbols, end=tomorrow, session=self.session,
+ threads=t, ignore_tz=i, multi_level_index=m)
+ self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+ self.assertFalse(data.empty, "data is empty")
+ if i:
+ self.assertIsNone(data.index.tz)
+ else:
+ self.assertIsNotNone(data.index.tz)
+ if (not m) and n == 1:
+ self.assertFalse(isinstance(data.columns, pd.MultiIndex))
+ else:
+ self.assertIsInstance(data.columns, pd.MultiIndex)
def test_no_expensive_calls_introduced(self):
"""
@@ -825,7 +838,7 @@ def test_growth_estimates(self):
columns = ['stock', 'industry', 'sector', 'index']
self.assertEqual(data.columns.values.tolist(), columns, "data has wrong column names")
- index = ['0q', '+1q', '0y', '+1y', '+5y', '-5y']
+ index = ['0q', '+1q', '0y', '+1y']
self.assertEqual(data.index.values.tolist(), index, "data has wrong row names")
data_cached = self.ticker.growth_estimates
diff --git a/yfinance/base.py b/yfinance/base.py
index 96805d317..a22dd1088 100644
--- a/yfinance/base.py
+++ b/yfinance/base.py
@@ -560,12 +560,15 @@ def get_news(self, proxy=None) -> list:
def get_earnings_dates(self, limit=12, proxy=None) -> Optional[pd.DataFrame]:
"""
Get earning dates (future and historic)
- :param limit: max amount of upcoming and recent earnings dates to return.
- Default value 12 should return next 4 quarters and last 8 quarters.
- Increase if more history is needed.
-
- :param proxy: requests proxy to use.
- :return: pandas dataframe
+
+ Args:
+ limit (int): max amount of upcoming and recent earnings dates to return.
+ Default value 12 should return next 4 quarters and last 8 quarters.
+ Increase if more history is needed.
+ proxy: requests proxy to use.
+
+ Returns:
+ pd.DataFrame
"""
if self._earnings_dates and limit in self._earnings_dates:
return self._earnings_dates[limit]
diff --git a/yfinance/const.py b/yfinance/const.py
index 4e902a68f..2735f2544 100644
--- a/yfinance/const.py
+++ b/yfinance/const.py
@@ -427,119 +427,108 @@
}
}
EQUITY_SCREENER_FIELDS = {
- # EQ Fields
- "region",
- "sector",
- "peer_group",
- "exchanges",
-
- # price
- "eodprice",
- "intradaypricechange",
- "lastclosemarketcap.lasttwelvemonths",
- "percentchange",
- "lastclose52weekhigh.lasttwelvemonths",
- "fiftytwowkpercentchange",
- "intradayprice",
- "lastclose52weeklow.lasttwelvemonths",
- "intradaymarketcap",
-
- # trading
- "beta",
- "avgdailyvol3m",
- "pctheldinsider",
- "pctheldinst",
- "dayvolume",
- "eodvolume",
-
- # short interest
- "short_percentage_of_shares_outstanding.value",
- "short_interest.value",
- "short_percentage_of_float.value",
- "days_to_cover_short.value",
- "short_interest_percentage_change.value",
-
- # valuation
- "bookvalueshare.lasttwelvemonths",
- "lastclosemarketcaptotalrevenue.lasttwelvemonths",
- "lastclosetevtotalrevenue.lasttwelvemonths",
- "pricebookratio.quarterly",
- "peratio.lasttwelvemonths",
- "lastclosepricetangiblebookvalue.lasttwelvemonths",
- "lastclosepriceearnings.lasttwelvemonths",
- "pegratio_5y",
-
- # profitability
- "consecutive_years_of_dividend_growth_count",
- "returnonassets.lasttwelvemonths",
- "returnonequity.lasttwelvemonths",
- "forward_dividend_per_share",
- "forward_dividend_yield",
- "returnontotalcapital.lasttwelvemonths",
-
- # leverage
- "lastclosetevebit.lasttwelvemonths",
- "netdebtebitda.lasttwelvemonths",
- "totaldebtequity.lasttwelvemonths",
- "ltdebtequity.lasttwelvemonths",
- "ebitinterestexpense.lasttwelvemonths",
- "ebitdainterestexpense.lasttwelvemonths",
- "lastclosetevebitda.lasttwelvemonths",
- "totaldebtebitda.lasttwelvemonths",
-
- # liquidity
- "quickratio.lasttwelvemonths",
- "altmanzscoreusingtheaveragestockinformationforaperiod.lasttwelvemonths",
- "currentratio.lasttwelvemonths",
- "operatingcashflowtocurrentliabilities.lasttwelvemonths",
-
- # income statement
- "totalrevenues.lasttwelvemonths",
- "netincomemargin.lasttwelvemonths",
- "grossprofit.lasttwelvemonths",
- "ebitda1yrgrowth.lasttwelvemonths",
- "dilutedepscontinuingoperations.lasttwelvemonths",
- "quarterlyrevenuegrowth.quarterly",
- "epsgrowth.lasttwelvemonths",
- "netincomeis.lasttwelvemonths",
- "ebitda.lasttwelvemonths",
- "dilutedeps1yrgrowth.lasttwelvemonths",
- "totalrevenues1yrgrowth.lasttwelvemonths",
- "operatingincome.lasttwelvemonths",
- "netincome1yrgrowth.lasttwelvemonths",
- "grossprofitmargin.lasttwelvemonths",
- "ebitdamargin.lasttwelvemonths",
- "ebit.lasttwelvemonths",
- "basicepscontinuingoperations.lasttwelvemonths",
- "netepsbasic.lasttwelvemonths"
- "netepsdiluted.lasttwelvemonths",
-
- # balance sheet
- "totalassets.lasttwelvemonths",
- "totalcommonsharesoutstanding.lasttwelvemonths",
- "totaldebt.lasttwelvemonths",
- "totalequity.lasttwelvemonths",
- "totalcurrentassets.lasttwelvemonths",
- "totalcashandshortterminvestments.lasttwelvemonths",
- "totalcommonequity.lasttwelvemonths",
- "totalcurrentliabilities.lasttwelvemonths",
- "totalsharesoutstanding",
-
- # cash flow
- "forward_dividend_yield",
- "leveredfreecashflow.lasttwelvemonths",
- "capitalexpenditure.lasttwelvemonths",
- "cashfromoperations.lasttwelvemonths",
- "leveredfreecashflow1yrgrowth.lasttwelvemonths",
- "unleveredfreecashflow.lasttwelvemonths",
- "cashfromoperations1yrgrowth.lasttwelvemonths",
-
- # ESG
- "esg_score",
- "environmental_score",
- "governance_score",
- "social_score",
- "highest_controversy"
+ "eq_fields": {
+ "region",
+ "sector",
+ "peer_group",
+ "exchanges"},
+ "price":{
+ "eodprice",
+ "intradaypricechange",
+ "lastclosemarketcap.lasttwelvemonths",
+ "percentchange",
+ "lastclose52weekhigh.lasttwelvemonths",
+ "fiftytwowkpercentchange",
+ "intradayprice",
+ "lastclose52weeklow.lasttwelvemonths",
+ "intradaymarketcap"},
+ "trading":{
+ "beta",
+ "avgdailyvol3m",
+ "pctheldinsider",
+ "pctheldinst",
+ "dayvolume",
+ "eodvolume"},
+ "short_interest":{
+ "short_percentage_of_shares_outstanding.value",
+ "short_interest.value",
+ "short_percentage_of_float.value",
+ "days_to_cover_short.value",
+ "short_interest_percentage_change.value"},
+ "valuation":{
+ "bookvalueshare.lasttwelvemonths",
+ "lastclosemarketcaptotalrevenue.lasttwelvemonths",
+ "lastclosetevtotalrevenue.lasttwelvemonths",
+ "pricebookratio.quarterly",
+ "peratio.lasttwelvemonths",
+ "lastclosepricetangiblebookvalue.lasttwelvemonths",
+ "lastclosepriceearnings.lasttwelvemonths",
+ "pegratio_5y"},
+ "profitability":{
+ "consecutive_years_of_dividend_growth_count",
+ "returnonassets.lasttwelvemonths",
+ "returnonequity.lasttwelvemonths",
+ "forward_dividend_per_share",
+ "forward_dividend_yield",
+ "returnontotalcapital.lasttwelvemonths"},
+ "leverage":{
+ "lastclosetevebit.lasttwelvemonths",
+ "netdebtebitda.lasttwelvemonths",
+ "totaldebtequity.lasttwelvemonths",
+ "ltdebtequity.lasttwelvemonths",
+ "ebitinterestexpense.lasttwelvemonths",
+ "ebitdainterestexpense.lasttwelvemonths",
+ "lastclosetevebitda.lasttwelvemonths",
+ "totaldebtebitda.lasttwelvemonths"},
+ "liquidity":{
+ "quickratio.lasttwelvemonths",
+ "altmanzscoreusingtheaveragestockinformationforaperiod.lasttwelvemonths",
+ "currentratio.lasttwelvemonths",
+ "operatingcashflowtocurrentliabilities.lasttwelvemonths"},
+ "income_statement":{
+ "totalrevenues.lasttwelvemonths",
+ "netincomemargin.lasttwelvemonths",
+ "grossprofit.lasttwelvemonths",
+ "ebitda1yrgrowth.lasttwelvemonths",
+ "dilutedepscontinuingoperations.lasttwelvemonths",
+ "quarterlyrevenuegrowth.quarterly",
+ "epsgrowth.lasttwelvemonths",
+ "netincomeis.lasttwelvemonths",
+ "ebitda.lasttwelvemonths",
+ "dilutedeps1yrgrowth.lasttwelvemonths",
+ "totalrevenues1yrgrowth.lasttwelvemonths",
+ "operatingincome.lasttwelvemonths",
+ "netincome1yrgrowth.lasttwelvemonths",
+ "grossprofitmargin.lasttwelvemonths",
+ "ebitdamargin.lasttwelvemonths",
+ "ebit.lasttwelvemonths",
+ "basicepscontinuingoperations.lasttwelvemonths",
+ "netepsbasic.lasttwelvemonths"
+ "netepsdiluted.lasttwelvemonths"},
+ "balance_sheet":{
+ "totalassets.lasttwelvemonths",
+ "totalcommonsharesoutstanding.lasttwelvemonths",
+ "totaldebt.lasttwelvemonths",
+ "totalequity.lasttwelvemonths",
+ "totalcurrentassets.lasttwelvemonths",
+ "totalcashandshortterminvestments.lasttwelvemonths",
+ "totalcommonequity.lasttwelvemonths",
+ "totalcurrentliabilities.lasttwelvemonths",
+ "totalsharesoutstanding"},
+ "cash_flow":{
+ "forward_dividend_yield",
+ "leveredfreecashflow.lasttwelvemonths",
+ "capitalexpenditure.lasttwelvemonths",
+ "cashfromoperations.lasttwelvemonths",
+ "leveredfreecashflow1yrgrowth.lasttwelvemonths",
+ "unleveredfreecashflow.lasttwelvemonths",
+ "cashfromoperations1yrgrowth.lasttwelvemonths"},
+ "esg":{
+ "esg_score",
+ "environmental_score",
+ "governance_score",
+ "social_score",
+ "highest_controversy"}
}
PREDEFINED_SCREENER_BODY_MAP = {
diff --git a/yfinance/domain/domain.py b/yfinance/domain/domain.py
index 5ad1ad084..5a23ea068 100644
--- a/yfinance/domain/domain.py
+++ b/yfinance/domain/domain.py
@@ -1,14 +1,27 @@
+from abc import ABC, abstractmethod
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:
+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, proxy=None):
+ """
+ Initializes the Domain object with a key, session, and proxy.
+
+ Args:
+ key (str): Unique key identifying the domain entity.
+ session (Optional[requests.Session]): Session object for HTTP requests. Defaults to None.
+ proxy (Optional[Dict]): Proxy settings. Defaults to None.
+ """
self._key: str = key
self.proxy = proxy
self.session = session
@@ -19,47 +32,105 @@ def __init__(self, key: str, session=None, proxy=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, proxy) -> Dict:
+ """
+ Fetches data from the given query URL.
+
+ Args:
+ query_url (str): The URL used for the data query.
+ proxy (Dict): Proxy settings for the request.
+
+ 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, user_agent_headers=self._data.user_agent_headers, params=params_dict, proxy=proxy)
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', {}))
@@ -67,6 +138,15 @@ def _parse_and_assign_common(self, data) -> None:
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),
@@ -78,6 +158,15 @@ def _parse_overview(self, overview) -> Dict:
}
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'),
@@ -87,11 +176,22 @@ def _parse_top_companies(self, top_companies) -> Optional[_pd.DataFrame]:
if not top_companies_values:
return None
- return _pd.DataFrame(top_companies_values, columns = top_companies_column).set_index('symbol')
+ 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()
\ No newline at end of file
diff --git a/yfinance/domain/industry.py b/yfinance/domain/industry.py
index 698bce0d8..605249253 100644
--- a/yfinance/domain/industry.py
+++ b/yfinance/domain/industry.py
@@ -7,7 +7,17 @@
from .. import utils
class Industry(Domain):
+ """
+ Represents an industry within a sector.
+ """
+
def __init__(self, key, session=None, proxy=None):
+ """
+ Args:
+ key (str): The key identifier for the industry.
+ session (optional): The session to use for requests.
+ proxy (optional): The proxy to use for requests.
+ """
super(Industry, self).__init__(key, session, proxy)
self._query_url = f'{_QUERY_URL_}/industries/{self._key}'
@@ -17,29 +27,68 @@ def __init__(self, key, session=None, proxy=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),
@@ -53,6 +102,15 @@ def _parse_top_performing_companies(self, top_performing_companies: Dict) -> Opt
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),
@@ -65,6 +123,9 @@ def _parse_top_growth_companies(self, top_growth_companies: Dict) -> Optional[_p
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:
diff --git a/yfinance/domain/sector.py b/yfinance/domain/sector.py
index 2ae3a1137..c19e3523e 100644
--- a/yfinance/domain/sector.py
+++ b/yfinance/domain/sector.py
@@ -1,5 +1,7 @@
from __future__ import print_function
from typing import Dict, Optional
+from ..utils import dynamic_docstring, generate_list_table_from_dict
+from ..const import SECTOR_INDUSTY_MAPPING
import pandas as _pd
@@ -7,48 +9,127 @@
from .. import utils
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, proxy=None):
+ """
+ Args:
+ key (str): The key representing the sector.
+ session (requests.Session, optional): A session for making requests. Defaults to None.
+ proxy (dict, optional): A dictionary containing proxy settings for the request. Defaults to None.
+
+ .. seealso::
+
+ :attr:`Sector.industries `
+ Map of sector and industry
+ """
super(Sector, self).__init__(key, session, proxy)
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,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')
+ 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:
diff --git a/yfinance/multi.py b/yfinance/multi.py
index ba0646b6d..7da0b3ff8 100644
--- a/yfinance/multi.py
+++ b/yfinance/multi.py
@@ -24,6 +24,7 @@
import logging
import time as _time
import traceback
+from typing import Union
import multitasking as _multitasking
import pandas as _pd
@@ -38,8 +39,9 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
ignore_tz=None, group_by='column', auto_adjust=False, back_adjust=False,
repair=False, keepna=False, progress=True, period="max", interval="1d",
prepost=False, proxy=None, rounding=False, timeout=10, session=None,
- multi_level_index=True):
- """Download yahoo tickers
+ multi_level_index=True) -> Union[_pd.DataFrame, None]:
+ """
+ Download yahoo tickers
:Parameters:
tickers : str, list
List of tickers to download
@@ -210,7 +212,7 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
_realign_dfs()
data = _pd.concat(shared._DFS.values(), axis=1, sort=True,
keys=shared._DFS.keys(), names=['Ticker', 'Price'])
- data.index = _pd.to_datetime(data.index, utc=True)
+ data.index = _pd.to_datetime(data.index, utc=not ignore_tz)
# switch names back to isins if applicable
data.rename(columns=shared._ISINS, inplace=True)
diff --git a/yfinance/scrapers/analysis.py b/yfinance/scrapers/analysis.py
index f2778f283..8bdc94290 100644
--- a/yfinance/scrapers/analysis.py
+++ b/yfinance/scrapers/analysis.py
@@ -205,13 +205,18 @@ def growth_estimates(self) -> pd.DataFrame:
self._growth_estimates = pd.DataFrame()
return self._growth_estimates
+ # LTG is not defined in yahoo finance front-end as at 2024-11-14.
+ # But its addition is breaking the retrieval of growth estimates.
+ # Also, support for 5 year seem to have dropped.
+ # TODO: Revisit this change and consider permanently removing these keys.
data_dict = {
'0q': [],
'+1q': [],
'0y': [],
'+1y': [],
- '+5y': [],
- '-5y': []
+ # 'LTG': [],
+ # '+5y': [],
+ # '-5y': []
}
# make sure no column is empty
@@ -222,19 +227,23 @@ def growth_estimates(self) -> pd.DataFrame:
for item in self._earnings_trend:
period = item['period']
- data_dict[period].append(item.get('growth', {}).get('raw', None))
+ if period in data_dict:
+ data_dict[period].append(item.get('growth', {}).get('raw', None))
for item in industry_trend:
period = item['period']
- data_dict[period].append(item.get('growth', None))
+ if period in data_dict:
+ data_dict[period].append(item.get('growth', None))
for item in sector_trend:
period = item['period']
- data_dict[period].append(item.get('growth', None))
+ if period in data_dict:
+ data_dict[period].append(item.get('growth', None))
for item in index_trend:
period = item['period']
- data_dict[period].append(item.get('growth', None))
+ if period in data_dict:
+ data_dict[period].append(item.get('growth', None))
cols = ['stock', 'industry', 'sector', 'index']
self._growth_estimates = pd.DataFrame(data_dict, index=cols).T
diff --git a/yfinance/scrapers/funds.py b/yfinance/scrapers/funds.py
index 61faef615..ef760d9a5 100644
--- a/yfinance/scrapers/funds.py
+++ b/yfinance/scrapers/funds.py
@@ -9,15 +9,21 @@
_QUOTE_SUMMARY_URL_ = f"{_BASE_URL_}/v10/finance/quoteSummary/"
-'''
-Supports ETF and Mutual Funds Data
-Queried Modules: quoteType, summaryProfile, fundProfile, topHoldings
-
-Notes:
-- fundPerformance module is not implemented as better data is queryable using history
-'''
class FundsData:
+ """
+ ETF and Mutual Funds Data
+ Queried Modules: quoteType, summaryProfile, fundProfile, topHoldings
+
+ Notes:
+ - fundPerformance module is not implemented as better data is queryable using history
+ """
def __init__(self, data: YfData, symbol: str, proxy=None):
+ """
+ Args:
+ data (YfData): The YfData object for fetching data.
+ symbol (str): The symbol of the fund.
+ proxy (optional): Proxy settings for fetching data.
+ """
self._data = data
self._symbol = symbol
self.proxy = proxy
@@ -41,71 +47,143 @@ def __init__(self, data: YfData, symbol: str, proxy=None):
self._sector_weightings = None
def quote_type(self) -> str:
+ """
+ Returns the quote type of the fund.
+
+ Returns:
+ str: The quote type.
+ """
if self._quote_type is None:
self._fetch_and_parse()
return self._quote_type
@property
def description(self) -> str:
+ """
+ Returns the description of the fund.
+
+ Returns:
+ str: The description.
+ """
if self._description is None:
self._fetch_and_parse()
return self._description
@property
def fund_overview(self) -> Dict[str, Optional[str]]:
+ """
+ Returns the fund overview.
+
+ Returns:
+ Dict[str, Optional[str]]: The fund overview.
+ """
if self._fund_overview is None:
self._fetch_and_parse()
return self._fund_overview
@property
def fund_operations(self) -> pd.DataFrame:
+ """
+ Returns the fund operations.
+
+ Returns:
+ pd.DataFrame: The fund operations.
+ """
if self._fund_operations is None:
self._fetch_and_parse()
return self._fund_operations
@property
def asset_classes(self) -> Dict[str, float]:
+ """
+ Returns the asset classes of the fund.
+
+ Returns:
+ Dict[str, float]: The asset classes.
+ """
if self._asset_classes is None:
self._fetch_and_parse()
return self._asset_classes
@property
def top_holdings(self) -> pd.DataFrame:
+ """
+ Returns the top holdings of the fund.
+
+ Returns:
+ pd.DataFrame: The top holdings.
+ """
if self._top_holdings is None:
self._fetch_and_parse()
return self._top_holdings
@property
def equity_holdings(self) -> pd.DataFrame:
+ """
+ Returns the equity holdings of the fund.
+
+ Returns:
+ pd.DataFrame: The equity holdings.
+ """
if self._equity_holdings is None:
self._fetch_and_parse()
return self._equity_holdings
@property
def bond_holdings(self) -> pd.DataFrame:
+ """
+ Returns the bond holdings of the fund.
+
+ Returns:
+ pd.DataFrame: The bond holdings.
+ """
if self._bond_holdings is None:
self._fetch_and_parse()
return self._bond_holdings
@property
def bond_ratings(self) -> Dict[str, float]:
+ """
+ Returns the bond ratings of the fund.
+
+ Returns:
+ Dict[str, float]: The bond ratings.
+ """
if self._bond_ratings is None:
self._fetch_and_parse()
return self._bond_ratings
@property
def sector_weightings(self) -> Dict[str,float]:
+ """
+ Returns the sector weightings of the fund.
+
+ Returns:
+ Dict[str, float]: The sector weightings.
+ """
if self._sector_weightings is None:
self._fetch_and_parse()
return self._sector_weightings
def _fetch(self, proxy):
+ """
+ Fetches the raw JSON data from the API.
+
+ Args:
+ proxy: Proxy settings for fetching data.
+
+ Returns:
+ dict: The raw JSON data.
+ """
modules = ','.join(["quoteType", "summaryProfile", "topHoldings", "fundProfile"])
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "symbol": self._symbol, "formatted": "false"}
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_+self._symbol, user_agent_headers=self._data.user_agent_headers, params=params_dict, proxy=proxy)
return result
def _fetch_and_parse(self) -> None:
+ """
+ Fetches and parses the data from the API.
+ """
result = self._fetch(self.proxy)
try:
data = result["quoteSummary"]["result"][0]
@@ -128,15 +206,37 @@ def _fetch_and_parse(self) -> None:
@staticmethod
def _parse_raw_values(data, default=None):
+ """
+ Parses raw values from the data.
+
+ Args:
+ data: The data to parse.
+ default: The default value if data is not a dictionary.
+
+ Returns:
+ The parsed value or the default value.
+ """
if not isinstance(data, dict):
return data
return data.get("raw", default)
def _parse_description(self, data) -> None:
+ """
+ Parses the description from the data.
+
+ Args:
+ data: The data to parse.
+ """
self._description = data.get("longBusinessSummary", "")
- def _parse_top_holdings(self, data) -> None: # done
+ def _parse_top_holdings(self, data) -> None:
+ """
+ Parses the top holdings from the data.
+
+ Args:
+ data: The data to parse.
+ """
# asset classes
self._asset_classes = {
"cashPosition": self._parse_raw_values(data.get("cashPosition", None)),
@@ -207,6 +307,12 @@ def _parse_top_holdings(self, data) -> None: # done
self._sector_weightings = dict((key, d[key]) for d in data.get("sectorWeightings", []) for key in d)
def _parse_fund_profile(self, data):
+ """
+ Parses the fund profile from the data.
+
+ Args:
+ data: The data to parse.
+ """
self._fund_overview = {
"categoryName": data.get("categoryName", None),
"family": data.get("family", None),
diff --git a/yfinance/scrapers/history.py b/yfinance/scrapers/history.py
index 20958e2d8..bda169eaa 100644
--- a/yfinance/scrapers/history.py
+++ b/yfinance/scrapers/history.py
@@ -953,7 +953,7 @@ def _standardise_currency(self, df, currency):
if prices_in_subunits:
for c in _PRICE_COLNAMES_:
df[c] *= m
- self._history_metadata["currency"] = currency
+ self._history_metadata["currency"] = currency2
f_div = df['Dividends']!=0.0
if f_div.any():
diff --git a/yfinance/screener/screener.py b/yfinance/screener/screener.py
index 2d35968e8..cf6e16881 100644
--- a/yfinance/screener/screener.py
+++ b/yfinance/screener/screener.py
@@ -4,11 +4,28 @@
from yfinance.data import YfData
from yfinance.const import _BASE_URL_, PREDEFINED_SCREENER_BODY_MAP
from .screener_query import Query
+from ..utils import dynamic_docstring, generate_list_table_from_dict_of_dict
_SCREENER_URL_ = f"{_BASE_URL_}/v1/finance/screener"
class Screener:
+ """
+ The `Screener` class is used to execute the queries and return the filtered results.
+
+ The Screener class provides methods to set and manipulate the body of a screener request,
+ fetch and parse the screener results, and access predefined screener bodies.
+ """
def __init__(self, session=None, proxy=None):
+ """
+ Args:
+ session (requests.Session, optional): A requests session object to be used for making HTTP requests. Defaults to None.
+ proxy (str, optional): A proxy URL to be used for making HTTP requests. Defaults to None.
+
+ .. seealso::
+
+ :attr:`Screener.predefined_bodies `
+ supported predefined screens
+ """
self.proxy = proxy
self.session = session
@@ -25,17 +42,41 @@ def body(self) -> Dict:
@property
def response(self) -> Dict:
+ """
+ Fetch screen result
+
+ Example:
+
+ .. code-block:: python
+
+ result = screener.response
+ symbols = [quote['symbol'] for quote in result['quotes']]
+ """
if self._body_updated or self._response is None:
self._fetch_and_parse()
self._body_updated = False
return self._response
+ @dynamic_docstring({"predefined_screeners": generate_list_table_from_dict_of_dict(PREDEFINED_SCREENER_BODY_MAP,bullets=False)})
@property
def predefined_bodies(self) -> Dict:
+ """
+ Predefined Screeners
+ {predefined_screeners}
+ """
return self._predefined_bodies
def set_default_body(self, query: Query, offset: int = 0, size: int = 100, sortField: str = "ticker", sortType: str = "desc", quoteType: str = "equity", userId: str = "", userIdType: str = "guid") -> None:
+ """
+ Set the default body using a custom query
+
+ Example:
+
+ .. code-block:: python
+
+ screener.set_default_body(qf)
+ """
self._body_updated = True
self._body = {
@@ -50,6 +91,21 @@ def set_default_body(self, query: Query, offset: int = 0, size: int = 100, sortF
}
def set_predefined_body(self, k: str) -> None:
+ """
+ Set a predefined body
+
+ Example:
+
+ .. code-block:: python
+
+ screener.set_predefined_body('day_gainers')
+
+
+ .. seealso::
+
+ :attr:`Screener.predefined_bodies `
+ supported predefined screens
+ """
body = PREDEFINED_SCREENER_BODY_MAP.get(k, None)
if not body:
raise ValueError(f'Invalid key {k} provided for predefined screener')
@@ -58,6 +114,24 @@ def set_predefined_body(self, k: str) -> None:
self._body = body
def set_body(self, body: Dict) -> None:
+ """
+ Set the fully custom body
+
+ Example:
+
+ .. code-block:: python
+
+ screener.set_body({
+ "offset": 0,
+ "size": 100,
+ "sortField": "ticker",
+ "sortType": "desc",
+ "quoteType": "equity",
+ "query": qf.to_dict(),
+ "userId": "",
+ "userIdType": "guid"
+ })
+ """
missing_keys = [key for key in self._accepted_body_keys if key not in body]
if missing_keys:
raise ValueError(f"Missing required keys in body: {missing_keys}")
@@ -71,6 +145,15 @@ def set_body(self, body: Dict) -> None:
def patch_body(self, values: Dict) -> None:
+ """
+ Patch parts of the body
+
+ Example:
+
+ .. code-block:: python
+
+ screener.patch_body({"offset": 100})
+ """
extra_keys = [key for key in values if key not in self._accepted_body_keys]
if extra_keys:
raise ValueError(f"Body contains extra keys: {extra_keys}")
diff --git a/yfinance/screener/screener_query.py b/yfinance/screener/screener_query.py
index a027af509..65c937591 100644
--- a/yfinance/screener/screener_query.py
+++ b/yfinance/screener/screener_query.py
@@ -1,19 +1,67 @@
+from abc import ABC, abstractmethod
import numbers
-from typing import List, Union, Dict, Set
+from typing import List, Union, Dict
from yfinance.const import EQUITY_SCREENER_EQ_MAP, EQUITY_SCREENER_FIELDS
from yfinance.exceptions import YFNotImplementedError
+from ..utils import dynamic_docstring, generate_list_table_from_dict
-class Query:
+class Query(ABC):
def __init__(self, operator: str, operand: Union[numbers.Real, str, List['Query']]):
self.operator = operator
self.operands = operand
-
+
+ @abstractmethod
def to_dict(self) -> Dict:
raise YFNotImplementedError('to_dict() needs to be implemented by children classes')
class EquityQuery(Query):
+ """
+ The `EquityQuery` class constructs filters for stocks based on specific criteria such as region, sector, exchange, and peer group.
+
+ The queries support operators: `GT` (greater than), `LT` (less than), `BTWN` (between), `EQ` (equals), and logical operators `AND` and `OR` for combining multiple conditions.
+
+ Example:
+ Screen for stocks where the end-of-day price is greater than 3.
+
+ .. code-block:: python
+
+ gt = yf.EquityQuery('gt', ['eodprice', 3])
+
+ Screen for stocks where the average daily volume over the last 3 months is less than a very large number.
+
+ .. code-block:: python
+
+ lt = yf.EquityQuery('lt', ['avgdailyvol3m', 99999999999])
+
+ Screen for stocks where the intraday market cap is between 0 and 100 million.
+
+ .. code-block:: python
+
+ btwn = yf.EquityQuery('btwn', ['intradaymarketcap', 0, 100000000])
+
+ Screen for stocks in the Technology sector.
+
+ .. code-block:: python
+
+ eq = yf.EquityQuery('eq', ['sector', 'Technology'])
+
+ Combine queries using AND/OR.
+
+ .. code-block:: python
+
+ qt = yf.EquityQuery('and', [gt, lt])
+ qf = yf.EquityQuery('or', [qt, btwn, eq])
+ """
def __init__(self, operator: str, operand: Union[numbers.Real, str, List['EquityQuery']]):
+ """
+ .. seealso::
+
+ :attr:`EquityQuery.valid_operand_fields `
+ supported operand values for query
+ :attr:`EquityQuery.valid_eq_operand_map `
+ supported `EQ query operand parameters`
+ """
operator = operator.upper()
if not isinstance(operand, list):
@@ -34,16 +82,26 @@ def __init__(self, operator: str, operand: Union[numbers.Real, str, List['Equity
self.operator = operator
self.operands = operand
- self._valid_eq_map = EQUITY_SCREENER_EQ_MAP
- self._valid_fields = EQUITY_SCREENER_FIELDS
-
+ self._valid_eq_operand_map = EQUITY_SCREENER_EQ_MAP
+ self._valid_operand_fields = EQUITY_SCREENER_FIELDS
+
+ @dynamic_docstring({"valid_eq_operand_map_table": generate_list_table_from_dict(EQUITY_SCREENER_EQ_MAP)})
@property
- def valid_eq_map(self) -> Dict:
- return self._valid_eq_map
+ def valid_eq_operand_map(self) -> Dict:
+ """
+ Valid Operand Map for Operator "EQ"
+ {valid_eq_operand_map_table}
+ """
+ return self._valid_eq_operand_map
+ @dynamic_docstring({"valid_operand_fields_table": generate_list_table_from_dict(EQUITY_SCREENER_FIELDS)})
@property
- def valid_fields(self) -> Set:
- return self._valid_fields
+ def valid_operand_fields(self) -> Dict:
+ """
+ Valid Operand Fields
+ {valid_operand_fields_table}
+ """
+ return self._valid_operand_fields
def _validate_or_and_operand(self, operand: List['EquityQuery']) -> None:
if len(operand) <= 1:
@@ -54,7 +112,8 @@ def _validate_or_and_operand(self, operand: List['EquityQuery']) -> None:
def _validate_eq_operand(self, operand: List[Union[str, numbers.Real]]) -> None:
if len(operand) != 2:
raise ValueError('Operand must be length 2 for EQ')
- if operand[0] not in EQUITY_SCREENER_FIELDS:
+
+ if not any(operand[0] in fields_by_type for fields_by_type in EQUITY_SCREENER_FIELDS.values()):
raise ValueError('Invalid field for Screener')
if operand[0] not in EQUITY_SCREENER_EQ_MAP:
raise ValueError('Invalid EQ key')
@@ -64,7 +123,7 @@ def _validate_eq_operand(self, operand: List[Union[str, numbers.Real]]) -> None:
def _validate_btwn_operand(self, operand: List[Union[str, numbers.Real]]) -> None:
if len(operand) != 3:
raise ValueError('Operand must be length 3 for BTWN')
- if operand[0] not in EQUITY_SCREENER_FIELDS:
+ if not any(operand[0] in fields_by_type for fields_by_type in EQUITY_SCREENER_FIELDS.values()):
raise ValueError('Invalid field for Screener')
if isinstance(operand[1], numbers.Real) is False:
raise TypeError('Invalid comparison type for BTWN')
@@ -74,7 +133,7 @@ def _validate_btwn_operand(self, operand: List[Union[str, numbers.Real]]) -> Non
def _validate_gt_lt(self, operand: List[Union[str, numbers.Real]]) -> None:
if len(operand) != 2:
raise ValueError('Operand must be length 2 for GT/LT')
- if operand[0] not in EQUITY_SCREENER_FIELDS:
+ if not any(operand[0] in fields_by_type for fields_by_type in EQUITY_SCREENER_FIELDS.values()):
raise ValueError('Invalid field for Screener')
if isinstance(operand[1], numbers.Real) is False:
raise TypeError('Invalid comparison type for GT/LT')
diff --git a/yfinance/utils.py b/yfinance/utils.py
index ebc8b99af..612e70b9c 100644
--- a/yfinance/utils.py
+++ b/yfinance/utils.py
@@ -199,7 +199,7 @@ def get_all_by_isin(isin, proxy=None, session=None):
'ticker': {
'symbol': ticker['symbol'],
'shortname': ticker['shortname'],
- 'longname': ticker['longname'],
+ 'longname': ticker.get('longname',''),
'type': ticker['quoteType'],
'exchange': ticker['exchDisp'],
},
@@ -932,3 +932,64 @@ def __update_amount(self, new_amount):
def __str__(self):
return str(self.prog_bar)
+def dynamic_docstring(placeholders: dict):
+ """
+ A decorator to dynamically update the docstring of a function or method.
+
+ Args:
+ placeholders (dict): A dictionary where keys are placeholder names and values are the strings to insert.
+ """
+ def decorator(func):
+ if func.__doc__:
+ docstring = func.__doc__
+ # Replace each placeholder with its corresponding value
+ for key, value in placeholders.items():
+ docstring = docstring.replace(f"{{{key}}}", value)
+ func.__doc__ = docstring
+ return func
+ return decorator
+
+def _generate_table_configurations() -> str:
+ import textwrap
+ table = textwrap.dedent("""
+ .. list-table:: Permitted Keys/Values
+ :widths: 25 75
+ :header-rows: 1
+
+ * - Key
+ - Values
+ """)
+
+ return table
+
+def generate_list_table_from_dict(data: dict, bullets: bool=True) -> str:
+ """
+ Generate a list-table for the docstring showing permitted keys/values.
+ """
+ table = _generate_table_configurations()
+ for key, values in data.items():
+ value_str = ', '.join(sorted(values))
+ table += f" * - {key}\n"
+ if bullets:
+ table += " -\n"
+ for value in sorted(values):
+ table += f" - {value}\n"
+ else:
+ table += f" - {value_str}\n"
+ return table
+
+def generate_list_table_from_dict_of_dict(data: dict, bullets: bool=True) -> str:
+ """
+ Generate a list-table for the docstring showing permitted keys/values.
+ """
+ table = _generate_table_configurations()
+ for key, values in data.items():
+ value_str = values
+ table += f" * - {key}\n"
+ if bullets:
+ table += " -\n"
+ for value in sorted(values):
+ table += f" - {value}\n"
+ else:
+ table += f" - {value_str}\n"
+ return table
\ No newline at end of file