Skip to content

Commit

Permalink
\#91: Fix growth estimate (use Zacks).
Browse files Browse the repository at this point in the history
  • Loading branch information
kocielnik committed Nov 23, 2024
1 parent 090c982 commit 8ae4c82
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 21 deletions.
42 changes: 42 additions & 0 deletions isthisstockgood/Active/Zacks.py
Original file line number Diff line number Diff line change
@@ -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)
42 changes: 28 additions & 14 deletions 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 @@ -41,17 +42,17 @@ 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.
for rpc in data_fetcher.rpcs:
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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions tests/test_DataSources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
7 changes: 5 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 8ae4c82

Please sign in to comment.