Skip to content
Open
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
30 changes: 24 additions & 6 deletions isthisstockgood/DataFetcher.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import random
import logging
import isthisstockgood.RuleOneInvestingCalculations as RuleOne
from requests.adapters import HTTPAdapter, Retry
from requests.exceptions import RetryError
from requests_futures.sessions import FuturesSession
from isthisstockgood.Active.MSNMoney import MSNMoney
from isthisstockgood.Active.YahooFinance import YahooFinanceAnalysis
Expand All @@ -10,7 +12,7 @@
logger = logging.getLogger("IsThisStockGood")


def fetchDataForTickerSymbol(ticker):
def fetchDataForTickerSymbol(ticker, growth_estimate=None):
"""Fetches and parses all of the financial data for the `ticker`.

Args:
Expand Down Expand Up @@ -48,16 +50,22 @@ def fetchDataForTickerSymbol(ticker):

# Wait for each RPC result before proceeding.
for rpc in data_fetcher.rpcs:
rpc.result()
try:
rpc.result()
except RetryError as e:
continue

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 zacks_analysis.five_year_growth_rate if zacks_analysis \
else 0
if (yahoo_finance_analysis
and yahoo_finance_analysis.five_year_growth_rate is not None):
five_year_growth_rate = yahoo_finance_analysis.five_year_growth_rate
elif (zacks_analysis and zacks_analysis.five_year_growth_rate is not None):
five_year_growth_rate = zacks_analysis.five_year_growth_rate
else:
five_year_growth_rate = growth_estimate
margin_of_safety_price, sticker_price = _calculateMarginOfSafetyPrice(
msn_money.equity_growth_rates[-1],
msn_money.pe_low,
Expand Down Expand Up @@ -143,6 +151,16 @@ def _create_session(self):
session.headers.update({
'User-Agent' : random.choice(DataFetcher.USER_AGENT_LIST)
})

retries = Retry(
total=3,
backoff_factor=0.1,
status_forcelist=[301]
)
adapter = HTTPAdapter(max_retries=retries)
session.mount('http://', adapter)
session.mount('https://', adapter)

return session

def fetch_msn_money_data(self):
Expand Down
18 changes: 14 additions & 4 deletions isthisstockgood/server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import re
from datetime import date
from flask import Flask, request, render_template, json

Expand All @@ -19,9 +20,17 @@ def get_logger():
def create_app(fetchDataForTickerSymbol):
app = Flask(__name__)

@app.route('/api/ticker/nvda')
def api_ticker():
template_values = fetchDataForTickerSymbol("NVDA")
@app.route(
'/api/ticker/<unsanitized_ticker>/<unsanitized_growth_estimate>'
)
def api_ticker(
unsanitized_ticker,
unsanitized_growth_estimate
):
ticker = re.sub(r'\W+', '', unsanitized_ticker)
growth_estimate = int(re.sub(r'\W+', '', unsanitized_growth_estimate))

template_values = fetchDataForTickerSymbol(ticker, growth_estimate=growth_estimate)

if not template_values:
data = render_template('json/error.json', **{'error' : 'Invalid ticker symbol'})
Expand Down Expand Up @@ -60,7 +69,8 @@ def search():
return '<meta http-equiv="refresh" content="0; url=http://isthisstockgood.com" />'

ticker = request.values.get('ticker')
template_values = fetchDataForTickerSymbol(ticker)
custom_growth_rate = request.values.get('custom_growth_rate')
template_values = fetchDataForTickerSymbol(ticker, growth_estimate=int(custom_growth_rate))
if not template_values:
return render_template('json/error.json', **{'error' : 'Invalid ticker symbol'})
return render_template('json/stock_data.json', **template_values)
Expand Down
7 changes: 6 additions & 1 deletion isthisstockgood/templates/js/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,16 @@ $(document).ready(function() {
return;
}

let $custom_growth_rate = $('#custom-growth-rate').val();
if ($custom_growth_rate.length == 0) {
$custom_growth_rate = 0;
}

// Start loading
loader.show();

// Post the data to the path.
let posting = $.post(path, { ticker: $ticker } );
let posting = $.post(path, { ticker: $ticker, custom_growth_rate: $custom_growth_rate } );

posting.fail(function(response) {
$.snackbar({
Expand Down
17 changes: 17 additions & 0 deletions isthisstockgood/templates/searchbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
placeholder="Ticker Symbol"
maxlength=8/>
</div>
<div class="col-md-2 nopadding">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found I couldn't submit the form while focus was on this field. It worked all right while focus was on the Ticker field, and on the "Analyze" button. @mrhappyasthma, any tips on how to fix that? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried including \n in the RegEx in the script below. Didn't help.

<input
type="text"
class="form-control"
id="custom-growth-rate"
placeholder="Custom Growth Rate (percent)"
maxlength=8/>
</div>
<div class="col-md-1 nopadding">
<input
type="submit"
Expand All @@ -30,6 +38,15 @@
});
</script>

<script>
document.querySelector("#custom-growth-rate").addEventListener("keypress", (event) => {
let key = event.key;
if (!key.match(/[0-9,.\-]/)) {
event.preventDefault();
}
});
</script>

<script>
{% include "js/search.js" %}
</script>
30 changes: 16 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
[tool.poetry]
[project]
name = "IsThisStockGood"
version = "0.1.0"
description = ""
authors = ["Mark Klara"]
authors = [{ name = "Mark Klara" }]
requires-python = "~=3.9"
readme = "README.md"
dependencies = [
"requests-futures>=1.0.1,<2",
"wheel>=0.43.0,<0.44",
"lxml>=5.2.2,<6",
"flask==3.0.3",
"virtualenv>=20.26.6,<21",
]

[tool.poetry.dependencies]
python = "^3.8"
requests-futures = "^1.0.1"
wheel = "^0.43.0"
lxml = "^5.2.2"
flask = "3.0.3"
virtualenv = "^20.26.6"

[tool.poetry.group.dev.dependencies]
pytest = "^8.2.1"
[dependency-groups]
dev = [
"pytest>=8.4.2,<9",
]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.pylint]
indent-string=" "
Expand Down
6 changes: 3 additions & 3 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_get_data():
with app.test_client() as test_client:
test_client = app.test_client()

res = test_client.get('/api/ticker/nvda')
res = test_client.get('/api/ticker/lulu/1')
assert res.status_code == 200

data = res.json
Expand All @@ -32,15 +32,15 @@ def test_get_ten_cap_price():

with app.test_client() as test_client:
test_client = app.test_client()
res = test_client.get('/api/ticker/nvda')
res = test_client.get('/api/ticker/nvda/1')
assert res.json['ten_cap_price'] > 0

def test_ten_cap_price_has_two_places_precision():
app = create_app(fetchDataForTickerSymbol)

with app.test_client() as test_client:
test_client = app.test_client()
res = test_client.get('/api/ticker/nvda')
res = test_client.get('/api/ticker/nvda/1')

price = res.json['ten_cap_price']

Expand Down
Loading