diff --git a/isthisstockgood/Active/Zacks.py b/isthisstockgood/Active/Zacks.py new file mode 100644 index 0000000..49d1ebd --- /dev/null +++ b/isthisstockgood/Active/Zacks.py @@ -0,0 +1,42 @@ +import re + +class Zacks: + def __init__(self, ticker_symbol): + base_url = "https://www.zacks.com/stock/quote" + + self.url = f"{base_url}/{ticker_symbol}/detailed-earning-estimates" + self.ticker_symbol = ticker_symbol + self.five_year_growth_rate = None + self.maintenance_capital_expenditures = None + + def parse(self, response, **kwargs): + if response.status_code != 200: + print(f"{response.status_code}: {response.text}") + return + + if not response.text: + print("Response was empty") + return + + try: + self.five_year_growth_rate = self.get_growth_rate(response.text) + except: + self.five_year_growth_rate = None + + def get_growth_rate(self, text): + lines = text.split("\n") + + for i, line in enumerate(lines): + if "Next 5 Years" in line: + result = lines[i+1] + + estimate = re.sub(r"[^\d\.]", "", result) + + try: + result = float(estimate) + except TypeError: + print( + "Unable to parse growth estimate from: {text}" + ) + + return float(estimate) diff --git a/isthisstockgood/DataFetcher.py b/isthisstockgood/DataFetcher.py index a546094..29d0a21 100644 --- a/isthisstockgood/DataFetcher.py +++ b/isthisstockgood/DataFetcher.py @@ -4,6 +4,7 @@ from requests_futures.sessions import FuturesSession from isthisstockgood.Active.MSNMoney import MSNMoney from isthisstockgood.Active.YahooFinance import YahooFinanceAnalysis +from isthisstockgood.Active.Zacks import Zacks from threading import Lock logger = logging.getLogger("IsThisStockGood") @@ -41,7 +42,7 @@ def fetchDataForTickerSymbol(ticker): # Make all network request asynchronously to build their portion of # the json results. data_fetcher.fetch_msn_money_data() - data_fetcher.fetch_yahoo_finance_analysis() + data_fetcher.fetch_growth_rate() # Wait for each RPC result before proceeding. @@ -49,9 +50,9 @@ def fetchDataForTickerSymbol(ticker): rpc.result() msn_money = data_fetcher.msn_money - yahoo_finance_analysis = data_fetcher.yahoo_finance_analysis + future_growth_rate = data_fetcher.future_growth_rate # NOTE: Some stocks won't have analyst growth rates, such as newly listed stocks or some foreign stocks. - five_year_growth_rate = yahoo_finance_analysis.five_year_growth_rate if yahoo_finance_analysis else 0 + five_year_growth_rate = future_growth_rate.five_year_growth_rate if future_growth_rate 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) @@ -121,7 +122,7 @@ def __init__(self,): self.rpcs = [] self.ticker_symbol = '' self.msn_money = None - self.yahoo_finance_analysis = None + self.future_growth_rate = None self.yahoo_finance_chart = None self.error = False @@ -196,25 +197,38 @@ def parse_msn_money_annual_statement_data(self, response, *args, **kwargs): result = response.text self.msn_money.parse_annual_report_data(result) - def fetch_yahoo_finance_analysis(self): - self.yahoo_finance_analysis = YahooFinanceAnalysis(self.ticker_symbol) + def fetch_growth_rate_estimate(self): + self.future_growth_rate = YahooFinanceAnalysis(self.ticker_symbol) session = self._create_session() - rpc = session.get(self.yahoo_finance_analysis.url, allow_redirects=True, hooks={ - 'response': self.parse_yahoo_finance_analysis, + rpc = session.get(self.future_growth_rate.url, allow_redirects=True, hooks={ + 'response': self.parse_growth_rate_estimate, }) self.rpcs.append(rpc) + def fetch_growth_rate(self): + session = self._create_session() + self.future_growth_rate = Zacks(self.ticker_symbol) + + rpc = session.get( + self.future_growth_rate.url, + allow_redirects=True, + hooks={ + 'response': self.future_growth_rate.parse, + } + ) + self.rpcs.append(rpc) + # Called asynchronously upon completion of the URL fetch from - # `fetch_yahoo_finance_analysis`. - def parse_yahoo_finance_analysis(self, response, *args, **kwargs): + # `fetch_growth_rate_estimate`. + def parse_growth_rate_estimate(self, response, *args, **kwargs): if response.status_code != 200: return - if not self.yahoo_finance_analysis: + if not self.future_growth_rate: return result = response.text - success = self.yahoo_finance_analysis.parse_analyst_five_year_growth_rate(result) + success = self.future_growth_rate.parse_analyst_five_year_growth_rate(result) if not success: - self.yahoo_finance_analysis = None + self.future_growth_rate = None def fetch_yahoo_finance_chart(self): self.yahoo_finance_chart = YahooFinanceChart(self.ticker_symbol) @@ -225,7 +239,7 @@ def fetch_yahoo_finance_chart(self): self.rpcs.append(rpc) # Called asynchronously upon completion of the URL fetch from - # `fetch_yahoo_finance_analysis`. + # `fetch_growth_rate_estimate`. def parse_yahoo_finance_chart(self, response, *args, **kwargs): if response.status_code != 200: return diff --git a/tests/test_DataSources.py b/tests/test_DataSources.py index e07e190..cfc0f56 100644 --- a/tests/test_DataSources.py +++ b/tests/test_DataSources.py @@ -32,11 +32,11 @@ def test_msn_money(): assert data.last_year_net_income > 0.0 assert data.total_debt >= 0.0 -def test_yahoo(): +def test_future_growth_rate(): test_ticker = 'MSFT' test_name = 'Microsoft Corp' - data = get_yahoo_data(test_ticker) + data = get_growth_rate(test_ticker) assert data.ticker_symbol == test_ticker assert float(data.five_year_growth_rate) > 0.0 @@ -55,16 +55,16 @@ def get_msn_money_data(ticker): return CompanyInfo(**vars(data_fetcher.msn_money)) -def get_yahoo_data(ticker): +def get_growth_rate(ticker): data_fetcher = DataFetcher() data_fetcher.ticker_symbol = ticker # Make all network request asynchronously to build their portion of # the json results. - data_fetcher.fetch_yahoo_finance_analysis() + data_fetcher.fetch_growth_rate() # Wait for each RPC result before proceeding. for rpc in data_fetcher.rpcs: rpc.result() - return data_fetcher.yahoo_finance_analysis + return data_fetcher.future_growth_rate diff --git a/tests/test_api.py b/tests/test_api.py index ca53b9e..4c26d9f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -19,6 +19,9 @@ def test_get_data(): with app.test_client() as test_client: test_client = app.test_client() res = test_client.get('/api/ticker/nvda') - data = res.text - assert json.loads(data)['debt_payoff_time'] == 0 assert res.status_code == 200 + + data = json.loads(res.text) + assert data['debt_payoff_time'] == 0 + assert data['sticker_price'] > 0.0 + assert data['payback_time'] > 1