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

Add option to extract trailing data out of yahoo finance. #1643

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions tests/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ def test_badTicker(self):

dat.income_stmt
dat.quarterly_income_stmt
dat.ttm_income_stmt
dat.ttm_incomestmt
dat.balance_sheet
dat.quarterly_balance_sheet
dat.cashflow
dat.quarterly_cashflow

dat.ttm_cashflow
# These haven't been ported Yahoo API
# dat.shares
# dat.info
Expand Down Expand Up @@ -127,11 +129,13 @@ def test_goodTicker(self):

dat.income_stmt
dat.quarterly_income_stmt
dat.ttm_income_stmt
dat.ttm_incomestmt
dat.balance_sheet
dat.quarterly_balance_sheet
dat.cashflow
dat.quarterly_cashflow

dat.ttm_cashflow
# These require decryption which is broken:
# dat.shares
# dat.info
Expand Down Expand Up @@ -224,6 +228,10 @@ def test_goodTicker_withProxy(self):
self.assertIsNotNone(v)
self.assertFalse(v.empty)

v = dat.get_income_stmt(proxy=self.proxy, freq="trailing")
self.assertIsNotNone(v)
self.assertFalse(v.empty)

v = dat.get_financials(proxy=self.proxy)
self.assertIsNotNone(v)
self.assertFalse(v.empty)
Expand All @@ -244,6 +252,10 @@ def test_goodTicker_withProxy(self):
self.assertIsNotNone(v)
self.assertFalse(v.empty)

v = dat.get_cashflow(proxy=self.proxy, freq="trailing")
self.assertIsNotNone(v)
self.assertFalse(v.empty)

v = dat.get_shares(proxy=self.proxy)
self.assertIsNotNone(v)
self.assertFalse(v.empty)
Expand Down Expand Up @@ -523,6 +535,35 @@ def test_income_statement(self):
data = self.ticker.get_income_stmt(as_dict=True)
self.assertIsInstance(data, dict, "data has wrong type")

def test_ttm_income_statement(self):
expected_keys = ["Total Revenue", "Pretax Income", "Normalized EBITDA"]
expected_periods_days = 365

# Test contents of table
data = self.ticker.get_income_stmt(pretty=True, freq='trailing')
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), 366, "Difference between TTM calculations should be less than a year.")

# Test property defaults
data2 = self.ticker.ttm_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, freq='trailing')
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
Expand Down Expand Up @@ -639,6 +680,35 @@ def test_cash_flow(self):
data = self.ticker.get_cashflow(as_dict=True)
self.assertIsInstance(data, dict, "data has wrong type")

def test_ttm_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, freq='trailing')
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), 366, "Difference between TTM calculations should be less than a year.")

# Test property defaults
data2 = self.ticker.ttm_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='trailing')
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, freq='trailing')
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
Expand Down
4 changes: 2 additions & 2 deletions yfinance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1588,7 +1588,7 @@ def get_income_stmt(self, proxy=None, as_dict=False, pretty=False, freq="yearly"
Format row names nicely for readability
Default is False
freq: str
"yearly" or "quarterly"
"yearly" or "quarterly" or "trailing"
Default is "yearly"
proxy: str
Optional. Proxy server URL scheme
Expand Down Expand Up @@ -1651,7 +1651,7 @@ def get_cash_flow(self, proxy=None, as_dict=False, pretty=False, freq="yearly"):
Format row names nicely for readability
Default is False
freq: str
"yearly" or "quarterly"
"yearly" or "quarterly" or "trailing"
Default is "yearly"
proxy: str
Optional. Proxy server URL scheme
Expand Down
11 changes: 9 additions & 2 deletions yfinance/scrapers/fundamentals.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,16 @@ def _fetch_time_series(self, name, timescale, proxy=None):
# despite 'QuoteSummaryStore' containing valid data.

allowed_names = ["income", "balance-sheet", "cash-flow"]
allowed_timescales = ["yearly", "quarterly"]
allowed_timescales = ["yearly", "quarterly", "trailing"]

if name not in allowed_names:
raise ValueError("Illegal argument: name must be one of: {}".format(allowed_names))
if timescale not in allowed_timescales:
raise ValueError("Illegal argument: timescale must be one of: {}".format(allowed_names))

if timescale == "trailing" and name not in ('income', 'cash-flow'):
raise ValueError("Illegal argument: frequency 'trailing'" +
" only available for cash-flow or income data.")
try:
statement = self._create_financials_table(name, timescale, proxy)

Expand All @@ -102,7 +105,11 @@ def _create_financials_table(self, name, timescale, proxy):
pass

def get_financials_time_series(self, timescale, keys: list, proxy=None) -> pd.DataFrame:
timescale_translation = {"yearly": "annual", "quarterly": "quarterly"}
timescale_translation = {
"yearly": "annual",
"quarterly": "quarterly",
"trailing": "trailing"
}
timescale = timescale_translation[timescale]

# Step 2: construct url:
Expand Down
12 changes: 12 additions & 0 deletions yfinance/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ def quarterly_earnings(self) -> _pd.DataFrame:
def income_stmt(self) -> _pd.DataFrame:
return self.get_income_stmt(pretty=True)

@property
def ttm_income_stmt(self) -> _pd.DataFrame:
return self.get_income_stmt(pretty=True, freq='trailing')

@property
def quarterly_income_stmt(self) -> _pd.DataFrame:
return self.get_income_stmt(pretty=True, freq='quarterly')
Expand All @@ -169,6 +173,10 @@ def quarterly_income_stmt(self) -> _pd.DataFrame:
def incomestmt(self) -> _pd.DataFrame:
return self.income_stmt

@property
def ttm_incomestmt(self) -> _pd.DataFrame:
return self.ttm_income_stmt

@property
def quarterly_incomestmt(self) -> _pd.DataFrame:
return self.quarterly_income_stmt
Expand Down Expand Up @@ -209,6 +217,10 @@ def quarterly_cash_flow(self) -> _pd.DataFrame:
def cashflow(self) -> _pd.DataFrame:
return self.cash_flow

@property
def ttm_cashflow(self) -> _pd.DataFrame:
return self.get_cash_flow(pretty=True, freq='trailing')

@property
def quarterly_cashflow(self) -> _pd.DataFrame:
return self.quarterly_cash_flow
Expand Down
2 changes: 1 addition & 1 deletion yfinance/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = "0.2.26"
version = "0.2.27"