Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01ef1bb813 | ||
|
|
1db6be75b8 | ||
|
|
7902ec8667 | ||
|
|
ff42a3ac87 | ||
|
|
51f2c7301d | ||
|
|
632a16670a | ||
|
|
fea0dca6f4 | ||
|
|
c7e95152a0 | ||
|
|
a52e972d04 | ||
|
|
a197d9f78e | ||
|
|
dbb9bbfbf3 | ||
|
|
a7b053addd | ||
|
|
e8ca256c10 | ||
|
|
f651dd1e93 | ||
|
|
f40cf0aae1 | ||
|
|
200f57c458 | ||
|
|
e5d45eaa85 | ||
|
|
42b77a9b54 | ||
|
|
bca005a2c0 | ||
|
|
ca891bb187 | ||
|
|
0939ff3c78 | ||
|
|
6f5c5635be | ||
|
|
809622e426 | ||
|
|
eec1f3dbad | ||
|
|
1de789ad72 | ||
|
|
cd68ff68c6 | ||
|
|
9673970f45 | ||
|
|
6ea69a70ac | ||
|
|
c723a5ab44 | ||
|
|
50741d1409 | ||
|
|
69d0dcd62b | ||
|
|
5c9348f255 | ||
|
|
a472546e7b | ||
|
|
c914f1f183 | ||
|
|
92c82342fe | ||
|
|
7ae08b04f3 | ||
|
|
4b50f1e81c | ||
|
|
1ed58be749 | ||
|
|
375b4f9376 | ||
|
|
b6b4426ca9 | ||
|
|
149ebe46db | ||
|
|
d80b27cfde | ||
|
|
36e277317b | ||
|
|
0e1ea4d2c6 | ||
|
|
2d96c383ef | ||
|
|
ec6279736b | ||
|
|
5d942d9668 | ||
|
|
5782cb59fd | ||
|
|
4c4861a8f1 | ||
|
|
4d221ca70e | ||
|
|
1a8d045baf | ||
|
|
67a55c35ce | ||
|
|
e547fe4e41 | ||
|
|
9d5366d707 | ||
|
|
4b07d1dceb | ||
|
|
9440c1e1c1 | ||
|
|
773d003a67 | ||
|
|
a2905a0f8d | ||
|
|
1810455e15 | ||
|
|
9770f286b4 | ||
|
|
80eb0ddafb | ||
|
|
a9e9e8dcb3 | ||
|
|
69c0673345 | ||
|
|
8077cabf44 | ||
|
|
e35b98ef1d | ||
|
|
dc40c6d093 | ||
|
|
8572628ba6 | ||
|
|
f878ce5ea5 | ||
|
|
bbf68daf3f | ||
|
|
afbb5d81a4 | ||
|
|
670ec8e766 | ||
|
|
8fa9438072 | ||
|
|
8c56a0c3df | ||
|
|
6e90fc17cb | ||
|
|
0849f68dd8 | ||
|
|
77f3810cdd | ||
|
|
f3583b00e3 | ||
|
|
d0606cdb03 | ||
|
|
76a9b09e8e | ||
|
|
d757b8f25f | ||
|
|
ab50edf24c | ||
|
|
9b4bce7d06 | ||
|
|
a396d93601 | ||
|
|
79d6741f1e | ||
|
|
47a119f63b | ||
|
|
436c077ee2 | ||
|
|
72b1e9699e | ||
|
|
d2c2690cc2 | ||
|
|
65c1753fb2 | ||
|
|
93edc3c163 | ||
|
|
b2e2acad06 | ||
|
|
24e348e0e4 | ||
|
|
0ac69cfd49 | ||
|
|
cdced94a00 | ||
|
|
317239ec6c | ||
|
|
6904fa9875 | ||
|
|
2259a7a922 | ||
|
|
6ef4be3662 | ||
|
|
44d2a5e001 | ||
|
|
0b88d2f552 | ||
|
|
92a1470b86 | ||
|
|
4439d34d05 | ||
|
|
063495a270 | ||
|
|
1297cd10e8 | ||
|
|
9659af19ef | ||
|
|
16d7b54f4a | ||
|
|
9040571ddd | ||
|
|
4abad90a59 | ||
|
|
0e52e92d4c | ||
|
|
9eef951acc | ||
|
|
bc0cc1b42f |
@@ -6,7 +6,6 @@ fast_finish: true
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.7
|
||||
- python: 3.5
|
||||
- python: 3.6
|
||||
- python: 3.7
|
||||
- python: 3.8
|
||||
|
||||
@@ -1,6 +1,61 @@
|
||||
Change Log
|
||||
===========
|
||||
|
||||
0.1.78
|
||||
------
|
||||
- Fix download() when different timezones #1085
|
||||
|
||||
0.1.77
|
||||
------
|
||||
- Fix user experience bug #1078
|
||||
|
||||
0.1.75
|
||||
------
|
||||
- Fixed datetime-related issues: #1048
|
||||
- Add 'keepna' argument #1032
|
||||
- Speedup Ticker() creation #1042
|
||||
- Improve a bugfix #1033
|
||||
|
||||
0.1.74
|
||||
------
|
||||
- Fixed bug introduced in 0.1.73 (sorry :/)
|
||||
|
||||
0.1.73
|
||||
------
|
||||
- Merged several PR that fixed misc issues
|
||||
|
||||
0.1.72
|
||||
------
|
||||
- Misc bugfixs
|
||||
|
||||
0.1.71
|
||||
------
|
||||
- Added Tickers(…).news()
|
||||
- Return empty DF if YF missing earnings dates
|
||||
- Fix EPS % to 0->1
|
||||
- Fix timezone handling
|
||||
- Fix handling of missing data
|
||||
- Clean&format earnings_dates table
|
||||
- Add ``.get_earnings_dates()`` to retreive earnings calendar
|
||||
- Added ``.get_earnings_history()`` to fetch earnings data
|
||||
|
||||
0.1.70
|
||||
------
|
||||
- Bug fixed - Closes #937
|
||||
|
||||
0.1.69
|
||||
------
|
||||
- Bug fixed - #920
|
||||
|
||||
0.1.68
|
||||
------
|
||||
- Upgraded requests dependency
|
||||
- Removed Python 3.5 support
|
||||
|
||||
0.1.67
|
||||
------
|
||||
- Added legal disclaimers to make sure people are aware that this library is not affiliated, endorsed, or vetted by Yahoo, Inc.
|
||||
|
||||
0.1.66
|
||||
------
|
||||
- Merged PR to allow yfinance to be pickled
|
||||
|
||||
301
README.md
Normal file
301
README.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Download market data from Yahoo! Finance's API
|
||||
|
||||
<table border=1 cellpadding=10><tr><td>
|
||||
|
||||
#### \*\*\* 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.**
|
||||
|
||||
</td></tr></table>
|
||||
|
||||
---
|
||||
|
||||
<a target="new" href="https://pypi.python.org/pypi/yfinance"><img border=0 src="https://img.shields.io/badge/python-2.7,%203.6+-blue.svg?style=flat" alt="Python version"></a>
|
||||
<a target="new" href="https://pypi.python.org/pypi/yfinance"><img border=0 src="https://img.shields.io/pypi/v/yfinance.svg?maxAge=60%" alt="PyPi version"></a>
|
||||
<a target="new" href="https://pypi.python.org/pypi/yfinance"><img border=0 src="https://img.shields.io/pypi/status/yfinance.svg?maxAge=60" alt="PyPi status"></a>
|
||||
<a target="new" href="https://pypi.python.org/pypi/yfinance"><img border=0 src="https://img.shields.io/pypi/dm/yfinance.svg?maxAge=2592000&label=installs&color=%2327B1FF" alt="PyPi downloads"></a>
|
||||
<a target="new" href="https://travis-ci.com/github/ranaroussi/yfinance"><img border=0 src="https://img.shields.io/travis/ranaroussi/yfinance/main.svg?maxAge=1" alt="Travis-CI build status"></a>
|
||||
<a target="new" href="https://www.codefactor.io/repository/github/ranaroussi/yfinance"><img border=0 src="https://www.codefactor.io/repository/github/ranaroussi/yfinance/badge" alt="CodeFactor"></a>
|
||||
<a target="new" href="https://github.com/ranaroussi/yfinance"><img border=0 src="https://img.shields.io/github/stars/ranaroussi/yfinance.svg?style=social&label=Star&maxAge=60" alt="Star this repo"></a>
|
||||
<a target="new" href="https://twitter.com/aroussi"><img border=0 src="https://img.shields.io/twitter/follow/aroussi.svg?style=social&label=Follow&maxAge=60" alt="Follow me on twitter"></a>
|
||||
|
||||
|
||||
**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.
|
||||
|
||||
[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:
|
||||
|
||||
Note: yahoo finance datetimes are received as UTC.
|
||||
|
||||
```python
|
||||
import yfinance as yf
|
||||
|
||||
msft = yf.Ticker("MSFT")
|
||||
|
||||
# get stock info
|
||||
msft.info
|
||||
|
||||
# get historical market data
|
||||
hist = msft.history(period="max")
|
||||
|
||||
# show actions (dividends, splits)
|
||||
msft.actions
|
||||
|
||||
# show dividends
|
||||
msft.dividends
|
||||
|
||||
# show splits
|
||||
msft.splits
|
||||
|
||||
# show financials
|
||||
msft.financials
|
||||
msft.quarterly_financials
|
||||
|
||||
# show major holders
|
||||
msft.major_holders
|
||||
|
||||
# show institutional holders
|
||||
msft.institutional_holders
|
||||
|
||||
# show balance sheet
|
||||
msft.balance_sheet
|
||||
msft.quarterly_balance_sheet
|
||||
|
||||
# show cashflow
|
||||
msft.cashflow
|
||||
msft.quarterly_cashflow
|
||||
|
||||
# show earnings
|
||||
msft.earnings
|
||||
msft.quarterly_earnings
|
||||
|
||||
# show sustainability
|
||||
msft.sustainability
|
||||
|
||||
# show analysts recommendations
|
||||
msft.recommendations
|
||||
|
||||
# show next event (earnings, etc)
|
||||
msft.calendar
|
||||
|
||||
# show all earnings dates
|
||||
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
|
||||
```
|
||||
|
||||
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_balance_sheet(proxy="PROXY_SERVER")
|
||||
msft.get_cashflow(proxy="PROXY_SERVER")
|
||||
msft.option_chain(..., proxy="PROXY_SERVER")
|
||||
...
|
||||
```
|
||||
|
||||
To use a custom `requests` session (for example to cache calls to the
|
||||
API or customize the `User-agent` header), pass a `session=` argument to
|
||||
the Ticker constructor.
|
||||
|
||||
```python
|
||||
import requests_cache
|
||||
session = requests_cache.CachedSession('yfinance.cache')
|
||||
session.headers['User-agent'] = 'my-program/1.0'
|
||||
ticker = yf.Ticker('msft aapl goog', session=session)
|
||||
# The scraped response will be stored in the cache
|
||||
ticker.actions
|
||||
```
|
||||
|
||||
To initialize multiple `Ticker` objects, use
|
||||
|
||||
```python
|
||||
import yfinance as yf
|
||||
|
||||
tickers = yf.Tickers('msft aapl goog')
|
||||
# ^ returns a named tuple of Ticker objects
|
||||
|
||||
# access each ticker using (example)
|
||||
tickers.tickers.MSFT.info
|
||||
tickers.tickers.AAPL.history(period="1mo")
|
||||
tickers.tickers.GOOG.actions
|
||||
```
|
||||
|
||||
### Fetching data for multiple tickers
|
||||
|
||||
```python
|
||||
import yfinance as yf
|
||||
data = yf.download("SPY AAPL", start="2017-01-01", end="2017-04-30")
|
||||
```
|
||||
|
||||
I've also added some options to make life easier :)
|
||||
|
||||
```python
|
||||
data = yf.download( # or pdr.get_data_yahoo(...
|
||||
# tickers list or string as well
|
||||
tickers = "SPY AAPL MSFT",
|
||||
|
||||
# use "period" instead of start/end
|
||||
# valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
|
||||
# (optional, default is '1mo')
|
||||
period = "ytd",
|
||||
|
||||
# fetch data by interval (including intraday if period < 60 days)
|
||||
# valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo
|
||||
# (optional, default is '1d')
|
||||
interval = "1m",
|
||||
|
||||
# Whether to ignore timezone when aligning ticker data from
|
||||
# different timezones. Default is True. False may be useful for
|
||||
# minute/hourly data.
|
||||
ignore_tz = False,
|
||||
|
||||
# group by ticker (to access via data['SPY'])
|
||||
# (optional, default is 'column')
|
||||
group_by = 'ticker',
|
||||
|
||||
# adjust all OHLC automatically
|
||||
# (optional, default is False)
|
||||
auto_adjust = True,
|
||||
|
||||
# download pre/post regular market hours data
|
||||
# (optional, default is False)
|
||||
prepost = True,
|
||||
|
||||
# use threads for mass downloading? (True/False/Integer)
|
||||
# (optional, default is True)
|
||||
threads = True,
|
||||
|
||||
# proxy URL scheme use use when downloading?
|
||||
# (optional, default is None)
|
||||
proxy = None
|
||||
)
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
---
|
||||
|
||||
## `pandas_datareader` override
|
||||
|
||||
If your code uses `pandas_datareader` and you want to download data
|
||||
faster, you can "hijack" `pandas_datareader.data.get_data_yahoo()`
|
||||
method to use **yfinance** while making sure the returned data is in the
|
||||
same format as **pandas\_datareader**'s `get_data_yahoo()`.
|
||||
|
||||
```python
|
||||
from pandas_datareader import data as pdr
|
||||
|
||||
import yfinance as yf
|
||||
yf.pdr_override() # <== that's all it takes :-)
|
||||
|
||||
# download dataframe
|
||||
data = pdr.get_data_yahoo("SPY", start="2017-01-01", end="2017-04-30")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Install `yfinance` using `pip`:
|
||||
|
||||
``` {.sourceCode .bash}
|
||||
$ pip install yfinance --upgrade --no-cache-dir
|
||||
```
|
||||
|
||||
To install `yfinance` using `conda`, see
|
||||
[this](https://anaconda.org/ranaroussi/yfinance).
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Python](https://www.python.org) \>= 2.7, 3.4+
|
||||
- [Pandas](https://github.com/pydata/pandas) (tested to work with
|
||||
\>=0.23.1)
|
||||
- [Numpy](http://www.numpy.org) \>= 1.11.1
|
||||
- [requests](http://docs.python-requests.org/en/master/) \>= 2.14.2
|
||||
- [lxml](https://pypi.org/project/lxml/) \>= 4.5.1
|
||||
- [appdirs](https://pypi.org/project/appdirs) \>=1.4.4
|
||||
|
||||
### Optional (if you want to use `pandas_datareader`)
|
||||
|
||||
- [pandas\_datareader](https://github.com/pydata/pandas-datareader)
|
||||
\>= 0.4.0
|
||||
|
||||
---
|
||||
|
||||
### Legal Stuff
|
||||
|
||||
**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
|
||||
([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
|
||||
detailes on your rights to use the actual data downloaded.
|
||||
|
||||
---
|
||||
|
||||
### P.S.
|
||||
|
||||
Please drop me an note with any feedback you have.
|
||||
|
||||
**Ran Aroussi**
|
||||
301
README.rst
301
README.rst
@@ -1,301 +0,0 @@
|
||||
Yahoo! Finance market data downloader
|
||||
=====================================
|
||||
|
||||
.. image:: https://img.shields.io/badge/python-2.7,%203.4+-blue.svg?style=flat
|
||||
:target: https://pypi.python.org/pypi/yfinance
|
||||
:alt: Python version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/yfinance.svg?maxAge=60
|
||||
:target: https://pypi.python.org/pypi/yfinance
|
||||
:alt: PyPi version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/status/yfinance.svg?maxAge=60
|
||||
:target: https://pypi.python.org/pypi/yfinance
|
||||
:alt: PyPi status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/yfinance.svg?maxAge=2592000&label=installs&color=%2327B1FF
|
||||
:target: https://pypi.python.org/pypi/yfinance
|
||||
:alt: PyPi downloads
|
||||
|
||||
.. image:: https://img.shields.io/travis/ranaroussi/yfinance/main.svg?maxAge=1
|
||||
:target: https://travis-ci.com/github/ranaroussi/yfinance
|
||||
:alt: Travis-CI build status
|
||||
|
||||
.. image:: https://www.codefactor.io/repository/github/ranaroussi/yfinance/badge
|
||||
:target: https://www.codefactor.io/repository/github/ranaroussi/yfinance
|
||||
:alt: CodeFactor
|
||||
|
||||
.. image:: https://img.shields.io/github/stars/ranaroussi/yfinance.svg?style=social&label=Star&maxAge=60
|
||||
:target: https://github.com/ranaroussi/yfinance
|
||||
:alt: Star this repo
|
||||
|
||||
.. image:: https://img.shields.io/twitter/follow/aroussi.svg?style=social&label=Follow&maxAge=60
|
||||
:target: https://twitter.com/aroussi
|
||||
:alt: Follow me on twitter
|
||||
|
||||
\
|
||||
|
||||
Ever since `Yahoo! finance <https://finance.yahoo.com>`_ decommissioned
|
||||
their historical data API, many programs that relied on it to stop working.
|
||||
|
||||
**yfinance** aims to solve this problem by offering a reliable, threaded,
|
||||
and Pythonic way to download historical market data from Yahoo! finance.
|
||||
|
||||
|
||||
NOTE
|
||||
~~~~
|
||||
|
||||
The library was originally named ``fix-yahoo-finance``, but
|
||||
I've since renamed it to ``yfinance`` as I no longer consider it a mere "fix".
|
||||
For reasons of backward-compatibility, ``fix-yahoo-finance`` now import and
|
||||
uses ``yfinance``, but you should install and use ``yfinance`` directly.
|
||||
|
||||
`Changelog » <https://github.com/ranaroussi/yfinance/blob/main/CHANGELOG.rst>`__
|
||||
|
||||
-----
|
||||
|
||||
==> Check out this `Blog post <https://aroussi.com/#post/python-yahoo-finance>`_ for a detailed tutorial with code examples.
|
||||
|
||||
-----
|
||||
|
||||
Quick Start
|
||||
===========
|
||||
|
||||
The Ticker module
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``Ticker`` module, which allows you to access
|
||||
ticker data in a more Pythonic way:
|
||||
|
||||
Note: yahoo finance datetimes are received as UTC.
|
||||
|
||||
.. code:: python
|
||||
|
||||
import yfinance as yf
|
||||
|
||||
msft = yf.Ticker("MSFT")
|
||||
|
||||
# get stock info
|
||||
msft.info
|
||||
|
||||
# get historical market data
|
||||
hist = msft.history(period="max")
|
||||
|
||||
# show actions (dividends, splits)
|
||||
msft.actions
|
||||
|
||||
# show dividends
|
||||
msft.dividends
|
||||
|
||||
# show splits
|
||||
msft.splits
|
||||
|
||||
# show financials
|
||||
msft.financials
|
||||
msft.quarterly_financials
|
||||
|
||||
# show major holders
|
||||
msft.major_holders
|
||||
|
||||
# show institutional holders
|
||||
msft.institutional_holders
|
||||
|
||||
# show balance sheet
|
||||
msft.balance_sheet
|
||||
msft.quarterly_balance_sheet
|
||||
|
||||
# show cashflow
|
||||
msft.cashflow
|
||||
msft.quarterly_cashflow
|
||||
|
||||
# show earnings
|
||||
msft.earnings
|
||||
msft.quarterly_earnings
|
||||
|
||||
# show sustainability
|
||||
msft.sustainability
|
||||
|
||||
# show analysts recommendations
|
||||
msft.recommendations
|
||||
|
||||
# show next event (earnings, etc)
|
||||
msft.calendar
|
||||
|
||||
# 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
|
||||
|
||||
If you want to use a proxy server for downloading data, use:
|
||||
|
||||
.. code:: 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_balance_sheet(proxy="PROXY_SERVER")
|
||||
msft.get_cashflow(proxy="PROXY_SERVER")
|
||||
msft.option_chain(..., proxy="PROXY_SERVER")
|
||||
...
|
||||
|
||||
To use a custom ``requests`` session (for example to cache calls to the API
|
||||
or customize the ``User-agent`` header), pass a ``session=`` argument to the
|
||||
Ticker constructor.
|
||||
|
||||
.. code:: python
|
||||
|
||||
import requests_cache
|
||||
session = requests_cache.CachedSession('yfinance.cache')
|
||||
session.headers['User-agent'] = 'my-program/1.0'
|
||||
ticker = yf.Ticker('msft aapl goog', session=session)
|
||||
# The scraped response will be stored in the cache
|
||||
ticker.actions
|
||||
|
||||
To initialize multiple ``Ticker`` objects, use
|
||||
|
||||
.. code:: python
|
||||
|
||||
import yfinance as yf
|
||||
|
||||
tickers = yf.Tickers('msft aapl goog')
|
||||
# ^ returns a named tuple of Ticker objects
|
||||
|
||||
# access each ticker using (example)
|
||||
tickers.tickers.MSFT.info
|
||||
tickers.tickers.AAPL.history(period="1mo")
|
||||
tickers.tickers.GOOG.actions
|
||||
|
||||
|
||||
Fetching data for multiple tickers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: python
|
||||
|
||||
import yfinance as yf
|
||||
data = yf.download("SPY AAPL", start="2017-01-01", end="2017-04-30")
|
||||
|
||||
|
||||
I've also added some options to make life easier :)
|
||||
|
||||
.. code:: python
|
||||
|
||||
data = yf.download( # or pdr.get_data_yahoo(...
|
||||
# tickers list or string as well
|
||||
tickers = "SPY AAPL MSFT",
|
||||
|
||||
# use "period" instead of start/end
|
||||
# valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
|
||||
# (optional, default is '1mo')
|
||||
period = "ytd",
|
||||
|
||||
# fetch data by interval (including intraday if period < 60 days)
|
||||
# valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo
|
||||
# (optional, default is '1d')
|
||||
interval = "1m",
|
||||
|
||||
# group by ticker (to access via data['SPY'])
|
||||
# (optional, default is 'column')
|
||||
group_by = 'ticker',
|
||||
|
||||
# adjust all OHLC automatically
|
||||
# (optional, default is False)
|
||||
auto_adjust = True,
|
||||
|
||||
# download pre/post regular market hours data
|
||||
# (optional, default is False)
|
||||
prepost = True,
|
||||
|
||||
# use threads for mass downloading? (True/False/Integer)
|
||||
# (optional, default is True)
|
||||
threads = True,
|
||||
|
||||
# proxy URL scheme use use when downloading?
|
||||
# (optional, default is None)
|
||||
proxy = None
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
``pandas_datareader`` override
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If your code uses ``pandas_datareader`` and you want to download data faster,
|
||||
you can "hijack" ``pandas_datareader.data.get_data_yahoo()`` method to use
|
||||
**yfinance** while making sure the returned data is in the same format as
|
||||
**pandas_datareader**'s ``get_data_yahoo()``.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from pandas_datareader import data as pdr
|
||||
|
||||
import yfinance as yf
|
||||
yf.pdr_override() # <== that's all it takes :-)
|
||||
|
||||
# download dataframe
|
||||
data = pdr.get_data_yahoo("SPY", start="2017-01-01", end="2017-04-30")
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install ``yfinance`` using ``pip``:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ pip install yfinance --upgrade --no-cache-dir
|
||||
|
||||
|
||||
To install ``yfinance`` using ``conda``, see `this <https://anaconda.org/ranaroussi/yfinance>`_.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* `Python <https://www.python.org>`_ >= 2.7, 3.4+
|
||||
* `Pandas <https://github.com/pydata/pandas>`_ (tested to work with >=0.23.1)
|
||||
* `Numpy <http://www.numpy.org>`_ >= 1.11.1
|
||||
* `requests <http://docs.python-requests.org/en/master/>`_ >= 2.14.2
|
||||
* `lxml <https://pypi.org/project/lxml/>`_ >= 4.5.1
|
||||
|
||||
Optional (if you want to use ``pandas_datareader``)
|
||||
---------------------------------------------------
|
||||
|
||||
* `pandas_datareader <https://github.com/pydata/pandas-datareader>`_ >= 0.4.0
|
||||
|
||||
Legal Stuff
|
||||
------------
|
||||
|
||||
**yfinance** is distributed under the **Apache Software License**. See the `LICENSE.txt <./LICENSE.txt>`_ file in the release for details.
|
||||
|
||||
|
||||
P.S.
|
||||
------------
|
||||
|
||||
Please drop me an note with any feedback you have.
|
||||
|
||||
**Ran Aroussi**
|
||||
@@ -1,3 +0,0 @@
|
||||
# Ticker
|
||||
|
||||
::: yfinance.ticker.Ticker
|
||||
@@ -1,3 +0,0 @@
|
||||
# TickerBase Reference
|
||||
|
||||
::: yfinance.base.TickerBase
|
||||
@@ -1,3 +0,0 @@
|
||||
# Tickers Reference
|
||||
|
||||
::: yfinance.tickers.Tickers
|
||||
@@ -1,18 +0,0 @@
|
||||
Yahoo! Finance market data downloader
|
||||
=====================================
|
||||
|
||||
Ever since [Yahoo! finance](https://finance.yahoo.com) decommissioned
|
||||
their historical data API, many programs that relied on it to stop
|
||||
working.
|
||||
|
||||
**yfinance** aims to solve this problem by offering a reliable,
|
||||
threaded, and Pythonic way to download historical market data from
|
||||
Yahoo! finance.
|
||||
|
||||
NOTE
|
||||
----
|
||||
|
||||
The library was originally named `fix-yahoo-finance`, but I've since
|
||||
renamed it to `yfinance` as I no longer consider it a mere "fix". For
|
||||
reasons of backward-compatibility, `fix-yahoo-finance` now import and
|
||||
uses `yfinance`, but you should install and use `yfinance` directly.
|
||||
@@ -1,28 +0,0 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
Install `yfinance` using `pip`:
|
||||
|
||||
``` {.sourceCode .bash}
|
||||
$ pip install yfinance --upgrade --no-cache-dir
|
||||
```
|
||||
|
||||
Install `yfinance` using `conda`:
|
||||
|
||||
``` {.sourceCode .bash}
|
||||
$ conda install -c ranaroussi yfinance
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Python](https://www.python.org) \>= 2.7, 3.4+
|
||||
- [Pandas](https://github.com/pydata/pandas) (tested to work with
|
||||
\>=0.23.1)
|
||||
- [Numpy](http://www.numpy.org) \>= 1.11.1
|
||||
- [requests](http://docs.python-requests.org/en/master/) \>= 2.14.2
|
||||
- [lxml](https://pypi.org/project/lxml/) \>= 4.5.1
|
||||
|
||||
### Optional (if you want to use `pandas_datareader`)
|
||||
|
||||
- [pandas\_datareader](https://github.com/pydata/pandas-datareader)
|
||||
\>= 0.4.0
|
||||
@@ -1,3 +0,0 @@
|
||||
# multi.py Reference
|
||||
|
||||
::: yfinance.multi
|
||||
@@ -1,201 +0,0 @@
|
||||
Quick Start
|
||||
===========
|
||||
|
||||
The Ticker module
|
||||
-----------------
|
||||
|
||||
The `Ticker` module, which allows you to access ticker data in a more
|
||||
Pythonic way:
|
||||
|
||||
Note: yahoo finance datetimes are received as UTC.
|
||||
|
||||
``` {.sourceCode .python}
|
||||
import yfinance as yf
|
||||
|
||||
msft = yf.Ticker("MSFT")
|
||||
|
||||
# get stock info
|
||||
msft.info
|
||||
|
||||
# get historical market data
|
||||
hist = msft.history(period="max")
|
||||
|
||||
# show actions (dividends, splits)
|
||||
msft.actions
|
||||
|
||||
# show dividends
|
||||
msft.dividends
|
||||
|
||||
# show splits
|
||||
msft.splits
|
||||
|
||||
# show financials
|
||||
msft.financials
|
||||
msft.quarterly_financials
|
||||
|
||||
# show major holders
|
||||
msft.major_holders
|
||||
|
||||
# show institutional holders
|
||||
msft.institutional_holders
|
||||
|
||||
# show balance sheet
|
||||
msft.balance_sheet
|
||||
msft.quarterly_balance_sheet
|
||||
|
||||
# show cashflow
|
||||
msft.cashflow
|
||||
msft.quarterly_cashflow
|
||||
|
||||
# show earnings
|
||||
msft.earnings
|
||||
msft.quarterly_earnings
|
||||
|
||||
# show sustainability
|
||||
msft.sustainability
|
||||
|
||||
# show analysts recommendations
|
||||
msft.recommendations
|
||||
|
||||
# show next event (earnings, etc)
|
||||
msft.calendar
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
If you want to use a proxy server for downloading data, use:
|
||||
|
||||
``` {.sourceCode .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_balance_sheet(proxy="PROXY_SERVER")
|
||||
msft.get_cashflow(proxy="PROXY_SERVER")
|
||||
msft.option_chain(..., proxy="PROXY_SERVER")
|
||||
...
|
||||
```
|
||||
|
||||
To use a custom `requests` session (for example to cache calls to the
|
||||
API or customize the `User-agent` header), pass a `session=` argument to
|
||||
the Ticker constructor.
|
||||
|
||||
``` {.sourceCode .python}
|
||||
import requests_cache
|
||||
session = requests_cache.CachedSession('yfinance.cache')
|
||||
session.headers['User-agent'] = 'my-program/1.0'
|
||||
ticker = yf.Ticker('msft aapl goog', session=session)
|
||||
# The scraped response will be stored in the cache
|
||||
ticker.actions
|
||||
```
|
||||
|
||||
To initialize multiple `Ticker` objects, use
|
||||
|
||||
``` {.sourceCode .python}
|
||||
import yfinance as yf
|
||||
|
||||
tickers = yf.Tickers('msft aapl goog')
|
||||
# ^ returns a named tuple of Ticker objects
|
||||
|
||||
# access each ticker using (example)
|
||||
tickers.tickers.MSFT.info
|
||||
tickers.tickers.AAPL.history(period="1mo")
|
||||
tickers.tickers.GOOG.actions
|
||||
```
|
||||
|
||||
Fetching data for multiple tickers
|
||||
----------------------------------
|
||||
|
||||
``` {.sourceCode .python}
|
||||
import yfinance as yf
|
||||
data = yf.download("SPY AAPL", start="2017-01-01", end="2017-04-30")
|
||||
```
|
||||
|
||||
I've also added some options to make life easier :)
|
||||
|
||||
``` {.sourceCode .python}
|
||||
data = yf.download( # or pdr.get_data_yahoo(...
|
||||
# tickers list or string as well
|
||||
tickers = "SPY AAPL MSFT",
|
||||
|
||||
# use "period" instead of start/end
|
||||
# valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
|
||||
# (optional, default is '1mo')
|
||||
period = "ytd",
|
||||
|
||||
# fetch data by interval (including intraday if period < 60 days)
|
||||
# valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo
|
||||
# (optional, default is '1d')
|
||||
interval = "1m",
|
||||
|
||||
# group by ticker (to access via data['SPY'])
|
||||
# (optional, default is 'column')
|
||||
group_by = 'ticker',
|
||||
|
||||
# adjust all OHLC automatically
|
||||
# (optional, default is False)
|
||||
auto_adjust = True,
|
||||
|
||||
# download pre/post regular market hours data
|
||||
# (optional, default is False)
|
||||
prepost = True,
|
||||
|
||||
# use threads for mass downloading? (True/False/Integer)
|
||||
# (optional, default is True)
|
||||
threads = True,
|
||||
|
||||
# proxy URL scheme use use when downloading?
|
||||
# (optional, default is None)
|
||||
proxy = None
|
||||
)
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
`pandas_datareader` override
|
||||
----------------------------
|
||||
|
||||
If your code uses `pandas_datareader` and you want to download data
|
||||
faster, you can "hijack" `pandas_datareader.data.get_data_yahoo()`
|
||||
method to use **yfinance** while making sure the returned data is in the
|
||||
same format as **pandas\_datareader**'s `get_data_yahoo()`.
|
||||
|
||||
``` {.sourceCode .python}
|
||||
from pandas_datareader import data as pdr
|
||||
|
||||
import yfinance as yf
|
||||
yf.pdr_override() # <== that's all it takes :-)
|
||||
|
||||
# download dataframe
|
||||
data = pdr.get_data_yahoo("SPY", start="2017-01-01", end="2017-04-30")
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# utils.py Reference
|
||||
|
||||
::: yfinance.utils
|
||||
@@ -21,6 +21,7 @@ requirements:
|
||||
- requests >=2.21
|
||||
- multitasking >=0.0.7
|
||||
- lxml >=4.5.1
|
||||
- appdirs >= 1.4.4
|
||||
- pip
|
||||
- python
|
||||
|
||||
@@ -30,6 +31,7 @@ requirements:
|
||||
- requests >=2.21
|
||||
- multitasking >=0.0.7
|
||||
- lxml >=4.5.1
|
||||
- appdirs >= 1.4.4
|
||||
- python
|
||||
|
||||
test:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pandas>=0.24.0
|
||||
numpy>=1.16.5
|
||||
requests>=2.21
|
||||
requests>=2.26
|
||||
multitasking>=0.0.7
|
||||
lxml>=4.5.1
|
||||
appdirs>=1.4.4
|
||||
|
||||
23
setup.py
23
setup.py
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
|
||||
"""Yahoo! Finance market data downloader (+fix for Pandas Datareader)"""
|
||||
"""yfinance - market data downloader"""
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
# from codecs import open
|
||||
@@ -22,14 +22,15 @@ with open("yfinance/version.py") as f:
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
# Get the long description from the README file
|
||||
with io.open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||
with io.open(path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='yfinance',
|
||||
version=version,
|
||||
description='Yahoo! Finance market data downloader',
|
||||
description='Download market data from Yahoo! Finance API',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/ranaroussi/yfinance',
|
||||
author='Ran Aroussi',
|
||||
author_email='ran@aroussi.com',
|
||||
@@ -52,7 +53,7 @@ setup(
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
# 'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
@@ -60,12 +61,18 @@ setup(
|
||||
platforms=['any'],
|
||||
keywords='pandas, yahoo finance, pandas datareader',
|
||||
packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']),
|
||||
install_requires=['pandas>=0.24', 'numpy>=1.15',
|
||||
'requests>=2.20', 'multitasking>=0.0.7',
|
||||
'lxml>=4.5.1'],
|
||||
install_requires=['pandas>=0.24.0', 'numpy>=1.15',
|
||||
'requests>=2.26', 'multitasking>=0.0.7',
|
||||
'lxml>=4.5.1', 'appdirs>=1.4.4'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'sample=sample:main',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
print("""
|
||||
NOTE: yfinance is not affiliated, endorsed, or vetted by Yahoo, Inc.
|
||||
|
||||
You should refer to Yahoo!'s terms of use for details on your rights
|
||||
to use the actual data downloaded.""")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
|
||||
"""
|
||||
@@ -25,7 +25,8 @@ class TestTicker(unittest.TestCase):
|
||||
for ticker in tickers:
|
||||
# always should have info and history for valid symbols
|
||||
assert(ticker.info is not None and ticker.info != {})
|
||||
assert(ticker.history(period="max").empty is False)
|
||||
history = ticker.history(period="max")
|
||||
assert(history.empty is False and history is not None)
|
||||
|
||||
def test_attributes(self):
|
||||
for ticker in tickers:
|
||||
@@ -50,6 +51,9 @@ class TestTicker(unittest.TestCase):
|
||||
ticker.sustainability
|
||||
ticker.options
|
||||
ticker.news
|
||||
ticker.shares
|
||||
ticker.earnings_history
|
||||
ticker.earnings_dates
|
||||
|
||||
def test_holders(self):
|
||||
for ticker in tickers:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
#
|
||||
# Copyright 2017-2019 Ran Aroussi
|
||||
|
||||
481
yfinance/base.py
481
yfinance/base.py
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
#
|
||||
# Copyright 2017-2019 Ran Aroussi
|
||||
@@ -23,6 +23,7 @@ from __future__ import print_function
|
||||
|
||||
import time as _time
|
||||
import datetime as _datetime
|
||||
import pytz as _tz
|
||||
import requests as _requests
|
||||
import pandas as _pd
|
||||
import numpy as _np
|
||||
@@ -35,7 +36,7 @@ except ImportError:
|
||||
|
||||
from . import utils
|
||||
|
||||
# import json as _json
|
||||
import json as _json
|
||||
# import re as _re
|
||||
# import sys as _sys
|
||||
|
||||
@@ -43,6 +44,8 @@ from . import shared
|
||||
|
||||
_BASE_URL_ = 'https://query2.finance.yahoo.com'
|
||||
_SCRAPE_URL_ = 'https://finance.yahoo.com/quote'
|
||||
_ROOT_URL_ = 'https://finance.yahoo.com'
|
||||
|
||||
|
||||
class TickerBase():
|
||||
def __init__(self, ticker, session=None):
|
||||
@@ -51,6 +54,7 @@ class TickerBase():
|
||||
self._history = None
|
||||
self._base_url = _BASE_URL_
|
||||
self._scrape_url = _SCRAPE_URL_
|
||||
self._tz = None
|
||||
|
||||
self._fundamentals = False
|
||||
self._info = None
|
||||
@@ -62,22 +66,17 @@ class TickerBase():
|
||||
self._mutualfund_holders = None
|
||||
self._isin = None
|
||||
self._news = []
|
||||
self._shares = None
|
||||
|
||||
self._calendar = None
|
||||
self._expirations = {}
|
||||
self._earnings_dates = None
|
||||
self._earnings_history = None
|
||||
|
||||
self._earnings = {
|
||||
"yearly": utils.empty_df(),
|
||||
"quarterly": utils.empty_df()}
|
||||
self._financials = {
|
||||
"yearly": utils.empty_df(),
|
||||
"quarterly": utils.empty_df()}
|
||||
self._balancesheet = {
|
||||
"yearly": utils.empty_df(),
|
||||
"quarterly": utils.empty_df()}
|
||||
self._cashflow = {
|
||||
"yearly": utils.empty_df(),
|
||||
"quarterly": utils.empty_df()}
|
||||
self._earnings = None
|
||||
self._financials = None
|
||||
self._balancesheet = None
|
||||
self._cashflow = None
|
||||
|
||||
# accept isin as ticker
|
||||
if utils.is_isin(self.ticker):
|
||||
@@ -101,8 +100,8 @@ class TickerBase():
|
||||
|
||||
def history(self, period="1mo", interval="1d",
|
||||
start=None, end=None, prepost=False, actions=True,
|
||||
auto_adjust=True, back_adjust=False,
|
||||
proxy=None, rounding=False, tz=None, timeout=None, **kwargs):
|
||||
auto_adjust=True, back_adjust=False, keepna=False,
|
||||
proxy=None, rounding=False, timeout=None, **kwargs):
|
||||
"""
|
||||
:Parameters:
|
||||
period : str
|
||||
@@ -124,14 +123,14 @@ class TickerBase():
|
||||
Adjust all OHLC automatically? Default is True
|
||||
back_adjust: bool
|
||||
Back-adjusted data to mimic true historical prices
|
||||
keepna: bool
|
||||
Keep NaN rows returned by Yahoo?
|
||||
Default is False
|
||||
proxy: str
|
||||
Optional. Proxy server URL scheme. Default is None
|
||||
rounding: bool
|
||||
Round values to 2 decimal places?
|
||||
Optional. Default is False = precision suggested by Yahoo!
|
||||
tz: str
|
||||
Optional timezone locale for dates.
|
||||
(default data is returned as non-localized dates)
|
||||
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)
|
||||
@@ -142,21 +141,38 @@ class TickerBase():
|
||||
error message printing to console.
|
||||
"""
|
||||
|
||||
# Work with errors
|
||||
debug_mode = True
|
||||
if "debug" in kwargs and isinstance(kwargs["debug"], bool):
|
||||
debug_mode = kwargs["debug"]
|
||||
|
||||
err_msg = "No data found for this date range, symbol may be delisted"
|
||||
|
||||
if start or period is None or period.lower() == "max":
|
||||
if start is None:
|
||||
start = -631159200
|
||||
elif isinstance(start, _datetime.datetime):
|
||||
start = int(_time.mktime(start.timetuple()))
|
||||
else:
|
||||
start = int(_time.mktime(
|
||||
_time.strptime(str(start), '%Y-%m-%d')))
|
||||
# Check can get TZ. Fail => probably delisted
|
||||
try:
|
||||
tz = self._get_ticker_tz()
|
||||
except KeyError as e:
|
||||
if "exchangeTimezoneName" in str(e):
|
||||
shared._DFS[self.ticker] = utils.empty_df()
|
||||
shared._ERRORS[self.ticker] = err_msg
|
||||
if "many" not in kwargs and debug_mode:
|
||||
print('- %s: %s' % (self.ticker, err_msg))
|
||||
return utils.empty_df()
|
||||
else:
|
||||
raise
|
||||
|
||||
if end is None:
|
||||
end = int(_time.time())
|
||||
elif isinstance(end, _datetime.datetime):
|
||||
end = int(_time.mktime(end.timetuple()))
|
||||
else:
|
||||
end = int(_time.mktime(_time.strptime(str(end), '%Y-%m-%d')))
|
||||
|
||||
end = utils._parse_user_dt(end, tz)
|
||||
if start is None:
|
||||
if interval == "1m":
|
||||
start = end - 604800 # Subtract 7 days
|
||||
else:
|
||||
start = -631159200
|
||||
else:
|
||||
start = utils._parse_user_dt(start, tz)
|
||||
params = {"period1": start, "period2": end}
|
||||
else:
|
||||
period = period.lower()
|
||||
@@ -180,25 +196,32 @@ class TickerBase():
|
||||
url = "{}/v8/finance/chart/{}".format(self._base_url, self.ticker)
|
||||
|
||||
session = self.session or _requests
|
||||
data = session.get(
|
||||
url=url,
|
||||
params=params,
|
||||
proxies=proxy,
|
||||
headers=utils.user_agent_headers,
|
||||
timeout=timeout
|
||||
)
|
||||
if "Will be right back" in data.text:
|
||||
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
|
||||
"Our engineers are working quickly to resolve "
|
||||
"the issue. Thank you for your patience.")
|
||||
data = data.json()
|
||||
data = None
|
||||
|
||||
# Work with errors
|
||||
debug_mode = True
|
||||
if "debug" in kwargs and isinstance(kwargs["debug"], bool):
|
||||
debug_mode = kwargs["debug"]
|
||||
try:
|
||||
data = session.get(
|
||||
url=url,
|
||||
params=params,
|
||||
proxies=proxy,
|
||||
headers=utils.user_agent_headers,
|
||||
timeout=timeout
|
||||
)
|
||||
if "Will be right back" in data.text or data is None:
|
||||
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
|
||||
"Our engineers are working quickly to resolve "
|
||||
"the issue. Thank you for your patience.")
|
||||
|
||||
data = data.json()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if data is None or not type(data) is dict or 'status_code' in data.keys():
|
||||
shared._DFS[self.ticker] = utils.empty_df()
|
||||
shared._ERRORS[self.ticker] = err_msg
|
||||
if "many" not in kwargs and debug_mode:
|
||||
print('- %s: %s' % (self.ticker, err_msg))
|
||||
return utils.empty_df()
|
||||
|
||||
err_msg = "No data found for this date range, symbol may be delisted"
|
||||
if "chart" in data and data["chart"]["error"]:
|
||||
err_msg = data["chart"]["error"]["description"]
|
||||
shared._DFS[self.ticker] = utils.empty_df()
|
||||
@@ -217,7 +240,12 @@ class TickerBase():
|
||||
|
||||
# parse quotes
|
||||
try:
|
||||
quotes = utils.parse_quotes(data["chart"]["result"][0], tz)
|
||||
quotes = utils.parse_quotes(data["chart"]["result"][0])
|
||||
# Yahoo bug fix - it often appends latest price even if after end date
|
||||
if end and not quotes.empty:
|
||||
endDt = _pd.to_datetime(_datetime.datetime.utcfromtimestamp(end))
|
||||
if quotes.index[quotes.shape[0]-1] >= endDt:
|
||||
quotes = quotes.iloc[0:quotes.shape[0]-1]
|
||||
except Exception:
|
||||
shared._DFS[self.ticker] = utils.empty_df()
|
||||
shared._ERRORS[self.ticker] = err_msg
|
||||
@@ -265,10 +293,13 @@ class TickerBase():
|
||||
"chart"]["result"][0]["meta"]["priceHint"])
|
||||
quotes['Volume'] = quotes['Volume'].fillna(0).astype(_np.int64)
|
||||
|
||||
quotes.dropna(inplace=True)
|
||||
if not keepna:
|
||||
quotes.dropna(inplace=True)
|
||||
|
||||
# actions
|
||||
dividends, splits = utils.parse_actions(data["chart"]["result"][0], tz)
|
||||
dividends, splits = utils.parse_actions(data["chart"]["result"][0])
|
||||
|
||||
tz_exchange = data["chart"]["result"][0]["meta"]["exchangeTimezoneName"]
|
||||
|
||||
# combine
|
||||
df = _pd.concat([quotes, dividends, splits], axis=1, sort=True)
|
||||
@@ -276,17 +307,16 @@ class TickerBase():
|
||||
df["Stock Splits"].fillna(0, inplace=True)
|
||||
|
||||
# index eod/intraday
|
||||
df.index = df.index.tz_localize("UTC").tz_convert(
|
||||
data["chart"]["result"][0]["meta"]["exchangeTimezoneName"])
|
||||
df.index = df.index.tz_localize("UTC").tz_convert(tz_exchange)
|
||||
|
||||
df = utils.fix_Yahoo_dst_issue(df, params["interval"])
|
||||
|
||||
if params["interval"][-1] == "m":
|
||||
df.index.name = "Datetime"
|
||||
elif params["interval"] == "1h":
|
||||
pass
|
||||
else:
|
||||
df.index = _pd.to_datetime(df.index.date)
|
||||
if tz is not None:
|
||||
df.index = df.index.tz_localize(tz)
|
||||
df.index = _pd.to_datetime(df.index.date).tz_localize(tz_exchange)
|
||||
df.index.name = "Date"
|
||||
|
||||
# duplicates and missing rows cleanup
|
||||
@@ -302,32 +332,30 @@ class TickerBase():
|
||||
|
||||
# ------------------------
|
||||
|
||||
def _get_fundamentals(self, kind=None, proxy=None):
|
||||
def cleanup(data):
|
||||
df = _pd.DataFrame(data).drop(columns=['maxAge'])
|
||||
for col in df.columns:
|
||||
df[col] = _np.where(
|
||||
df[col].astype(str) == '-', _np.nan, df[col])
|
||||
def _get_ticker_tz(self):
|
||||
if not self._tz is None:
|
||||
return self._tz
|
||||
|
||||
df.set_index('endDate', inplace=True)
|
||||
try:
|
||||
df.index = _pd.to_datetime(df.index, unit='s')
|
||||
except ValueError:
|
||||
df.index = _pd.to_datetime(df.index)
|
||||
df = df.T
|
||||
df.columns.name = ''
|
||||
df.index.name = 'Breakdown'
|
||||
tkr_tz = utils.cache_lookup_tkr_tz(self.ticker)
|
||||
if tkr_tz is None:
|
||||
tkr_tz = self.info["exchangeTimezoneName"]
|
||||
# info fetch is relatively slow so cache timezone
|
||||
utils.cache_store_tkr_tz(self.ticker, tkr_tz)
|
||||
|
||||
df.index = utils.camel2title(df.index)
|
||||
return df
|
||||
self._tz = tkr_tz
|
||||
return tkr_tz
|
||||
|
||||
def _get_info(self, proxy=None):
|
||||
# setup proxy in requests format
|
||||
if proxy is not None:
|
||||
if isinstance(proxy, dict) and "https" in proxy:
|
||||
proxy = proxy["https"]
|
||||
proxy = {"https": proxy}
|
||||
|
||||
if self._fundamentals:
|
||||
if (self._info is None) or (self._sustainability is None) or (self._recommendations is None):
|
||||
## Need to fetch
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
ticker_url = "{}/{}".format(self._scrape_url, self.ticker)
|
||||
@@ -335,42 +363,6 @@ class TickerBase():
|
||||
# get info and sustainability
|
||||
data = utils.get_json(ticker_url, proxy, self.session)
|
||||
|
||||
# holders
|
||||
try:
|
||||
resp = utils.get_html(ticker_url + '/holders', proxy, self.session)
|
||||
holders = _pd.read_html(resp)
|
||||
except Exception:
|
||||
holders = []
|
||||
|
||||
if len(holders) >= 3:
|
||||
self._major_holders = holders[0]
|
||||
self._institutional_holders = holders[1]
|
||||
self._mutualfund_holders = holders[2]
|
||||
elif len(holders) >= 2:
|
||||
self._major_holders = holders[0]
|
||||
self._institutional_holders = holders[1]
|
||||
elif len(holders) >= 1:
|
||||
self._major_holders = holders[0]
|
||||
|
||||
# self._major_holders = holders[0]
|
||||
# self._institutional_holders = holders[1]
|
||||
|
||||
if self._institutional_holders is not None:
|
||||
if 'Date Reported' in self._institutional_holders:
|
||||
self._institutional_holders['Date Reported'] = _pd.to_datetime(
|
||||
self._institutional_holders['Date Reported'])
|
||||
if '% Out' in self._institutional_holders:
|
||||
self._institutional_holders['% Out'] = self._institutional_holders[
|
||||
'% Out'].str.replace('%', '').astype(float) / 100
|
||||
|
||||
if self._mutualfund_holders is not None:
|
||||
if 'Date Reported' in self._mutualfund_holders:
|
||||
self._mutualfund_holders['Date Reported'] = _pd.to_datetime(
|
||||
self._mutualfund_holders['Date Reported'])
|
||||
if '% Out' in self._mutualfund_holders:
|
||||
self._mutualfund_holders['% Out'] = self._mutualfund_holders[
|
||||
'% Out'].str.replace('%', '').astype(float) / 100
|
||||
|
||||
# sustainability
|
||||
d = {}
|
||||
try:
|
||||
@@ -401,7 +393,7 @@ class TickerBase():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# For ETFs, provide this valuable data: the top holdings of the ETF
|
||||
# For ETFs, provide this valuable data: the top holdings of the ETF
|
||||
try:
|
||||
if 'topHoldings' in data:
|
||||
self._info.update(data['topHoldings'])
|
||||
@@ -462,10 +454,85 @@ class TickerBase():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get_fundamentals(self, proxy=None):
|
||||
def cleanup(data):
|
||||
df = _pd.DataFrame(data).drop(columns=['maxAge'])
|
||||
for col in df.columns:
|
||||
df[col] = _np.where(
|
||||
df[col].astype(str) == '-', _np.nan, df[col])
|
||||
|
||||
df.set_index('endDate', inplace=True)
|
||||
try:
|
||||
df.index = _pd.to_datetime(df.index, unit='s')
|
||||
except ValueError:
|
||||
df.index = _pd.to_datetime(df.index)
|
||||
df = df.T
|
||||
df.columns.name = ''
|
||||
df.index.name = 'Breakdown'
|
||||
|
||||
# rename incorrect yahoo key
|
||||
df.rename(index={'treasuryStock': 'Gains Losses Not Affecting Retained Earnings'}, inplace=True)
|
||||
|
||||
df.index = utils.camel2title(df.index)
|
||||
return df
|
||||
|
||||
# setup proxy in requests format
|
||||
if proxy is not None:
|
||||
if isinstance(proxy, dict) and "https" in proxy:
|
||||
proxy = proxy["https"]
|
||||
proxy = {"https": proxy}
|
||||
|
||||
if self._fundamentals:
|
||||
return
|
||||
|
||||
ticker_url = "{}/{}".format(self._scrape_url, self.ticker)
|
||||
|
||||
# holders
|
||||
try:
|
||||
resp = utils.get_html(ticker_url + '/holders', proxy, self.session)
|
||||
holders = _pd.read_html(resp)
|
||||
except Exception:
|
||||
holders = []
|
||||
|
||||
if len(holders) >= 3:
|
||||
self._major_holders = holders[0]
|
||||
self._institutional_holders = holders[1]
|
||||
self._mutualfund_holders = holders[2]
|
||||
elif len(holders) >= 2:
|
||||
self._major_holders = holders[0]
|
||||
self._institutional_holders = holders[1]
|
||||
elif len(holders) >= 1:
|
||||
self._major_holders = holders[0]
|
||||
|
||||
# self._major_holders = holders[0]
|
||||
# self._institutional_holders = holders[1]
|
||||
|
||||
if self._institutional_holders is not None:
|
||||
if 'Date Reported' in self._institutional_holders:
|
||||
self._institutional_holders['Date Reported'] = _pd.to_datetime(
|
||||
self._institutional_holders['Date Reported'])
|
||||
if '% Out' in self._institutional_holders:
|
||||
self._institutional_holders['% Out'] = self._institutional_holders[
|
||||
'% Out'].str.replace('%', '').astype(float) / 100
|
||||
|
||||
if self._mutualfund_holders is not None:
|
||||
if 'Date Reported' in self._mutualfund_holders:
|
||||
self._mutualfund_holders['Date Reported'] = _pd.to_datetime(
|
||||
self._mutualfund_holders['Date Reported'])
|
||||
if '% Out' in self._mutualfund_holders:
|
||||
self._mutualfund_holders['% Out'] = self._mutualfund_holders[
|
||||
'% Out'].str.replace('%', '').astype(float) / 100
|
||||
|
||||
self._get_info(proxy)
|
||||
|
||||
# get fundamentals
|
||||
data = utils.get_json(ticker_url + '/financials', proxy, self.session)
|
||||
|
||||
# generic patterns
|
||||
self._earnings = {"yearly": utils.empty_df(), "quarterly": utils.empty_df()}
|
||||
self._cashflow = {"yearly": utils.empty_df(), "quarterly": utils.empty_df()}
|
||||
self._balancesheet = {"yearly": utils.empty_df(), "quarterly": utils.empty_df()}
|
||||
self._financials = {"yearly": utils.empty_df(), "quarterly": utils.empty_df()}
|
||||
for key in (
|
||||
(self._cashflow, 'cashflowStatement', 'cashflowStatements'),
|
||||
(self._balancesheet, 'balanceSheet', 'balanceSheetStatements'),
|
||||
@@ -503,6 +570,21 @@ class TickerBase():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# shares outstanding
|
||||
try:
|
||||
# keep only years with non None data
|
||||
available_shares = [shares_data for shares_data in data['annualBasicAverageShares'] if shares_data]
|
||||
shares = _pd.DataFrame(available_shares)
|
||||
shares['Year'] = shares['asOfDate'].agg(lambda x: int(x[:4]))
|
||||
shares.set_index('Year', inplace=True)
|
||||
shares.drop(columns=['dataId', 'asOfDate',
|
||||
'periodType', 'currencyCode'], inplace=True)
|
||||
shares.rename(
|
||||
columns={'reportedValue': "BasicShares"}, inplace=True)
|
||||
self._shares = shares
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Analysis
|
||||
data = utils.get_json(ticker_url + '/analysis', proxy, self.session)
|
||||
|
||||
@@ -522,24 +604,68 @@ class TickerBase():
|
||||
if isinstance(colval, dict):
|
||||
dict_cols.append(colname)
|
||||
for k, v in colval.items():
|
||||
new_colname = colname + ' ' + utils.camel2title([k])[0]
|
||||
new_colname = colname + ' ' + \
|
||||
utils.camel2title([k])[0]
|
||||
analysis.loc[idx, new_colname] = v
|
||||
|
||||
self._analysis = analysis[[c for c in analysis.columns if c not in dict_cols]]
|
||||
self._analysis = analysis[[
|
||||
c for c in analysis.columns if c not in dict_cols]]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Complementary key-statistics (currently fetching the important trailingPegRatio which is the value shown in the website)
|
||||
res = {}
|
||||
try:
|
||||
my_headers = {'user-agent': 'curl/7.55.1', 'accept': 'application/json', 'content-type': 'application/json',
|
||||
'referer': 'https://finance.yahoo.com/', 'cache-control': 'no-cache', 'connection': 'close'}
|
||||
p = _re.compile(r'root\.App\.main = (.*);')
|
||||
r = _requests.session().get('https://finance.yahoo.com/quote/{}/key-statistics?p={}'.format(self.ticker,
|
||||
self.ticker), headers=my_headers)
|
||||
q_results = {}
|
||||
my_qs_keys = ['pegRatio'] # QuoteSummaryStore
|
||||
# , 'quarterlyPegRatio'] # QuoteTimeSeriesStore
|
||||
my_ts_keys = ['trailingPegRatio']
|
||||
|
||||
# Complementary key-statistics
|
||||
data = _json.loads(p.findall(r.text)[0])
|
||||
key_stats = data['context']['dispatcher']['stores']['QuoteTimeSeriesStore']
|
||||
q_results.setdefault(self.ticker, [])
|
||||
for i in my_ts_keys:
|
||||
# j=0
|
||||
try:
|
||||
# res = {i: key_stats['timeSeries'][i][1]['reportedValue']['raw']}
|
||||
# We need to loop over multiple items, if they exist: 0,1,2,..
|
||||
zzz = key_stats['timeSeries'][i]
|
||||
for j in range(len(zzz)):
|
||||
if key_stats['timeSeries'][i][j]:
|
||||
res = {i: key_stats['timeSeries']
|
||||
[i][j]['reportedValue']['raw']}
|
||||
q_results[self.ticker].append(res)
|
||||
|
||||
# print(res)
|
||||
# q_results[ticker].append(res)
|
||||
except:
|
||||
q_results[ticker].append({i: np.nan})
|
||||
|
||||
res = {'Company': ticker}
|
||||
q_results[ticker].append(res)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if 'trailingPegRatio' in res:
|
||||
self._info['trailingPegRatio'] = res['trailingPegRatio']
|
||||
|
||||
self._fundamentals = True
|
||||
|
||||
def get_recommendations(self, proxy=None, as_dict=False, *args, **kwargs):
|
||||
self._get_fundamentals(proxy=proxy)
|
||||
self._get_info(proxy)
|
||||
data = self._recommendations
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_calendar(self, proxy=None, as_dict=False, *args, **kwargs):
|
||||
self._get_fundamentals(proxy=proxy)
|
||||
self._get_info(proxy)
|
||||
data = self._calendar
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
@@ -569,14 +695,14 @@ class TickerBase():
|
||||
return data
|
||||
|
||||
def get_info(self, proxy=None, as_dict=False, *args, **kwargs):
|
||||
self._get_fundamentals(proxy=proxy)
|
||||
self._get_info(proxy)
|
||||
data = self._info
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_sustainability(self, proxy=None, as_dict=False, *args, **kwargs):
|
||||
self._get_fundamentals(proxy=proxy)
|
||||
self._get_info(proxy)
|
||||
data = self._sustainability
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
@@ -646,6 +772,13 @@ class TickerBase():
|
||||
return actions[actions != 0].dropna(how='all').fillna(0)
|
||||
return []
|
||||
|
||||
def get_shares(self, proxy=None, as_dict=False, *args, **kwargs):
|
||||
self._get_fundamentals(proxy=proxy)
|
||||
data = self._shares
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_isin(self, proxy=None):
|
||||
# *** experimental ***
|
||||
if self._isin is not None:
|
||||
@@ -719,3 +852,121 @@ class TickerBase():
|
||||
# parse news
|
||||
self._news = data.get("news", [])
|
||||
return self._news
|
||||
|
||||
def get_earnings_dates(self, proxy=None):
|
||||
if self._earnings_dates is not None:
|
||||
return self._earnings_dates
|
||||
|
||||
# setup proxy in requests format
|
||||
if proxy is not None:
|
||||
if isinstance(proxy, dict) and "https" in proxy:
|
||||
proxy = proxy["https"]
|
||||
proxy = {"https": proxy}
|
||||
|
||||
page_size = 100 # YF caps at 100, don't go higher
|
||||
page_offset = 0
|
||||
dates = None
|
||||
while True:
|
||||
url = "{}/calendar/earnings?symbol={}&offset={}&size={}".format(
|
||||
_ROOT_URL_, self.ticker, page_offset, page_size)
|
||||
|
||||
session = self.session or _requests
|
||||
data = session.get(
|
||||
url=url,
|
||||
proxies=proxy,
|
||||
headers=utils.user_agent_headers
|
||||
).text
|
||||
|
||||
if "Will be right back" in data:
|
||||
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
|
||||
"Our engineers are working quickly to resolve "
|
||||
"the issue. Thank you for your patience.")
|
||||
|
||||
try:
|
||||
data = _pd.read_html(data)[0]
|
||||
except ValueError:
|
||||
if page_offset == 0:
|
||||
# Should not fail on first page
|
||||
if "Showing Earnings for:" in data:
|
||||
# Actually YF was successful, problem is company doesn't have earnings history
|
||||
dates = utils.empty_earnings_dates_df()
|
||||
break
|
||||
|
||||
if dates is None:
|
||||
dates = data
|
||||
else:
|
||||
dates = _pd.concat([dates, data], axis=0)
|
||||
page_offset += page_size
|
||||
|
||||
if dates is None:
|
||||
raise Exception("No data found, symbol may be delisted")
|
||||
dates = dates.reset_index(drop=True)
|
||||
|
||||
# Drop redundant columns
|
||||
dates = dates.drop(["Symbol", "Company"], axis=1)
|
||||
|
||||
# Convert types
|
||||
for cn in ["EPS Estimate", "Reported EPS", "Surprise(%)"]:
|
||||
dates.loc[dates[cn] == '-', cn] = "NaN"
|
||||
dates[cn] = dates[cn].astype(float)
|
||||
|
||||
# Convert % to range 0->1:
|
||||
dates["Surprise(%)"] *= 0.01
|
||||
|
||||
# Parse earnings date string
|
||||
cn = "Earnings Date"
|
||||
# - remove AM/PM and timezone from date string
|
||||
tzinfo = dates[cn].str.extract('([AP]M[a-zA-Z]*)$')
|
||||
dates[cn] = dates[cn].replace(' [AP]M[a-zA-Z]*$', '', regex=True)
|
||||
# - split AM/PM from timezone
|
||||
tzinfo = tzinfo[0].str.extract('([AP]M)([a-zA-Z]*)', expand=True)
|
||||
tzinfo.columns = ["AM/PM", "TZ"]
|
||||
# - combine and parse
|
||||
dates[cn] = dates[cn] + ' ' + tzinfo["AM/PM"]
|
||||
dates[cn] = _pd.to_datetime(dates[cn], format="%b %d, %Y, %I %p")
|
||||
# - instead of attempting decoding of ambiguous timezone abbreviation, just use 'info':
|
||||
dates[cn] = dates[cn].dt.tz_localize(
|
||||
tz=self.info["exchangeTimezoneName"])
|
||||
|
||||
dates = dates.set_index("Earnings Date")
|
||||
|
||||
self._earnings_dates = dates
|
||||
|
||||
return dates
|
||||
|
||||
def get_earnings_history(self, proxy=None):
|
||||
if self._earnings_history:
|
||||
return self._earnings_history
|
||||
|
||||
# setup proxy in requests format
|
||||
if proxy is not None:
|
||||
if isinstance(proxy, dict) and "https" in proxy:
|
||||
proxy = proxy["https"]
|
||||
proxy = {"https": proxy}
|
||||
|
||||
url = "{}/calendar/earnings?symbol={}".format(_ROOT_URL_, self.ticker)
|
||||
session = self.session or _requests
|
||||
data = session.get(
|
||||
url=url,
|
||||
proxies=proxy,
|
||||
headers=utils.user_agent_headers
|
||||
).text
|
||||
|
||||
if "Will be right back" in data:
|
||||
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
|
||||
"Our engineers are working quickly to resolve "
|
||||
"the issue. Thank you for your patience.")
|
||||
|
||||
try:
|
||||
# read_html returns a list of pandas Dataframes of all the tables in `data`
|
||||
data = _pd.read_html(data)[0]
|
||||
data.replace("-", _np.nan, inplace=True)
|
||||
|
||||
data['EPS Estimate'] = _pd.to_numeric(data['EPS Estimate'])
|
||||
data['Reported EPS'] = _pd.to_numeric(data['Reported EPS'])
|
||||
self._earnings_history = data
|
||||
# if no tables are found a ValueError is thrown
|
||||
except ValueError:
|
||||
print("Could not find data for {}.".format(self.ticker))
|
||||
return
|
||||
return data
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
#
|
||||
# Copyright 2017-2019 Ran Aroussi
|
||||
@@ -29,8 +29,8 @@ from . import Ticker, utils
|
||||
from . import shared
|
||||
|
||||
|
||||
def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
group_by='column', auto_adjust=False, back_adjust=False,
|
||||
def download(tickers, start=None, end=None, actions=False, threads=True, ignore_tz=True,
|
||||
group_by='column', auto_adjust=False, back_adjust=False, keepna=False,
|
||||
progress=True, period="max", show_errors=True, interval="1d", prepost=False,
|
||||
proxy=None, rounding=False, timeout=None, **kwargs):
|
||||
"""Download yahoo tickers
|
||||
@@ -56,16 +56,22 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
Default is False
|
||||
auto_adjust: bool
|
||||
Adjust all OHLC automatically? 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 is True
|
||||
proxy: str
|
||||
Optional. Proxy server URL scheme. Default is None
|
||||
rounding: bool
|
||||
Optional. Round values to 2 decimal places?
|
||||
show_errors: bool
|
||||
Optional. Doesn't print errors if True
|
||||
Optional. Doesn't print errors if False
|
||||
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)
|
||||
@@ -105,7 +111,7 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
_download_one_threaded(ticker, period=period, interval=interval,
|
||||
start=start, end=end, prepost=prepost,
|
||||
actions=actions, auto_adjust=auto_adjust,
|
||||
back_adjust=back_adjust,
|
||||
back_adjust=back_adjust, keepna=keepna,
|
||||
progress=(progress and i > 0), proxy=proxy,
|
||||
rounding=rounding, timeout=timeout)
|
||||
while len(shared._DFS) < len(tickers):
|
||||
@@ -117,7 +123,7 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
data = _download_one(ticker, period=period, interval=interval,
|
||||
start=start, end=end, prepost=prepost,
|
||||
actions=actions, auto_adjust=auto_adjust,
|
||||
back_adjust=back_adjust, proxy=proxy,
|
||||
back_adjust=back_adjust, keepna=keepna, proxy=proxy,
|
||||
rounding=rounding, timeout=timeout)
|
||||
shared._DFS[ticker.upper()] = data
|
||||
if progress:
|
||||
@@ -137,12 +143,17 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
ticker = tickers[0]
|
||||
return shared._DFS[shared._ISINS.get(ticker, ticker)]
|
||||
|
||||
if ignore_tz:
|
||||
for tkr in shared._DFS.keys():
|
||||
if (shared._DFS[tkr] is not None) and (shared._DFS[tkr].shape[0]>0):
|
||||
shared._DFS[tkr].index = shared._DFS[tkr].index.tz_localize(None)
|
||||
|
||||
try:
|
||||
data = _pd.concat(shared._DFS.values(), axis=1,
|
||||
data = _pd.concat(shared._DFS.values(), axis=1, sort=True,
|
||||
keys=shared._DFS.keys())
|
||||
except Exception:
|
||||
_realign_dfs()
|
||||
data = _pd.concat(shared._DFS.values(), axis=1,
|
||||
data = _pd.concat(shared._DFS.values(), axis=1, sort=True,
|
||||
keys=shared._DFS.keys())
|
||||
|
||||
# switch names back to isins if applicable
|
||||
@@ -183,11 +194,11 @@ def _download_one_threaded(ticker, start=None, end=None,
|
||||
auto_adjust=False, back_adjust=False,
|
||||
actions=False, progress=True, period="max",
|
||||
interval="1d", prepost=False, proxy=None,
|
||||
rounding=False, timeout=None):
|
||||
keepna=False, rounding=False, timeout=None):
|
||||
|
||||
data = _download_one(ticker, start, end, auto_adjust, back_adjust,
|
||||
actions, period, interval, prepost, proxy, rounding,
|
||||
timeout)
|
||||
keepna, timeout)
|
||||
shared._DFS[ticker.upper()] = data
|
||||
if progress:
|
||||
shared._PROGRESS_BAR.animate()
|
||||
@@ -197,11 +208,11 @@ def _download_one(ticker, start=None, end=None,
|
||||
auto_adjust=False, back_adjust=False,
|
||||
actions=False, period="max", interval="1d",
|
||||
prepost=False, proxy=None, rounding=False,
|
||||
timeout=None):
|
||||
keepna=False, timeout=None):
|
||||
|
||||
return Ticker(ticker).history(period=period, interval=interval,
|
||||
start=start, end=end, prepost=prepost,
|
||||
actions=actions, auto_adjust=auto_adjust,
|
||||
back_adjust=back_adjust, proxy=proxy,
|
||||
rounding=rounding, many=True,
|
||||
rounding=rounding, keepna=keepna, many=True,
|
||||
timeout=timeout)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
#
|
||||
# Copyright 2017-2019 Ran Aroussi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
#
|
||||
# Copyright 2017-2019 Ran Aroussi
|
||||
@@ -84,9 +84,9 @@ class Ticker(TickerBase):
|
||||
'currency'])
|
||||
|
||||
data['lastTradeDate'] = _pd.to_datetime(
|
||||
data['lastTradeDate'], unit='s')
|
||||
data['lastTradeDate'], unit='s', utc=True)
|
||||
if tz is not None:
|
||||
data['lastTradeDate'] = data['lastTradeDate'].tz_localize(tz)
|
||||
data['lastTradeDate'] = data['lastTradeDate'].dt.tz_convert(tz)
|
||||
return data
|
||||
|
||||
def option_chain(self, date=None, proxy=None, tz=None):
|
||||
@@ -138,6 +138,10 @@ class Ticker(TickerBase):
|
||||
def actions(self):
|
||||
return self.get_actions()
|
||||
|
||||
@property
|
||||
def shares(self):
|
||||
return self.get_shares()
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
return self.get_info()
|
||||
@@ -207,3 +211,11 @@ class Ticker(TickerBase):
|
||||
@property
|
||||
def analysis(self):
|
||||
return self.get_analysis()
|
||||
|
||||
@property
|
||||
def earnings_history(self):
|
||||
return self.get_earnings_history()
|
||||
|
||||
@property
|
||||
def earnings_dates(self):
|
||||
return self.get_earnings_dates()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
#
|
||||
# Copyright 2017-2019 Ran Aroussi
|
||||
@@ -30,14 +30,14 @@ class Tickers():
|
||||
def __repr__(self):
|
||||
return 'yfinance.Tickers object <%s>' % ",".join(self.symbols)
|
||||
|
||||
def __init__(self, tickers):
|
||||
def __init__(self, tickers, session=None):
|
||||
tickers = tickers if isinstance(
|
||||
tickers, list) else tickers.replace(',', ' ').split()
|
||||
self.symbols = [ticker.upper() for ticker in tickers]
|
||||
ticker_objects = {}
|
||||
|
||||
for ticker in self.symbols:
|
||||
ticker_objects[ticker] = Ticker(ticker)
|
||||
ticker_objects[ticker] = Ticker(ticker, session=session)
|
||||
|
||||
self.tickers = ticker_objects
|
||||
# self.tickers = _namedtuple(
|
||||
@@ -51,11 +51,11 @@ class Tickers():
|
||||
timeout=None, **kwargs):
|
||||
|
||||
return self.download(
|
||||
period, interval,
|
||||
start, end, prepost,
|
||||
actions, auto_adjust, proxy,
|
||||
threads, group_by, progress,
|
||||
timeout, **kwargs)
|
||||
period, interval,
|
||||
start, end, prepost,
|
||||
actions, auto_adjust, proxy,
|
||||
threads, group_by, progress,
|
||||
timeout, **kwargs)
|
||||
|
||||
def download(self, period="1mo", interval="1d",
|
||||
start=None, end=None, prepost=False,
|
||||
@@ -85,3 +85,12 @@ class Tickers():
|
||||
data.sort_index(level=0, axis=1, inplace=True)
|
||||
|
||||
return data
|
||||
|
||||
def news(self):
|
||||
collection = {}
|
||||
for ticker in self.symbols:
|
||||
collection[ticker] = []
|
||||
items = Ticker(ticker).news
|
||||
for item in items:
|
||||
collection[ticker].append(item)
|
||||
return collection
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Yahoo! Finance market data downloader (+fix for Pandas Datareader)
|
||||
# yfinance - market data downloader
|
||||
# https://github.com/ranaroussi/yfinance
|
||||
#
|
||||
# Copyright 2017-2019 Ran Aroussi
|
||||
@@ -21,11 +21,15 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime as _datetime
|
||||
import pytz as _tz
|
||||
import requests as _requests
|
||||
import re as _re
|
||||
import pandas as _pd
|
||||
import numpy as _np
|
||||
import sys as _sys
|
||||
import os as _os
|
||||
import appdirs as _ad
|
||||
|
||||
try:
|
||||
import ujson as _json
|
||||
@@ -33,7 +37,8 @@ except ImportError:
|
||||
import json as _json
|
||||
|
||||
|
||||
user_agent_headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
|
||||
user_agent_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
|
||||
|
||||
|
||||
def is_isin(string):
|
||||
@@ -88,6 +93,13 @@ def empty_df(index=[]):
|
||||
return empty
|
||||
|
||||
|
||||
def empty_earnings_dates_df():
|
||||
empty = _pd.DataFrame(
|
||||
columns=["Symbol", "Company", "Earnings Date",
|
||||
"EPS Estimate", "Reported EPS", "Surprise(%)"])
|
||||
return empty
|
||||
|
||||
|
||||
def get_html(url, proxy=None, session=None):
|
||||
session = session or _requests
|
||||
html = session.get(url=url, proxies=proxy, headers=user_agent_headers).text
|
||||
@@ -107,6 +119,13 @@ def get_json(url, proxy=None, session=None):
|
||||
'(this)')[0].split(';\n}')[0].strip()
|
||||
data = _json.loads(json_str)[
|
||||
'context']['dispatcher']['stores']['QuoteSummaryStore']
|
||||
# add data about Shares Outstanding for companies' tickers if they are available
|
||||
try:
|
||||
data['annualBasicAverageShares'] = _json.loads(
|
||||
json_str)['context']['dispatcher']['stores'][
|
||||
'QuoteTimeSeriesStore']['timeSeries']['annualBasicAverageShares']
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# return data
|
||||
new_data = _json.dumps(data).replace('{}', 'null')
|
||||
@@ -120,6 +139,23 @@ def camel2title(o):
|
||||
return [_re.sub("([a-z])([A-Z])", r"\g<1> \g<2>", i).title() for i in o]
|
||||
|
||||
|
||||
def _parse_user_dt(dt, exchange_tz):
|
||||
if isinstance(dt, int):
|
||||
## Should already be epoch, test with conversion:
|
||||
_datetime.datetime.fromtimestamp(dt)
|
||||
else:
|
||||
# Convert str/date -> datetime, set tzinfo=exchange, get timestamp:
|
||||
if isinstance(dt, str):
|
||||
dt = _datetime.datetime.strptime(str(dt), '%Y-%m-%d')
|
||||
if isinstance(dt, _datetime.date) and not isinstance(dt, _datetime.datetime):
|
||||
dt = _datetime.datetime.combine(dt, _datetime.time(0))
|
||||
if isinstance(dt, _datetime.datetime) and dt.tzinfo is None:
|
||||
# Assume user is referring to exchange's timezone
|
||||
dt = _tz.timezone(exchange_tz).localize(dt)
|
||||
dt = int(dt.timestamp())
|
||||
return dt
|
||||
|
||||
|
||||
def auto_adjust(data):
|
||||
df = data.copy()
|
||||
ratio = df["Close"] / df["Adj Close"]
|
||||
@@ -161,7 +197,7 @@ def back_adjust(data):
|
||||
return df[["Open", "High", "Low", "Close", "Volume"]]
|
||||
|
||||
|
||||
def parse_quotes(data, tz=None):
|
||||
def parse_quotes(data):
|
||||
timestamps = data["timestamp"]
|
||||
ohlc = data["indicators"]["quote"][0]
|
||||
volumes = ohlc["volume"]
|
||||
@@ -184,15 +220,14 @@ def parse_quotes(data, tz=None):
|
||||
quotes.index = _pd.to_datetime(timestamps, unit="s")
|
||||
quotes.sort_index(inplace=True)
|
||||
|
||||
if tz is not None:
|
||||
quotes.index = quotes.index.tz_localize(tz)
|
||||
|
||||
return quotes
|
||||
|
||||
|
||||
def parse_actions(data, tz=None):
|
||||
dividends = _pd.DataFrame(columns=["Dividends"])
|
||||
splits = _pd.DataFrame(columns=["Stock Splits"])
|
||||
def parse_actions(data):
|
||||
dividends = _pd.DataFrame(
|
||||
columns=["Dividends"], index=_pd.DatetimeIndex([]))
|
||||
splits = _pd.DataFrame(
|
||||
columns=["Stock Splits"], index=_pd.DatetimeIndex([]))
|
||||
|
||||
if "events" in data:
|
||||
if "dividends" in data["events"]:
|
||||
@@ -201,8 +236,6 @@ def parse_actions(data, tz=None):
|
||||
dividends.set_index("date", inplace=True)
|
||||
dividends.index = _pd.to_datetime(dividends.index, unit="s")
|
||||
dividends.sort_index(inplace=True)
|
||||
if tz is not None:
|
||||
dividends.index = dividends.index.tz_localize(tz)
|
||||
|
||||
dividends.columns = ["Dividends"]
|
||||
|
||||
@@ -212,8 +245,6 @@ def parse_actions(data, tz=None):
|
||||
splits.set_index("date", inplace=True)
|
||||
splits.index = _pd.to_datetime(splits.index, unit="s")
|
||||
splits.sort_index(inplace=True)
|
||||
if tz is not None:
|
||||
splits.index = splits.index.tz_localize(tz)
|
||||
splits["Stock Splits"] = splits["numerator"] / \
|
||||
splits["denominator"]
|
||||
splits = splits["Stock Splits"]
|
||||
@@ -221,6 +252,19 @@ def parse_actions(data, tz=None):
|
||||
return dividends, splits
|
||||
|
||||
|
||||
def fix_Yahoo_dst_issue(df, interval):
|
||||
if interval in ["1d","1w","1wk"]:
|
||||
# These intervals should start at time 00:00. But for some combinations of date and timezone,
|
||||
# Yahoo has time off by few hours (e.g. Brazil 23:00 around Jan-2022). Suspect DST problem.
|
||||
# The clue is (a) minutes=0 and (b) hour near 0.
|
||||
# Obviously Yahoo meant 00:00, so ensure this doesn't affect date conversion:
|
||||
f_pre_midnight = (df.index.minute == 0) & (df.index.hour.isin([22,23]))
|
||||
dst_error_hours = _np.array([0]*df.shape[0])
|
||||
dst_error_hours[f_pre_midnight] = 24-df.index[f_pre_midnight].hour
|
||||
df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')
|
||||
return df
|
||||
|
||||
|
||||
class ProgressBar:
|
||||
def __init__(self, iterations, text='completed'):
|
||||
self.text = text
|
||||
@@ -269,3 +313,37 @@ class ProgressBar:
|
||||
|
||||
def __str__(self):
|
||||
return str(self.prog_bar)
|
||||
|
||||
|
||||
# Simple file cache of ticker->timezone:
|
||||
def get_cache_dirpath():
|
||||
return _os.path.join(_ad.user_cache_dir(), "py-yfinance")
|
||||
def cache_lookup_tkr_tz(tkr):
|
||||
fp = _os.path.join(get_cache_dirpath(), "tkr-tz.csv")
|
||||
if not _os.path.isfile(fp):
|
||||
return None
|
||||
|
||||
df = _pd.read_csv(fp)
|
||||
f = df["Ticker"] == tkr
|
||||
if sum(f) == 0:
|
||||
return None
|
||||
|
||||
return df["Tz"][f].iloc[0]
|
||||
def cache_store_tkr_tz(tkr,tz):
|
||||
df = _pd.DataFrame({"Ticker":[tkr], "Tz":[tz]})
|
||||
|
||||
dp = get_cache_dirpath()
|
||||
if not _os.path.isdir(dp):
|
||||
_os.makedirs(dp)
|
||||
fp = _os.path.join(dp, "tkr-tz.csv")
|
||||
if not _os.path.isfile(fp):
|
||||
df.to_csv(fp, index=False)
|
||||
return
|
||||
|
||||
df_all = _pd.read_csv(fp)
|
||||
f = df_all["Ticker"]==tkr
|
||||
if sum(f) > 0:
|
||||
raise Exception("Tkr {} tz already in cache".format(tkr))
|
||||
|
||||
_pd.concat([df_all,df]).to_csv(fp, index=False)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version = "0.1.66"
|
||||
version = "0.1.78"
|
||||
|
||||
Reference in New Issue
Block a user