From 930058ce5e27dc072ead85b6db30be9dc7364bb8 Mon Sep 17 00:00:00 2001 From: Patryk Kocielnik Date: Mon, 17 Feb 2025 20:29:09 +0100 Subject: [PATCH 1/7] Use TTM EPS (#97). --- isthisstockgood/Active/MSNMoney.py | 3 +++ isthisstockgood/DataFetcher.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/isthisstockgood/Active/MSNMoney.py b/isthisstockgood/Active/MSNMoney.py index 26482c8..ee46940 100644 --- a/isthisstockgood/Active/MSNMoney.py +++ b/isthisstockgood/Active/MSNMoney.py @@ -108,6 +108,9 @@ def parse_ratios_data(self, content): # Debt self._parse_debt_to_equity(quarterly_data) + + # Quarterly EPS for MOSP valuation + self.quarterly_eps = _extract_data_for_key(quarterly_data, "earningsPerShare") return True diff --git a/isthisstockgood/DataFetcher.py b/isthisstockgood/DataFetcher.py index 3e74db0..24ad196 100644 --- a/isthisstockgood/DataFetcher.py +++ b/isthisstockgood/DataFetcher.py @@ -60,8 +60,13 @@ def fetchDataForTickerSymbol(ticker): else zacks_analysis.five_year_growth_rate if zacks_analysis \ else 0 # TODO: Use TTM EPS instead of most recent year - margin_of_safety_price, sticker_price = \ - _calculateMarginOfSafetyPrice(msn_money.equity_growth_rates[-1], msn_money.pe_low, msn_money.pe_high, msn_money.eps[-1], five_year_growth_rate) + margin_of_safety_price, sticker_price = _calculateMarginOfSafetyPrice( + msn_money.equity_growth_rates[-1], + msn_money.pe_low, + msn_money.pe_high, + msn_money.quarterly_eps[-1], + five_year_growth_rate + ) payback_time = _calculatePaybackTime(msn_money.equity_growth_rates[-1], msn_money.last_year_net_income, msn_money.market_cap, five_year_growth_rate) free_cash_flow_per_share = float(msn_money.free_cash_flow[-1]) computed_free_cash_flow = round(free_cash_flow_per_share * msn_money.shares_outstanding) From 285db37d5cfa20277eae709ac99b2352c73130ae Mon Sep 17 00:00:00 2001 From: Patryk Kocielnik Date: Mon, 17 Feb 2025 20:38:19 +0100 Subject: [PATCH 2/7] Sum the quarterly values for last 4 quarters. --- isthisstockgood/DataFetcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/isthisstockgood/DataFetcher.py b/isthisstockgood/DataFetcher.py index 24ad196..dfa8281 100644 --- a/isthisstockgood/DataFetcher.py +++ b/isthisstockgood/DataFetcher.py @@ -59,12 +59,11 @@ def fetchDataForTickerSymbol(ticker): yahoo_finance_analysis.five_year_growth_rate if yahoo_finance_analysis \ else zacks_analysis.five_year_growth_rate if zacks_analysis \ else 0 - # TODO: Use TTM EPS instead of most recent year margin_of_safety_price, sticker_price = _calculateMarginOfSafetyPrice( msn_money.equity_growth_rates[-1], msn_money.pe_low, msn_money.pe_high, - msn_money.quarterly_eps[-1], + sum(msn_money.quarterly_eps[-4:]), five_year_growth_rate ) payback_time = _calculatePaybackTime(msn_money.equity_growth_rates[-1], msn_money.last_year_net_income, msn_money.market_cap, five_year_growth_rate) From a56d878940de69546b2c9da4c222ac1b5af6413d Mon Sep 17 00:00:00 2001 From: Patryk Kocielnik Date: Mon, 17 Feb 2025 20:53:42 +0100 Subject: [PATCH 3/7] Use TTM for "last year net income". PBT for NVDA: - Before: 17 years, - After: 13 years. --- isthisstockgood/Active/MSNMoney.py | 10 +++++++--- isthisstockgood/DataFetcher.py | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/isthisstockgood/Active/MSNMoney.py b/isthisstockgood/Active/MSNMoney.py index ee46940..5f21052 100644 --- a/isthisstockgood/Active/MSNMoney.py +++ b/isthisstockgood/Active/MSNMoney.py @@ -82,9 +82,6 @@ def parse_annual_report_data(self, content): return self.total_debt = float(most_recent_statement.get('longTermDebt', 0)) self.shares_outstanding = float(most_recent_statement.get('sharesOutstanding', 0)) - - key_metrics = data.get('analysis', {}).get('keyMetrics', {}) - self.last_year_net_income = key_metrics.get('latestIncome', '') def parse_ratios_data(self, content): @@ -111,6 +108,13 @@ def parse_ratios_data(self, content): # Quarterly EPS for MOSP valuation self.quarterly_eps = _extract_data_for_key(quarterly_data, "earningsPerShare") + + # For Payback Time valuation: + # "EPS is calculated by dividing a company's net income + # by the total number of outstanding shares." + # - https://www.investopedia.com/terms/e/eps.asp + self.last_year_net_income = sum(self.quarterly_eps[-4:]) \ + * self.shares_outstanding return True diff --git a/isthisstockgood/DataFetcher.py b/isthisstockgood/DataFetcher.py index dfa8281..85a8d23 100644 --- a/isthisstockgood/DataFetcher.py +++ b/isthisstockgood/DataFetcher.py @@ -109,7 +109,6 @@ def _calculateMarginOfSafetyPrice(one_year_equity_growth_rate, pe_low, pe_high, return margin_of_safety_price, sticker_price -# TODO: Figure out how to get TTM net income instead of previous year net income. def _calculatePaybackTime(one_year_equity_growth_rate, last_year_net_income, market_cap, analyst_five_year_growth_rate): if not one_year_equity_growth_rate or not last_year_net_income or not market_cap or not analyst_five_year_growth_rate: return None From dcb89004a1871553039752dd4247622cddf9bf5a Mon Sep 17 00:00:00 2001 From: Patryk Kocielnik Date: Tue, 18 Feb 2025 10:58:11 +0100 Subject: [PATCH 4/7] Fix tests. --- isthisstockgood/CompanyInfo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/isthisstockgood/CompanyInfo.py b/isthisstockgood/CompanyInfo.py index 38be852..223843c 100644 --- a/isthisstockgood/CompanyInfo.py +++ b/isthisstockgood/CompanyInfo.py @@ -22,6 +22,7 @@ class CompanyInfo: revenue: float revenue_growth_rates: [float] eps: float + quarterly_eps: [float] eps_growth_rates: [float] debt_equity_ratio: float last_year_net_income: float From 09914db1baaf5df9303be0ac705f811dd9cba117 Mon Sep 17 00:00:00 2001 From: Patryk Kocielnik Date: Tue, 18 Feb 2025 14:19:07 +0100 Subject: [PATCH 5/7] Break the problematic line into two. Error was: ``` File "isthisstockgood/Active/MSNMoney.py", line 116, in parse_ratios_data self.last_year_net_income = sum(self.quarterly_eps[-4:]) \ TypeError: can't multiply sequence by non-int of type 'float' ``` The problem here was: how is the result of `sum()` a "sequence"? --- isthisstockgood/Active/MSNMoney.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/isthisstockgood/Active/MSNMoney.py b/isthisstockgood/Active/MSNMoney.py index 5f21052..5dbc4bd 100644 --- a/isthisstockgood/Active/MSNMoney.py +++ b/isthisstockgood/Active/MSNMoney.py @@ -113,8 +113,8 @@ def parse_ratios_data(self, content): # "EPS is calculated by dividing a company's net income # by the total number of outstanding shares." # - https://www.investopedia.com/terms/e/eps.asp - self.last_year_net_income = sum(self.quarterly_eps[-4:]) \ - * self.shares_outstanding + ttm_eps = sum(self.quarterly_eps[-4:]) + self.last_year_net_income = ttm_eps * self.shares_outstanding return True From 020c20f3c662b5c8231d82187ca3cebaca501497 Mon Sep 17 00:00:00 2001 From: Patryk Kocielnik Date: Tue, 18 Feb 2025 14:21:59 +0100 Subject: [PATCH 6/7] Move "ticker" property initialization to the constructor. --- isthisstockgood/DataFetcher.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/isthisstockgood/DataFetcher.py b/isthisstockgood/DataFetcher.py index 85a8d23..ed1651a 100644 --- a/isthisstockgood/DataFetcher.py +++ b/isthisstockgood/DataFetcher.py @@ -37,8 +37,7 @@ def fetchDataForTickerSymbol(ticker): if not ticker: return None - data_fetcher = DataFetcher() - data_fetcher.ticker_symbol = ticker + data_fetcher = DataFetcher(ticker) # Make all network request asynchronously to build their portion of # the json results. @@ -129,10 +128,10 @@ class DataFetcher(): 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36', ] - def __init__(self,): + def __init__(self, ticker): self.lock = Lock() self.rpcs = [] - self.ticker_symbol = '' + self.ticker_symbol = ticker self.msn_money = None self.yahoo_finance_analysis = None self.zacks_analysis = None From 626a9f8b0210b45a2e8edcb1fe8e593d8e6a5b51 Mon Sep 17 00:00:00 2001 From: Patryk Kocielnik Date: Tue, 18 Feb 2025 15:25:39 +0100 Subject: [PATCH 7/7] Fix tests. --- tests/test_DataSources.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_DataSources.py b/tests/test_DataSources.py index 78f0e13..cff3837 100644 --- a/tests/test_DataSources.py +++ b/tests/test_DataSources.py @@ -42,8 +42,7 @@ def test_future_growth_rate(): assert float(data.five_year_growth_rate) > 0.0 def get_msn_money_data(ticker): - data_fetcher = DataFetcher() - data_fetcher.ticker_symbol = ticker + data_fetcher = DataFetcher(ticker) # Make all network request asynchronously to build their portion of # the json results. @@ -56,8 +55,7 @@ def get_msn_money_data(ticker): return CompanyInfo(**vars(data_fetcher.msn_money)) def get_growth_rate(ticker): - data_fetcher = DataFetcher() - data_fetcher.ticker_symbol = ticker + data_fetcher = DataFetcher(ticker) # Make all network request asynchronously to build their portion of # the json results.