Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b27cc0cf40 | ||
|
|
1d7f8139d6 | ||
|
|
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 |
@@ -1,6 +1,29 @@
|
||||
Change Log
|
||||
===========
|
||||
|
||||
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
|
||||
|
||||
@@ -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`)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -3,3 +3,4 @@ numpy>=1.16.5
|
||||
requests>=2.26
|
||||
multitasking>=0.0.7
|
||||
lxml>=4.5.1
|
||||
appdirs>=1.4.4
|
||||
|
||||
2
setup.py
2
setup.py
@@ -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',
|
||||
|
||||
259
yfinance/base.py
259
yfinance/base.py
@@ -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,16 +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])
|
||||
|
||||
# Yahoo bug fix - it often appends latest price even if after end date
|
||||
if quotes.shape[0] > 0:
|
||||
endDt = _pd.to_datetime(_datetime.datetime.fromtimestamp(end))
|
||||
if quotes.index[quotes.shape[0]-1] > endDt:
|
||||
quotes = quotes.iloc[0:quotes.shape[0]-1]
|
||||
tz_exchange = data["chart"]["result"][0]["meta"]["exchangeTimezoneName"]
|
||||
|
||||
# combine
|
||||
df = _pd.concat([quotes, dividends, splits], axis=1, sort=True)
|
||||
@@ -304,61 +304,55 @@ 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
|
||||
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, 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)
|
||||
|
||||
# rename incorrect yahoo key
|
||||
df.rename(index={'treasuryStock': 'Gains Losses Not Affecting Retained Earnings'}, inplace=True)
|
||||
|
||||
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)
|
||||
@@ -366,42 +360,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:
|
||||
@@ -432,7 +390,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'])
|
||||
@@ -493,10 +451,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'),
|
||||
@@ -622,14 +655,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()
|
||||
@@ -659,14 +692,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()
|
||||
|
||||
@@ -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,10 +56,16 @@ 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version = "0.1.73"
|
||||
version = "0.1.79"
|
||||
|
||||
Reference in New Issue
Block a user