Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions isthisstockgood/Active/Zacks.py
Original file line number Diff line number Diff line change
@@ -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)
32 changes: 31 additions & 1 deletion isthisstockgood/DataFetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
15 changes: 12 additions & 3 deletions tests/test_DataSources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
5 changes: 4 additions & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line and the next one verify that each valuation method still works.

assert data['payback_time'] > 1

def test_get_ten_cap_price():
app = create_app(fetchDataForTickerSymbol)
Expand Down
Loading