938 lines
34 KiB
Python
938 lines
34 KiB
Python
"""
|
|
Tests for Ticker
|
|
|
|
To run all tests in suite from commandline:
|
|
python -m unittest tests.ticker
|
|
|
|
Specific test class:
|
|
python -m unittest tests.ticker.TestTicker
|
|
|
|
"""
|
|
import pandas as pd
|
|
import numpy as np
|
|
|
|
from .context import yfinance as yf
|
|
from .context import session_gbl
|
|
|
|
import unittest
|
|
import requests_cache
|
|
|
|
|
|
class TestTicker(unittest.TestCase):
|
|
session = None
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.session = session_gbl
|
|
|
|
cls.proxy = None
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls.session is not None:
|
|
cls.session.close()
|
|
|
|
def test_getTz(self):
|
|
tkrs = ["IMP.JO", "BHG.JO", "SSW.JO", "BP.L", "INTC"]
|
|
for tkr in tkrs:
|
|
# First step: remove ticker from tz-cache
|
|
yf.utils.get_tz_cache().store(tkr, None)
|
|
|
|
# Test:
|
|
dat = yf.Ticker(tkr, session=self.session)
|
|
tz = dat._get_ticker_tz(proxy=None, timeout=None)
|
|
|
|
self.assertIsNotNone(tz)
|
|
|
|
def test_badTicker(self):
|
|
# Check yfinance doesn't die when ticker delisted
|
|
|
|
tkr = "DJI" # typo of "^DJI"
|
|
dat = yf.Ticker(tkr, session=self.session)
|
|
|
|
dat.history(period="1wk")
|
|
dat.history(start="2022-01-01")
|
|
dat.history(start="2022-01-01", end="2022-03-01")
|
|
yf.download([tkr], period="1wk", threads=False, ignore_tz=False)
|
|
yf.download([tkr], period="1wk", threads=True, ignore_tz=False)
|
|
yf.download([tkr], period="1wk", threads=False, ignore_tz=True)
|
|
yf.download([tkr], period="1wk", threads=True, ignore_tz=True)
|
|
|
|
for k in dat.fast_info:
|
|
dat.fast_info[k]
|
|
|
|
dat.isin
|
|
dat.major_holders
|
|
dat.institutional_holders
|
|
dat.mutualfund_holders
|
|
dat.dividends
|
|
dat.splits
|
|
dat.actions
|
|
dat.get_shares_full()
|
|
dat.options
|
|
dat.news
|
|
dat.earnings_dates
|
|
|
|
dat.income_stmt
|
|
dat.quarterly_income_stmt
|
|
dat.balance_sheet
|
|
dat.quarterly_balance_sheet
|
|
dat.cashflow
|
|
dat.quarterly_cashflow
|
|
|
|
# These haven't been ported Yahoo API
|
|
# dat.shares
|
|
# dat.info
|
|
# dat.calendar
|
|
# dat.recommendations
|
|
# dat.earnings
|
|
# dat.quarterly_earnings
|
|
# dat.recommendations_summary
|
|
# dat.analyst_price_target
|
|
# dat.revenue_forecasts
|
|
# dat.sustainability
|
|
# dat.earnings_trend
|
|
# dat.earnings_forecasts
|
|
|
|
def test_goodTicker(self):
|
|
# that yfinance works when full api is called on same instance of ticker
|
|
|
|
tkrs = ["IBM"]
|
|
tkrs.append("QCSTIX") # weird ticker, no price history but has previous close
|
|
for tkr in tkrs:
|
|
dat = yf.Ticker(tkr, session=self.session)
|
|
|
|
dat.history(period="1wk")
|
|
dat.history(start="2022-01-01")
|
|
dat.history(start="2022-01-01", end="2022-03-01")
|
|
yf.download([tkr], period="1wk", threads=False, ignore_tz=False)
|
|
yf.download([tkr], period="1wk", threads=True, ignore_tz=False)
|
|
yf.download([tkr], period="1wk", threads=False, ignore_tz=True)
|
|
yf.download([tkr], period="1wk", threads=True, ignore_tz=True)
|
|
|
|
for k in dat.fast_info:
|
|
dat.fast_info[k]
|
|
|
|
dat.isin
|
|
dat.major_holders
|
|
dat.institutional_holders
|
|
dat.mutualfund_holders
|
|
dat.dividends
|
|
dat.splits
|
|
dat.actions
|
|
dat.get_shares_full()
|
|
dat.options
|
|
dat.news
|
|
dat.earnings_dates
|
|
|
|
dat.income_stmt
|
|
dat.quarterly_income_stmt
|
|
dat.balance_sheet
|
|
dat.quarterly_balance_sheet
|
|
dat.cashflow
|
|
dat.quarterly_cashflow
|
|
|
|
# These require decryption which is broken:
|
|
# dat.shares
|
|
# dat.info
|
|
# dat.calendar
|
|
# dat.recommendations
|
|
# dat.earnings
|
|
# dat.quarterly_earnings
|
|
# dat.recommendations_summary
|
|
# dat.analyst_price_target
|
|
# dat.revenue_forecasts
|
|
# dat.sustainability
|
|
# dat.earnings_trend
|
|
# dat.earnings_forecasts
|
|
|
|
def test_goodTicker_withProxy(self):
|
|
# that yfinance works when full api is called on same instance of ticker
|
|
|
|
tkr = "IBM"
|
|
dat = yf.Ticker(tkr, session=self.session)
|
|
|
|
dat._fetch_ticker_tz(proxy=self.proxy, timeout=5, debug_mode=False, raise_errors=False)
|
|
dat._get_ticker_tz(proxy=self.proxy, timeout=5, debug_mode=False, raise_errors=False)
|
|
dat.history(period="1wk", proxy=self.proxy)
|
|
|
|
v = dat.stats(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertTrue(len(v) > 0)
|
|
|
|
v = dat.get_recommendations(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_calendar(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_major_holders(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_institutional_holders(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_mutualfund_holders(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_info(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertTrue(len(v) > 0)
|
|
|
|
v = dat.get_sustainability(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_recommendations_summary(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_analyst_price_target(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_rev_forecast(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_earnings_forecast(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_trend_details(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_earnings_trend(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_earnings(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_income_stmt(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_incomestmt(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_financials(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_balance_sheet(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_balancesheet(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_cash_flow(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_cashflow(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_shares(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_shares_full(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
v = dat.get_isin(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertTrue(v != "")
|
|
|
|
v = dat.get_news(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertTrue(len(v) > 0)
|
|
|
|
v = dat.get_earnings_dates(proxy=self.proxy)
|
|
self.assertIsNotNone(v)
|
|
self.assertFalse(v.empty)
|
|
|
|
# TODO: enable after merge
|
|
# dat.get_history_metadata(proxy=self.proxy)
|
|
# self.assertIsNotNone(v)
|
|
# self.assertTrue(len(v) > 0)
|
|
|
|
|
|
class TestTickerHistory(unittest.TestCase):
|
|
session = None
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.session = session_gbl
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls.session is not None:
|
|
cls.session.close()
|
|
|
|
def setUp(self):
|
|
# use a ticker that has dividends
|
|
self.symbol = "IBM"
|
|
self.ticker = yf.Ticker(self.symbol, session=self.session)
|
|
|
|
self.symbols = ["AMZN", "MSFT", "NVDA"]
|
|
|
|
def tearDown(self):
|
|
self.ticker = None
|
|
|
|
def test_history(self):
|
|
md = self.ticker.history_metadata
|
|
self.assertIn("IBM", md.values(), "metadata missing")
|
|
data = self.ticker.history("1y")
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
def test_download(self):
|
|
for t in [False, True]:
|
|
for i in [False, True]:
|
|
data = yf.download(self.symbols, threads=t, ignore_tz=i)
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
def test_no_expensive_calls_introduced(self):
|
|
"""
|
|
Make sure calling history to get price data has not introduced more calls to yahoo than absolutely necessary.
|
|
As doing other type of scraping calls than "query2.finance.yahoo.com/v8/finance/chart" to yahoo website
|
|
will quickly trigger spam-block when doing bulk download of history data.
|
|
"""
|
|
session = requests_cache.CachedSession(backend='memory')
|
|
ticker = yf.Ticker("GOOGL", session=session)
|
|
ticker.history("1y")
|
|
actual_urls_called = tuple([r.url for r in session.cache.filter()])
|
|
session.close()
|
|
expected_urls = (
|
|
'https://query2.finance.yahoo.com/v8/finance/chart/GOOGL?events=div,splits,capitalGains&includePrePost=False&interval=1d&range=1y',
|
|
)
|
|
self.assertEqual(expected_urls, actual_urls_called, "Different than expected url used to fetch history.")
|
|
|
|
def test_dividends(self):
|
|
data = self.ticker.dividends
|
|
self.assertIsInstance(data, pd.Series, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
def test_splits(self):
|
|
data = self.ticker.splits
|
|
self.assertIsInstance(data, pd.Series, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
def test_actions(self):
|
|
data = self.ticker.actions
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
|
|
# Below will fail because not ported to Yahoo API
|
|
# class TestTickerEarnings(unittest.TestCase):
|
|
# session = None
|
|
|
|
# @classmethod
|
|
# def setUpClass(cls):
|
|
# cls.session = session_gbl
|
|
|
|
# @classmethod
|
|
# def tearDownClass(cls):
|
|
# if cls.session is not None:
|
|
# cls.session.close()
|
|
|
|
# def setUp(self):
|
|
# self.ticker = yf.Ticker("GOOGL", session=self.session)
|
|
|
|
# def tearDown(self):
|
|
# self.ticker = None
|
|
|
|
# def test_earnings(self):
|
|
# data = self.ticker.earnings
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.earnings
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_quarterly_earnings(self):
|
|
# data = self.ticker.quarterly_earnings
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.quarterly_earnings
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_earnings_forecasts(self):
|
|
# data = self.ticker.earnings_forecasts
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.earnings_forecasts
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_earnings_dates(self):
|
|
# data = self.ticker.earnings_dates
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.earnings_dates
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_earnings_trend(self):
|
|
# data = self.ticker.earnings_trend
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.earnings_trend
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_earnings_dates_with_limit(self):
|
|
# # use ticker with lots of historic earnings
|
|
# ticker = yf.Ticker("IBM")
|
|
# limit = 110
|
|
# data = ticker.get_earnings_dates(limit=limit)
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
# self.assertEqual(len(data), limit, "Wrong number or rows")
|
|
|
|
# data_cached = ticker.get_earnings_dates(limit=limit)
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
|
|
class TestTickerHolders(unittest.TestCase):
|
|
session = None
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.session = session_gbl
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls.session is not None:
|
|
cls.session.close()
|
|
|
|
def setUp(self):
|
|
self.ticker = yf.Ticker("GOOGL", session=self.session)
|
|
|
|
def tearDown(self):
|
|
self.ticker = None
|
|
|
|
def test_major_holders(self):
|
|
data = self.ticker.major_holders
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
data_cached = self.ticker.major_holders
|
|
self.assertIs(data, data_cached, "data not cached")
|
|
|
|
def test_institutional_holders(self):
|
|
data = self.ticker.institutional_holders
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
data_cached = self.ticker.institutional_holders
|
|
self.assertIs(data, data_cached, "data not cached")
|
|
|
|
def test_mutualfund_holders(self):
|
|
data = self.ticker.mutualfund_holders
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
data_cached = self.ticker.mutualfund_holders
|
|
self.assertIs(data, data_cached, "data not cached")
|
|
|
|
|
|
class TestTickerMiscFinancials(unittest.TestCase):
|
|
session = None
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.session = session_gbl
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls.session is not None:
|
|
cls.session.close()
|
|
|
|
def setUp(self):
|
|
self.ticker = yf.Ticker("GOOGL", session=self.session)
|
|
|
|
# For ticker 'BSE.AX' (and others), Yahoo not returning
|
|
# full quarterly financials (usually cash-flow) with all entries,
|
|
# instead returns a smaller version in different data store.
|
|
self.ticker_old_fmt = yf.Ticker("BSE.AX", session=self.session)
|
|
|
|
def tearDown(self):
|
|
self.ticker = None
|
|
|
|
def test_isin(self):
|
|
data = self.ticker.isin
|
|
self.assertIsInstance(data, str, "data has wrong type")
|
|
self.assertEqual("ARDEUT116159", data, "data is empty")
|
|
|
|
data_cached = self.ticker.isin
|
|
self.assertIs(data, data_cached, "data not cached")
|
|
|
|
def test_options(self):
|
|
data = self.ticker.options
|
|
self.assertIsInstance(data, tuple, "data has wrong type")
|
|
self.assertTrue(len(data) > 1, "data is empty")
|
|
|
|
def test_shares_full(self):
|
|
data = self.ticker.get_shares_full()
|
|
self.assertIsInstance(data, pd.Series, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
|
|
def test_income_statement(self):
|
|
expected_keys = ["Total Revenue", "Basic EPS"]
|
|
expected_periods_days = 365
|
|
|
|
# Test contents of table
|
|
data = self.ticker.get_income_stmt(pretty=True)
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
period = abs((data.columns[0]-data.columns[1]).days)
|
|
self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
|
|
|
|
# Test property defaults
|
|
data2 = self.ticker.income_stmt
|
|
self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
|
|
|
|
# Test pretty=False
|
|
expected_keys = [k.replace(' ', '') for k in expected_keys]
|
|
data = self.ticker.get_income_stmt(pretty=False)
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
|
|
# Test to_dict
|
|
data = self.ticker.get_income_stmt(as_dict=True)
|
|
self.assertIsInstance(data, dict, "data has wrong type")
|
|
|
|
def test_quarterly_income_statement(self):
|
|
expected_keys = ["Total Revenue", "Basic EPS"]
|
|
expected_periods_days = 365//4
|
|
|
|
# Test contents of table
|
|
data = self.ticker.get_income_stmt(pretty=True, freq="quarterly")
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
period = abs((data.columns[0]-data.columns[1]).days)
|
|
self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
|
|
|
|
# Test property defaults
|
|
data2 = self.ticker.quarterly_income_stmt
|
|
self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
|
|
|
|
# Test pretty=False
|
|
expected_keys = [k.replace(' ', '') for k in expected_keys]
|
|
data = self.ticker.get_income_stmt(pretty=False, freq="quarterly")
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
|
|
# Test to_dict
|
|
data = self.ticker.get_income_stmt(as_dict=True)
|
|
self.assertIsInstance(data, dict, "data has wrong type")
|
|
|
|
def test_balance_sheet(self):
|
|
expected_keys = ["Total Assets", "Net PPE"]
|
|
expected_periods_days = 365
|
|
|
|
# Test contents of table
|
|
data = self.ticker.get_balance_sheet(pretty=True)
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
period = abs((data.columns[0]-data.columns[1]).days)
|
|
self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
|
|
|
|
# Test property defaults
|
|
data2 = self.ticker.balance_sheet
|
|
self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
|
|
|
|
# Test pretty=False
|
|
expected_keys = [k.replace(' ', '') for k in expected_keys]
|
|
data = self.ticker.get_balance_sheet(pretty=False)
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
|
|
# Test to_dict
|
|
data = self.ticker.get_balance_sheet(as_dict=True)
|
|
self.assertIsInstance(data, dict, "data has wrong type")
|
|
|
|
def test_quarterly_balance_sheet(self):
|
|
expected_keys = ["Total Assets", "Net PPE"]
|
|
expected_periods_days = 365//4
|
|
|
|
# Test contents of table
|
|
data = self.ticker.get_balance_sheet(pretty=True, freq="quarterly")
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
period = abs((data.columns[0]-data.columns[1]).days)
|
|
self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
|
|
|
|
# Test property defaults
|
|
data2 = self.ticker.quarterly_balance_sheet
|
|
self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
|
|
|
|
# Test pretty=False
|
|
expected_keys = [k.replace(' ', '') for k in expected_keys]
|
|
data = self.ticker.get_balance_sheet(pretty=False, freq="quarterly")
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
|
|
# Test to_dict
|
|
data = self.ticker.get_balance_sheet(as_dict=True, freq="quarterly")
|
|
self.assertIsInstance(data, dict, "data has wrong type")
|
|
|
|
def test_cash_flow(self):
|
|
expected_keys = ["Operating Cash Flow", "Net PPE Purchase And Sale"]
|
|
expected_periods_days = 365
|
|
|
|
# Test contents of table
|
|
data = self.ticker.get_cashflow(pretty=True)
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
period = abs((data.columns[0]-data.columns[1]).days)
|
|
self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
|
|
|
|
# Test property defaults
|
|
data2 = self.ticker.cashflow
|
|
self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
|
|
|
|
# Test pretty=False
|
|
expected_keys = [k.replace(' ', '') for k in expected_keys]
|
|
data = self.ticker.get_cashflow(pretty=False)
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
|
|
# Test to_dict
|
|
data = self.ticker.get_cashflow(as_dict=True)
|
|
self.assertIsInstance(data, dict, "data has wrong type")
|
|
|
|
def test_quarterly_cash_flow(self):
|
|
expected_keys = ["Operating Cash Flow", "Net PPE Purchase And Sale"]
|
|
expected_periods_days = 365//4
|
|
|
|
# Test contents of table
|
|
data = self.ticker.get_cashflow(pretty=True, freq="quarterly")
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
period = abs((data.columns[0]-data.columns[1]).days)
|
|
self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
|
|
|
|
# Test property defaults
|
|
data2 = self.ticker.quarterly_cashflow
|
|
self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
|
|
|
|
# Test pretty=False
|
|
expected_keys = [k.replace(' ', '') for k in expected_keys]
|
|
data = self.ticker.get_cashflow(pretty=False, freq="quarterly")
|
|
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
self.assertFalse(data.empty, "data is empty")
|
|
for k in expected_keys:
|
|
self.assertIn(k, data.index, "Did not find expected row in index")
|
|
|
|
# Test to_dict
|
|
data = self.ticker.get_cashflow(as_dict=True)
|
|
self.assertIsInstance(data, dict, "data has wrong type")
|
|
|
|
def test_income_alt_names(self):
|
|
i1 = self.ticker.income_stmt
|
|
i2 = self.ticker.incomestmt
|
|
self.assertTrue(i1.equals(i2))
|
|
i3 = self.ticker.financials
|
|
self.assertTrue(i1.equals(i3))
|
|
|
|
i1 = self.ticker.get_income_stmt()
|
|
i2 = self.ticker.get_incomestmt()
|
|
self.assertTrue(i1.equals(i2))
|
|
i3 = self.ticker.get_financials()
|
|
self.assertTrue(i1.equals(i3))
|
|
|
|
i1 = self.ticker.quarterly_income_stmt
|
|
i2 = self.ticker.quarterly_incomestmt
|
|
self.assertTrue(i1.equals(i2))
|
|
i3 = self.ticker.quarterly_financials
|
|
self.assertTrue(i1.equals(i3))
|
|
|
|
i1 = self.ticker.get_income_stmt(freq="quarterly")
|
|
i2 = self.ticker.get_incomestmt(freq="quarterly")
|
|
self.assertTrue(i1.equals(i2))
|
|
i3 = self.ticker.get_financials(freq="quarterly")
|
|
self.assertTrue(i1.equals(i3))
|
|
|
|
def test_balance_sheet_alt_names(self):
|
|
i1 = self.ticker.balance_sheet
|
|
i2 = self.ticker.balancesheet
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
i1 = self.ticker.get_balance_sheet()
|
|
i2 = self.ticker.get_balancesheet()
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
i1 = self.ticker.quarterly_balance_sheet
|
|
i2 = self.ticker.quarterly_balancesheet
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
i1 = self.ticker.get_balance_sheet(freq="quarterly")
|
|
i2 = self.ticker.get_balancesheet(freq="quarterly")
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
def test_cash_flow_alt_names(self):
|
|
i1 = self.ticker.cash_flow
|
|
i2 = self.ticker.cashflow
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
i1 = self.ticker.get_cash_flow()
|
|
i2 = self.ticker.get_cashflow()
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
i1 = self.ticker.quarterly_cash_flow
|
|
i2 = self.ticker.quarterly_cashflow
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
i1 = self.ticker.get_cash_flow(freq="quarterly")
|
|
i2 = self.ticker.get_cashflow(freq="quarterly")
|
|
self.assertTrue(i1.equals(i2))
|
|
|
|
def test_bad_freq_value_raises_exception(self):
|
|
self.assertRaises(ValueError, lambda: self.ticker.get_cashflow(freq="badarg"))
|
|
|
|
# Below will fail because not ported to Yahoo API
|
|
|
|
# def test_sustainability(self):
|
|
# data = self.ticker.sustainability
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.sustainability
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_recommendations(self):
|
|
# data = self.ticker.recommendations
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.recommendations
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_recommendations_summary(self):
|
|
# data = self.ticker.recommendations_summary
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.recommendations_summary
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_analyst_price_target(self):
|
|
# data = self.ticker.analyst_price_target
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.analyst_price_target
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_revenue_forecasts(self):
|
|
# data = self.ticker.revenue_forecasts
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.revenue_forecasts
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_calendar(self):
|
|
# data = self.ticker.calendar
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
# data_cached = self.ticker.calendar
|
|
# self.assertIs(data, data_cached, "data not cached")
|
|
|
|
# def test_shares(self):
|
|
# data = self.ticker.shares
|
|
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
|
|
# self.assertFalse(data.empty, "data is empty")
|
|
|
|
|
|
class TestTickerInfo(unittest.TestCase):
|
|
session = None
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.session = session_gbl
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls.session is not None:
|
|
cls.session.close()
|
|
|
|
def setUp(self):
|
|
self.symbols = []
|
|
self.symbols += ["ESLT.TA", "BP.L", "GOOGL"]
|
|
self.symbols.append("QCSTIX") # good for testing, doesn't trade
|
|
self.symbols += ["BTC-USD", "IWO", "VFINX", "^GSPC"]
|
|
self.symbols += ["SOKE.IS", "ADS.DE"] # detected bugs
|
|
self.tickers = [yf.Ticker(s, session=self.session) for s in self.symbols]
|
|
|
|
def tearDown(self):
|
|
self.ticker = None
|
|
|
|
def test_fast_info(self):
|
|
f = yf.Ticker("AAPL", session=self.session).fast_info
|
|
for k in f:
|
|
self.assertIsNotNone(f[k])
|
|
|
|
def test_info(self):
|
|
data = self.tickers[0].info
|
|
self.assertIsInstance(data, dict, "data has wrong type")
|
|
expected_keys = ['industry', 'currentPrice', 'exchange', 'floatShares', 'companyOfficers', 'bid']
|
|
for k in expected_keys:
|
|
print(k)
|
|
self.assertIn("symbol", data.keys(), f"Did not find expected key '{k}' in info dict")
|
|
self.assertEqual(self.symbols[0], data["symbol"], "Wrong symbol value in info dict")
|
|
|
|
# def test_fast_info_matches_info(self):
|
|
# fast_info_keys = set()
|
|
# for ticker in self.tickers:
|
|
# fast_info_keys.update(set(ticker.fast_info.keys()))
|
|
# fast_info_keys = sorted(list(fast_info_keys))
|
|
|
|
# key_rename_map = {}
|
|
# key_rename_map["currency"] = "currency"
|
|
# key_rename_map["quote_type"] = "quoteType"
|
|
# key_rename_map["timezone"] = "exchangeTimezoneName"
|
|
|
|
# key_rename_map["last_price"] = ["currentPrice", "regularMarketPrice"]
|
|
# key_rename_map["open"] = ["open", "regularMarketOpen"]
|
|
# key_rename_map["day_high"] = ["dayHigh", "regularMarketDayHigh"]
|
|
# key_rename_map["day_low"] = ["dayLow", "regularMarketDayLow"]
|
|
# key_rename_map["previous_close"] = ["previousClose"]
|
|
# key_rename_map["regular_market_previous_close"] = ["regularMarketPreviousClose"]
|
|
|
|
# key_rename_map["fifty_day_average"] = "fiftyDayAverage"
|
|
# key_rename_map["two_hundred_day_average"] = "twoHundredDayAverage"
|
|
# key_rename_map["year_change"] = ["52WeekChange", "fiftyTwoWeekChange"]
|
|
# key_rename_map["year_high"] = "fiftyTwoWeekHigh"
|
|
# key_rename_map["year_low"] = "fiftyTwoWeekLow"
|
|
|
|
# key_rename_map["last_volume"] = ["volume", "regularMarketVolume"]
|
|
# key_rename_map["ten_day_average_volume"] = ["averageVolume10days", "averageDailyVolume10Day"]
|
|
# key_rename_map["three_month_average_volume"] = "averageVolume"
|
|
|
|
# key_rename_map["market_cap"] = "marketCap"
|
|
# key_rename_map["shares"] = "sharesOutstanding"
|
|
|
|
# for k in list(key_rename_map.keys()):
|
|
# if '_' in k:
|
|
# key_rename_map[yf.utils.snake_case_2_camelCase(k)] = key_rename_map[k]
|
|
|
|
# # Note: share count items in info[] are bad. Sometimes the float > outstanding!
|
|
# # So often fast_info["shares"] does not match.
|
|
# # Why isn't fast_info["shares"] wrong? Because using it to calculate market cap always correct.
|
|
# bad_keys = {"shares"}
|
|
|
|
# # Loose tolerance for averages, no idea why don't match info[]. Is info wrong?
|
|
# custom_tolerances = {}
|
|
# custom_tolerances["year_change"] = 1.0
|
|
# # custom_tolerances["ten_day_average_volume"] = 1e-3
|
|
# custom_tolerances["ten_day_average_volume"] = 1e-1
|
|
# # custom_tolerances["three_month_average_volume"] = 1e-2
|
|
# custom_tolerances["three_month_average_volume"] = 5e-1
|
|
# custom_tolerances["fifty_day_average"] = 1e-2
|
|
# custom_tolerances["two_hundred_day_average"] = 1e-2
|
|
# for k in list(custom_tolerances.keys()):
|
|
# if '_' in k:
|
|
# custom_tolerances[yf.utils.snake_case_2_camelCase(k)] = custom_tolerances[k]
|
|
|
|
# for k in fast_info_keys:
|
|
# if k in key_rename_map:
|
|
# k2 = key_rename_map[k]
|
|
# else:
|
|
# k2 = k
|
|
|
|
# if not isinstance(k2, list):
|
|
# k2 = [k2]
|
|
|
|
# for m in k2:
|
|
# for ticker in self.tickers:
|
|
# if not m in ticker.info:
|
|
# # print(f"symbol={ticker.ticker}: fast_info key '{k}' mapped to info key '{m}' but not present in info")
|
|
# continue
|
|
|
|
# if k in bad_keys:
|
|
# continue
|
|
|
|
# if k in custom_tolerances:
|
|
# rtol = custom_tolerances[k]
|
|
# else:
|
|
# rtol = 5e-3
|
|
# # rtol = 1e-4
|
|
|
|
# correct = ticker.info[m]
|
|
# test = ticker.fast_info[k]
|
|
# # print(f"Testing: symbol={ticker.ticker} m={m} k={k}: test={test} vs correct={correct}")
|
|
# if k in ["market_cap","marketCap"] and ticker.fast_info["currency"] in ["GBp", "ILA"]:
|
|
# # Adjust for currency to match Yahoo:
|
|
# test *= 0.01
|
|
# try:
|
|
# if correct is None:
|
|
# self.assertTrue(test is None or (not np.isnan(test)), f"{k}: {test} must be None or real value because correct={correct}")
|
|
# elif isinstance(test, float) or isinstance(correct, int):
|
|
# self.assertTrue(np.isclose(test, correct, rtol=rtol), f"{ticker.ticker} {k}: {test} != {correct}")
|
|
# else:
|
|
# self.assertEqual(test, correct, f"{k}: {test} != {correct}")
|
|
# except:
|
|
# if k in ["regularMarketPreviousClose"] and ticker.ticker in ["ADS.DE"]:
|
|
# # Yahoo is wrong, is returning post-market close not regular
|
|
# continue
|
|
# else:
|
|
# raise
|
|
|
|
|
|
|
|
def suite():
|
|
suite = unittest.TestSuite()
|
|
suite.addTest(TestTicker('Test ticker'))
|
|
suite.addTest(TestTickerEarnings('Test earnings'))
|
|
suite.addTest(TestTickerHolders('Test holders'))
|
|
suite.addTest(TestTickerHistory('Test Ticker history'))
|
|
suite.addTest(TestTickerMiscFinancials('Test misc financials'))
|
|
suite.addTest(TestTickerInfo('Test info & fast_info'))
|
|
return suite
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|