Compare commits

...

33 Commits

Author SHA1 Message Date
ValueRaider
930b305327 Version 0.2.40 2024-05-19 17:25:01 +01:00
ValueRaider
7af213dea2 Fix unmatched quotes (0.2.39 regression) 2024-05-19 17:21:59 +01:00
ValueRaider
049337327e Version 0.2.39 2024-05-19 15:14:45 +01:00
ValueRaider
e65ca40d95 Merge pull request #1927 from ranaroussi/dev
sync dev -> main
2024-05-19 15:10:34 +01:00
ValueRaider
fe00fd5152 Ruff fixes 2024-05-19 15:09:57 +01:00
ValueRaider
cb691df586 Merge pull request #1941 from ranaroussi/main
sync main -> dev
2024-05-19 15:07:18 +01:00
ValueRaider
4bc546cb71 Update ci.yml to Node20 2024-05-19 15:05:35 +01:00
ValueRaider
f3c9f9962d Fix tests ; Fine-tune split repair ; Fix UTC warning 2024-05-19 15:01:52 +01:00
ValueRaider
da1c466550 Merge pull request #1931 from ranaroussi/feature/improve-price-repair-bad-splits
Price repair: improve 'sudden change' repair for splits & currency
2024-05-19 11:40:18 +01:00
ValueRaider
97f35b721c Price repair: improve 'sudden change' repair for splits & currency
Original logic for repairing missing split adjustment only checked latest split.
Improved logic checks ALL splits in data, because any can be missing.

Then related changes to 'sudden change detection':
- use prices median not mean, reduce sensitivity to noise.
- handle Kuwait Dinar, which sub-divides into 1000x not 100x.
2024-05-19 11:39:03 +01:00
ValueRaider
7c41434f44 Merge pull request #1930 from ranaroussi/fix/session-switching
Fix switching session from/to requests_cache
2024-05-11 21:40:41 +01:00
ValueRaider
070f13577e Merge pull request #1928 from marcofognog/dev
Add more specific error thowring base on PR 1918
2024-05-11 21:37:35 +01:00
Marcao
7628bec2a6 Adjust and fix according to feedback 2024-05-11 19:20:19 +02:00
ValueRaider
ac4efa3e3d Fix switching session from/to requests_cache
Session switch logic was not recalculating 'self._session_is_caching'.
Also removed message 'help stress-test cookie & crumb & requests_cache', clearly works now.
2024-05-11 09:33:17 +01:00
Elijah Lopez
5a683b916d Add raise missing ticker tests, replace deprecated datetime methods
- renamed test files conform with standards
- replaced utcfromtimestamp
2024-05-10 17:23:14 +02:00
Elijah Lopez
30fdc96157 Fix: PricesMissingError not being raised 2024-05-10 17:15:08 +02:00
Elijah Lopez
ee87a95b8d Rename errors from YFinance to YF 2024-05-10 17:15:08 +02:00
Elijah Lopez
685ef71d9f Add error classes for symbol delisting errors, closes #270 2024-05-10 17:15:08 +02:00
ValueRaider
098e77659c Merge pull request #1922 from ranaroussi/fix/datetime-utc-warning 2024-05-09 10:05:43 +01:00
ValueRaider
dc5c718556 Fix: datetime.datetime.utcnow() is deprecated ...
Python 3.12 deprecates datetime.datetime.utcnow().
Instead of switching to datetime.datetime.now(datetime.UTC), which won't work in Python 3.11,
just switch to Pandas.utcnow().
2024-05-02 22:45:26 +01:00
ValueRaider
84ba6d7d88 Merge pull request #1920 from ranaroussi/feature/price-repair-fx
Don't price-repair FX volume=0, is normal
2024-04-28 12:51:59 +01:00
ValueRaider
e238ac1f95 Merge pull request #1919 from ranaroussi/feature/readme-table-of-contents
Add table-of-contents to README
2024-04-28 12:51:15 +01:00
ValueRaider
efe15e1907 Add table-of-contents to README 2024-04-27 12:50:15 +01:00
ValueRaider
2dcbe34910 Don't price-repair FX volume=0, is normal 2024-04-26 21:32:39 +01:00
ValueRaider
bb47cd4182 Merge pull request #1917 from ranaroussi/main
sync main -> dev
2024-04-24 21:06:43 +01:00
ValueRaider
94e3833e90 Merge pull request #1913 from vittoboa/add_functools_wraps_to_wrapper
Fix help(yf.download) not showing the information about the function
2024-04-22 21:12:22 +01:00
vittoboa
f8e8eecf44 Add functools.wraps to log_indent_decorator's wrapper function 2024-04-22 21:08:10 +02:00
ValueRaider
82a3145fdf Merge pull request #1897 from ranaroussi/feature/deprecate-pdr
Deprecate 'pandas_datareader', remove a deprecated argument
2024-04-06 12:42:52 +01:00
ValueRaider
48e9075a2d Deprecate 'pandas_datareader', remove a deprecated argument.
Drop official support for 'pandas_datareader', tag pdr_override() as deprecated.
Also removed deprecated argument 'download(show_errors)'.
2024-04-06 12:42:04 +01:00
ValueRaider
88e8ddb7f5 Merge pull request #1896 from ranaroussi/feature/replace-dead-appdirs
Replace dead 'appdirs' package with 'platformdirs'
2024-04-06 12:22:37 +01:00
ValueRaider
812931ff98 Replace dead 'appdirs' package with 'platformdirs' 2024-04-06 12:19:46 +01:00
ValueRaider
1967e974c1 Merge pull request #1874 from ranaroussi/fix/price-repair-subtle-bug
Fix potential for price repair to discard price=0 rows
2024-03-04 19:46:59 +00:00
Value Raider
942a26fd37 Fix potential for price repair to discard price=0 rows 2024-03-01 22:03:03 +00:00
25 changed files with 305 additions and 204 deletions

View File

@@ -8,7 +8,7 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: 3.x

View File

@@ -1,6 +1,26 @@
Change Log
===========
0.2.40
------
Fix typo in 0.2.39 c7af213
0.2.39
------
Fixes:
- Fix switching session from/to requests_cache #1930
Price repair:
- Fix potential for price repair to discard price=0 rows #1874
- Don't price-repair FX volume=0, is normal #1920
- Improve 'sudden change' repair for splits & currency #1931
Information:
- Fix help(yf.download) not showing the information about the function #1913 @vittoboa
- Add more specific error throwing based on PR 1918 #1928 @elibroftw @marcofognog
Maintenance:
- Replace dead 'appdirs' package with 'platformdirs' #1896
- Deprecate 'pandas_datareader', remove a deprecated argument #1897
- Fix: datetime.datetime.utcnow() is deprecated ... #1922
0.2.38
------
Fix holders & insiders #1908

View File

@@ -42,6 +42,14 @@ Yahoo! finance API is intended for personal use only.**
---
- [Installation](#installation)
- [Quick start](#quick-start)
- [Advanced](#logging)
- [Wiki](https://github.com/ranaroussi/yfinance/wiki)
- [Contribute](#developers-want-to-contribute)
---
## Installation
Install `yfinance` using `pip`:
@@ -116,7 +124,7 @@ msft.recommendations
msft.recommendations_summary
msft.upgrades_downgrades
# Show future and historic earnings dates, returns at most next 4 quarters and last 8 quarters by default.
# 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
@@ -183,7 +191,7 @@ data = yf.download("SPY AAPL", period="1mo")
### 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.
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.
@@ -228,31 +236,16 @@ yfinance?](https://stackoverflow.com/questions/63107801)
- 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")
```
### 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")
@@ -279,7 +272,7 @@ intended for research and educational purposes. You should refer to Yahoo!'s ter
([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.
details on your rights to use the actual data downloaded.
---

View File

@@ -1,5 +1,5 @@
{% set name = "yfinance" %}
{% set version = "0.2.38" %}
{% set version = "0.2.40" %}
package:
name: "{{ name|lower }}"
@@ -21,7 +21,7 @@ requirements:
- requests >=2.31
- multitasking >=0.0.7
- lxml >=4.9.1
- appdirs >=1.4.4
- platformdirs >=2.0.0
- pytz >=2022.5
- frozendict >=2.3.4
- beautifulsoup4 >=4.11.1
@@ -37,7 +37,7 @@ requirements:
- requests >=2.31
- multitasking >=0.0.7
- lxml >=4.9.1
- appdirs >=1.4.4
- platformdirs >=2.0.0
- pytz >=2022.5
- frozendict >=2.3.4
- beautifulsoup4 >=4.11.1

View File

@@ -3,7 +3,7 @@ numpy>=1.16.5
requests>=2.31
multitasking>=0.0.7
lxml>=4.9.1
appdirs>=1.4.4
platformdirs>=2.0.0
pytz>=2022.5
frozendict>=2.3.4
beautifulsoup4>=4.11.1

View File

@@ -61,7 +61,7 @@ setup(
packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']),
install_requires=['pandas>=1.3.0', 'numpy>=1.16.5',
'requests>=2.31', 'multitasking>=0.0.7',
'lxml>=4.9.1', 'appdirs>=1.4.4', 'pytz>=2022.5',
'lxml>=4.9.1', 'platformdirs>=2.0.0', 'pytz>=2022.5',
'frozendict>=2.3.4', 'peewee>=3.16.2',
'beautifulsoup4>=4.11.1', 'html5lib>=1.1'],
extras_require={

View File

@@ -1 +0,0 @@
#!/usr/bin/env python

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import appdirs as _ad
import platformdirs as _ad
import datetime as _dt
import sys
import os

View File

@@ -1,27 +1,27 @@
Date,Open,High,Low,Close,Adj Close,Volume,Dividends,Stock Splits
2021-12-13 00:00:00+00:00,393.999975585938,406.6,391.4,402.899916992188,291.232287597656,62714764.4736842,0,0
2021-12-20 00:00:00+00:00,393.999975585938,412.199990234375,392.502983398438,409.899997558594,296.292243652344,46596651.3157895,0,0
2021-12-27 00:00:00+00:00,409.899997558594,416.550971679688,408.387001953125,410.4,296.653642578125,10818482.8947368,0,0
2022-01-03 00:00:00+00:00,410.4,432.199995117188,410.4,432.099985351563,312.339265136719,44427327.6315789,0,0
2022-01-10 00:00:00+00:00,431.3,439.199982910156,429.099970703125,436.099912109375,315.230618896484,29091400,0,0
2022-01-17 00:00:00+00:00,437.999912109375,445.199965820313,426.999997558594,431.999975585938,312.267017822266,43787351.3157895,0,0
2022-01-24 00:00:00+00:00,430.099975585938,440.999973144531,420.999968261719,433.499982910156,313.351237792969,58487296.0526316,0,0
2022-01-31 00:00:00+00:00,436.199968261719,443.049987792969,432.099985351563,435.199916992188,314.580045166016,43335806.5789474,0,0
2022-02-07 00:00:00+00:00,437.899995117188,448.799992675781,436.051994628906,444.39998046875,321.230207519531,39644061.8421053,0,0
2022-02-14 00:00:00+00:00,437.699975585938,441.999978027344,426.699968261719,432.199995117188,312.411558837891,49972693.4210526,0,0
2022-02-21 00:00:00+00:00,435.499992675781,438.476999511719,408.29998046875,423.399970703125,306.050571289063,65719596.0526316,0,0
2022-02-28 00:00:00+00:00,415.099995117188,427.999909667969,386.199932861328,386.799945068359,279.594578857422,94057936.8421053,4.1875,0
2022-03-07 00:00:00+00:00,374.999952392578,417.299978027344,361.101981201172,409.599968261719,298.389248046875,71269101.3157895,0,0
2022-03-14 00:00:00+00:00,413.099985351563,426.699968261719,408.899992675781,422.399965820313,307.713929443359,55431927.6315789,0,0
2022-03-21 00:00:00+00:00,422.699995117188,442.7,422.399965820313,437.799985351563,318.932696533203,39896352.6315789,0,0
2022-03-28 00:00:00+01:00,442.49998046875,460.999978027344,440.097983398438,444.6,323.886403808594,56413515.7894737,0,0
2022-04-04 00:00:00+01:00,439.699985351563,445.399985351563,421.999973144531,425.799973144531,310.190817871094,49415836.8421053,19.342106,0
2022-04-11 00:00:00+01:00,425.39998046875,435.599909667969,420.799995117188,434.299968261719,327.211427001953,29875081.5789474,0,0
2022-04-18 00:00:00+01:00,434.299968261719,447.799987792969,433.599992675781,437.799985351563,329.848419189453,49288272.3684211,0,0
2022-04-25 00:00:00+01:00,430.699987792969,438.799990234375,423.999982910156,433.299916992188,326.457967529297,44656776.3157895,0,0
2022-05-02 00:00:00+01:00,433.299916992188,450.999975585938,414.499982910156,414.899975585938,312.595018310547,29538167.1052632,0,0
2022-05-09 00:00:00+01:00,413.199995117188,417.449992675781,368.282923583984,408.199970703125,307.547099609375,73989611.8421053,0,0
2022-05-16 00:00:00+01:00,384,423.600006103516,384,412.100006103516,310.485473632813,81938261,101.69,0.76
2021-12-13 00:00:00+00:00,518.421020507813,535,515,530.131469726563,383.200378417969,47663221,0,0
2021-12-20 00:00:00+00:00,518.421020507813,542.368408203125,516.451293945313,539.342102050781,389.858215332031,35413455,0,0
2021-12-27 00:00:00+00:00,539.342102050781,548.093383789063,537.351318359375,540,390.333740234375,8222047,0,0
2022-01-03 00:00:00+00:00,540,568.684204101563,540,568.552612304688,410.972717285156,33764769,0,0
2022-01-10 00:00:00+00:00,567.5,577.894714355469,564.605224609375,573.815673828125,414.777130126953,22109464,0,0
2022-01-17 00:00:00+00:00,576.315673828125,585.789428710938,561.842102050781,568.421020507813,410.877655029297,33278387,0,0
2022-01-24 00:00:00+00:00,565.921020507813,580.263122558594,553.947326660156,570.394714355469,412.304260253906,44450345,0,0
2022-01-31 00:00:00+00:00,573.947326660156,582.960510253906,568.552612304688,572.631469726563,413.921112060547,32935213,0,0
2022-02-07 00:00:00+00:00,576.184204101563,590.526306152344,573.752624511719,584.73681640625,422.671325683594,30129487,0,0
2022-02-14 00:00:00+00:00,575.921020507813,581.578918457031,561.447326660156,568.684204101563,411.067840576172,37979247,0,0
2022-02-21 00:00:00+00:00,573.026306152344,576.943420410156,537.23681640625,557.105224609375,402.698120117188,49946893,0,0
2022-02-28 00:00:00+00:00,546.184204101563,563.157775878906,508.157806396484,508.947296142578,367.887603759766,71484032,4.1875,0
2022-03-07 00:00:00+00:00,493.420989990234,549.078918457031,475.134185791016,538.947326660156,392.617431640625,54164517,0,0
2022-03-14 00:00:00+00:00,543.552612304688,561.447326660156,538.026306152344,555.789428710938,404.886749267578,42128265,0,0
2022-03-21 00:00:00+00:00,556.184204101563,582.5,555.789428710938,576.052612304688,419.648284912109,30321228,0,0
2022-03-28 00:00:00+01:00,582.23681640625,606.578918457031,579.076293945313,585,426.166320800781,42874272,0,0
2022-04-04 00:00:00+01:00,578.552612304688,586.052612304688,555.263122558594,560.263122558594,408.145812988281,37556036,19.342106,0
2022-04-11 00:00:00+01:00,559.73681640625,573.157775878906,553.684204101563,571.447326660156,430.541351318359,22705062,0,0
2022-04-18 00:00:00+01:00,571.447326660156,589.210510253906,570.526306152344,576.052612304688,434.011077880859,37459087,0,0
2022-04-25 00:00:00+01:00,566.710510253906,577.368408203125,557.894714355469,570.131469726563,429.549957275391,33939150,0,0
2022-05-02 00:00:00+01:00,570.131469726563,593.421020507813,545.394714355469,545.921020507813,411.309234619141,22449007,0,0
2022-05-09 00:00:00+01:00,543.684204101563,549.276306152344,484.582794189453,537.105224609375,404.667236328125,56232105,0,0
2022-05-16 00:00:00+01:00,505.263157894737,557.368429083573,505.263157894737,542.236850136205,408.533517937911,62273078.36,101.69,0.76
2022-05-23 00:00:00+01:00,416.100006103516,442.399993896484,341.915008544922,440.899993896484,409.764678955078,45432941,0,0
2022-05-30 00:00:00+01:00,442.700012207031,444.200012207031,426.600006103516,428.700012207031,398.426239013672,37906659,0,0
2022-06-06 00:00:00+01:00,425.299987792969,434.010009765625,405.200012207031,405.399993896484,376.771606445313,40648810,0,0
1 Date Open High Low Close Adj Close Volume Dividends Stock Splits
2 2021-12-13 00:00:00+00:00 393.999975585938 518.421020507813 406.6 535 391.4 515 402.899916992188 530.131469726563 291.232287597656 383.200378417969 62714764.4736842 47663221 0 0
3 2021-12-20 00:00:00+00:00 393.999975585938 518.421020507813 412.199990234375 542.368408203125 392.502983398438 516.451293945313 409.899997558594 539.342102050781 296.292243652344 389.858215332031 46596651.3157895 35413455 0 0
4 2021-12-27 00:00:00+00:00 409.899997558594 539.342102050781 416.550971679688 548.093383789063 408.387001953125 537.351318359375 410.4 540 296.653642578125 390.333740234375 10818482.8947368 8222047 0 0
5 2022-01-03 00:00:00+00:00 410.4 540 432.199995117188 568.684204101563 410.4 540 432.099985351563 568.552612304688 312.339265136719 410.972717285156 44427327.6315789 33764769 0 0
6 2022-01-10 00:00:00+00:00 431.3 567.5 439.199982910156 577.894714355469 429.099970703125 564.605224609375 436.099912109375 573.815673828125 315.230618896484 414.777130126953 29091400 22109464 0 0
7 2022-01-17 00:00:00+00:00 437.999912109375 576.315673828125 445.199965820313 585.789428710938 426.999997558594 561.842102050781 431.999975585938 568.421020507813 312.267017822266 410.877655029297 43787351.3157895 33278387 0 0
8 2022-01-24 00:00:00+00:00 430.099975585938 565.921020507813 440.999973144531 580.263122558594 420.999968261719 553.947326660156 433.499982910156 570.394714355469 313.351237792969 412.304260253906 58487296.0526316 44450345 0 0
9 2022-01-31 00:00:00+00:00 436.199968261719 573.947326660156 443.049987792969 582.960510253906 432.099985351563 568.552612304688 435.199916992188 572.631469726563 314.580045166016 413.921112060547 43335806.5789474 32935213 0 0
10 2022-02-07 00:00:00+00:00 437.899995117188 576.184204101563 448.799992675781 590.526306152344 436.051994628906 573.752624511719 444.39998046875 584.73681640625 321.230207519531 422.671325683594 39644061.8421053 30129487 0 0
11 2022-02-14 00:00:00+00:00 437.699975585938 575.921020507813 441.999978027344 581.578918457031 426.699968261719 561.447326660156 432.199995117188 568.684204101563 312.411558837891 411.067840576172 49972693.4210526 37979247 0 0
12 2022-02-21 00:00:00+00:00 435.499992675781 573.026306152344 438.476999511719 576.943420410156 408.29998046875 537.23681640625 423.399970703125 557.105224609375 306.050571289063 402.698120117188 65719596.0526316 49946893 0 0
13 2022-02-28 00:00:00+00:00 415.099995117188 546.184204101563 427.999909667969 563.157775878906 386.199932861328 508.157806396484 386.799945068359 508.947296142578 279.594578857422 367.887603759766 94057936.8421053 71484032 4.1875 0
14 2022-03-07 00:00:00+00:00 374.999952392578 493.420989990234 417.299978027344 549.078918457031 361.101981201172 475.134185791016 409.599968261719 538.947326660156 298.389248046875 392.617431640625 71269101.3157895 54164517 0 0
15 2022-03-14 00:00:00+00:00 413.099985351563 543.552612304688 426.699968261719 561.447326660156 408.899992675781 538.026306152344 422.399965820313 555.789428710938 307.713929443359 404.886749267578 55431927.6315789 42128265 0 0
16 2022-03-21 00:00:00+00:00 422.699995117188 556.184204101563 442.7 582.5 422.399965820313 555.789428710938 437.799985351563 576.052612304688 318.932696533203 419.648284912109 39896352.6315789 30321228 0 0
17 2022-03-28 00:00:00+01:00 442.49998046875 582.23681640625 460.999978027344 606.578918457031 440.097983398438 579.076293945313 444.6 585 323.886403808594 426.166320800781 56413515.7894737 42874272 0 0
18 2022-04-04 00:00:00+01:00 439.699985351563 578.552612304688 445.399985351563 586.052612304688 421.999973144531 555.263122558594 425.799973144531 560.263122558594 310.190817871094 408.145812988281 49415836.8421053 37556036 19.342106 0
19 2022-04-11 00:00:00+01:00 425.39998046875 559.73681640625 435.599909667969 573.157775878906 420.799995117188 553.684204101563 434.299968261719 571.447326660156 327.211427001953 430.541351318359 29875081.5789474 22705062 0 0
20 2022-04-18 00:00:00+01:00 434.299968261719 571.447326660156 447.799987792969 589.210510253906 433.599992675781 570.526306152344 437.799985351563 576.052612304688 329.848419189453 434.011077880859 49288272.3684211 37459087 0 0
21 2022-04-25 00:00:00+01:00 430.699987792969 566.710510253906 438.799990234375 577.368408203125 423.999982910156 557.894714355469 433.299916992188 570.131469726563 326.457967529297 429.549957275391 44656776.3157895 33939150 0 0
22 2022-05-02 00:00:00+01:00 433.299916992188 570.131469726563 450.999975585938 593.421020507813 414.499982910156 545.394714355469 414.899975585938 545.921020507813 312.595018310547 411.309234619141 29538167.1052632 22449007 0 0
23 2022-05-09 00:00:00+01:00 413.199995117188 543.684204101563 417.449992675781 549.276306152344 368.282923583984 484.582794189453 408.199970703125 537.105224609375 307.547099609375 404.667236328125 73989611.8421053 56232105 0 0
24 2022-05-16 00:00:00+01:00 384 505.263157894737 423.600006103516 557.368429083573 384 505.263157894737 412.100006103516 542.236850136205 310.485473632813 408.533517937911 81938261 62273078.36 101.69 0.76
25 2022-05-23 00:00:00+01:00 416.100006103516 442.399993896484 341.915008544922 440.899993896484 409.764678955078 45432941 0 0
26 2022-05-30 00:00:00+01:00 442.700012207031 444.200012207031 426.600006103516 428.700012207031 398.426239013672 37906659 0 0
27 2022-06-06 00:00:00+01:00 425.299987792969 434.010009765625 405.200012207031 405.399993896484 376.771606445313 40648810 0 0

View File

@@ -43,14 +43,14 @@ class TestPriceHistory(unittest.TestCase):
df_tkrs = df.columns.levels[1]
self.assertEqual(sorted(tkrs), sorted(df_tkrs))
def test_download_with_invalid_ticker(self):
#Checks if using an invalid symbol gives the same output as not using an invalid symbol in combination with a valid symbol (AAPL)
#Checks to make sure that invalid symbol handling for the date column is the same as the base case (no invalid symbols)
invalid_tkrs = ["AAPL", "ATVI"] #AAPL exists and ATVI does not exist
valid_tkrs = ["AAPL", "INTC"] #AAPL and INTC both exist
data_invalid_sym = yf.download(invalid_tkrs, start='2023-11-16', end='2023-11-17')
data_valid_sym = yf.download(valid_tkrs, start='2023-11-16', end='2023-11-17')
@@ -62,7 +62,7 @@ class TestPriceHistory(unittest.TestCase):
dat = yf.Ticker(tkr, session=self.session)
tz = dat._get_ticker_tz(proxy=None, timeout=None)
dt_utc = _tz.timezone("UTC").localize(_dt.datetime.utcnow())
dt_utc = _pd.Timestamp.utcnow()
dt = dt_utc.astimezone(_tz.timezone(tz))
start_d = dt.date() - _dt.timedelta(days=7)
df = dat.history(start=start_d, interval="1h")
@@ -82,7 +82,7 @@ class TestPriceHistory(unittest.TestCase):
dat = yf.Ticker(tkr, session=self.session)
tz = dat._get_ticker_tz(proxy=None, timeout=None)
dt_utc = _tz.timezone("UTC").localize(_dt.datetime.utcnow())
dt_utc = _pd.Timestamp.utcnow()
dt = dt_utc.astimezone(_tz.timezone(tz))
if dt.time() < _dt.time(17, 0):
continue
@@ -359,13 +359,6 @@ class TestPriceHistory(unittest.TestCase):
dfd_divs = dfd[dfd['Dividends'] != 0]
self.assertEqual(dfm_divs.shape[0], dfd_divs.shape[0])
dfm = yf.Ticker("F").history(period="50mo", interval="1mo")
dfd = yf.Ticker("F").history(period="50mo", interval="1d")
dfd = dfd[dfd.index > dfm.index[0]]
dfm_divs = dfm[dfm['Dividends'] != 0]
dfd_divs = dfd[dfd['Dividends'] != 0]
self.assertEqual(dfm_divs.shape[0], dfd_divs.shape[0])
def test_tz_dst_ambiguous(self):
# Reproduce issue #1100
try:
@@ -791,7 +784,7 @@ class TestPriceRepair(unittest.TestCase):
tz_exchange = dat.fast_info["timezone"]
hist = dat._lazy_load_price_history()
correct_df = hist.history(period="1wk", interval="1h", auto_adjust=False, repair=True)
correct_df = hist.history(period="5d", interval="1h", auto_adjust=False, repair=True)
df_bad = correct_df.copy()
bad_idx = correct_df.index[10]
@@ -820,7 +813,7 @@ class TestPriceRepair(unittest.TestCase):
self.assertTrue("Repaired?" in repaired_df.columns)
self.assertFalse(repaired_df["Repaired?"].isna().any())
def test_repair_bad_stock_split(self):
def test_repair_bad_stock_splits(self):
# Stocks that split in 2022 but no problems in Yahoo data,
# so repair should change nothing
good_tkrs = ['AMZN', 'DXCM', 'FTNT', 'GOOG', 'GME', 'PANW', 'SHOP', 'TSLA']
@@ -836,7 +829,7 @@ class TestPriceRepair(unittest.TestCase):
_dp = os.path.dirname(__file__)
df_good = dat.history(start='2020-01-01', end=_dt.date.today(), interval=interval, auto_adjust=False)
repaired_df = hist._fix_bad_stock_split(df_good, interval, tz_exchange)
repaired_df = hist._fix_bad_stock_splits(df_good, interval, tz_exchange)
# Expect no change from repair
df_good = df_good.sort_index()
@@ -867,7 +860,7 @@ class TestPriceRepair(unittest.TestCase):
df_bad = _pd.read_csv(fp, index_col="Date")
df_bad.index = _pd.to_datetime(df_bad.index, utc=True)
repaired_df = hist._fix_bad_stock_split(df_bad, "1d", tz_exchange)
repaired_df = hist._fix_bad_stock_splits(df_bad, "1d", tz_exchange)
fp = os.path.join(_dp, "data", tkr.replace('.','-')+'-'+interval+"-bad-stock-split-fixed.csv")
correct_df = _pd.read_csv(fp, index_col="Date")
@@ -902,7 +895,7 @@ class TestPriceRepair(unittest.TestCase):
_dp = os.path.dirname(__file__)
df_good = hist.history(start='2020-11-30', end='2021-04-01', interval=interval, auto_adjust=False)
repaired_df = hist._fix_bad_stock_split(df_good, interval, tz_exchange)
repaired_df = hist._fix_bad_stock_splits(df_good, interval, tz_exchange)
# Expect no change from repair
df_good = df_good.sort_index()

View File

@@ -12,7 +12,7 @@ import pandas as pd
from .context import yfinance as yf
from .context import session_gbl
from yfinance.exceptions import YFNotImplementedError
from yfinance.exceptions import YFChartError, YFInvalidPeriodError, YFNotImplementedError, YFTickerMissingError, YFTzMissingError
import unittest
@@ -100,13 +100,13 @@ class TestTicker(unittest.TestCase):
tkr = "DJI" # typo of "^DJI"
dat = yf.Ticker(tkr, session=self.session)
dat.history(period="1wk")
dat.history(period="5d")
dat.history(start="2022-01-01")
dat.history(start="2022-01-01", end="2022-03-01")
yf.download([tkr], period="1wk", threads=False, ignore_tz=False)
yf.download([tkr], period="1wk", threads=True, ignore_tz=False)
yf.download([tkr], period="1wk", threads=False, ignore_tz=True)
yf.download([tkr], period="1wk", threads=True, ignore_tz=True)
yf.download([tkr], period="5d", threads=False, ignore_tz=False)
yf.download([tkr], period="5d", threads=True, ignore_tz=False)
yf.download([tkr], period="5d", threads=False, ignore_tz=True)
yf.download([tkr], period="5d", threads=True, ignore_tz=True)
for k in dat.fast_info:
dat.fast_info[k]
@@ -129,6 +129,30 @@ class TestTicker(unittest.TestCase):
assert isinstance(dat.actions, pd.DataFrame)
assert dat.actions.empty
def test_invalid_period(self):
tkr = 'VALE'
dat = yf.Ticker(tkr, session=self.session)
with self.assertRaises(YFInvalidPeriodError):
dat.history(period="2wks", interval="1d", raise_errors=True)
with self.assertRaises(YFInvalidPeriodError):
dat.history(period="2mo", interval="1d", raise_errors=True)
def test_prices_missing(self):
# this test will need to be updated every time someone wants to run a test
# hard to find a ticker that matches this error other than options
# META call option, 2024 April 26th @ strike of 180000
tkr = 'META240426C00180000'
dat = yf.Ticker(tkr, session=self.session)
with self.assertRaises(YFChartError):
dat.history(period="5d", interval="1m", raise_errors=True)
def test_ticker_missing(self):
tkr = 'ATVI'
dat = yf.Ticker(tkr, session=self.session)
# A missing ticker can trigger either a niche error or the generalized error
with self.assertRaises((YFTickerMissingError, YFTzMissingError, YFChartError)):
dat.history(period="3mo", interval="1d", raise_errors=True)
def test_goodTicker(self):
# that yfinance works when full api is called on same instance of ticker
@@ -138,32 +162,32 @@ class TestTicker(unittest.TestCase):
for tkr in tkrs:
dat = yf.Ticker(tkr, session=self.session)
dat.history(period="1wk")
dat.history(period="5d")
dat.history(start="2022-01-01")
dat.history(start="2022-01-01", end="2022-03-01")
yf.download([tkr], period="1wk", threads=False, ignore_tz=False)
yf.download([tkr], period="1wk", threads=True, ignore_tz=False)
yf.download([tkr], period="1wk", threads=False, ignore_tz=True)
yf.download([tkr], period="1wk", threads=True, ignore_tz=True)
yf.download([tkr], period="5d", threads=False, ignore_tz=False)
yf.download([tkr], period="5d", threads=True, ignore_tz=False)
yf.download([tkr], period="5d", threads=False, ignore_tz=True)
yf.download([tkr], period="5d", threads=True, ignore_tz=True)
for k in dat.fast_info:
dat.fast_info[k]
for attribute_name, attribute_type in ticker_attributes:
assert_attribute_type(self, dat, attribute_name, attribute_type)
assert_attribute_type(self, dat, attribute_name, attribute_type)
def test_goodTicker_withProxy(self):
tkr = "IBM"
dat = yf.Ticker(tkr, session=self.session, proxy=self.proxy)
dat._fetch_ticker_tz(proxy=None, timeout=5)
dat._get_ticker_tz(proxy=None, timeout=5)
dat.history(period="1wk")
dat.history(period="5d")
for attribute_name, attribute_type in ticker_attributes:
assert_attribute_type(self, dat, attribute_name, attribute_type)
class TestTickerHistory(unittest.TestCase):
session = None
@@ -370,7 +394,7 @@ class TestTickerHolders(unittest.TestCase):
data_cached = self.ticker.insider_transactions
self.assertIs(data, data_cached, "data not cached")
def test_insider_purchases(self):
data = self.ticker.insider_purchases
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
@@ -402,9 +426,9 @@ class TestTickerMiscFinancials(unittest.TestCase):
def setUp(self):
self.ticker = yf.Ticker("GOOGL", session=self.session)
# For ticker 'BSE.AX' (and others), Yahoo not returning
# full quarterly financials (usually cash-flow) with all entries,
# For ticker 'BSE.AX' (and others), Yahoo not returning
# full quarterly financials (usually cash-flow) with all entries,
# instead returns a smaller version in different data store.
self.ticker_old_fmt = yf.Ticker("BSE.AX", session=self.session)
@@ -713,7 +737,7 @@ class TestTickerAnalysts(unittest.TestCase):
def setUp(self):
self.ticker = yf.Ticker("GOOGL", session=self.session)
def tearDown(self):
self.ticker = None
@@ -813,7 +837,6 @@ class TestTickerInfo(unittest.TestCase):
# This one should have a trailing PEG ratio
data2 = self.tickers[2].info
self.assertIsInstance(data2['trailingPegRatio'], float)
pass
# def test_fast_info_matches_info(self):
# fast_info_keys = set()
@@ -851,7 +874,7 @@ class TestTickerInfo(unittest.TestCase):
# key_rename_map[yf.utils.snake_case_2_camelCase(k)] = key_rename_map[k]
# # Note: share count items in info[] are bad. Sometimes the float > outstanding!
# # So often fast_info["shares"] does not match.
# # So often fast_info["shares"] does not match.
# # Why isn't fast_info["shares"] wrong? Because using it to calculate market cap always correct.
# bad_keys = {"shares"}

View File

@@ -35,6 +35,8 @@ def pdr_override():
make pandas datareader optional
otherwise can be called via fix_yahoo_finance.download(...)
"""
from .utils import print_once
print_once("yfinance: pandas_datareader support is deprecated & semi-broken so will be removed in a future verison. Just use yfinance.")
try:
import pandas_datareader
pandas_datareader.data.get_data_yahoo = download

View File

@@ -32,6 +32,7 @@ import requests
from . import utils, cache
from .data import YfData
from .exceptions import YFEarningsDateMissing
from .scrapers.analysis import Analysis
from .scrapers.fundamentals import Fundamentals
from .scrapers.holders import Holders
@@ -192,7 +193,7 @@ class TickerBase:
if as_dict:
return data.to_dict()
return data
def get_insider_purchases(self, proxy=None, as_dict=False):
self._holders.proxy = proxy or self.proxy
data = self._holders.insider_purchases
@@ -567,7 +568,8 @@ class TickerBase:
page_size = min(limit - len(dates), page_size)
if dates is None or dates.shape[0] == 0:
err_msg = "No earnings dates found, symbol may be delisted"
_exception = YFEarningsDateMissing(self.ticker)
err_msg = str(_exception)
logger.error(f'{self.ticker}: {err_msg}')
return None
dates = dates.reset_index(drop=True)

View File

@@ -1,7 +1,7 @@
import peewee as _peewee
from threading import Lock
import os as _os
import appdirs as _ad
import platformdirs as _ad
import atexit as _atexit
import datetime as _datetime
import pickle as _pkl

View File

@@ -60,7 +60,23 @@ class YfData(metaclass=SingletonMeta):
'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 __init__(self, session=None):
self._session = session or requests.Session()
self._crumb = None
self._cookie = None
# Default to using 'basic' strategy
self._cookie_strategy = 'basic'
# If it fails, then fallback method is 'csrf'
# self._cookie_strategy = 'csrf'
self._cookie_lock = threading.Lock()
self._set_session(session or requests.Session())
def _set_session(self, session):
if session is None:
return
with self._cookie_lock:
self._session = session
try:
self._session.cache
@@ -74,23 +90,6 @@ class YfData(metaclass=SingletonMeta):
self._session_is_caching = True
from requests_cache import DO_NOT_CACHE
self._expire_after = DO_NOT_CACHE
self._crumb = None
self._cookie = None
if self._session_is_caching and self._cookie is None:
utils.print_once("WARNING: cookie & crumb does not work well with requests_cache. Am experimenting with 'expire_after=DO_NOT_CACHE', but you need to help stress-test.")
# Default to using 'basic' strategy
self._cookie_strategy = 'basic'
# If it fails, then fallback method is 'csrf'
# self._cookie_strategy = 'csrf'
self._cookie_lock = threading.Lock()
def _set_session(self, session):
if session is None:
return
with self._cookie_lock:
self._session = session
def _set_cookie_strategy(self, strategy, have_lock=False):
if strategy == self._cookie_strategy:

View File

@@ -1,12 +1,50 @@
class YFinanceException(Exception):
class YFException(Exception):
def __init__(self, description=""):
super().__init__(description)
class YFDataException(YFException):
pass
class YFinanceDataException(YFinanceException):
pass
class YFChartError(YFException):
def __init__(self, ticker, description):
self.ticker = ticker
super().__init__(f"{self.ticker}: {description}")
class YFNotImplementedError(NotImplementedError):
def __init__(self, method_name):
super().__init__(f"Have not implemented fetching '{method_name}' from Yahoo API")
class YFTickerMissingError(YFException):
def __init__(self, ticker, rationale):
super().__init__(f"${ticker}: possibly delisted; {rationale}")
self.rationale = rationale
self.ticker = ticker
class YFTzMissingError(YFTickerMissingError):
def __init__(self, ticker):
super().__init__(ticker, "No timezone found")
class YFPricesMissingError(YFTickerMissingError):
def __init__(self, ticker, debug_info):
self.debug_info = debug_info
super().__init__(ticker, f"No price data found {debug_info}")
class YFEarningsDateMissing(YFTickerMissingError):
# note that this does not get raised. Added in case of raising it in the future
def __init__(self, ticker):
super().__init__(ticker, "No earnings dates found")
class YFInvalidPeriodError(YFException):
def __init__(self, ticker, invalid_period, valid_ranges):
self.ticker = ticker
self.invalid_period = invalid_period
self.valid_ranges = valid_ranges
super().__init__(f"{self.ticker}: Period '{invalid_period}' is invalid, must be one of {valid_ranges}")

View File

@@ -36,7 +36,7 @@ from . import shared
@utils.log_indent_decorator
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", show_errors=None, interval="1d", prepost=False,
progress=True, period="max", interval="1d", prepost=False,
proxy=None, rounding=False, timeout=10, session=None):
"""Download yahoo tickers
:Parameters:
@@ -80,9 +80,6 @@ def download(tickers, start=None, end=None, actions=False, threads=True, ignore_
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 False
DEPRECATED, will be removed in future version
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)
@@ -91,14 +88,6 @@ def download(tickers, start=None, end=None, actions=False, threads=True, ignore_
"""
logger = utils.get_yf_logger()
if show_errors is not None:
if show_errors:
utils.print_once(f"yfinance: download(show_errors={show_errors}) argument is deprecated and will be removed in future version. Do this instead: logging.getLogger('yfinance').setLevel(logging.ERROR)")
logger.setLevel(logging.ERROR)
else:
utils.print_once(f"yfinance: download(show_errors={show_errors}) argument is deprecated and will be removed in future version. Do this instead to suppress error messages: logging.getLogger('yfinance').setLevel(logging.CRITICAL)")
logger.setLevel(logging.CRITICAL)
if logger.isEnabledFor(logging.DEBUG):
if threads:
# With DEBUG, each thread generates a lot of log messages.

View File

@@ -5,7 +5,7 @@ import pandas as pd
from yfinance import utils, const
from yfinance.data import YfData
from yfinance.exceptions import YFinanceException, YFNotImplementedError
from yfinance.exceptions import YFException, YFNotImplementedError
class Fundamentals:
@@ -70,7 +70,7 @@ class Financials:
@utils.log_indent_decorator
def _fetch_time_series(self, name, timescale, proxy=None):
# Fetching time series preferred over scraping 'QuoteSummaryStore',
# because it matches what Yahoo shows. But for some tickers returns nothing,
# because it matches what Yahoo shows. But for some tickers returns nothing,
# despite 'QuoteSummaryStore' containing valid data.
allowed_names = ["income", "balance-sheet", "cash-flow"]
@@ -86,7 +86,7 @@ class Financials:
if statement is not None:
return statement
except YFinanceException as e:
except YFException as e:
utils.get_yf_logger().error(f"{self._symbol}: Failed to create {name} financials table for reason: {e}")
return pd.DataFrame()

View File

@@ -1,4 +1,3 @@
import datetime as _datetime
import dateutil as _dateutil
import logging
@@ -8,6 +7,7 @@ import time as _time
from yfinance import shared, utils
from yfinance.const import _BASE_URL_, _PRICE_COLNAMES_
from yfinance.exceptions import YFChartError, YFInvalidPeriodError, YFPricesMissingError, YFTzMissingError
class PriceHistory:
def __init__(self, data, ticker, tz, session=None, proxy=None):
@@ -23,7 +23,7 @@ class PriceHistory:
# Limit recursion depth when repairing prices
self._reconstruct_start_interval = None
@utils.log_indent_decorator
def history(self, period="1mo", interval="1d",
start=None, end=None, prepost=False, actions=True,
@@ -80,14 +80,15 @@ class PriceHistory:
# Check can get TZ. Fail => probably delisted
tz = self.tz
if tz is None:
# Every valid ticker has a timezone. Missing = problem
err_msg = "No timezone found, symbol may be delisted"
# Every valid ticker has a timezone. A missing timezone is a problem.
_exception = YFTzMissingError(self.ticker)
err_msg = str(_exception)
shared._DFS[self.ticker] = utils.empty_df()
shared._ERRORS[self.ticker] = err_msg
shared._ERRORS[self.ticker] = err_msg.split(': ', 1)[1]
if raise_errors:
raise Exception(f'{self.ticker}: {err_msg}')
raise _exception
else:
logger.error(f'{self.ticker}: {err_msg}')
logger.error(err_msg)
return utils.empty_df()
if end is None:
@@ -159,48 +160,54 @@ class PriceHistory:
self._history_metadata = {}
intraday = params["interval"][-1] in ("m", 'h')
err_msg = "No price data found, symbol may be delisted"
_price_data_debug = ''
_exception = YFPricesMissingError(self.ticker, '')
if start or period is None or period.lower() == "max":
err_msg += f' ({params["interval"]} '
_price_data_debug += f' ({params["interval"]} '
if start_user is not None:
err_msg += f'{start_user}'
_price_data_debug += f'{start_user}'
elif not intraday:
err_msg += f'{pd.Timestamp(start, unit="s").tz_localize("UTC").tz_convert(tz).date()}'
_price_data_debug += f'{pd.Timestamp(start, unit="s").tz_localize("UTC").tz_convert(tz).date()}'
else:
err_msg += f'{pd.Timestamp(start, unit="s").tz_localize("UTC").tz_convert(tz)}'
err_msg += ' -> '
_price_data_debug += f'{pd.Timestamp(start, unit="s").tz_localize("UTC").tz_convert(tz)}'
_price_data_debug += ' -> '
if end_user is not None:
err_msg += f'{end_user})'
_price_data_debug += f'{end_user})'
elif not intraday:
err_msg += f'{pd.Timestamp(end, unit="s").tz_localize("UTC").tz_convert(tz).date()})'
_price_data_debug += f'{pd.Timestamp(end, unit="s").tz_localize("UTC").tz_convert(tz).date()})'
else:
err_msg += f'{pd.Timestamp(end, unit="s").tz_localize("UTC").tz_convert(tz)})'
_price_data_debug += f'{pd.Timestamp(end, unit="s").tz_localize("UTC").tz_convert(tz)})'
else:
err_msg += f' (period={period})'
_price_data_debug += f' (period={period})'
fail = False
if data is None or not isinstance(data, dict):
fail = True
elif isinstance(data, dict) and 'status_code' in data:
err_msg += f"(Yahoo status_code = {data['status_code']})"
_price_data_debug += f"(Yahoo status_code = {data['status_code']})"
fail = True
elif "chart" in data and data["chart"]["error"]:
err_msg = data["chart"]["error"]["description"]
_exception = YFChartError(self.ticker, data["chart"]["error"]["description"])
fail = True
elif "chart" not in data or data["chart"]["result"] is None or not data["chart"]["result"]:
fail = True
elif period is not None and "timestamp" not in data["chart"]["result"][0] and period not in \
self._history_metadata["validRanges"]:
elif period is not None and period not in self._history_metadata["validRanges"]:
# even if timestamp is in the data, the data doesn't encompass the period requested
# User provided a bad period. The minimum should be '1d', but sometimes Yahoo accepts '1h'.
err_msg = f"Period '{period}' is invalid, must be one of {self._history_metadata['validRanges']}"
_exception = YFInvalidPeriodError(self.ticker, period, self._history_metadata['validRanges'])
fail = True
if isinstance(_exception, YFPricesMissingError):
_exception = YFPricesMissingError(self.ticker, _price_data_debug)
err_msg = str(_exception)
if fail:
shared._DFS[self.ticker] = utils.empty_df()
shared._ERRORS[self.ticker] = err_msg
shared._ERRORS[self.ticker] = err_msg.split(': ', 1)[1]
if raise_errors:
raise Exception(f'{self.ticker}: {err_msg}')
raise _exception
else:
logger.error(f'{self.ticker}: {err_msg}')
logger.error(err_msg)
if self._reconstruct_start_interval is not None and self._reconstruct_start_interval == interval:
self._reconstruct_start_interval = None
return utils.empty_df()
@@ -210,16 +217,17 @@ class PriceHistory:
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))
endDt = pd.to_datetime(end, unit='s')
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
print(err_msg)
shared._ERRORS[self.ticker] = err_msg.split(': ', 1)[1]
if raise_errors:
raise Exception(f'{self.ticker}: {err_msg}')
raise _exception
else:
logger.error(f'{self.ticker}: {err_msg}')
logger.error(err_msg)
if self._reconstruct_start_interval is not None and self._reconstruct_start_interval == interval:
self._reconstruct_start_interval = None
return shared._DFS[self.ticker]
@@ -330,7 +338,7 @@ class PriceHistory:
# Do this before auto/back adjust
logger.debug(f'{self.ticker}: checking OHLC for repairs ...')
df = self._fix_unit_mixups(df, interval, tz_exchange, prepost)
df = self._fix_bad_stock_split(df, interval, tz_exchange)
df = self._fix_bad_stock_splits(df, interval, tz_exchange)
# Must repair 100x and split errors before price reconstruction
df = self._fix_zeroes(df, interval, tz_exchange, prepost)
df = self._fix_missing_div_adjust(df, interval, tz_exchange)
@@ -852,7 +860,7 @@ class PriceHistory:
if f_zeroes.any():
df2_zeroes = df2[f_zeroes]
df2 = df2[~f_zeroes]
df = df[~f_zeroes] # all row slicing must be applied to both df and df2
df_orig = df[~f_zeroes] # all row slicing must be applied to both df and df2
else:
df2_zeroes = None
if df2.shape[0] <= 1:
@@ -954,7 +962,7 @@ class PriceHistory:
fj = f_either[:, j]
if fj.any():
c = data_cols[j]
df2.loc[fj, c] = df.loc[fj, c]
df2.loc[fj, c] = df_orig.loc[fj, c]
if df2_zeroes is not None:
if "Repaired?" not in df2_zeroes.columns:
df2_zeroes["Repaired?"] = False
@@ -973,7 +981,12 @@ class PriceHistory:
# This function fixes the second.
# Eventually Yahoo fixes but could take them 2 weeks.
return self._fix_prices_sudden_change(df, interval, tz_exchange, 100.0)
if self._history_metadata['currency'] == 'KWF':
# Kuwaiti Dinar divided into 1000 not 100
n = 1000
else:
n = 100
return self._fix_prices_sudden_change(df, interval, tz_exchange, n)
@utils.log_indent_decorator
def _fix_zeroes(self, df, interval, tz_exchange, prepost):
@@ -1014,9 +1027,13 @@ class PriceHistory:
df2 = df2[~f_zero_or_nan_ignore]
f_prices_bad = (df2[price_cols] == 0.0) | df2[price_cols].isna()
f_high_low_good = (~df2["High"].isna().to_numpy()) & (~df2["Low"].isna().to_numpy())
f_change = df2["High"].to_numpy() != df2["Low"].to_numpy()
f_vol_bad = (df2["Volume"] == 0).to_numpy() & f_high_low_good & f_change
if self.ticker.endswith("=X"):
# FX, volume always 0
f_vol_bad = None
else:
f_high_low_good = (~df2["High"].isna().to_numpy()) & (~df2["Low"].isna().to_numpy())
f_vol_bad = (df2["Volume"] == 0).to_numpy() & f_high_low_good & f_change
# If stock split occurred, then trading must have happened.
# I should probably rename the function, because prices aren't zero ...
@@ -1029,7 +1046,9 @@ class PriceHistory:
# Check whether worth attempting repair
f_prices_bad = f_prices_bad.to_numpy()
f_bad_rows = f_prices_bad.any(axis=1) | f_vol_bad
f_bad_rows = f_prices_bad.any(axis=1)
if f_vol_bad is not None:
f_bad_rows = f_bad_rows | f_vol_bad
if not f_bad_rows.any():
logger.info("price-repair-missing: No price=0 errors to repair")
if "Repaired?" not in df.columns:
@@ -1157,9 +1176,12 @@ class PriceHistory:
return df2
@utils.log_indent_decorator
def _fix_bad_stock_split(self, df, interval, tz_exchange):
# Repair idea is to look for BIG daily price changes that closely match the
# most recent stock split ratio. This indicates Yahoo failed to apply a new
def _fix_bad_stock_splits(self, df, interval, tz_exchange):
# Original logic only considered latest split adjustment could be missing, but
# actually **any** split adjustment can be missing. So check all splits in df.
#
# Improved logic looks for BIG daily price changes that closely match the
# **nearest future** stock split ratio. This indicates Yahoo failed to apply a new
# stock split to old price data.
#
# There is a slight complication, because Yahoo does another stupid thing.
@@ -1176,22 +1198,39 @@ class PriceHistory:
if not interday:
return df
# Find the most recent stock split
df = df.sort_index(ascending=False)
df = df.sort_index() # scan splits oldest -> newest
split_f = df['Stock Splits'].to_numpy() != 0
if not split_f.any():
logger.debug('price-repair-split: No splits in data')
return df
most_recent_split_day = df.index[split_f].max()
split = df.loc[most_recent_split_day, 'Stock Splits']
if most_recent_split_day == df.index[0]:
logger.info(
"price-repair-split: Need 1+ day of price data after split to determine true price. Won't repair")
return df
logger.debug(f'price-repair-split: Most recent split = {split:.4f} @ {most_recent_split_day.date()}')
logger.debug(f'price-repair-split: Splits: {str(df["Stock Splits"][split_f].to_dict())}')
return self._fix_prices_sudden_change(df, interval, tz_exchange, split, correct_volume=True)
if 'Repaired?' not in df.columns:
df['Repaired?'] = False
for split_idx in np.where(split_f)[0]:
split_dt = df.index[split_idx]
split = df.loc[split_dt, 'Stock Splits']
if split_dt == df.index[0]:
continue
# Add on a week:
if interval in ['1wk', '1mo', '3mo']:
split_idx += 1
else:
split_idx += 5
cutoff_idx = min(df.shape[0], split_idx) # add one row after to detect big change
df_pre_split = df.iloc[0:cutoff_idx+1]
logger.debug(f'price-repair-split: split_idx={split_idx} split_dt={split_dt}')
logger.debug(f'price-repair-split: df dt range: {df_pre_split.index[0].date()} -> {df_pre_split.index[-1].date()}')
df_pre_split_repaired = self._fix_prices_sudden_change(df_pre_split, interval, tz_exchange, split, correct_volume=True)
# Merge back in:
if cutoff_idx == df.shape[0]-1:
df = df_pre_split_repaired
else:
df = pd.concat([df_pre_split_repaired.sort_index(), df.iloc[cutoff_idx+1:]])
return df
@utils.log_indent_decorator
def _fix_prices_sudden_change(self, df, interval, tz_exchange, change, correct_volume=False):
@@ -1212,7 +1251,7 @@ class PriceHistory:
# start_min = 1 year before oldest split
f = df['Stock Splits'].to_numpy() != 0.0
start_min = (df.index[f].min() - _dateutil.relativedelta.relativedelta(years=1)).date()
logger.debug(f'price-repair-split: start_min={start_min}')
logger.debug(f'price-repair-split: start_min={start_min} change={change}')
OHLC = ['Open', 'High', 'Low', 'Close']
@@ -1288,10 +1327,12 @@ class PriceHistory:
# average change
_1d_change_minx = np.average(_1d_change_x, axis=1)
else:
# change nearest to 1.0
diff = np.abs(_1d_change_x - 1.0)
j_indices = np.argmin(diff, axis=1)
_1d_change_minx = _1d_change_x[np.arange(n), j_indices]
# # change nearest to 1.0
# diff = np.abs(_1d_change_x - 1.0)
# j_indices = np.argmin(diff, axis=1)
# _1d_change_minx = _1d_change_x[np.arange(n), j_indices]
# Still sensitive to extreme-low low. Try median:
_1d_change_minx = np.median(_1d_change_x, axis=1)
f_na = np.isnan(_1d_change_minx)
if f_na.any():
# Possible if data was too old for reconstruction.
@@ -1408,8 +1449,13 @@ class PriceHistory:
# if logger.isEnabledFor(logging.DEBUG):
# df_debug['i'] = list(range(0, df_debug.shape[0]))
# df_debug['i_rev'] = df_debug.shape[0]-1 - df_debug['i']
# if correct_columns_individually:
# f_change = df_debug[[c+'_f_down' for c in debug_cols]].any(axis=1) | df_debug[[c+'_f_up' for c in debug_cols]].any(axis=1)
# else:
# f_change = df_debug['f_down'] | df_debug['f_up']
# f_change = f_change | np.roll(f_change, -1) | np.roll(f_change, 1) | np.roll(f_change, -2) | np.roll(f_change, 2)
# with pd.option_context('display.max_rows', None, 'display.max_columns', 10, 'display.width', 1000): # more options can be specified also
# logger.debug(f"price-repair-split: my workings:" + '\n' + str(df_debug))
# logger.debug(f"price-repair-split: my workings:" + '\n' + str(df_debug[f_change]))
def map_signals_to_ranges(f, f_up, f_down):
# Ensure 0th element is False, because True is nonsense

View File

@@ -6,7 +6,7 @@ import requests
from yfinance import utils
from yfinance.data import YfData
from yfinance.const import _BASE_URL_
from yfinance.exceptions import YFinanceDataException
from yfinance.exceptions import YFDataException
_QUOTE_SUMMARY_URL_ = f"{_BASE_URL_}/v10/finance/quoteSummary/"
@@ -104,7 +104,7 @@ class Holders:
self._parse_insider_holders(data["insiderHolders"])
self._parse_net_share_purchase_activity(data["netSharePurchaseActivity"])
except (KeyError, IndexError):
raise YFinanceDataException("Failed to parse holders json data.")
raise YFDataException("Failed to parse holders json data.")
@staticmethod
def _parse_raw_values(data):
@@ -189,7 +189,7 @@ class Holders:
if not df.empty:
df["positionDirectDate"] = pd.to_datetime(df["positionDirectDate"], unit="s")
df["latestTransDate"] = pd.to_datetime(df["latestTransDate"], unit="s")
df.rename(columns={
"name": "Name",
"relation": "Position",
@@ -242,5 +242,3 @@ class Holders:
}
).convert_dtypes()
self._insider_purchases = df

View File

@@ -10,7 +10,7 @@ import requests
from yfinance import utils
from yfinance.data import YfData
from yfinance.const import quote_summary_valid_modules, _BASE_URL_
from yfinance.exceptions import YFNotImplementedError, YFinanceDataException, YFinanceException
from yfinance.exceptions import YFNotImplementedError, YFDataException, YFException
info_retired_keys_price = {"currentPrice", "dayHigh", "dayLow", "open", "previousClose", "volume", "volume24Hr"}
info_retired_keys_price.update({"regularMarket"+s for s in ["DayHigh", "DayLow", "Open", "PreviousClose", "Price", "Volume"]})
@@ -181,7 +181,7 @@ class FastInfo:
def _get_1y_prices(self, fullDaysOnly=False):
if self._prices_1y is None:
self._prices_1y = self._tkr.history(period="380d", auto_adjust=False, keepna=True, proxy=self.proxy)
self._prices_1y = self._tkr.history(period="1y", auto_adjust=False, keepna=True, proxy=self.proxy)
self._md = self._tkr.get_history_metadata(proxy=self.proxy)
try:
ctp = self._md["currentTradingPeriod"]
@@ -207,12 +207,12 @@ class FastInfo:
def _get_1wk_1h_prepost_prices(self):
if self._prices_1wk_1h_prepost is None:
self._prices_1wk_1h_prepost = self._tkr.history(period="1wk", interval="1h", auto_adjust=False, prepost=True, proxy=self.proxy)
self._prices_1wk_1h_prepost = self._tkr.history(period="5d", interval="1h", auto_adjust=False, prepost=True, proxy=self.proxy)
return self._prices_1wk_1h_prepost
def _get_1wk_1h_reg_prices(self):
if self._prices_1wk_1h_reg is None:
self._prices_1wk_1h_reg = self._tkr.history(period="1wk", interval="1h", auto_adjust=False, prepost=False, proxy=self.proxy)
self._prices_1wk_1h_reg = self._tkr.history(period="5d", interval="1h", auto_adjust=False, prepost=False, proxy=self.proxy)
return self._prices_1wk_1h_reg
def _get_exchange_metadata(self):
@@ -578,7 +578,7 @@ class Quote:
try:
data = result["quoteSummary"]["result"][0]["recommendationTrend"]["trend"]
except (KeyError, IndexError):
raise YFinanceDataException(f"Failed to parse json response from Yahoo Finance: {result}")
raise YFDataException(f"Failed to parse json response from Yahoo Finance: {result}")
self._recommendations = pd.DataFrame(data)
return self._recommendations
@@ -592,14 +592,14 @@ class Quote:
try:
data = result["quoteSummary"]["result"][0]["upgradeDowngradeHistory"]["history"]
if len(data) == 0:
raise YFinanceDataException(f"No upgrade/downgrade history found for {self._symbol}")
raise YFDataException(f"No upgrade/downgrade history found for {self._symbol}")
df = pd.DataFrame(data)
df.rename(columns={"epochGradeDate": "GradeDate", 'firm': 'Firm', 'toGrade': 'ToGrade', 'fromGrade': 'FromGrade', 'action': 'Action'}, inplace=True)
df.set_index('GradeDate', inplace=True)
df.index = pd.to_datetime(df.index, unit='s')
self._upgrades_downgrades = df
except (KeyError, IndexError):
raise YFinanceDataException(f"Failed to parse json response from Yahoo Finance: {result}")
raise YFDataException(f"Failed to parse json response from Yahoo Finance: {result}")
return self._upgrades_downgrades
@property
@@ -614,11 +614,11 @@ class Quote:
def _fetch(self, proxy, modules: list):
if not isinstance(modules, list):
raise YFinanceException("Should provide a list of modules, see available modules using `valid_modules`")
raise YFException("Should provide a list of modules, see available modules using `valid_modules`")
modules = ','.join([m for m in modules if m in quote_summary_valid_modules])
if len(modules) == 0:
raise YFinanceException("No valid modules provided, see available modules using `valid_modules`")
raise YFException("No valid modules provided, see available modules using `valid_modules`")
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "formatted": "false", "symbol": self._symbol}
try:
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", user_agent_headers=self._data.user_agent_headers, params=params_dict, proxy=proxy)
@@ -721,7 +721,7 @@ class Quote:
json_data = json.loads(json_str)
json_result = json_data.get("timeseries") or json_data.get("finance")
if json_result["error"] is not None:
raise YFinanceException("Failed to parse json response from Yahoo Finance: " + str(json_result["error"]))
raise YFException("Failed to parse json response from Yahoo Finance: " + str(json_result["error"]))
for k in keys:
keydict = json_result["result"][0]
if k in keydict:
@@ -754,4 +754,4 @@ class Quote:
self._calendar['Revenue Low'] = earnings.get('revenueLow', None)
self._calendar['Revenue Average'] = earnings.get('revenueAverage', None)
except (KeyError, IndexError):
raise YFinanceDataException(f"Failed to parse json response from Yahoo Finance: {result}")
raise YFDataException(f"Failed to parse json response from Yahoo Finance: {result}")

View File

@@ -21,7 +21,6 @@
from __future__ import print_function
import datetime as _datetime
from collections import namedtuple as _namedtuple
import pandas as _pd
@@ -48,8 +47,7 @@ class Ticker(TickerBase):
r = self._data.get(url=url, proxy=self.proxy).json()
if len(r.get('optionChain', {}).get('result', [])) > 0:
for exp in r['optionChain']['result'][0]['expirationDates']:
self._expirations[_datetime.datetime.utcfromtimestamp(
exp).strftime('%Y-%m-%d')] = exp
self._expirations[_pd.Timestamp(exp, unit='s').strftime('%Y-%m-%d')] = exp
self._underlying = r['optionChain']['result'][0].get('quote', {})

View File

@@ -26,7 +26,7 @@ import logging
import re as _re
import sys as _sys
import threading
from functools import lru_cache
from functools import lru_cache, wraps
from inspect import getmembers
from types import FunctionType
from typing import List, Optional
@@ -95,6 +95,7 @@ def get_indented_logger(name=None):
def log_indent_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger = get_indented_logger('yfinance')
logger.debug(f'Entering {func.__name__}()')

View File

@@ -1 +1 @@
version = "0.2.38"
version = "0.2.40"