diff --git a/isthisstockgood/Active/Zacks.py b/isthisstockgood/Active/Zacks.py new file mode 100644 index 0000000..826aa92 --- /dev/null +++ b/isthisstockgood/Active/Zacks.py @@ -0,0 +1,32 @@ +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: + return + + if not response.text: + 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) + return float(estimate) diff --git a/isthisstockgood/DataFetcher.py b/isthisstockgood/DataFetcher.py index e7caa7f..3e74db0 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") @@ -43,6 +44,7 @@ def fetchDataForTickerSymbol(ticker): # the json results. data_fetcher.fetch_msn_money_data() data_fetcher.fetch_yahoo_finance_analysis() + data_fetcher.fetch_zacks_analysis() # Wait for each RPC result before proceeding. @@ -51,8 +53,12 @@ def fetchDataForTickerSymbol(ticker): msn_money = data_fetcher.msn_money yahoo_finance_analysis = data_fetcher.yahoo_finance_analysis + zacks_analysis = data_fetcher.zacks_analysis # 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 = \ + 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.eps[-1], five_year_growth_rate) @@ -126,6 +132,7 @@ def __init__(self,): self.ticker_symbol = '' self.msn_money = None self.yahoo_finance_analysis = None + self.zacks_analysis = None self.yahoo_finance_chart = None self.error = False @@ -220,6 +227,29 @@ def parse_yahoo_finance_analysis(self, response, *args, **kwargs): if not success: self.yahoo_finance_analysis = None + def fetch_zacks_analysis(self): + session = self._create_session() + self.zacks_analysis = Zacks(self.ticker_symbol) + + rpc = session.get( + self.zacks_analysis.url, + allow_redirects=True, + hooks={ + 'response': self.zacks_analysis.parse, + } + ) + self.rpcs.append(rpc) + + def parse_growth_rate_estimate(self, response, *args, **kwargs): + if response.status_code != 200: + return + if not self.zacks_analysis: + return + result = response.text + success = self.zacks_analysis.parse_analyst_five_year_growth_rate(result) + if not success: + self.zacks_analysis = None + def fetch_yahoo_finance_chart(self): self.yahoo_finance_chart = YahooFinanceChart(self.ticker_symbol) session = self._create_session() diff --git a/tests/test_DataSources.py b/tests/test_DataSources.py index 1677d0a..78f0e13 100644 --- a/tests/test_DataSources.py +++ b/tests/test_DataSources.py @@ -32,6 +32,15 @@ def test_msn_money(): assert data.last_year_net_income > 0.0 assert data.total_debt >= 0.0 +def test_future_growth_rate(): + test_ticker = 'MSFT' + test_name = 'Microsoft Corp' + + data = get_growth_rate(test_ticker) + + assert data.ticker_symbol == test_ticker + assert float(data.five_year_growth_rate) > 0.0 + def get_msn_money_data(ticker): data_fetcher = DataFetcher() data_fetcher.ticker_symbol = ticker @@ -46,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_zacks_analysis() # Wait for each RPC result before proceeding. for rpc in data_fetcher.rpcs: rpc.result() - return data_fetcher.yahoo_finance_analysis + return data_fetcher.zacks_analysis diff --git a/tests/test_api.py b/tests/test_api.py index c5c6c14..ec31c12 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,7 +22,10 @@ def test_get_data(): res = test_client.get('/api/ticker/nvda') assert res.status_code == 200 - assert res.json['debt_payoff_time'] >= 0 + data = res.json + assert data['debt_payoff_time'] >= 0 + assert data['sticker_price'] > 0.0 + assert data['payback_time'] > 1 def test_get_ten_cap_price(): app = create_app(fetchDataForTickerSymbol)