Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix fundamentals regression bug #1157

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 34 additions & 14 deletions tests/ticker.py
@@ -1,28 +1,40 @@
"""
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

from .context import yfinance as yf

import unittest
import requests_cache

log_requests = False
# Set this to see the exact requests that are made during tests
DEBUG_LOG_REQUESTS = True

if log_requests:
if DEBUG_LOG_REQUESTS:
import logging

logging.basicConfig(level=logging.DEBUG)

# Create temp session
import requests_cache, tempfile
td = tempfile.TemporaryDirectory()

class TestTicker(unittest.TestCase):
def setUp(self):
global td
self.td = td
self.session = requests_cache.CachedSession(self.td.name + '/' + "yfinance.cache")
session = None

def tearDown(self):
self.session.close()
@classmethod
def setUpClass(cls):
cls.session = requests_cache.CachedSession()

@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"]
Expand Down Expand Up @@ -74,6 +86,7 @@ def test_badTicker(self):
dat.earnings_dates
dat.earnings_forecasts


class TestTickerEarnings(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -173,33 +186,41 @@ def tearDown(self):
self.ticker = None

def test_balance_sheet(self):
expected_row = "TotalAssets"
data = self.ticker.balance_sheet
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertIn(expected_row, data.index, "Did not find expected row in index")

data_cached = self.ticker.balance_sheet
self.assertIs(data, data_cached, "data not cached")

def test_quarterly_balance_sheet(self):
expected_row = "TotalAssets"
data = self.ticker.quarterly_balance_sheet
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertIn(expected_row, data.index, "Did not find expected row in index")

data_cached = self.ticker.quarterly_balance_sheet
self.assertIs(data, data_cached, "data not cached")

def test_cashflow(self):
expected_row = "OperatingCashFlow"
data = self.ticker.cashflow
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertIn(expected_row, data.index, "Did not find expected row in index")

data_cached = self.ticker.cashflow
self.assertIs(data, data_cached, "data not cached")

def test_quarterly_cashflow(self):
expected_row = "OperatingCashFlow"
data = self.ticker.quarterly_cashflow
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertIn(expected_row, data.index, "Did not find expected row in index")

data_cached = self.ticker.quarterly_cashflow
self.assertIs(data, data_cached, "data not cached")
Expand Down Expand Up @@ -271,10 +292,9 @@ def suite():
suite.addTest(TestTicker('Test ticker'))
suite.addTest(TestTickerEarnings('Test earnings'))
suite.addTest(TestTickerHolders('Test holders'))
suite.addTest(TestTickerMiscFinancials('Test balance sheet'))
suite.addTest(TestTickerMiscFinancials('Test misc financials'))
return suite


if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
unittest.main()
10 changes: 6 additions & 4 deletions yfinance/base.py
Expand Up @@ -869,7 +869,7 @@ def cleanup(data):

# generic patterns
for name in ["income", "balance-sheet", "cash-flow"]:
annual, qtr = self._create_financials_table(name, financials_data, proxy)
annual, qtr = self._create_financials_table(name, proxy)
if annual is not None:
self._financials[name]["yearly"] = annual
if qtr is not None:
Expand Down Expand Up @@ -977,7 +977,7 @@ def cleanup(data):

self._fundamentals = True

def _create_financials_table(self, name, financials_data, proxy):
def _create_financials_table(self, name, proxy):
acceptable_names = ["income", "balance-sheet", "cash-flow"]
if not name in acceptable_names:
raise Exception("name '{}' must be one of: {}".format(name, acceptable_names))
Expand All @@ -986,15 +986,17 @@ def _create_financials_table(self, name, financials_data, proxy):
# Yahoo stores the 'income' table internally under 'financials' key
name = "financials"

ticker_url = "{}/{}".format(self._scrape_url, self.ticker)
data_stores = self._data.get_json_data_stores(ticker_url + '/' + name, proxy)
_stmt_annual = None
_stmt_qtr = None
try:
# Developers note: TTM and template stuff allows for reproducing the nested structure
# visible on Yahoo website. But more work needed to make it user-friendly! Ideally
# return a tree data structure instead of Pandas MultiIndex
# So until this is implemented, just return simple tables
_stmt_annual = self._data.get_financials_time_series(name, "annual", financials_data, proxy)
_stmt_qtr = self._data.get_financials_time_series(name, "quarterly", financials_data, proxy)
_stmt_annual = self._data.get_financials_time_series("annual", data_stores, proxy)
_stmt_qtr = self._data.get_financials_time_series("quarterly", data_stores, proxy)

# template_ttm_order, template_annual_order, template_order, level_detail = utils.build_template(data_store["FinancialTemplateStore"])
# TTM_dicts, Annual_dicts = utils.retreive_financial_details(data_store['QuoteTimeSeriesStore'])
Expand Down
5 changes: 1 addition & 4 deletions yfinance/data.py
Expand Up @@ -86,11 +86,8 @@ def get_json_data_stores(self, url, proxy=None):
return json.loads(new_data)

# Note cant use lru_cache as financials_data is a nested dict (freezeargs only handle flat dicts)
def get_financials_time_series(self, name, timescale, financials_data, proxy=None):
def get_financials_time_series(self, timescale, financials_data, proxy=None):

acceptable_names = ["financials", "balance-sheet", "cash-flow"]
if name not in acceptable_names:
raise Exception("name '{}' must be one of: {}".format(name, acceptable_names))
acceptable_timestamps = ["annual", "quarterly"]
if timescale not in acceptable_timestamps:
raise Exception("timescale '{}' must be one of: {}".format(timescale, acceptable_timestamps))
Expand Down