Config 1st version: just proxy
This commit is contained in:
@@ -66,7 +66,7 @@ class TestPriceHistory(unittest.TestCase):
|
||||
tkrs = ["IMP.JO", "BHG.JO", "SSW.JO", "BP.L", "INTC"]
|
||||
for tkr in tkrs:
|
||||
dat = yf.Ticker(tkr, session=self.session)
|
||||
tz = dat._get_ticker_tz(proxy=None, timeout=None)
|
||||
tz = dat._get_ticker_tz(timeout=None)
|
||||
|
||||
dt_utc = _pd.Timestamp.utcnow()
|
||||
dt = dt_utc.astimezone(_tz.timezone(tz))
|
||||
@@ -86,7 +86,7 @@ class TestPriceHistory(unittest.TestCase):
|
||||
test_run = False
|
||||
for tkr in tkrs:
|
||||
dat = yf.Ticker(tkr, session=self.session)
|
||||
tz = dat._get_ticker_tz(proxy=None, timeout=None)
|
||||
tz = dat._get_ticker_tz(timeout=None)
|
||||
|
||||
dt_utc = _pd.Timestamp.utcnow()
|
||||
dt = dt_utc.astimezone(_tz.timezone(tz))
|
||||
@@ -112,7 +112,7 @@ class TestPriceHistory(unittest.TestCase):
|
||||
test_run = False
|
||||
for tkr in tkrs:
|
||||
dat = yf.Ticker(tkr, session=self.session)
|
||||
tz = dat._get_ticker_tz(proxy=None, timeout=None)
|
||||
tz = dat._get_ticker_tz(timeout=None)
|
||||
|
||||
dt = _tz.timezone(tz).localize(_dt.datetime.now())
|
||||
if dt.date().weekday() not in [1, 2, 3, 4]:
|
||||
|
||||
@@ -80,8 +80,6 @@ class TestTicker(unittest.TestCase):
|
||||
def setUpClass(cls):
|
||||
cls.session = session_gbl
|
||||
|
||||
cls.proxy = None
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls.session is not None:
|
||||
@@ -95,7 +93,7 @@ class TestTicker(unittest.TestCase):
|
||||
|
||||
# Test:
|
||||
dat = yf.Ticker(tkr, session=self.session)
|
||||
tz = dat._get_ticker_tz(proxy=None, timeout=5)
|
||||
tz = dat._get_ticker_tz(timeout=5)
|
||||
|
||||
self.assertIsNotNone(tz)
|
||||
|
||||
@@ -227,10 +225,10 @@ class TestTicker(unittest.TestCase):
|
||||
|
||||
def test_goodTicker_withProxy(self):
|
||||
tkr = "IBM"
|
||||
dat = yf.Ticker(tkr, session=self.session, proxy=self.proxy)
|
||||
dat = yf.Ticker(tkr, session=self.session)
|
||||
|
||||
dat._fetch_ticker_tz(proxy=None, timeout=5)
|
||||
dat._get_ticker_tz(proxy=None, timeout=5)
|
||||
dat._fetch_ticker_tz(timeout=5)
|
||||
dat._get_ticker_tz(timeout=5)
|
||||
dat.history(period="5d")
|
||||
|
||||
for attribute_name, attribute_type in ticker_attributes:
|
||||
|
||||
@@ -29,6 +29,7 @@ from .cache import set_tz_cache_location
|
||||
from .domain.sector import Sector
|
||||
from .domain.industry import Industry
|
||||
from .domain.market import Market
|
||||
from .data import YfData
|
||||
|
||||
from .screener.query import EquityQuery, FundQuery
|
||||
from .screener.screener import screen, PREDEFINED_SCREENER_QUERIES
|
||||
@@ -41,4 +42,11 @@ warnings.filterwarnings('default', category=DeprecationWarning, module='^yfinanc
|
||||
|
||||
__all__ = ['download', 'Market', 'Search', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', 'Industry']
|
||||
# screener stuff:
|
||||
__all__ += ['EquityQuery', 'FundQuery', 'screen', 'PREDEFINED_SCREENER_QUERIES']
|
||||
__all__ += ['EquityQuery', 'FundQuery', 'screen', 'PREDEFINED_SCREENER_QUERIES']
|
||||
|
||||
# Config stuff:
|
||||
_NOTSET=object()
|
||||
def set_config(proxy=_NOTSET):
|
||||
if proxy is not _NOTSET:
|
||||
YfData(proxy=proxy)
|
||||
__all__ += ["set_config"]
|
||||
|
||||
324
yfinance/base.py
324
yfinance/base.py
@@ -40,15 +40,14 @@ from .scrapers.quote import Quote, FastInfo
|
||||
from .scrapers.history import PriceHistory
|
||||
from .scrapers.funds import FundsData
|
||||
|
||||
from .const import _BASE_URL_, _ROOT_URL_, _QUERY1_URL_
|
||||
from .const import _BASE_URL_, _ROOT_URL_, _QUERY1_URL_, _SENTINEL_
|
||||
|
||||
|
||||
_tz_info_fetch_ctr = 0
|
||||
|
||||
class TickerBase:
|
||||
def __init__(self, ticker, session=None, proxy=None):
|
||||
def __init__(self, ticker, session=None, proxy=_SENTINEL_):
|
||||
self.ticker = ticker.upper()
|
||||
self.proxy = proxy
|
||||
self.session = session
|
||||
self._tz = None
|
||||
|
||||
@@ -66,6 +65,9 @@ class TickerBase:
|
||||
self.ticker = utils.get_ticker_by_isin(self.ticker, None, session)
|
||||
|
||||
self._data: YfData = YfData(session=session)
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
# self._price_history = PriceHistory(self._data, self.ticker)
|
||||
self._price_history = None # lazy-load
|
||||
@@ -85,11 +87,10 @@ class TickerBase:
|
||||
|
||||
def _lazy_load_price_history(self):
|
||||
if self._price_history is None:
|
||||
self._price_history = PriceHistory(self._data, self.ticker, self._get_ticker_tz(self.proxy, timeout=10))
|
||||
self._price_history = PriceHistory(self._data, self.ticker, self._get_ticker_tz(timeout=10))
|
||||
return self._price_history
|
||||
|
||||
def _get_ticker_tz(self, proxy, timeout):
|
||||
proxy = proxy or self.proxy
|
||||
def _get_ticker_tz(self, timeout):
|
||||
if self._tz is not None:
|
||||
return self._tz
|
||||
c = cache.get_tz_cache()
|
||||
@@ -101,7 +102,7 @@ class TickerBase:
|
||||
tz = None
|
||||
|
||||
if tz is None:
|
||||
tz = self._fetch_ticker_tz(proxy, timeout)
|
||||
tz = self._fetch_ticker_tz(timeout)
|
||||
if tz is None:
|
||||
# _fetch_ticker_tz works in 99.999% of cases.
|
||||
# For rare fail get from info.
|
||||
@@ -123,9 +124,8 @@ class TickerBase:
|
||||
return tz
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def _fetch_ticker_tz(self, proxy, timeout):
|
||||
def _fetch_ticker_tz(self, timeout):
|
||||
# Query Yahoo for fast price data just to get returned timezone
|
||||
proxy = proxy or self.proxy
|
||||
logger = utils.get_yf_logger()
|
||||
|
||||
params = {"range": "1d", "interval": "1d"}
|
||||
@@ -134,7 +134,7 @@ class TickerBase:
|
||||
url = f"{_BASE_URL_}/v8/finance/chart/{self.ticker}"
|
||||
|
||||
try:
|
||||
data = self._data.cache_get(url=url, params=params, proxy=proxy, timeout=timeout)
|
||||
data = self._data.cache_get(url=url, params=params, timeout=timeout)
|
||||
data = data.json()
|
||||
except YFRateLimitError:
|
||||
# Must propagate this
|
||||
@@ -158,95 +158,136 @@ class TickerBase:
|
||||
logger.debug("-------------")
|
||||
return None
|
||||
|
||||
def get_recommendations(self, proxy=None, as_dict=False):
|
||||
def get_recommendations(self, proxy=_SENTINEL_, as_dict=False):
|
||||
"""
|
||||
Returns a DataFrame with the recommendations
|
||||
Columns: period strongBuy buy hold sell strongSell
|
||||
"""
|
||||
self._quote.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._quote.recommendations
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_recommendations_summary(self, proxy=None, as_dict=False):
|
||||
return self.get_recommendations(proxy=proxy, as_dict=as_dict)
|
||||
def get_recommendations_summary(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
def get_upgrades_downgrades(self, proxy=None, as_dict=False):
|
||||
return self.get_recommendations(as_dict=as_dict)
|
||||
|
||||
def get_upgrades_downgrades(self, proxy=_SENTINEL_, as_dict=False):
|
||||
"""
|
||||
Returns a DataFrame with the recommendations changes (upgrades/downgrades)
|
||||
Index: date of grade
|
||||
Columns: firm toGrade fromGrade action
|
||||
"""
|
||||
self._quote.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._quote.upgrades_downgrades
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_calendar(self, proxy=None) -> dict:
|
||||
self._quote.proxy = proxy or self.proxy
|
||||
def get_calendar(self, proxy=_SENTINEL_) -> dict:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
return self._quote.calendar
|
||||
|
||||
def get_sec_filings(self, proxy=None) -> dict:
|
||||
self._quote.proxy = proxy or self.proxy
|
||||
def get_sec_filings(self, proxy=_SENTINEL_) -> dict:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
return self._quote.sec_filings
|
||||
|
||||
def get_major_holders(self, proxy=None, as_dict=False):
|
||||
self._holders.proxy = proxy or self.proxy
|
||||
def get_major_holders(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._holders.major
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_institutional_holders(self, proxy=None, as_dict=False):
|
||||
self._holders.proxy = proxy or self.proxy
|
||||
def get_institutional_holders(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._holders.institutional
|
||||
if data is not None:
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_mutualfund_holders(self, proxy=None, as_dict=False):
|
||||
self._holders.proxy = proxy or self.proxy
|
||||
def get_mutualfund_holders(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._holders.mutualfund
|
||||
if data is not None:
|
||||
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
|
||||
def get_insider_purchases(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._holders.insider_purchases
|
||||
if data is not None:
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_insider_transactions(self, proxy=None, as_dict=False):
|
||||
self._holders.proxy = proxy or self.proxy
|
||||
def get_insider_transactions(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._holders.insider_transactions
|
||||
if data is not None:
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_insider_roster_holders(self, proxy=None, as_dict=False):
|
||||
self._holders.proxy = proxy or self.proxy
|
||||
def get_insider_roster_holders(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._holders.insider_roster
|
||||
if data is not None:
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_info(self, proxy=None) -> dict:
|
||||
self._quote.proxy = proxy or self.proxy
|
||||
def get_info(self, proxy=_SENTINEL_) -> dict:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._quote.info
|
||||
return data
|
||||
|
||||
def get_fast_info(self, proxy=None):
|
||||
def get_fast_info(self, proxy=_SENTINEL_):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
if self._fast_info is None:
|
||||
self._fast_info = FastInfo(self, proxy=proxy)
|
||||
self._fast_info = FastInfo(self)
|
||||
return self._fast_info
|
||||
|
||||
@property
|
||||
@@ -254,76 +295,100 @@ class TickerBase:
|
||||
warnings.warn("'Ticker.basic_info' is deprecated and will be removed in future, Switch to 'Ticker.fast_info'", DeprecationWarning)
|
||||
return self.fast_info
|
||||
|
||||
def get_sustainability(self, proxy=None, as_dict=False):
|
||||
self._quote.proxy = proxy or self.proxy
|
||||
def get_sustainability(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._quote.sustainability
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_analyst_price_targets(self, proxy=None) -> dict:
|
||||
def get_analyst_price_targets(self, proxy=_SENTINEL_) -> dict:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
"""
|
||||
Keys: current low high mean median
|
||||
"""
|
||||
self._analysis.proxy = proxy or self.proxy
|
||||
data = self._analysis.analyst_price_targets
|
||||
return data
|
||||
|
||||
def get_earnings_estimate(self, proxy=None, as_dict=False):
|
||||
def get_earnings_estimate(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
"""
|
||||
Index: 0q +1q 0y +1y
|
||||
Columns: numberOfAnalysts avg low high yearAgoEps growth
|
||||
"""
|
||||
self._analysis.proxy = proxy or self.proxy
|
||||
data = self._analysis.earnings_estimate
|
||||
return data.to_dict() if as_dict else data
|
||||
|
||||
def get_revenue_estimate(self, proxy=None, as_dict=False):
|
||||
def get_revenue_estimate(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
"""
|
||||
Index: 0q +1q 0y +1y
|
||||
Columns: numberOfAnalysts avg low high yearAgoRevenue growth
|
||||
"""
|
||||
self._analysis.proxy = proxy or self.proxy
|
||||
data = self._analysis.revenue_estimate
|
||||
return data.to_dict() if as_dict else data
|
||||
|
||||
def get_earnings_history(self, proxy=None, as_dict=False):
|
||||
def get_earnings_history(self, proxy=_SENTINEL_, as_dict=False):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
"""
|
||||
Index: pd.DatetimeIndex
|
||||
Columns: epsEstimate epsActual epsDifference surprisePercent
|
||||
"""
|
||||
self._analysis.proxy = proxy or self.proxy
|
||||
data = self._analysis.earnings_history
|
||||
return data.to_dict() if as_dict else data
|
||||
|
||||
def get_eps_trend(self, proxy=None, as_dict=False):
|
||||
def get_eps_trend(self, proxy=_SENTINEL_, as_dict=False):
|
||||
"""
|
||||
Index: 0q +1q 0y +1y
|
||||
Columns: current 7daysAgo 30daysAgo 60daysAgo 90daysAgo
|
||||
"""
|
||||
self._analysis.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._analysis.eps_trend
|
||||
return data.to_dict() if as_dict else data
|
||||
|
||||
def get_eps_revisions(self, proxy=None, as_dict=False):
|
||||
def get_eps_revisions(self, proxy=_SENTINEL_, as_dict=False):
|
||||
"""
|
||||
Index: 0q +1q 0y +1y
|
||||
Columns: upLast7days upLast30days downLast7days downLast30days
|
||||
"""
|
||||
self._analysis.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._analysis.eps_revisions
|
||||
return data.to_dict() if as_dict else data
|
||||
|
||||
def get_growth_estimates(self, proxy=None, as_dict=False):
|
||||
def get_growth_estimates(self, proxy=_SENTINEL_, as_dict=False):
|
||||
"""
|
||||
Index: 0q +1q 0y +1y +5y -5y
|
||||
Columns: stock industry sector index
|
||||
"""
|
||||
self._analysis.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._analysis.growth_estimates
|
||||
return data.to_dict() if as_dict else data
|
||||
|
||||
def get_earnings(self, proxy=None, as_dict=False, freq="yearly"):
|
||||
def get_earnings(self, proxy=_SENTINEL_, as_dict=False, freq="yearly"):
|
||||
"""
|
||||
:Parameters:
|
||||
as_dict: bool
|
||||
@@ -332,11 +397,11 @@ class TickerBase:
|
||||
freq: str
|
||||
"yearly" or "quarterly" or "trailing"
|
||||
Default is "yearly"
|
||||
proxy: str
|
||||
Optional. Proxy server URL scheme
|
||||
Default is None
|
||||
"""
|
||||
self._fundamentals.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
if self._fundamentals.earnings is None:
|
||||
return None
|
||||
data = self._fundamentals.earnings[freq]
|
||||
@@ -347,7 +412,7 @@ class TickerBase:
|
||||
return dict_data
|
||||
return data
|
||||
|
||||
def get_income_stmt(self, proxy=None, as_dict=False, pretty=False, freq="yearly"):
|
||||
def get_income_stmt(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
|
||||
"""
|
||||
:Parameters:
|
||||
as_dict: bool
|
||||
@@ -359,13 +424,12 @@ class TickerBase:
|
||||
freq: str
|
||||
"yearly" or "quarterly" or "trailing"
|
||||
Default is "yearly"
|
||||
proxy: str
|
||||
Optional. Proxy server URL scheme
|
||||
Default is None
|
||||
"""
|
||||
self._fundamentals.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._fundamentals.financials.get_income_time_series(freq=freq, proxy=proxy)
|
||||
data = self._fundamentals.financials.get_income_time_series(freq=freq)
|
||||
|
||||
if pretty:
|
||||
data = data.copy()
|
||||
@@ -374,13 +438,21 @@ class TickerBase:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_incomestmt(self, proxy=None, as_dict=False, pretty=False, freq="yearly"):
|
||||
def get_incomestmt(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
return self.get_income_stmt(proxy, as_dict, pretty, freq)
|
||||
|
||||
def get_financials(self, proxy=None, as_dict=False, pretty=False, freq="yearly"):
|
||||
def get_financials(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
return self.get_income_stmt(proxy, as_dict, pretty, freq)
|
||||
|
||||
def get_balance_sheet(self, proxy=None, as_dict=False, pretty=False, freq="yearly"):
|
||||
def get_balance_sheet(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
|
||||
"""
|
||||
:Parameters:
|
||||
as_dict: bool
|
||||
@@ -392,13 +464,13 @@ class TickerBase:
|
||||
freq: str
|
||||
"yearly" or "quarterly"
|
||||
Default is "yearly"
|
||||
proxy: str
|
||||
Optional. Proxy server URL scheme
|
||||
Default is None
|
||||
"""
|
||||
self._fundamentals.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._fundamentals.financials.get_balance_sheet_time_series(freq=freq, proxy=proxy)
|
||||
|
||||
data = self._fundamentals.financials.get_balance_sheet_time_series(freq=freq)
|
||||
|
||||
if pretty:
|
||||
data = data.copy()
|
||||
@@ -407,10 +479,14 @@ class TickerBase:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_balancesheet(self, proxy=None, as_dict=False, pretty=False, freq="yearly"):
|
||||
def get_balancesheet(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
return self.get_balance_sheet(proxy, as_dict, pretty, freq)
|
||||
|
||||
def get_cash_flow(self, proxy=None, as_dict=False, pretty=False, freq="yearly") -> Union[pd.DataFrame, dict]:
|
||||
def get_cash_flow(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly") -> Union[pd.DataFrame, dict]:
|
||||
"""
|
||||
:Parameters:
|
||||
as_dict: bool
|
||||
@@ -422,13 +498,13 @@ class TickerBase:
|
||||
freq: str
|
||||
"yearly" or "quarterly"
|
||||
Default is "yearly"
|
||||
proxy: str
|
||||
Optional. Proxy server URL scheme
|
||||
Default is None
|
||||
"""
|
||||
self._fundamentals.proxy = proxy or self.proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = self._fundamentals.financials.get_cash_flow_time_series(freq=freq, proxy=proxy)
|
||||
|
||||
data = self._fundamentals.financials.get_cash_flow_time_series(freq=freq)
|
||||
|
||||
if pretty:
|
||||
data = data.copy()
|
||||
@@ -437,34 +513,56 @@ class TickerBase:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
def get_cashflow(self, proxy=None, as_dict=False, pretty=False, freq="yearly"):
|
||||
def get_cashflow(self, proxy=_SENTINEL_, as_dict=False, pretty=False, freq="yearly"):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
return self.get_cash_flow(proxy, as_dict, pretty, freq)
|
||||
|
||||
def get_dividends(self, proxy=None, period="max") -> pd.Series:
|
||||
return self._lazy_load_price_history().get_dividends(period=period, proxy=proxy)
|
||||
def get_dividends(self, proxy=_SENTINEL_, period="max") -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
return self._lazy_load_price_history().get_dividends(period=period)
|
||||
|
||||
def get_capital_gains(self, proxy=None, period="max") -> pd.Series:
|
||||
return self._lazy_load_price_history().get_capital_gains(period=period, proxy=proxy)
|
||||
def get_capital_gains(self, proxy=_SENTINEL_, period="max") -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
return self._lazy_load_price_history().get_capital_gains(period=period)
|
||||
|
||||
def get_splits(self, proxy=None, period="max") -> pd.Series:
|
||||
return self._lazy_load_price_history().get_splits(period=period, proxy=proxy)
|
||||
def get_splits(self, proxy=_SENTINEL_, period="max") -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
return self._lazy_load_price_history().get_splits(period=period)
|
||||
|
||||
def get_actions(self, proxy=None, period="max") -> pd.Series:
|
||||
return self._lazy_load_price_history().get_actions(period=period, proxy=proxy)
|
||||
def get_actions(self, proxy=_SENTINEL_, period="max") -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
return self._lazy_load_price_history().get_actions(period=period)
|
||||
|
||||
def get_shares(self, proxy=_SENTINEL_, as_dict=False) -> Union[pd.DataFrame, dict]:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
def get_shares(self, proxy=None, as_dict=False) -> Union[pd.DataFrame, dict]:
|
||||
self._fundamentals.proxy = proxy or self.proxy
|
||||
data = self._fundamentals.shares
|
||||
if as_dict:
|
||||
return data.to_dict()
|
||||
return data
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def get_shares_full(self, start=None, end=None, proxy=None):
|
||||
def get_shares_full(self, start=None, end=None, proxy=_SENTINEL_):
|
||||
logger = utils.get_yf_logger()
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
# Process dates
|
||||
tz = self._get_ticker_tz(proxy=proxy, timeout=10)
|
||||
tz = self._get_ticker_tz(timeout=10)
|
||||
dt_now = pd.Timestamp.utcnow().tz_convert(tz)
|
||||
if start is not None:
|
||||
start_ts = utils._parse_user_dt(start, tz)
|
||||
@@ -486,7 +584,7 @@ class TickerBase:
|
||||
ts_url_base = f"https://query2.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/{self.ticker}?symbol={self.ticker}"
|
||||
shares_url = f"{ts_url_base}&period1={int(start.timestamp())}&period2={int(end.timestamp())}"
|
||||
try:
|
||||
json_data = self._data.cache_get(url=shares_url, proxy=proxy)
|
||||
json_data = self._data.cache_get(url=shares_url)
|
||||
json_data = json_data.json()
|
||||
except (_json.JSONDecodeError, requests.exceptions.RequestException):
|
||||
logger.error(f"{self.ticker}: Yahoo web request for share count failed")
|
||||
@@ -512,7 +610,11 @@ class TickerBase:
|
||||
df = df.sort_index()
|
||||
return df
|
||||
|
||||
def get_isin(self, proxy=None) -> Optional[str]:
|
||||
def get_isin(self, proxy=_SENTINEL_) -> Optional[str]:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
# *** experimental ***
|
||||
if self._isin is not None:
|
||||
return self._isin
|
||||
@@ -525,7 +627,6 @@ class TickerBase:
|
||||
|
||||
q = ticker
|
||||
|
||||
self._quote.proxy = proxy or self.proxy
|
||||
if self._quote.info is None:
|
||||
# Don't print error message cause self._quote.info will print one
|
||||
return None
|
||||
@@ -533,7 +634,7 @@ class TickerBase:
|
||||
q = self._quote.info['shortName']
|
||||
|
||||
url = f'https://markets.businessinsider.com/ajax/SearchController_Suggest?max_results=25&query={urlencode(q)}'
|
||||
data = self._data.cache_get(url=url, proxy=proxy).text
|
||||
data = self._data.cache_get(url=url).text
|
||||
|
||||
search_str = f'"{ticker}|'
|
||||
if search_str not in data:
|
||||
@@ -549,13 +650,17 @@ class TickerBase:
|
||||
self._isin = data.split(search_str)[1].split('"')[0].split('|')[0]
|
||||
return self._isin
|
||||
|
||||
def get_news(self, count=10, tab="news", proxy=None) -> list:
|
||||
def get_news(self, count=10, tab="news", proxy=_SENTINEL_) -> list:
|
||||
"""Allowed options for tab: "news", "all", "press releases"""
|
||||
if self._news:
|
||||
return self._news
|
||||
|
||||
logger = utils.get_yf_logger()
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
tab_queryrefs = {
|
||||
"all": "newsAll",
|
||||
"news": "latestNews",
|
||||
@@ -574,7 +679,7 @@ class TickerBase:
|
||||
}
|
||||
}
|
||||
|
||||
data = self._data.post(url, body=payload, proxy=proxy)
|
||||
data = self._data.post(url, body=payload)
|
||||
if data is None or "Will be right back" in data.text:
|
||||
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
|
||||
"Our engineers are working quickly to resolve "
|
||||
@@ -591,7 +696,7 @@ class TickerBase:
|
||||
return self._news
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def get_earnings_dates(self, limit=12, proxy=None) -> Optional[pd.DataFrame]:
|
||||
def get_earnings_dates(self, limit=12, proxy=_SENTINEL_) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
Get earning dates (future and historic)
|
||||
|
||||
@@ -599,12 +704,15 @@ class TickerBase:
|
||||
limit (int): max amount of upcoming and recent earnings dates to return.
|
||||
Default value 12 should return next 4 quarters and last 8 quarters.
|
||||
Increase if more history is needed.
|
||||
proxy: requests proxy to use.
|
||||
|
||||
Returns:
|
||||
pd.DataFrame
|
||||
"""
|
||||
logger = utils.get_yf_logger()
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
clamped_limit = min(limit, 100) # YF caps at 100, don't go higher
|
||||
|
||||
if self._earnings_dates and clamped_limit in self._earnings_dates:
|
||||
@@ -627,7 +735,7 @@ class TickerBase:
|
||||
"entityIdType": "earnings",
|
||||
"includeFields": ["startdatetime", "timeZoneShortName", "epsestimate", "epsactual", "epssurprisepct"]
|
||||
}
|
||||
response = self._data.post(url, params=params, body=body, proxy=proxy)
|
||||
response = self._data.post(url, params=params, body=body)
|
||||
json_data = response.json()
|
||||
|
||||
# Extract data
|
||||
@@ -643,7 +751,7 @@ class TickerBase:
|
||||
|
||||
# Calculate earnings date
|
||||
df['Earnings Date'] = pd.to_datetime(df['Event Start Date'])
|
||||
tz = self._get_ticker_tz(proxy=proxy, timeout=30)
|
||||
tz = self._get_ticker_tz(timeout=30)
|
||||
if df['Earnings Date'].dt.tz is None:
|
||||
df['Earnings Date'] = df['Earnings Date'].dt.tz_localize(tz)
|
||||
else:
|
||||
@@ -661,10 +769,18 @@ class TickerBase:
|
||||
self._earnings_dates[clamped_limit] = df
|
||||
return df
|
||||
|
||||
def get_history_metadata(self, proxy=None) -> dict:
|
||||
def get_history_metadata(self, proxy=_SENTINEL_) -> dict:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
return self._lazy_load_price_history().get_history_metadata(proxy)
|
||||
|
||||
def get_funds_data(self, proxy=None) -> Optional[FundsData]:
|
||||
def get_funds_data(self, proxy=_SENTINEL_) -> Optional[FundsData]:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
if not self._funds_data:
|
||||
self._funds_data = FundsData(self._data, self.ticker)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ _QUERY1_URL_ = 'https://query1.finance.yahoo.com'
|
||||
_BASE_URL_ = 'https://query2.finance.yahoo.com'
|
||||
_ROOT_URL_ = 'https://finance.yahoo.com'
|
||||
|
||||
_SENTINEL_ = object()
|
||||
|
||||
fundamentals_keys = {
|
||||
'financials': ["TaxEffectOfUnusualItems", "TaxRateForCalcs", "NormalizedEBITDA", "NormalizedDilutedEPS",
|
||||
"NormalizedBasicEPS", "TotalUnusualItems", "TotalUnusualItemsExcludingGoodwill",
|
||||
|
||||
@@ -51,7 +51,13 @@ class SingletonMeta(type):
|
||||
instance = super().__call__(*args, **kwargs)
|
||||
cls._instances[cls] = instance
|
||||
else:
|
||||
cls._instances[cls]._set_session(*args, **kwargs)
|
||||
# Update the existing instance
|
||||
if 'session' in kwargs or (args and len(args) > 0):
|
||||
session = kwargs.get('session') if 'session' in kwargs else args[0]
|
||||
cls._instances[cls]._set_session(session)
|
||||
if 'proxy' in kwargs or (args and len(args) > 1):
|
||||
proxy = kwargs.get('proxy') if 'proxy' in kwargs else args[1]
|
||||
cls._instances[cls]._set_proxy(proxy)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
@@ -64,7 +70,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
'User-Agent': random.choice(USER_AGENTS)
|
||||
}
|
||||
|
||||
def __init__(self, session=None):
|
||||
def __init__(self, session=None, proxy=None):
|
||||
self._crumb = None
|
||||
self._cookie = None
|
||||
|
||||
@@ -75,7 +81,9 @@ class YfData(metaclass=SingletonMeta):
|
||||
|
||||
self._cookie_lock = threading.Lock()
|
||||
|
||||
self._session, self._proxy = None, None
|
||||
self._set_session(session or requests.Session())
|
||||
self._set_proxy(proxy)
|
||||
|
||||
utils.get_yf_logger().debug(f"Using User-Agent: {self.user_agent_headers['User-Agent']}")
|
||||
|
||||
@@ -84,6 +92,8 @@ class YfData(metaclass=SingletonMeta):
|
||||
return
|
||||
with self._cookie_lock:
|
||||
self._session = session
|
||||
if self._proxy is not None:
|
||||
self._session.proxies = self._proxy
|
||||
|
||||
try:
|
||||
self._session.cache
|
||||
@@ -98,6 +108,15 @@ class YfData(metaclass=SingletonMeta):
|
||||
from requests_cache import DO_NOT_CACHE
|
||||
self._expire_after = DO_NOT_CACHE
|
||||
|
||||
def _set_proxy(self, proxy=None):
|
||||
with self._cookie_lock:
|
||||
if proxy is not None:
|
||||
proxy = {'http': proxy, 'https': proxy} if isinstance(proxy, str) else proxy
|
||||
else:
|
||||
proxy = {}
|
||||
self._proxy = proxy
|
||||
self._session.proxies = proxy
|
||||
|
||||
def _set_cookie_strategy(self, strategy, have_lock=False):
|
||||
if strategy == self._cookie_strategy:
|
||||
return
|
||||
@@ -154,7 +173,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
utils.get_yf_logger().debug('loaded persistent cookie')
|
||||
return cookie_dict['cookie']
|
||||
|
||||
def _get_cookie_basic(self, proxy=None, timeout=30):
|
||||
def _get_cookie_basic(self, timeout=30):
|
||||
if self._cookie is not None:
|
||||
utils.get_yf_logger().debug('reusing cookie')
|
||||
return self._cookie
|
||||
@@ -168,7 +187,6 @@ class YfData(metaclass=SingletonMeta):
|
||||
response = self._session.get(
|
||||
url='https://fc.yahoo.com',
|
||||
headers=self.user_agent_headers,
|
||||
proxies=proxy,
|
||||
timeout=timeout,
|
||||
allow_redirects=True)
|
||||
|
||||
@@ -183,7 +201,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
utils.get_yf_logger().debug(f"fetched basic cookie = {self._cookie}")
|
||||
return self._cookie
|
||||
|
||||
def _get_crumb_basic(self, proxy=None, timeout=30):
|
||||
def _get_crumb_basic(self, timeout=30):
|
||||
if self._crumb is not None:
|
||||
utils.get_yf_logger().debug('reusing crumb')
|
||||
return self._crumb
|
||||
@@ -197,7 +215,6 @@ class YfData(metaclass=SingletonMeta):
|
||||
'url': "https://query1.finance.yahoo.com/v1/test/getcrumb",
|
||||
'headers': self.user_agent_headers,
|
||||
'cookies': {cookie.name: cookie.value},
|
||||
'proxies': proxy,
|
||||
'timeout': timeout,
|
||||
'allow_redirects': True
|
||||
}
|
||||
@@ -215,12 +232,12 @@ class YfData(metaclass=SingletonMeta):
|
||||
return self._crumb
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def _get_cookie_and_crumb_basic(self, proxy, timeout):
|
||||
cookie = self._get_cookie_basic(proxy, timeout)
|
||||
crumb = self._get_crumb_basic(proxy, timeout)
|
||||
def _get_cookie_and_crumb_basic(self, timeout):
|
||||
cookie = self._get_cookie_basic(timeout)
|
||||
crumb = self._get_crumb_basic(timeout)
|
||||
return cookie, crumb
|
||||
|
||||
def _get_cookie_csrf(self, proxy, timeout):
|
||||
def _get_cookie_csrf(self, timeout):
|
||||
if self._cookie is not None:
|
||||
utils.get_yf_logger().debug('reusing cookie')
|
||||
return True
|
||||
@@ -232,7 +249,6 @@ class YfData(metaclass=SingletonMeta):
|
||||
|
||||
base_args = {
|
||||
'headers': self.user_agent_headers,
|
||||
'proxies': proxy,
|
||||
'timeout': timeout}
|
||||
|
||||
get_args = {**base_args, 'url': 'https://guce.yahoo.com/consent'}
|
||||
@@ -291,21 +307,20 @@ class YfData(metaclass=SingletonMeta):
|
||||
return True
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def _get_crumb_csrf(self, proxy=None, timeout=30):
|
||||
def _get_crumb_csrf(self, timeout=30):
|
||||
# Credit goes to @bot-unit #1729
|
||||
|
||||
if self._crumb is not None:
|
||||
utils.get_yf_logger().debug('reusing crumb')
|
||||
return self._crumb
|
||||
|
||||
if not self._get_cookie_csrf(proxy, timeout):
|
||||
if not self._get_cookie_csrf(timeout):
|
||||
# This cookie stored in session
|
||||
return None
|
||||
|
||||
get_args = {
|
||||
'url': 'https://query2.finance.yahoo.com/v1/test/getcrumb',
|
||||
'headers': self.user_agent_headers,
|
||||
'proxies': proxy,
|
||||
'timeout': timeout}
|
||||
if self._session_is_caching:
|
||||
get_args['expire_after'] = self._expire_after
|
||||
@@ -322,7 +337,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
return self._crumb
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def _get_cookie_and_crumb(self, proxy=None, timeout=30):
|
||||
def _get_cookie_and_crumb(self, timeout=30):
|
||||
cookie, crumb, strategy = None, None, None
|
||||
|
||||
utils.get_yf_logger().debug(f"cookie_mode = '{self._cookie_strategy}'")
|
||||
@@ -333,10 +348,10 @@ class YfData(metaclass=SingletonMeta):
|
||||
if crumb is None:
|
||||
# Fail
|
||||
self._set_cookie_strategy('basic', have_lock=True)
|
||||
cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout)
|
||||
cookie, crumb = self._get_cookie_and_crumb_basic(timeout)
|
||||
else:
|
||||
# Fallback strategy
|
||||
cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout)
|
||||
cookie, crumb = self._get_cookie_and_crumb_basic(timeout)
|
||||
if cookie is None or crumb is None:
|
||||
# Fail
|
||||
self._set_cookie_strategy('csrf', have_lock=True)
|
||||
@@ -345,15 +360,15 @@ class YfData(metaclass=SingletonMeta):
|
||||
return cookie, crumb, strategy
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def get(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30):
|
||||
return self._make_request(url, request_method = self._session.get, user_agent_headers=user_agent_headers, params=params, proxy=proxy, timeout=timeout)
|
||||
def get(self, url, user_agent_headers=None, params=None, timeout=30):
|
||||
return self._make_request(url, request_method = self._session.get, user_agent_headers=user_agent_headers, params=params, timeout=timeout)
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def post(self, url, body, user_agent_headers=None, params=None, proxy=None, timeout=30):
|
||||
return self._make_request(url, request_method = self._session.post, user_agent_headers=user_agent_headers, body=body, params=params, proxy=proxy, timeout=timeout)
|
||||
def post(self, url, body, user_agent_headers=None, params=None, timeout=30):
|
||||
return self._make_request(url, request_method = self._session.post, user_agent_headers=user_agent_headers, body=body, params=params, timeout=timeout)
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def _make_request(self, url, request_method, user_agent_headers=None, body=None, params=None, proxy=None, timeout=30):
|
||||
def _make_request(self, url, request_method, user_agent_headers=None, body=None, params=None, timeout=30):
|
||||
# Important: treat input arguments as immutable.
|
||||
|
||||
if len(url) > 200:
|
||||
@@ -361,7 +376,6 @@ class YfData(metaclass=SingletonMeta):
|
||||
else:
|
||||
utils.get_yf_logger().debug(f'url={url}')
|
||||
utils.get_yf_logger().debug(f'params={params}')
|
||||
proxy = self._get_proxy(proxy)
|
||||
|
||||
if params is None:
|
||||
params = {}
|
||||
@@ -383,7 +397,6 @@ class YfData(metaclass=SingletonMeta):
|
||||
'url': url,
|
||||
'params': {**params, **crumbs},
|
||||
'cookies': cookies,
|
||||
'proxies': proxy,
|
||||
'timeout': timeout,
|
||||
'headers': user_agent_headers or self.user_agent_headers
|
||||
}
|
||||
@@ -399,7 +412,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
self._set_cookie_strategy('csrf')
|
||||
else:
|
||||
self._set_cookie_strategy('basic')
|
||||
cookie, crumb, strategy = self._get_cookie_and_crumb(proxy, timeout)
|
||||
cookie, crumb, strategy = self._get_cookie_and_crumb(timeout)
|
||||
request_args['params']['crumb'] = crumb
|
||||
if strategy == 'basic':
|
||||
request_args['cookies'] = {cookie.name: cookie.value}
|
||||
@@ -414,19 +427,11 @@ class YfData(metaclass=SingletonMeta):
|
||||
|
||||
@lru_cache_freezeargs
|
||||
@lru_cache(maxsize=cache_maxsize)
|
||||
def cache_get(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30):
|
||||
return self.get(url, user_agent_headers, params, proxy, timeout)
|
||||
def cache_get(self, url, user_agent_headers=None, params=None, timeout=30):
|
||||
return self.get(url, user_agent_headers, params, timeout)
|
||||
|
||||
def _get_proxy(self, proxy):
|
||||
# setup proxy in requests format
|
||||
if proxy is not None:
|
||||
if isinstance(proxy, (dict, frozendict)) and "https" in proxy:
|
||||
proxy = proxy["https"]
|
||||
proxy = {"https": proxy}
|
||||
return proxy
|
||||
|
||||
def get_raw_json(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30):
|
||||
def get_raw_json(self, url, user_agent_headers=None, params=None, timeout=30):
|
||||
utils.get_yf_logger().debug(f'get_raw_json(): {url}')
|
||||
response = self.get(url, user_agent_headers=user_agent_headers, params=params, proxy=proxy, timeout=timeout)
|
||||
response = self.get(url, user_agent_headers=user_agent_headers, params=params, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
@@ -1,7 +1,8 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from ..ticker import Ticker
|
||||
from ..const import _QUERY1_URL_
|
||||
from ..const import _QUERY1_URL_, _SENTINEL_
|
||||
from ..data import YfData
|
||||
from ..utils import print_once
|
||||
from typing import Dict, List, Optional
|
||||
import pandas as _pd
|
||||
|
||||
@@ -13,20 +14,21 @@ class Domain(ABC):
|
||||
and methods for fetching and parsing data. Derived classes must implement the `_fetch_and_parse()` method.
|
||||
"""
|
||||
|
||||
def __init__(self, key: str, session=None, proxy=None):
|
||||
def __init__(self, key: str, session=None, proxy=_SENTINEL_):
|
||||
"""
|
||||
Initializes the Domain object with a key, session, and proxy.
|
||||
|
||||
Args:
|
||||
key (str): Unique key identifying the domain entity.
|
||||
session (Optional[requests.Session]): Session object for HTTP requests. Defaults to None.
|
||||
proxy (Optional[Dict]): Proxy settings. Defaults to None.
|
||||
"""
|
||||
self._key: str = key
|
||||
self.proxy = proxy
|
||||
self.session = session
|
||||
self._data: YfData = YfData(session=session)
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
self._name: Optional[str] = None
|
||||
self._symbol: Optional[str] = None
|
||||
self._overview: Optional[Dict] = None
|
||||
@@ -109,19 +111,18 @@ class Domain(ABC):
|
||||
self._ensure_fetched(self._research_reports)
|
||||
return self._research_reports
|
||||
|
||||
def _fetch(self, query_url, proxy) -> Dict:
|
||||
def _fetch(self, query_url) -> Dict:
|
||||
"""
|
||||
Fetches data from the given query URL.
|
||||
|
||||
Args:
|
||||
query_url (str): The URL used for the data query.
|
||||
proxy (Dict): Proxy settings for the request.
|
||||
|
||||
Returns:
|
||||
Dict: The JSON response data from the request.
|
||||
"""
|
||||
params_dict = {"formatted": "true", "withReturns": "true", "lang": "en-US", "region": "US"}
|
||||
result = self._data.get_raw_json(query_url, user_agent_headers=self._data.user_agent_headers, params=params_dict, proxy=proxy)
|
||||
result = self._data.get_raw_json(query_url, user_agent_headers=self._data.user_agent_headers, params=params_dict)
|
||||
return result
|
||||
|
||||
def _parse_and_assign_common(self, data) -> None:
|
||||
@@ -194,4 +195,4 @@ class Domain(ABC):
|
||||
attribute: The attribute to check and potentially fetch.
|
||||
"""
|
||||
if attribute is None:
|
||||
self._fetch_and_parse()
|
||||
self._fetch_and_parse()
|
||||
|
||||
@@ -5,20 +5,24 @@ import pandas as _pd
|
||||
|
||||
from .domain import Domain, _QUERY_URL_
|
||||
from .. import utils
|
||||
from ..data import YfData
|
||||
from ..const import _SENTINEL_
|
||||
|
||||
class Industry(Domain):
|
||||
"""
|
||||
Represents an industry within a sector.
|
||||
"""
|
||||
|
||||
def __init__(self, key, session=None, proxy=None):
|
||||
def __init__(self, key, session=None, proxy=_SENTINEL_):
|
||||
"""
|
||||
Args:
|
||||
key (str): The key identifier for the industry.
|
||||
session (optional): The session to use for requests.
|
||||
proxy (optional): The proxy to use for requests.
|
||||
"""
|
||||
super(Industry, self).__init__(key, session, proxy)
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
YfData(session=session, proxy=proxy)
|
||||
super(Industry, self).__init__(key, session)
|
||||
self._query_url = f'{_QUERY_URL_}/industries/{self._key}'
|
||||
|
||||
self._sector_key = None
|
||||
@@ -129,7 +133,7 @@ class Industry(Domain):
|
||||
result = None
|
||||
|
||||
try:
|
||||
result = self._fetch(self._query_url, self.proxy)
|
||||
result = self._fetch(self._query_url)
|
||||
data = result['data']
|
||||
self._parse_and_assign_common(data)
|
||||
|
||||
@@ -145,4 +149,4 @@ class Industry(Domain):
|
||||
logger.debug("Got response: ")
|
||||
logger.debug("-------------")
|
||||
logger.debug(f" {result}")
|
||||
logger.debug("-------------")
|
||||
logger.debug("-------------")
|
||||
|
||||
@@ -2,24 +2,27 @@ import datetime as dt
|
||||
|
||||
from ..data import YfData
|
||||
from ..data import utils
|
||||
from ..const import _QUERY1_URL_
|
||||
from ..const import _QUERY1_URL_, _SENTINEL_
|
||||
import json as _json
|
||||
|
||||
class Market:
|
||||
def __init__(self, market:'str', session=None, proxy=None, timeout=30):
|
||||
def __init__(self, market:'str', session=None, proxy=_SENTINEL_, timeout=30):
|
||||
self.market = market
|
||||
self.session = session
|
||||
self.proxy = proxy
|
||||
self.timeout = timeout
|
||||
|
||||
self._data = YfData(session=self.session)
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
self._logger = utils.get_yf_logger()
|
||||
|
||||
self._status = None
|
||||
self._summary = None
|
||||
|
||||
def _fetch_json(self, url, params):
|
||||
data = self._data.cache_get(url=url, params=params, proxy=self.proxy, timeout=self.timeout)
|
||||
data = self._data.cache_get(url=url, params=params, timeout=self.timeout)
|
||||
if data is None or "Will be right back" in data.text:
|
||||
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
|
||||
"Our engineers are working quickly to resolve "
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from __future__ import print_function
|
||||
from typing import Dict, Optional
|
||||
from ..utils import dynamic_docstring, generate_list_table_from_dict
|
||||
from ..const import SECTOR_INDUSTY_MAPPING
|
||||
from ..const import SECTOR_INDUSTY_MAPPING, _SENTINEL_
|
||||
|
||||
import pandas as _pd
|
||||
|
||||
from .domain import Domain, _QUERY_URL_
|
||||
from .. import utils
|
||||
from ..data import YfData
|
||||
|
||||
class Sector(Domain):
|
||||
"""
|
||||
@@ -14,7 +15,7 @@ class Sector(Domain):
|
||||
such as top ETFs, top mutual funds, and industry data.
|
||||
"""
|
||||
|
||||
def __init__(self, key, session=None, proxy=None):
|
||||
def __init__(self, key, session=None, proxy=_SENTINEL_):
|
||||
"""
|
||||
Args:
|
||||
key (str): The key representing the sector.
|
||||
@@ -26,7 +27,11 @@ class Sector(Domain):
|
||||
:attr:`Sector.industries <yfinance.Sector.industries>`
|
||||
Map of sector and industry
|
||||
"""
|
||||
super(Sector, self).__init__(key, session, proxy)
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
YfData(session=session, proxy=proxy)
|
||||
|
||||
super(Sector, self).__init__(key, session)
|
||||
self._query_url: str = f'{_QUERY_URL_}/sectors/{self._key}'
|
||||
self._top_etfs: Optional[Dict] = None
|
||||
self._top_mutual_funds: Optional[Dict] = None
|
||||
@@ -133,7 +138,7 @@ class Sector(Domain):
|
||||
result = None
|
||||
|
||||
try:
|
||||
result = self._fetch(self._query_url, self.proxy)
|
||||
result = self._fetch(self._query_url)
|
||||
data = result['data']
|
||||
self._parse_and_assign_common(data)
|
||||
|
||||
@@ -147,4 +152,4 @@ class Sector(Domain):
|
||||
logger.debug("Got response: ")
|
||||
logger.debug("-------------")
|
||||
logger.debug(f" {result}")
|
||||
logger.debug("-------------")
|
||||
logger.debug("-------------")
|
||||
|
||||
@@ -32,13 +32,13 @@ import pandas as _pd
|
||||
from . import Ticker, utils
|
||||
from .data import YfData
|
||||
from . import shared
|
||||
|
||||
from .const import _SENTINEL_
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
ignore_tz=None, group_by='column', auto_adjust=None, back_adjust=False,
|
||||
repair=False, keepna=False, progress=True, period="max", interval="1d",
|
||||
prepost=False, proxy=None, rounding=False, timeout=10, session=None,
|
||||
prepost=False, proxy=_SENTINEL_, rounding=False, timeout=10, session=None,
|
||||
multi_level_index=True) -> Union[_pd.DataFrame, None]:
|
||||
"""
|
||||
Download yahoo tickers
|
||||
@@ -79,8 +79,6 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
ignore_tz: bool
|
||||
When combining from different timezones, ignore that part of datetime.
|
||||
Default depends on interval. Intraday = False. Day+ = True.
|
||||
proxy: str
|
||||
Optional. Proxy server URL scheme. Default is None
|
||||
rounding: bool
|
||||
Optional. Round values to 2 decimal places?
|
||||
timeout: None or float
|
||||
@@ -127,7 +125,7 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
for ticker in tickers:
|
||||
if utils.is_isin(ticker):
|
||||
isin = ticker
|
||||
ticker = utils.get_ticker_by_isin(ticker, proxy, session=session)
|
||||
ticker = utils.get_ticker_by_isin(ticker, session=session)
|
||||
shared._ISINS[ticker] = isin
|
||||
_tickers_.append(ticker)
|
||||
|
||||
@@ -144,7 +142,11 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
shared._TRACEBACKS = {}
|
||||
|
||||
# Ensure data initialised with session.
|
||||
YfData(session=session)
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
YfData(session=session, proxy=proxy)
|
||||
else:
|
||||
YfData(session=session)
|
||||
|
||||
# download using threads
|
||||
if threads:
|
||||
@@ -156,7 +158,7 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
start=start, end=end, prepost=prepost,
|
||||
actions=actions, auto_adjust=auto_adjust,
|
||||
back_adjust=back_adjust, repair=repair, keepna=keepna,
|
||||
progress=(progress and i > 0), proxy=proxy,
|
||||
progress=(progress and i > 0),
|
||||
rounding=rounding, timeout=timeout)
|
||||
while len(shared._DFS) < len(tickers):
|
||||
_time.sleep(0.01)
|
||||
@@ -167,7 +169,6 @@ def download(tickers, start=None, end=None, actions=False, threads=True,
|
||||
start=start, end=end, prepost=prepost,
|
||||
actions=actions, auto_adjust=auto_adjust,
|
||||
back_adjust=back_adjust, repair=repair, keepna=keepna,
|
||||
proxy=proxy,
|
||||
rounding=rounding, timeout=timeout)
|
||||
if progress:
|
||||
shared._PROGRESS_BAR.animate()
|
||||
@@ -258,10 +259,10 @@ def _realign_dfs():
|
||||
def _download_one_threaded(ticker, start=None, end=None,
|
||||
auto_adjust=False, back_adjust=False, repair=False,
|
||||
actions=False, progress=True, period="max",
|
||||
interval="1d", prepost=False, proxy=None,
|
||||
interval="1d", prepost=False,
|
||||
keepna=False, rounding=False, timeout=10):
|
||||
_download_one(ticker, start, end, auto_adjust, back_adjust, repair,
|
||||
actions, period, interval, prepost, proxy, rounding,
|
||||
actions, period, interval, prepost, rounding,
|
||||
keepna, timeout)
|
||||
if progress:
|
||||
shared._PROGRESS_BAR.animate()
|
||||
@@ -270,7 +271,7 @@ def _download_one_threaded(ticker, start=None, end=None,
|
||||
def _download_one(ticker, start=None, end=None,
|
||||
auto_adjust=False, back_adjust=False, repair=False,
|
||||
actions=False, period="max", interval="1d",
|
||||
prepost=False, proxy=None, rounding=False,
|
||||
prepost=False, rounding=False,
|
||||
keepna=False, timeout=10):
|
||||
data = None
|
||||
try:
|
||||
@@ -278,7 +279,7 @@ def _download_one(ticker, start=None, end=None,
|
||||
period=period, interval=interval,
|
||||
start=start, end=end, prepost=prepost,
|
||||
actions=actions, auto_adjust=auto_adjust,
|
||||
back_adjust=back_adjust, repair=repair, proxy=proxy,
|
||||
back_adjust=back_adjust, repair=repair,
|
||||
rounding=rounding, keepna=keepna, timeout=timeout,
|
||||
raise_errors=True
|
||||
)
|
||||
|
||||
@@ -3,17 +3,19 @@ import requests
|
||||
|
||||
from yfinance import utils
|
||||
from yfinance.data import YfData
|
||||
from yfinance.const import quote_summary_valid_modules
|
||||
from yfinance.const import quote_summary_valid_modules, _SENTINEL_
|
||||
from yfinance.scrapers.quote import _QUOTE_SUMMARY_URL_
|
||||
from yfinance.exceptions import YFException
|
||||
|
||||
|
||||
class Analysis:
|
||||
|
||||
def __init__(self, data: YfData, symbol: str, proxy=None):
|
||||
def __init__(self, data: YfData, symbol: str, proxy=_SENTINEL_):
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
data._set_proxy(proxy)
|
||||
|
||||
self._data = data
|
||||
self._symbol = symbol
|
||||
self.proxy = proxy
|
||||
|
||||
# In quoteSummary the 'earningsTrend' module contains most of the data below.
|
||||
# The format of data is not optimal so each function will process it's part of the data.
|
||||
@@ -175,7 +177,7 @@ class Analysis:
|
||||
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=self.proxy)
|
||||
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", user_agent_headers=self._data.user_agent_headers, params=params_dict)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
utils.get_yf_logger().error(str(e))
|
||||
return None
|
||||
|
||||
@@ -10,10 +10,13 @@ from yfinance.exceptions import YFException, YFNotImplementedError
|
||||
|
||||
class Fundamentals:
|
||||
|
||||
def __init__(self, data: YfData, symbol: str, proxy=None):
|
||||
def __init__(self, data: YfData, symbol: str, proxy=const._SENTINEL_):
|
||||
if proxy is not const._SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
data._set_proxy(proxy)
|
||||
|
||||
self._data = data
|
||||
self._symbol = symbol
|
||||
self.proxy = proxy
|
||||
|
||||
self._earnings = None
|
||||
self._financials = None
|
||||
@@ -48,26 +51,38 @@ class Financials:
|
||||
self._balance_sheet_time_series = {}
|
||||
self._cash_flow_time_series = {}
|
||||
|
||||
def get_income_time_series(self, freq="yearly", proxy=None) -> pd.DataFrame:
|
||||
def get_income_time_series(self, freq="yearly", proxy=const._SENTINEL_) -> pd.DataFrame:
|
||||
if proxy is not const._SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
res = self._income_time_series
|
||||
if freq not in res:
|
||||
res[freq] = self._fetch_time_series("income", freq, proxy)
|
||||
res[freq] = self._fetch_time_series("income", freq)
|
||||
return res[freq]
|
||||
|
||||
def get_balance_sheet_time_series(self, freq="yearly", proxy=None) -> pd.DataFrame:
|
||||
def get_balance_sheet_time_series(self, freq="yearly", proxy=const._SENTINEL_) -> pd.DataFrame:
|
||||
if proxy is not const._SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
res = self._balance_sheet_time_series
|
||||
if freq not in res:
|
||||
res[freq] = self._fetch_time_series("balance-sheet", freq, proxy)
|
||||
res[freq] = self._fetch_time_series("balance-sheet", freq)
|
||||
return res[freq]
|
||||
|
||||
def get_cash_flow_time_series(self, freq="yearly", proxy=None) -> pd.DataFrame:
|
||||
def get_cash_flow_time_series(self, freq="yearly", proxy=const._SENTINEL_) -> pd.DataFrame:
|
||||
if proxy is not const._SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
res = self._cash_flow_time_series
|
||||
if freq not in res:
|
||||
res[freq] = self._fetch_time_series("cash-flow", freq, proxy)
|
||||
res[freq] = self._fetch_time_series("cash-flow", freq)
|
||||
return res[freq]
|
||||
|
||||
@utils.log_indent_decorator
|
||||
def _fetch_time_series(self, name, timescale, proxy=None):
|
||||
def _fetch_time_series(self, name, timescale):
|
||||
# Fetching time series preferred over scraping 'QuoteSummaryStore',
|
||||
# because it matches what Yahoo shows. But for some tickers returns nothing,
|
||||
# despite 'QuoteSummaryStore' containing valid data.
|
||||
@@ -84,7 +99,7 @@ class Financials:
|
||||
" only available for cash-flow or income data.")
|
||||
|
||||
try:
|
||||
statement = self._create_financials_table(name, timescale, proxy)
|
||||
statement = self._create_financials_table(name, timescale)
|
||||
|
||||
if statement is not None:
|
||||
return statement
|
||||
@@ -92,7 +107,7 @@ class Financials:
|
||||
utils.get_yf_logger().error(f"{self._symbol}: Failed to create {name} financials table for reason: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def _create_financials_table(self, name, timescale, proxy):
|
||||
def _create_financials_table(self, name, timescale):
|
||||
if name == "income":
|
||||
# Yahoo stores the 'income' table internally under 'financials' key
|
||||
name = "financials"
|
||||
@@ -100,11 +115,11 @@ class Financials:
|
||||
keys = const.fundamentals_keys[name]
|
||||
|
||||
try:
|
||||
return self.get_financials_time_series(timescale, keys, proxy)
|
||||
return self._get_financials_time_series(timescale, keys)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_financials_time_series(self, timescale, keys: list, proxy=None) -> pd.DataFrame:
|
||||
def _get_financials_time_series(self, timescale, keys: list) -> pd.DataFrame:
|
||||
timescale_translation = {"yearly": "annual", "quarterly": "quarterly", "trailing": "trailing"}
|
||||
timescale = timescale_translation[timescale]
|
||||
|
||||
@@ -117,7 +132,7 @@ class Financials:
|
||||
url += f"&period1={int(start_dt.timestamp())}&period2={int(end.timestamp())}"
|
||||
|
||||
# Step 3: fetch and reshape data
|
||||
json_str = self._data.cache_get(url=url, proxy=proxy).text
|
||||
json_str = self._data.cache_get(url=url).text
|
||||
json_data = json.loads(json_str)
|
||||
data_raw = json_data["timeseries"]["result"]
|
||||
# data_raw = [v for v in data_raw if len(v) > 1] # Discard keys with no data
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pandas as pd
|
||||
|
||||
from yfinance.data import YfData
|
||||
from yfinance.const import _BASE_URL_
|
||||
from yfinance.const import _BASE_URL_, _SENTINEL_
|
||||
from yfinance.exceptions import YFDataException
|
||||
from yfinance import utils
|
||||
|
||||
@@ -17,16 +17,17 @@ class FundsData:
|
||||
Notes:
|
||||
- fundPerformance module is not implemented as better data is queryable using history
|
||||
"""
|
||||
def __init__(self, data: YfData, symbol: str, proxy=None):
|
||||
def __init__(self, data: YfData, symbol: str, proxy=_SENTINEL_):
|
||||
"""
|
||||
Args:
|
||||
data (YfData): The YfData object for fetching data.
|
||||
symbol (str): The symbol of the fund.
|
||||
proxy (optional): Proxy settings for fetching data.
|
||||
"""
|
||||
self._data = data
|
||||
self._symbol = symbol
|
||||
self.proxy = proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
# quoteType
|
||||
self._quote_type = None
|
||||
@@ -165,26 +166,23 @@ class FundsData:
|
||||
self._fetch_and_parse()
|
||||
return self._sector_weightings
|
||||
|
||||
def _fetch(self, proxy):
|
||||
def _fetch(self):
|
||||
"""
|
||||
Fetches the raw JSON data from the API.
|
||||
|
||||
Args:
|
||||
proxy: Proxy settings for fetching data.
|
||||
|
||||
Returns:
|
||||
dict: The raw JSON data.
|
||||
"""
|
||||
modules = ','.join(["quoteType", "summaryProfile", "topHoldings", "fundProfile"])
|
||||
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "symbol": self._symbol, "formatted": "false"}
|
||||
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_+self._symbol, user_agent_headers=self._data.user_agent_headers, params=params_dict, proxy=proxy)
|
||||
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_+self._symbol, user_agent_headers=self._data.user_agent_headers, params=params_dict)
|
||||
return result
|
||||
|
||||
def _fetch_and_parse(self) -> None:
|
||||
"""
|
||||
Fetches and parses the data from the API.
|
||||
"""
|
||||
result = self._fetch(self.proxy)
|
||||
result = self._fetch()
|
||||
try:
|
||||
data = result["quoteSummary"]["result"][0]
|
||||
# check quote type
|
||||
@@ -334,4 +332,4 @@ class FundsData:
|
||||
self._parse_raw_values(_fund_operations_cat.get("annualHoldingsTurnover", pd.NA)),
|
||||
self._parse_raw_values(_fund_operations_cat.get("totalNetAssets", pd.NA))
|
||||
]
|
||||
}).set_index("Attributes")
|
||||
}).set_index("Attributes")
|
||||
|
||||
@@ -8,15 +8,17 @@ import time as _time
|
||||
import bisect
|
||||
|
||||
from yfinance import shared, utils
|
||||
from yfinance.const import _BASE_URL_, _PRICE_COLNAMES_
|
||||
from yfinance.const import _BASE_URL_, _PRICE_COLNAMES_, _SENTINEL_
|
||||
from yfinance.exceptions import YFInvalidPeriodError, YFPricesMissingError, YFTzMissingError, YFRateLimitError
|
||||
|
||||
class PriceHistory:
|
||||
def __init__(self, data, ticker, tz, session=None, proxy=None):
|
||||
def __init__(self, data, ticker, tz, session=None, proxy=_SENTINEL_):
|
||||
self._data = data
|
||||
self.ticker = ticker.upper()
|
||||
self.tz = tz
|
||||
self.proxy = proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
self.session = session
|
||||
|
||||
self._history_cache = {}
|
||||
@@ -30,7 +32,7 @@ class PriceHistory:
|
||||
def history(self, period="1mo", interval="1d",
|
||||
start=None, end=None, prepost=False, actions=True,
|
||||
auto_adjust=True, back_adjust=False, repair=False, keepna=False,
|
||||
proxy=None, rounding=False, timeout=10,
|
||||
proxy=_SENTINEL_, rounding=False, timeout=10,
|
||||
raise_errors=False) -> pd.DataFrame:
|
||||
"""
|
||||
:Parameters:
|
||||
@@ -61,8 +63,6 @@ class PriceHistory:
|
||||
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!
|
||||
@@ -74,7 +74,10 @@ class PriceHistory:
|
||||
If True, then raise errors as Exceptions instead of logging.
|
||||
"""
|
||||
logger = utils.get_yf_logger()
|
||||
proxy = proxy or self.proxy
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
interval_user = interval
|
||||
period_user = period
|
||||
@@ -175,7 +178,6 @@ class PriceHistory:
|
||||
data = get_fn(
|
||||
url=url,
|
||||
params=params,
|
||||
proxy=proxy,
|
||||
timeout=timeout
|
||||
)
|
||||
if "Will be right back" in data.text or data is None:
|
||||
@@ -464,19 +466,23 @@ class PriceHistory:
|
||||
self._reconstruct_start_interval = None
|
||||
return df
|
||||
|
||||
def _get_history_cache(self, period="max", interval="1d", proxy=None) -> pd.DataFrame:
|
||||
def _get_history_cache(self, period="max", interval="1d") -> pd.DataFrame:
|
||||
cache_key = (interval, period)
|
||||
if cache_key in self._history_cache:
|
||||
return self._history_cache[cache_key]
|
||||
|
||||
df = self.history(period=period, interval=interval, prepost=True, proxy=proxy)
|
||||
df = self.history(period=period, interval=interval, prepost=True)
|
||||
self._history_cache[cache_key] = df
|
||||
return df
|
||||
|
||||
def get_history_metadata(self, proxy=None) -> dict:
|
||||
def get_history_metadata(self, proxy=_SENTINEL_) -> dict:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
if self._history_metadata is None or 'tradingPeriods' not in self._history_metadata:
|
||||
# Request intraday data, because then Yahoo returns exchange schedule (tradingPeriods).
|
||||
self._get_history_cache(period="5d", interval="1h", proxy=proxy)
|
||||
self._get_history_cache(period="5d", interval="1h")
|
||||
|
||||
if self._history_metadata_formatted is False:
|
||||
self._history_metadata = utils.format_history_metadata(self._history_metadata)
|
||||
@@ -484,29 +490,45 @@ class PriceHistory:
|
||||
|
||||
return self._history_metadata
|
||||
|
||||
def get_dividends(self, period="max", proxy=None) -> pd.Series:
|
||||
df = self._get_history_cache(period=period, proxy=proxy)
|
||||
def get_dividends(self, period="max", proxy=_SENTINEL_) -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
df = self._get_history_cache(period=period)
|
||||
if "Dividends" in df.columns:
|
||||
dividends = df["Dividends"]
|
||||
return dividends[dividends != 0]
|
||||
return pd.Series()
|
||||
|
||||
def get_capital_gains(self, period="max", proxy=None) -> pd.Series:
|
||||
df = self._get_history_cache(period=period, proxy=proxy)
|
||||
def get_capital_gains(self, period="max", proxy=_SENTINEL_) -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
df = self._get_history_cache(period=period)
|
||||
if "Capital Gains" in df.columns:
|
||||
capital_gains = df["Capital Gains"]
|
||||
return capital_gains[capital_gains != 0]
|
||||
return pd.Series()
|
||||
|
||||
def get_splits(self, period="max", proxy=None) -> pd.Series:
|
||||
df = self._get_history_cache(period=period, proxy=proxy)
|
||||
def get_splits(self, period="max", proxy=_SENTINEL_) -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
df = self._get_history_cache(period=period)
|
||||
if "Stock Splits" in df.columns:
|
||||
splits = df["Stock Splits"]
|
||||
return splits[splits != 0]
|
||||
return pd.Series()
|
||||
|
||||
def get_actions(self, period="max", proxy=None) -> pd.Series:
|
||||
df = self._get_history_cache(period=period, proxy=proxy)
|
||||
def get_actions(self, period="max", proxy=_SENTINEL_) -> pd.Series:
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
df = self._get_history_cache(period=period)
|
||||
|
||||
action_columns = []
|
||||
if "Dividends" in df.columns:
|
||||
|
||||
@@ -3,19 +3,20 @@ import requests
|
||||
|
||||
from yfinance import utils
|
||||
from yfinance.data import YfData
|
||||
from yfinance.const import _BASE_URL_
|
||||
from yfinance.const import _BASE_URL_, _SENTINEL_
|
||||
from yfinance.exceptions import YFDataException
|
||||
|
||||
_QUOTE_SUMMARY_URL_ = f"{_BASE_URL_}/v10/finance/quoteSummary"
|
||||
|
||||
|
||||
class Holders:
|
||||
_SCRAPE_URL_ = 'https://finance.yahoo.com/quote'
|
||||
|
||||
def __init__(self, data: YfData, symbol: str, proxy=None):
|
||||
def __init__(self, data: YfData, symbol: str, proxy=_SENTINEL_):
|
||||
self._data = data
|
||||
self._symbol = symbol
|
||||
self.proxy = proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
data._set_proxy(proxy)
|
||||
|
||||
self._major = None
|
||||
self._major_direct_holders = None
|
||||
@@ -62,16 +63,16 @@ class Holders:
|
||||
self._fetch_and_parse()
|
||||
return self._insider_roster
|
||||
|
||||
def _fetch(self, proxy):
|
||||
def _fetch(self):
|
||||
modules = ','.join(
|
||||
["institutionOwnership", "fundOwnership", "majorDirectHolders", "majorHoldersBreakdown", "insiderTransactions", "insiderHolders", "netSharePurchaseActivity"])
|
||||
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "formatted": "false"}
|
||||
result = self._data.get_raw_json(f"{_QUOTE_SUMMARY_URL_}/{self._symbol}", user_agent_headers=self._data.user_agent_headers, params=params_dict, proxy=proxy)
|
||||
result = self._data.get_raw_json(f"{_QUOTE_SUMMARY_URL_}/{self._symbol}", user_agent_headers=self._data.user_agent_headers, params=params_dict)
|
||||
return result
|
||||
|
||||
def _fetch_and_parse(self):
|
||||
try:
|
||||
result = self._fetch(self.proxy)
|
||||
result = self._fetch()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
utils.get_yf_logger().error(str(e))
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import requests
|
||||
|
||||
from yfinance import utils
|
||||
from yfinance.data import YfData
|
||||
from yfinance.const import quote_summary_valid_modules, _BASE_URL_, _QUERY1_URL_
|
||||
from yfinance.const import quote_summary_valid_modules, _BASE_URL_, _QUERY1_URL_, _SENTINEL_
|
||||
from yfinance.exceptions import YFDataException, YFException
|
||||
|
||||
info_retired_keys_price = {"currentPrice", "dayHigh", "dayLow", "open", "previousClose", "volume", "volume24Hr"}
|
||||
@@ -26,9 +26,11 @@ _QUOTE_SUMMARY_URL_ = f"{_BASE_URL_}/v10/finance/quoteSummary"
|
||||
class FastInfo:
|
||||
# Contain small subset of info[] items that can be fetched faster elsewhere.
|
||||
# Imitates a dict.
|
||||
def __init__(self, tickerBaseObject, proxy=None):
|
||||
def __init__(self, tickerBaseObject, proxy=_SENTINEL_):
|
||||
self._tkr = tickerBaseObject
|
||||
self.proxy = proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._tkr._data._set_proxy(proxy)
|
||||
|
||||
self._prices_1y = None
|
||||
self._prices_1wk_1h_prepost = None
|
||||
@@ -128,8 +130,8 @@ class FastInfo:
|
||||
|
||||
def _get_1y_prices(self, fullDaysOnly=False):
|
||||
if self._prices_1y is None:
|
||||
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)
|
||||
self._prices_1y = self._tkr.history(period="1y", auto_adjust=False, keepna=True)
|
||||
self._md = self._tkr.get_history_metadata()
|
||||
try:
|
||||
ctp = self._md["currentTradingPeriod"]
|
||||
self._today_open = pd.to_datetime(ctp["regular"]["start"], unit='s', utc=True).tz_convert(self.timezone)
|
||||
@@ -154,12 +156,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="5d", 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)
|
||||
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="5d", 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)
|
||||
return self._prices_1wk_1h_reg
|
||||
|
||||
def _get_exchange_metadata(self):
|
||||
@@ -167,7 +169,7 @@ class FastInfo:
|
||||
return self._md
|
||||
|
||||
self._get_1y_prices()
|
||||
self._md = self._tkr.get_history_metadata(proxy=self.proxy)
|
||||
self._md = self._tkr.get_history_metadata()
|
||||
return self._md
|
||||
|
||||
def _exchange_open_now(self):
|
||||
@@ -198,7 +200,7 @@ class FastInfo:
|
||||
if self._currency is not None:
|
||||
return self._currency
|
||||
|
||||
md = self._tkr.get_history_metadata(proxy=self.proxy)
|
||||
md = self._tkr.get_history_metadata()
|
||||
self._currency = md["currency"]
|
||||
return self._currency
|
||||
|
||||
@@ -207,7 +209,7 @@ class FastInfo:
|
||||
if self._quote_type is not None:
|
||||
return self._quote_type
|
||||
|
||||
md = self._tkr.get_history_metadata(proxy=self.proxy)
|
||||
md = self._tkr.get_history_metadata()
|
||||
self._quote_type = md["instrumentType"]
|
||||
return self._quote_type
|
||||
|
||||
@@ -232,7 +234,7 @@ class FastInfo:
|
||||
if self._shares is not None:
|
||||
return self._shares
|
||||
|
||||
shares = self._tkr.get_shares_full(start=pd.Timestamp.utcnow().date()-pd.Timedelta(days=548), proxy=self.proxy)
|
||||
shares = self._tkr.get_shares_full(start=pd.Timestamp.utcnow().date()-pd.Timedelta(days=548))
|
||||
# if shares is None:
|
||||
# # Requesting 18 months failed, so fallback to shares which should include last year
|
||||
# shares = self._tkr.get_shares()
|
||||
@@ -484,11 +486,12 @@ class FastInfo:
|
||||
|
||||
|
||||
class Quote:
|
||||
|
||||
def __init__(self, data: YfData, symbol: str, proxy=None):
|
||||
def __init__(self, data: YfData, symbol: str, proxy=_SENTINEL_):
|
||||
self._data = data
|
||||
self._symbol = symbol
|
||||
self.proxy = proxy
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
self._info = None
|
||||
self._retired_info = None
|
||||
@@ -505,15 +508,15 @@ class Quote:
|
||||
@property
|
||||
def info(self) -> dict:
|
||||
if self._info is None:
|
||||
self._fetch_info(self.proxy)
|
||||
self._fetch_complementary(self.proxy)
|
||||
self._fetch_info()
|
||||
self._fetch_complementary()
|
||||
|
||||
return self._info
|
||||
|
||||
@property
|
||||
def sustainability(self) -> pd.DataFrame:
|
||||
if self._sustainability is None:
|
||||
result = self._fetch(self.proxy, modules=['esgScores'])
|
||||
result = self._fetch(modules=['esgScores'])
|
||||
if result is None:
|
||||
self._sustainability = pd.DataFrame()
|
||||
else:
|
||||
@@ -527,7 +530,7 @@ class Quote:
|
||||
@property
|
||||
def recommendations(self) -> pd.DataFrame:
|
||||
if self._recommendations is None:
|
||||
result = self._fetch(self.proxy, modules=['recommendationTrend'])
|
||||
result = self._fetch(modules=['recommendationTrend'])
|
||||
if result is None:
|
||||
self._recommendations = pd.DataFrame()
|
||||
else:
|
||||
@@ -541,7 +544,7 @@ class Quote:
|
||||
@property
|
||||
def upgrades_downgrades(self) -> pd.DataFrame:
|
||||
if self._upgrades_downgrades is None:
|
||||
result = self._fetch(self.proxy, modules=['upgradeDowngradeHistory'])
|
||||
result = self._fetch(modules=['upgradeDowngradeHistory'])
|
||||
if result is None:
|
||||
self._upgrades_downgrades = pd.DataFrame()
|
||||
else:
|
||||
@@ -575,7 +578,7 @@ class Quote:
|
||||
def valid_modules():
|
||||
return quote_summary_valid_modules
|
||||
|
||||
def _fetch(self, proxy, modules: list):
|
||||
def _fetch(self, modules: list):
|
||||
if not isinstance(modules, list):
|
||||
raise YFException("Should provide a list of modules, see available modules using `valid_modules`")
|
||||
|
||||
@@ -584,30 +587,30 @@ class Quote:
|
||||
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)
|
||||
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", user_agent_headers=self._data.user_agent_headers, params=params_dict)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
utils.get_yf_logger().error(str(e))
|
||||
return None
|
||||
return result
|
||||
|
||||
def _fetch_additional_info(self, proxy):
|
||||
def _fetch_additional_info(self):
|
||||
params_dict = {"symbols": self._symbol, "formatted": "false"}
|
||||
try:
|
||||
result = self._data.get_raw_json(f"{_QUERY1_URL_}/v7/finance/quote?",
|
||||
user_agent_headers=self._data.user_agent_headers,
|
||||
params=params_dict, proxy=proxy)
|
||||
params=params_dict)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
utils.get_yf_logger().error(str(e))
|
||||
return None
|
||||
return result
|
||||
|
||||
def _fetch_info(self, proxy):
|
||||
def _fetch_info(self):
|
||||
if self._already_fetched:
|
||||
return
|
||||
self._already_fetched = True
|
||||
modules = ['financialData', 'quoteType', 'defaultKeyStatistics', 'assetProfile', 'summaryDetail']
|
||||
result = self._fetch(proxy, modules=modules)
|
||||
additional_info = self._fetch_additional_info(proxy)
|
||||
result = self._fetch(modules=modules)
|
||||
additional_info = self._fetch_additional_info()
|
||||
if additional_info is not None and result is not None:
|
||||
result.update(additional_info)
|
||||
else:
|
||||
@@ -658,13 +661,12 @@ class Quote:
|
||||
|
||||
self._info = {k: _format(k, v) for k, v in query1_info.items()}
|
||||
|
||||
def _fetch_complementary(self, proxy):
|
||||
def _fetch_complementary(self):
|
||||
if self._already_fetched_complementary:
|
||||
return
|
||||
self._already_fetched_complementary = True
|
||||
|
||||
# self._scrape(proxy) # decrypt broken
|
||||
self._fetch_info(proxy)
|
||||
self._fetch_info()
|
||||
if self._info is None:
|
||||
return
|
||||
|
||||
@@ -703,7 +705,7 @@ class Quote:
|
||||
end = int(end.timestamp())
|
||||
url += f"&period1={start}&period2={end}"
|
||||
|
||||
json_str = self._data.cache_get(url=url, proxy=proxy).text
|
||||
json_str = self._data.cache_get(url=url).text
|
||||
json_data = json.loads(json_str)
|
||||
json_result = json_data.get("timeseries") or json_data.get("finance")
|
||||
if json_result["error"] is not None:
|
||||
@@ -717,7 +719,7 @@ class Quote:
|
||||
|
||||
def _fetch_calendar(self):
|
||||
# secFilings return too old data, so not requesting it for now
|
||||
result = self._fetch(self.proxy, modules=['calendarEvents'])
|
||||
result = self._fetch(modules=['calendarEvents'])
|
||||
if result is None:
|
||||
self._calendar = {}
|
||||
return
|
||||
@@ -744,7 +746,7 @@ class Quote:
|
||||
|
||||
|
||||
def _fetch_sec_filings(self):
|
||||
result = self._fetch(self.proxy, modules=['secFilings'])
|
||||
result = self._fetch(modules=['secFilings'])
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ from .query import EquityQuery as EqyQy
|
||||
from .query import FundQuery as FndQy
|
||||
from .query import QueryBase, EquityQuery, FundQuery
|
||||
|
||||
from yfinance.const import _BASE_URL_
|
||||
from yfinance.const import _BASE_URL_, _SENTINEL_
|
||||
from yfinance.data import YfData
|
||||
|
||||
from ..utils import dynamic_docstring, generate_list_table_from_dict_universal
|
||||
from ..utils import dynamic_docstring, generate_list_table_from_dict_universal, print_once
|
||||
|
||||
from typing import Union
|
||||
import requests
|
||||
@@ -58,7 +58,7 @@ def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
sortAsc: bool = None,
|
||||
userId: str = None,
|
||||
userIdType: str = None,
|
||||
session = None, proxy = None):
|
||||
session = None, proxy = _SENTINEL_):
|
||||
"""
|
||||
Run a screen: predefined query, or custom query.
|
||||
|
||||
@@ -106,6 +106,12 @@ def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
{predefined_screeners}
|
||||
"""
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
_data = YfData(session=session, proxy=proxy)
|
||||
else:
|
||||
_data = YfData(session=session)
|
||||
|
||||
# Only use defaults when user NOT give a predefined, because
|
||||
# Yahoo's predefined endpoint auto-applies defaults. Also,
|
||||
# that endpoint might be ignoring these fields.
|
||||
@@ -121,10 +127,7 @@ def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
if size is not None and size > 250:
|
||||
raise ValueError("Yahoo limits query size to 250, reduce size.")
|
||||
|
||||
fields = dict(locals())
|
||||
for k in ['query', 'session', 'proxy']:
|
||||
if k in fields:
|
||||
del fields[k]
|
||||
fields = {'offset': offset, 'size': size, 'sortField': sortField, 'sortAsc': sortAsc, 'userId': userId, 'userIdType': userIdType}
|
||||
|
||||
params_dict = {"corsDomain": "finance.yahoo.com", "formatted": "false", "lang": "en-US", "region": "US"}
|
||||
|
||||
@@ -132,12 +135,11 @@ def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
if isinstance(query, str):
|
||||
# post_query = PREDEFINED_SCREENER_QUERIES[query]
|
||||
# Switch to Yahoo's predefined endpoint
|
||||
_data = YfData(session=session)
|
||||
params_dict['scrIds'] = query
|
||||
for k,v in fields.items():
|
||||
if v is not None:
|
||||
params_dict[k] = v
|
||||
resp = _data.get(url=_PREDEFINED_URL_, params=params_dict, proxy=proxy)
|
||||
resp = _data.get(url=_PREDEFINED_URL_, params=params_dict)
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
@@ -170,11 +172,9 @@ def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
post_query['query'] = post_query['query'].to_dict()
|
||||
|
||||
# Fetch
|
||||
_data = YfData(session=session)
|
||||
response = _data.post(_SCREENER_URL_,
|
||||
body=post_query,
|
||||
user_agent_headers=_data.user_agent_headers,
|
||||
params=params_dict,
|
||||
proxy=proxy)
|
||||
params=params_dict)
|
||||
response.raise_for_status()
|
||||
return response.json()['finance']['result'][0]
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
import json as _json
|
||||
|
||||
from . import utils
|
||||
from .const import _BASE_URL_
|
||||
from .const import _BASE_URL_, _SENTINEL_
|
||||
from .data import YfData
|
||||
|
||||
|
||||
class Search:
|
||||
def __init__(self, query, max_results=8, news_count=8, lists_count=8, include_cb=True, include_nav_links=False,
|
||||
include_research=False, include_cultural_assets=False, enable_fuzzy_query=False, recommended=8,
|
||||
session=None, proxy=None, timeout=30, raise_errors=True):
|
||||
session=None, proxy=_SENTINEL_, timeout=30, raise_errors=True):
|
||||
"""
|
||||
Fetches and organizes search results from Yahoo Finance, including stock quotes and news articles.
|
||||
|
||||
@@ -45,16 +45,18 @@ class Search:
|
||||
enable_fuzzy_query: Enable fuzzy search for typos (default False).
|
||||
recommended: Recommended number of results to return (default 8).
|
||||
session: Custom HTTP session for requests (default None).
|
||||
proxy: Proxy settings for requests (default None).
|
||||
timeout: Request timeout in seconds (default 30).
|
||||
raise_errors: Raise exceptions on error (default True).
|
||||
"""
|
||||
if proxy is not _SENTINEL_:
|
||||
utils.print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
self.query = query
|
||||
self.max_results = max_results
|
||||
self.enable_fuzzy_query = enable_fuzzy_query
|
||||
self.news_count = news_count
|
||||
self.session = session
|
||||
self.proxy = proxy
|
||||
self.timeout = timeout
|
||||
self.raise_errors = raise_errors
|
||||
|
||||
@@ -98,7 +100,7 @@ class Search:
|
||||
|
||||
self._logger.debug(f'{self.query}: Yahoo GET parameters: {str(dict(params))}')
|
||||
|
||||
data = self._data.cache_get(url=url, params=params, proxy=self.proxy, timeout=self.timeout)
|
||||
data = self._data.cache_get(url=url, params=params, timeout=self.timeout)
|
||||
if data is None or "Will be right back" in data.text:
|
||||
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
|
||||
"Our engineers are working quickly to resolve "
|
||||
|
||||
@@ -27,11 +27,11 @@ from .scrapers.funds import FundsData
|
||||
import pandas as _pd
|
||||
|
||||
from .base import TickerBase
|
||||
from .const import _BASE_URL_
|
||||
from .const import _BASE_URL_, _SENTINEL_
|
||||
|
||||
|
||||
class Ticker(TickerBase):
|
||||
def __init__(self, ticker, session=None, proxy=None):
|
||||
def __init__(self, ticker, session=None, proxy=_SENTINEL_):
|
||||
super(Ticker, self).__init__(ticker, session=session, proxy=proxy)
|
||||
self._expirations = {}
|
||||
self._underlying = {}
|
||||
@@ -45,7 +45,7 @@ class Ticker(TickerBase):
|
||||
else:
|
||||
url = f"{_BASE_URL_}/v7/finance/options/{self.ticker}?date={date}"
|
||||
|
||||
r = self._data.get(url=url, proxy=self.proxy).json()
|
||||
r = self._data.get(url=url).json()
|
||||
if len(r.get('optionChain', {}).get('result', [])) > 0:
|
||||
for exp in r['optionChain']['result'][0]['expirationDates']:
|
||||
self._expirations[_pd.Timestamp(exp, unit='s').strftime('%Y-%m-%d')] = exp
|
||||
@@ -321,4 +321,4 @@ class Ticker(TickerBase):
|
||||
|
||||
@property
|
||||
def funds_data(self) -> FundsData:
|
||||
return self.get_funds_data()
|
||||
return self.get_funds_data()
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
from __future__ import print_function
|
||||
|
||||
from . import Ticker, multi
|
||||
|
||||
|
||||
# from collections import namedtuple as _namedtuple
|
||||
from .utils import print_once
|
||||
from .data import YfData
|
||||
from .const import _SENTINEL_
|
||||
|
||||
|
||||
class Tickers:
|
||||
@@ -38,6 +38,8 @@ class Tickers:
|
||||
self.symbols = [ticker.upper() for ticker in tickers]
|
||||
self.tickers = {ticker: Ticker(ticker, session=session) for ticker in self.symbols}
|
||||
|
||||
self._data = YfData(session=session)
|
||||
|
||||
# self.tickers = _namedtuple(
|
||||
# "Tickers", ticker_objects.keys(), rename=True
|
||||
# )(*ticker_objects.values())
|
||||
@@ -45,25 +47,32 @@ class Tickers:
|
||||
def history(self, period="1mo", interval="1d",
|
||||
start=None, end=None, prepost=False,
|
||||
actions=True, auto_adjust=True, repair=False,
|
||||
proxy=None,
|
||||
proxy=_SENTINEL_,
|
||||
threads=True, group_by='column', progress=True,
|
||||
timeout=10, **kwargs):
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
return self.download(
|
||||
period, interval,
|
||||
start, end, prepost,
|
||||
actions, auto_adjust, repair,
|
||||
proxy,
|
||||
threads, group_by, progress,
|
||||
timeout, **kwargs)
|
||||
|
||||
def download(self, period="1mo", interval="1d",
|
||||
start=None, end=None, prepost=False,
|
||||
actions=True, auto_adjust=True, repair=False,
|
||||
proxy=None,
|
||||
proxy=_SENTINEL_,
|
||||
threads=True, group_by='column', progress=True,
|
||||
timeout=10, **kwargs):
|
||||
|
||||
if proxy is not _SENTINEL_:
|
||||
print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
self._data._set_proxy(proxy)
|
||||
|
||||
data = multi.download(self.symbols,
|
||||
start=start, end=end,
|
||||
actions=actions,
|
||||
@@ -72,7 +81,6 @@ class Tickers:
|
||||
period=period,
|
||||
interval=interval,
|
||||
prepost=prepost,
|
||||
proxy=proxy,
|
||||
group_by='ticker',
|
||||
threads=threads,
|
||||
progress=progress,
|
||||
|
||||
@@ -44,7 +44,6 @@ from yfinance import const
|
||||
user_agent_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
|
||||
|
||||
|
||||
# From https://stackoverflow.com/a/59128615
|
||||
def attributes(obj):
|
||||
disallowed_names = {
|
||||
@@ -186,10 +185,14 @@ def is_isin(string):
|
||||
return bool(_re.match("^([A-Z]{2})([A-Z0-9]{9})([0-9])$", string))
|
||||
|
||||
|
||||
def get_all_by_isin(isin, proxy=None, session=None):
|
||||
def get_all_by_isin(isin, proxy=const._SENTINEL_, session=None):
|
||||
if not (is_isin(isin)):
|
||||
raise ValueError("Invalid ISIN number")
|
||||
|
||||
if proxy is not const._SENTINEL_:
|
||||
print_once("YF deprecation warning: set proxy via new config function: yf.set_proxy(proxy)")
|
||||
proxy = None
|
||||
|
||||
# Deferred this to prevent circular imports
|
||||
from .search import Search
|
||||
|
||||
@@ -212,17 +215,17 @@ def get_all_by_isin(isin, proxy=None, session=None):
|
||||
}
|
||||
|
||||
|
||||
def get_ticker_by_isin(isin, proxy=None, session=None):
|
||||
def get_ticker_by_isin(isin, proxy=const._SENTINEL_, session=None):
|
||||
data = get_all_by_isin(isin, proxy, session)
|
||||
return data.get('ticker', {}).get('symbol', '')
|
||||
|
||||
|
||||
def get_info_by_isin(isin, proxy=None, session=None):
|
||||
def get_info_by_isin(isin, proxy=const._SENTINEL_, session=None):
|
||||
data = get_all_by_isin(isin, proxy, session)
|
||||
return data.get('ticker', {})
|
||||
|
||||
|
||||
def get_news_by_isin(isin, proxy=None, session=None):
|
||||
def get_news_by_isin(isin, proxy=const._SENTINEL_, session=None):
|
||||
data = get_all_by_isin(isin, proxy, session)
|
||||
return data.get('news', {})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user