Add Sphinx Documentation

- Initial draft of Sphinx documentation using rst
    - update docstrings
    - add deploy_doc.yml for automated deployment through GitHub Actions
This commit is contained in:
Eric Pien
2024-11-03 05:34:13 -08:00
parent 0bc22f82bb
commit deafc88520
47 changed files with 1613 additions and 500 deletions
Vendored
BIN
View File
Binary file not shown.
+42
View File
@@ -0,0 +1,42 @@
name: Build and Deploy Sphinx Docs
on:
push:
branches:
- dev-documented
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
+7
View File
@@ -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
+10 -295
View File
@@ -35,19 +35,16 @@ Yahoo! finance API is intended for personal use only.**
**yfinance** offers a threaded and Pythonic way to download market data from [Yahoo!Ⓡ finance](https://finance.yahoo.com).
→ Check out this [Blog post](https://aroussi.com/#post/python-yahoo-finance) for a detailed tutorial with code examples.
## Main Features
- `Ticker` module: Class for accessing single ticker data.
- `Tickers` module: Class for handling multiple tickers.
- `download` Efficiently download market data for multiple tickers.
- `Sector` and `Industry` modules : Classes for accessing sector and industry information.
- Market Screening: `EquityQuery` and `Screener` to build query and screen the market.
- Caching and Smart Scraping
[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)
---
## Documentation
The official documentation is available on [ranaroussi.github.io/yfinance](https://ranaroussi.github.io/yfinance/index.html)
## Installation
@@ -67,292 +64,10 @@ $ pip install "yfinance[optional]"
[Required dependencies](./requirements.txt) , [all dependencies](./setup.py#L62).
---
The list of changes can be found in the [changelog](https://github.com/ranaroussi/yfinance/blob/main/CHANGELOG.rst)
## 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")
...
```
### 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.
<!-- TODO: link to Github Pages for more including list of predefined bodies, supported fields, operands, and sample code -->
### 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/\<USER\>/AppData/Local/py-yfinance
- Linux = /home/\<USER\>/.cache/py-yfinance
- MacOS = /Users/\<USER\>/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")
...
```
---
## Developers: want to contribute?
`yfinance` relies on community to investigate bugs and contribute code. Developer guide: https://github.com/ranaroussi/yfinance/discussions/1084
---
+20
View File
@@ -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)
+35
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
/* Hide the "Section Navigation" title */
p.bd-links__title {
display: none;
}
@@ -0,0 +1,30 @@
{{ fullname | escape | underline}}
.. currentmodule:: {{ module }}
.. autoclass:: {{ objname }}
{% block attributes %}
{% if attributes %}
.. rubric:: Attributes
.. autosummary::
:toctree: attributes
{% for item in attributes %}
~{{ name }}.{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block methods %}
{% if methods %}
.. rubric:: Methods
.. autosummary::
:toctree: methods
{% for item in methods %}
~{{ name }}.{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
+45
View File
@@ -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']
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

+109
View File
@@ -0,0 +1,109 @@
********************************
Contributiong to yfinance
********************************
`yfinance` relies on the community to investigate bugs and contribute code. Heres 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 <https://github.com/ranaroussi/yfinance/network>`_ 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 <branch-name>
5. Open a pull request on the `yfinance` GitHub page.
For more information, see the `Developer Guide <https://github.com/ranaroussi/yfinance/discussions/1084>`_.
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 <https://miro.medium.com/max/700/1*2YagIpX6LuauC3ASpwHekg.png>`_
- **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 Pythons `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 <your-branch>
git rebase --onto dev main <branch-name>
git push --force-with-lease origin <branch-name>
Running the GitHub Version of yfinance
--------------------------------------
To download and run a GitHub version of `yfinance`, refer to `GitHub discussion <https://github.com/ranaroussi/yfinance/discussions/1080>`_
+46
View File
@@ -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.
+9
View File
@@ -0,0 +1,9 @@
Development
===============================
.. toctree::
:maxdepth: 1
contributing
documentation
reporting_bug
+5
View File
@@ -0,0 +1,5 @@
********************************
Reporting a Bug
********************************
Open a new issue on our `GitHub <https://github.com/ranaroussi/yfinance/issues>`_.
+9
View File
@@ -0,0 +1,9 @@
Getting Started
===============
.. toctree::
:maxdepth: 1
installation
quick_start
legal
@@ -0,0 +1,17 @@
********************
Installation Guide
********************
Install `yfinance` using `pip`:
.. code-block:: bash
$ pip install yfinance --upgrade --no-cache-dir
To install with optional dependencies, replace `optional` with: `nospam` for `caching-requests <https://github.com/ranaroussi/yfinance?tab=readme-ov-file#smarter-scraping>`_, `repair` for `price repair <https://github.com/ranaroussi/yfinance/wiki/Price-repair>`_, or `nospam`, `repair` for both:
.. code-block:: bash
$ pip install "yfinance[optional]"
For required dependencies, check out the `requirements file <./requirements.txt>`_, and for all dependencies, see the `setup.py file <./setup.py#L62>`_.
+12
View File
@@ -0,0 +1,12 @@
********************
Legal Information
********************
yfinance is distributed under the Apache Software License. See the `LICENSE.txt <../../../../LICENSE.txt>`_ file 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.
Refer to Yahoo!'s terms of use:
- `API Terms <https://policies.yahoo.com/us/en/yahoo/terms/product-atos/apiforydn/index.htm>`_
- `Yahoo Terms <https://legal.yahoo.com/us/en/yahoo/terms/otos/index.html>`_
@@ -0,0 +1,30 @@
********************
Quick Start
********************
The Ticker module allows you to access ticker data in a more Pythonic way:
.. code-block:: 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 actions (dividends, splits, capital gains)
msft.actions
msft.dividends
msft.splits
To work with multiple tickers, use:
.. code-block:: python
tickers = yf.Tickers('msft aapl goog')
tickers.tickers['MSFT'].info
tickers.tickers['AAPL'].history(period="1mo")
+30
View File
@@ -0,0 +1,30 @@
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 <https://policies.yahoo.com/us/en/yahoo/terms/product-atos/apiforydn/index.htm>`__),
(`here <https://legal.yahoo.com/us/en/yahoo/terms/otos/index.html>`__),
and (`here <https://policies.yahoo.com/us/en/yahoo/terms/index.htm>`__)
for details on your rights to use the actual data downloaded.
Remember - the Yahoo! finance API is intended for personal use only.
.. toctree::
:maxdepth: 3
:hidden:
:titlesonly:
getting_started/index
user_guide/index
reference/index
development/index
@@ -0,0 +1,2 @@
import yfinance as yf
data = yf.download("SPY AAPL", period="1mo")
@@ -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
+13
View File
@@ -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")
...
@@ -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
@@ -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()
+77
View File
@@ -0,0 +1,77 @@
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
+8
View File
@@ -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
+33
View File
@@ -0,0 +1,33 @@
=======================
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 <yfinance.Ticker>`: Class for accessing single ticker data.
- :attr:`Tickers <yfinance.Tickers>`: Class for handling multiple tickers.
- :attr:`Sector <yfinance.Sector>`: Domain class for accessing sector information.
- :attr:`Industry <yfinance.Industry>`: Domain class for accessing industry information.
- :attr:`download <yfinance.download>`: Function to download market data for multiple tickers.
- :attr:`EquityQuery <yfinance.EquityQuery>`: Class to build equity market query.
- :attr:`Screener <yfinance.Screener>`: Class to screen the market using defined query.
- :attr:`enable_debug_mode <yfinance.enable_debug_mode>`: Function to enable debug mode for logging.
- :attr:`set_tz_cache_location <yfinance.set_tz_cache_location>`: Function to set the timezone cache location.
.. toctree::
:maxdepth: 1
:hidden:
yfinance.ticker_tickers
yfinance.sector_industry
yfinance.functions
@@ -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 <yfinance.EquityQuery.valid_operand_fields>`
supported operand values for query
:attr:`EquityQuery.valid_eq_operand_map <yfinance.EquityQuery.valid_eq_operand_map>`
supported `EQ query operand parameters`
:attr:`Screener.predefined_bodies <yfinance.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
@@ -0,0 +1,32 @@
=======================
Sector and Industry
=======================
.. currentmodule:: yfinance
Class
------------
The `Sector` and `Industry` modules provide access to the Sector and Industry information.
.. autosummary::
:toctree: api/
:recursive:
Sector
Industry
.. seealso::
:attr:`Sector.industries <yfinance.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
@@ -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
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
+11
View File
@@ -0,0 +1,11 @@
User Guide
==========
.. toctree::
:maxdepth: 1
logging
proxy
smart_scraping
persistent_cache
multi_level_columns
+11
View File
@@ -0,0 +1,11 @@
Logging in yfinance
===================
`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()
@@ -0,0 +1,13 @@
******************************
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 singledataframe with single level column names and a ticker column
@@ -0,0 +1,16 @@
******************************
Persistent Cache Store
******************************
To reduce Yahoo, yfinance store some data locally: timezones to localize dates, and cookie. Cache location is:
- Windows = C:/Users/\<USER\>/AppData/Local/py-yfinance
- Linux = /home/\<USER\>/.cache/py-yfinance
- MacOS = /Users/\<USER\>/Library/Caches/py-yfinance
You can direct cache to use a different location with `set_tz_cache_location()`:
.. code-block:: python
import yfinance as yf
yf.set_tz_cache_location("custom/cache/location")
+11
View File
@@ -0,0 +1,11 @@
*********************
Using a Proxy Server
*********************
You can download data via a proxy:
.. code-block:: python
msft = yf.Ticker("MSFT")
msft.history(..., proxy="PROXY_SERVER")
+41
View File
@@ -0,0 +1,41 @@
******************************
Smarter Scraping with Caching
******************************
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"),
)
+1 -1
View File
@@ -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
+9 -6
View File
@@ -560,12 +560,15 @@ class TickerBase:
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]
+102 -113
View File
@@ -427,119 +427,108 @@ EQUITY_SCREENER_EQ_MAP = {
}
}
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 = {
+112 -12
View File
@@ -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 @@ class Domain:
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 @@ class Domain:
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 @@ class Domain:
}
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 @@ class Domain:
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()
+61
View File
@@ -7,7 +7,17 @@ from .domain import Domain, _QUERY_URL_
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 @@ class Industry(Domain):
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 @@ class Industry(Domain):
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 @@ class Industry(Domain):
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:
+83 -2
View File
@@ -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 .domain import Domain, _QUERY_URL_
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 <yfinance.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:
+36 -50
View File
@@ -24,6 +24,7 @@ from __future__ import print_function
import logging
import time as _time
import traceback
from typing import Union
import multitasking as _multitasking
import pandas as _pd
@@ -38,56 +39,41 @@ 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
:Parameters:
tickers : str, list
List of tickers to download
period : str
Valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
Either Use period parameter or use start and end
interval : str
Valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo
Intraday data cannot extend last 60 days
start: str
Download start date string (YYYY-MM-DD) or _datetime, inclusive.
Default is 99 years ago
E.g. for start="2020-01-01", the first data point will be on "2020-01-01"
end: str
Download end date string (YYYY-MM-DD) or _datetime, exclusive.
Default is now
E.g. for end="2023-01-01", the last data point will be on "2022-12-31"
group_by : str
Group by 'ticker' or 'column' (default)
prepost : bool
Include Pre and Post market data in results?
Default is False
auto_adjust: bool
Adjust all OHLC automatically? Default is False
repair: bool
Detect currency unit 100x mixups and attempt repair
Default is False
keepna: bool
Keep NaN rows returned by Yahoo?
Default is False
actions: bool
Download dividend + stock splits data. Default is False
threads: bool / int
How many threads to use for mass downloading. Default is True
ignore_tz: bool
When combining from different timezones, ignore that part of datetime.
Default depends on interval. Intraday = False. Day+ = True.
proxy: str
Optional. Proxy server URL scheme. Default is None
rounding: bool
Optional. Round values to 2 decimal places?
timeout: None or float
If not None stops waiting for a response after given number of
seconds. (Can also be a fraction of a second e.g. 0.01)
session: None or Session
Optional. Pass your own session object to be used for all requests
multi_level_index: bool
Optional. Always return a MultiIndex DataFrame? Default is False
multi_level_index=True) -> Union[_pd.DataFrame, None]:
"""
Download yahoo tickers
Args:
tickers (str or list): List of tickers to download.
period (str): Time period to download.
Valid periods are: '1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max'.
Either use `period` or specify `start` and `end`.
interval (str): Data interval.
Valid intervals are: '1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo'.
Intraday data is limited to the last 60 days.
start (str): Start date (YYYY-MM-DD) or _datetime, inclusive.
Default is 99 years ago.
Example: For `start="2020-01-01"`, the first data point will be "2020-01-01".
end (str): End date (YYYY-MM-DD) or _datetime, exclusive.
Default is the current date.
Example: For `end="2023-01-01"`, the last data point will be "2022-12-31".
group_by (str): Group data by 'ticker' or 'column'. Default is 'column'.
prepost (bool): Include pre and post market data in results? Default is False.
auto_adjust (bool): Automatically adjust all OHLC data? Default is False.
repair (bool): Detect and repair currency unit mixups (e.g., 100x errors)? Default is False.
keepna (bool): Keep rows with NaN values returned by Yahoo? Default is False.
actions (bool): Download dividend and stock split data? Default is False.
threads (bool or int): Number of threads for mass downloading. Default is True (automatically determines the number of threads).
ignore_tz (bool): Ignore timezones when combining data across timezones?
Default depends on the interval. For intraday intervals, the default is False. For daily and above, the default is True.
proxy (str, optional): URL of the proxy server. Default is None.
rounding (bool, optional): Round values to two decimal places? Default is False.
timeout (None or float, optional): Maximum time to wait for a response, in seconds. Can be a fraction of a second (e.g., 0.01). Default is None.
session (None or Session, optional): Pass a custom session object for all requests. Default is None.
multi_level_index (bool): Optional. Always return a MultiIndex DataFrame? Default is False
Returns:
pd.DataFrame or None
"""
logger = utils.get_yf_logger()
+114 -8
View File
@@ -9,15 +9,21 @@ from typing import Dict, Optional
_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 @@ class FundsData:
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 @@ class FundsData:
@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 @@ class FundsData:
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),
+83
View File
@@ -4,11 +4,28 @@ from yfinance import utils
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 <yfinance.Screener.predefined_bodies>`
supported predefined screens
"""
self.proxy = proxy
self.session = session
@@ -25,17 +42,41 @@ class Screener:
@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 @@ class Screener:
}
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 <yfinance.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 @@ class Screener:
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 @@ class Screener:
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}")
+72 -13
View File
@@ -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 <yfinance.EquityQuery.valid_operand_fields>`
supported operand values for query
:attr:`EquityQuery.valid_eq_operand_map <yfinance.EquityQuery.valid_eq_operand_map>`
supported `EQ query operand parameters`
"""
operator = operator.upper()
if not isinstance(operand, list):
@@ -34,16 +82,26 @@ class EquityQuery(Query):
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 @@ class EquityQuery(Query):
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 @@ class EquityQuery(Query):
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 @@ class EquityQuery(Query):
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')
+61
View File
@@ -932,3 +932,64 @@ class ProgressBar:
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