Compare commits

...

68 Commits

Author SHA1 Message Date
ValueRaider
42e6d0894e Bump version to 0.1.80 2022-10-22 13:25:59 +01:00
ValueRaider
de1c3c091b Merge pull request #1103 from ranaroussi/hotfix/download-timezones-patch
Fix download(ignore_tz=True) for single ticker
2022-10-22 13:15:52 +01:00
ValueRaider
c6c0fa3347 Fix download(ignore_tz=True) for single ticker 2022-10-22 13:14:35 +01:00
ValueRaider
75c823a72c Merge pull request #1101 from ranaroussi/hotfix/tz-dst-ambiguous
Fix tz-localize when DST-ambiguous
2022-10-21 15:29:52 +01:00
ValueRaider
f1ad8f0061 Fix tz-localize when DST-ambiguous 2022-10-21 12:43:50 +01:00
ValueRaider
b27cc0cf40 Update to 0.1.79 2022-10-18 20:07:40 +01:00
ValueRaider
1d7f8139d6 Fix when Yahoo returns price=NaNs on dividend day 2022-10-18 20:04:08 +01:00
ValueRaider
01ef1bb813 Update to 0.1.78 2022-10-18 12:53:22 +01:00
ValueRaider
1db6be75b8 Merge pull request #1093 from ranaroussi/fix/download-timezones
Add 'ignore_tz' arg to download()
2022-10-18 12:36:00 +01:00
ValueRaider
7902ec8667 Fix empty-df detection and date ordering 2022-10-18 12:31:51 +01:00
ValueRaider
ff42a3ac87 Add 'ignore_tz' arg to download() 2022-10-18 12:22:43 +01:00
ValueRaider
51f2c7301d Bump version again to 0.1.77 to skip bad tag 2022-10-07 22:04:16 +01:00
ValueRaider
632a16670a Bump version to 0.1.76 2022-10-07 21:55:15 +01:00
ValueRaider
fea0dca6f4 Merge pull request #1078 from ranaroussi/fix/info-access-when-unlisted
Fix/info access when unlisted
2022-10-07 21:44:44 +01:00
ValueRaider
c7e95152a0 Tidy code 2022-10-07 17:23:59 +01:00
ValueRaider
a52e972d04 Pretty error check for timezone retrieval 2022-10-07 17:20:58 +01:00
Ran Aroussi
a197d9f78e updated changelog 2022-10-04 12:09:19 +01:00
Ran Aroussi
dbb9bbfbf3 Updated to 0.1.75 2022-10-04 12:01:30 +01:00
ValueRaider
a7b053addd Merge pull request #1067 from ranaroussi/fix/deps
Add new dep 'appdirs' for recent tz-fixes PR
2022-10-01 13:52:10 +01:00
ValueRaider
e8ca256c10 Add new dep 'appdirs' for recent tz-fixes PR 2022-10-01 13:49:02 +01:00
ValueRaider
f651dd1e93 Merge pull request #1048 from yfinance-fork-team/fix/timezone
Fix timezone & datetime handling
2022-09-21 16:53:29 +01:00
ValueRaider
f40cf0aae1 Merge branch 'main' into fix/timezone 2022-09-20 23:28:17 +01:00
ValueRaider
200f57c458 Merge pull request #1033 from yfinance-fork-team/fix/yahoo-appending-latest-price
Bugfix: Relocate out-of-range check to before 15m->30m conversion
2022-09-20 20:24:13 +01:00
ValueRaider
e5d45eaa85 Merge pull request #1032 from yfinance-fork-team/feature/keep-na
Add 'keepna' argument
2022-09-20 14:17:34 +01:00
ValueRaider
42b77a9b54 Merge pull request #1042 from yfinance-fork-team/feature/init-perf-boost
Ticker.init() perf boost
2022-09-20 14:15:50 +01:00
ValueRaider
bca005a2c0 Move user dt parsing to utils.py 2022-09-04 17:45:37 +01:00
ValueRaider
ca891bb187 Restore index to DatetimeIndex ; remove tz arg ; refactor for clean merge 2022-09-04 16:09:55 +01:00
Value Raider
0939ff3c78 Support user providing epoch ints 2022-08-15 16:04:46 +01:00
Value Raider
6f5c5635be Preemptive bugfix 2022-07-28 12:30:13 +01:00
Value Raider
809622e426 Ticker init does unnecessary work - move it out 2022-07-28 12:25:43 +01:00
Value Raider
eec1f3dbad Fix when Yahoo messes up DST 2022-07-27 21:54:12 +01:00
Value Raider
1de789ad72 Fix timestamp->dt 2022-07-15 00:00:08 +01:00
Value Raider
cd68ff68c6 Adjust prices-after-end-dt fix from > to >= 2022-07-14 23:45:18 +01:00
Value Raider
9673970f45 Relocate out-of-range check to before 15m->30m interpolation 2022-07-14 23:04:41 +01:00
Value Raider
6ea69a70ac Add 'keepna' argument 2022-07-14 14:41:24 +01:00
Value Raider
c723a5ab44 Preemptive perf. boost 2022-07-14 01:03:24 +01:00
Value Raider
50741d1409 Add tz to daily/weekly - bugfix 2022-07-12 23:04:04 +01:00
Value Raider
69d0dcd62b Add tz to daily/weekly 2022-07-12 22:54:01 +01:00
Value Raider
5c9348f255 end time should default=00:00 not end-of-day - fix comment 2022-07-12 21:30:29 +01:00
Value Raider
a472546e7b end time should default=00:00 not end-of-day 2022-07-12 21:30:29 +01:00
Value Raider
c914f1f183 Fix user-supplied TZ ; reduce Git diff 2022-07-12 21:30:29 +01:00
Value Raider
92c82342fe Fix PYTZ misuse 2022-07-12 21:30:29 +01:00
Value Raider
7ae08b04f3 Fix setting end dt to midnight 2022-07-12 21:30:29 +01:00
Value Raider
4b50f1e81c Perf boost: store ticker timezone in user file cache 2022-07-12 21:30:29 +01:00
Value Raider
1ed58be749 Perf boost: separate info fetch from financials, to get tz fast 2022-07-12 21:30:28 +01:00
Value Raider
375b4f9376 Fix error when adding Dividends/Splits to df 2022-07-12 21:27:58 +01:00
Value Raider
b6b4426ca9 User-provided 'tz' should 'convert' not 'replace' UTC 2022-07-12 21:27:58 +01:00
Value Raider
149ebe46db Remove debugging print stmt 2022-07-12 21:27:58 +01:00
Value Raider
d80b27cfde Fix Yahoo returning tz in data 2022-07-12 21:27:58 +01:00
Value Raider
36e277317b Fix date->datetime conversion 2022-07-12 21:27:58 +01:00
Value Raider
0e1ea4d2c6 Replace zoneinfo with pytz (installed by pandas) 2022-07-12 21:27:58 +01:00
Value Raider
2d96c383ef Set start/end timezone to exchange if None 2022-07-12 21:27:57 +01:00
Ran Aroussi
ec6279736b Fixed bug introduced in 0.1.73 2022-07-11 23:20:47 +01:00
Ran Aroussi
5d942d9668 Update CHANGELOG.rst 2022-07-11 12:16:00 +01:00
Ran Aroussi
5782cb59fd Update version.py 2022-07-11 12:15:28 +01:00
Ran Aroussi
4c4861a8f1 Merge pull request #1000 from giorgossideris/shares_outstanding_none_values_bug_fix
Handle case that the API returns no data for shares of a specific year (#999)
2022-07-11 12:14:51 +01:00
Ran Aroussi
4d221ca70e Merge pull request #1009 from AndKraev/fix_724
rename incorrect yahoo key treasuryStock
2022-07-11 12:14:31 +01:00
Ran Aroussi
1a8d045baf Merge pull request #1023 from Ckend/main
bugfix: remove useless arg, otherwise affect the proxy arg.
2022-07-11 12:13:44 +01:00
Ran Aroussi
67a55c35ce Merge pull request #1024 from devpg/patch-1
Update multi.py
2022-07-11 12:13:21 +01:00
Ran Aroussi
e547fe4e41 Merge pull request #1026 from ValueRaider/fix/yahoo-appending-latest-price
Bug fix - Yahoo API appends latest price regardless of end date, remove it
2022-07-11 12:13:04 +01:00
Value Raider
9d5366d707 Bug fix - Yahoo API appends latest price regardless of end date, remove it 2022-07-09 18:35:12 +01:00
André Neubauer
4b07d1dceb Update multi.py
Documentation is wrong here. 'show_errors' does print errors if True, and doesn't print errors if False
2022-07-09 12:01:53 +02:00
ckend
9440c1e1c1 bugfix: remove useless arg, otherwise affect the proxy arg. 2022-07-08 17:53:54 +08:00
Ran Aroussi
773d003a67 Update CHANGELOG.rst 2022-06-16 16:32:33 +01:00
Ran Aroussi
a2905a0f8d Update version.py 2022-06-16 16:32:12 +01:00
Ran Aroussi
1810455e15 bugfix 2022-06-16 16:31:56 +01:00
andKraev
76a9b09e8e rename incorrect yahoo key treasuryStock 2022-05-27 16:53:17 +03:00
giorgossideris
d757b8f25f Handle case that the API returns no data for shares of a specific year 2022-05-09 15:24:10 +03:00
9 changed files with 292 additions and 129 deletions

View File

@@ -1,6 +1,42 @@
Change Log
===========
0.1.80
------
- Fix `download(ignore_tz=True)` for single ticker #1097
- Fix rare case of error "Cannot infer DST time" #1100
0.1.79
------
- Fix when Yahoo returns price=NaNs on dividend day
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()

View File

@@ -187,6 +187,11 @@ data = yf.download( # or pdr.get_data_yahoo(...
# (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',
@@ -264,6 +269,7 @@ To install `yfinance` using `conda`, see
- [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`)

View File

@@ -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:

View File

@@ -3,3 +3,4 @@ numpy>=1.16.5
requests>=2.26
multitasking>=0.0.7
lxml>=4.5.1
appdirs>=1.4.4

View File

@@ -63,7 +63,7 @@ setup(
packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']),
install_requires=['pandas>=0.24.0', 'numpy>=1.15',
'requests>=2.26', 'multitasking>=0.0.7',
'lxml>=4.5.1'],
'lxml>=4.5.1', 'appdirs>=1.4.4'],
entry_points={
'console_scripts': [
'sample=sample:main',

View File

@@ -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
@@ -53,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
@@ -71,18 +73,10 @@ class TickerBase():
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):
@@ -106,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
@@ -129,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)
@@ -147,23 +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":
# 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
if interval == "1m":
start = end - 604800 # Subtract 7 days
else:
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')))
start = utils._parse_user_dt(start, tz)
params = {"period1": start, "period2": end}
else:
period = period.lower()
@@ -187,7 +196,6 @@ class TickerBase():
url = "{}/v8/finance/chart/{}".format(self._base_url, self.ticker)
session = self.session or _requests
data = None
try:
@@ -207,13 +215,6 @@ class TickerBase():
except Exception:
pass
# 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 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
@@ -239,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
@@ -287,10 +293,10 @@ class TickerBase():
"chart"]["result"][0]["meta"]["priceHint"])
quotes['Volume'] = quotes['Volume'].fillna(0).astype(_np.int64)
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)
@@ -298,58 +304,57 @@ 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)
# If a midnight is during DST transition hour when clocks roll back,
# meaning clock hits midnight twice, then use the 2nd (ambiguous=True)
df.index = _pd.to_datetime(df.index.date).tz_localize(tz_exchange, ambiguous=True)
df.index.name = "Date"
# duplicates and missing rows cleanup
df.dropna(how='all', inplace=True)
df = df[~df.index.duplicated(keep='first')]
self._history = df.copy()
if not actions:
df.drop(columns=["Dividends", "Stock Splits"], inplace=True)
df = df.drop(columns=["Dividends", "Stock Splits"])
if not keepna:
mask_nan_or_zero = (df.isna()|(df==0)).all(axis=1)
df = df.drop(mask_nan_or_zero.index[mask_nan_or_zero])
return df
# ------------------------
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)
@@ -357,42 +362,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:
@@ -423,7 +392,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'])
@@ -484,10 +453,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'),
@@ -527,7 +571,9 @@ class TickerBase():
# shares outstanding
try:
shares = _pd.DataFrame(data['annualBasicAverageShares'])
# 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',
@@ -611,14 +657,14 @@ class TickerBase():
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()
@@ -648,14 +694,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()
@@ -807,7 +853,7 @@ class TickerBase():
return self._news
def get_earnings_dates(self, proxy=None):
if self._earnings_dates:
if self._earnings_dates is not None:
return self._earnings_dates
# setup proxy in requests format

View File

@@ -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:
@@ -133,16 +139,21 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
print("\n".join(['- %s: %s' %
v for v in list(shared._ERRORS.items())]))
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)
if len(tickers) == 1:
ticker = tickers[0]
return shared._DFS[shared._ISINS.get(ticker, ticker)]
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)

View File

@@ -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
@@ -135,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"]
@@ -176,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"]
@@ -199,13 +220,10 @@ 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):
def parse_actions(data):
dividends = _pd.DataFrame(
columns=["Dividends"], index=_pd.DatetimeIndex([]))
splits = _pd.DataFrame(
@@ -218,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"]
@@ -229,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"]
@@ -238,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
@@ -286,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)

View File

@@ -1 +1 @@
version = "0.1.71"
version = "0.1.80"