Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,17 +1,16 @@
|
|
1 |
-
import pandas as pd
|
2 |
-
import numpy as np
|
3 |
import gradio as gr
|
4 |
-
import
|
5 |
import requests
|
6 |
-
import os
|
7 |
-
from transformers import pipeline
|
8 |
import datetime
|
9 |
import tempfile
|
|
|
|
|
10 |
|
11 |
-
# Initialize
|
12 |
summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
|
|
|
13 |
|
14 |
-
#
|
15 |
POLYGON_API_KEY = os.getenv("POLYGON_API_KEY")
|
16 |
|
17 |
# Sector Averages
|
@@ -22,67 +21,60 @@ sector_averages = {
|
|
22 |
"Energy": {"P/E Ratio": 12, "P/S Ratio": 1.2, "P/B Ratio": 1.3},
|
23 |
}
|
24 |
|
25 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
|
|
27 |
def safe_request(url):
|
28 |
try:
|
29 |
response = requests.get(url)
|
30 |
response.raise_for_status()
|
31 |
return response
|
32 |
-
except
|
33 |
-
|
34 |
-
except Exception as err:
|
35 |
-
print(f"DEBUG: Other error occurred: {err}")
|
36 |
-
return None
|
37 |
-
|
38 |
|
39 |
def get_company_info(symbol):
|
40 |
-
|
41 |
-
if not api_key:
|
42 |
-
print("DEBUG: API Key is missing!")
|
43 |
-
return None
|
44 |
-
url = f"https://api.polygon.io/v3/reference/tickers/{symbol}?apiKey={api_key}"
|
45 |
response = safe_request(url)
|
46 |
if response:
|
47 |
data = response.json().get('results', {})
|
|
|
|
|
|
|
|
|
48 |
return {
|
49 |
'Name': data.get('name', 'N/A'),
|
50 |
'Industry': data.get('sic_description', 'N/A'),
|
51 |
-
'Sector':
|
52 |
'Market Cap': data.get('market_cap', 0),
|
53 |
'Total Revenue': data.get('total_employees', 0) * 100000
|
54 |
}
|
55 |
return None
|
56 |
|
57 |
-
|
58 |
def get_current_price(symbol):
|
59 |
-
|
60 |
-
url = f"https://api.polygon.io/v2/aggs/ticker/{symbol}/prev?adjusted=true&apiKey={api_key}"
|
61 |
response = safe_request(url)
|
62 |
if response:
|
63 |
-
|
64 |
-
return float(data['c'])
|
65 |
return None
|
66 |
|
67 |
-
|
68 |
def get_dividends(symbol):
|
69 |
-
|
70 |
-
url = f"https://api.polygon.io/v3/reference/dividends?ticker={symbol}&apiKey={api_key}"
|
71 |
response = safe_request(url)
|
72 |
if response:
|
73 |
-
|
74 |
-
|
75 |
-
'Dividend Amount': data.get('cash_amount', 0),
|
76 |
-
'Ex-Dividend Date': data.get('ex_dividend_date', 'N/A')
|
77 |
-
}
|
78 |
-
return {'Dividend Amount': 0, 'Ex-Dividend Date': 'N/A'}
|
79 |
-
|
80 |
|
81 |
def get_historical_prices(symbol):
|
82 |
-
api_key = os.getenv("POLYGON_API_KEY")
|
83 |
end = datetime.date.today()
|
84 |
start = end - datetime.timedelta(days=365)
|
85 |
-
url = f"https://api.polygon.io/v2/aggs/ticker/{symbol}/range/1/day/{start}/{end}?adjusted=true&sort=asc&apiKey={
|
86 |
response = safe_request(url)
|
87 |
if response:
|
88 |
results = response.json()['results']
|
@@ -91,58 +83,18 @@ def get_historical_prices(symbol):
|
|
91 |
return dates, prices
|
92 |
return [], []
|
93 |
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
recommendation = "Sell"
|
101 |
-
|
102 |
-
report = (
|
103 |
-
f"Company Overview:\n"
|
104 |
-
f"Name: {info['Name']}\n"
|
105 |
-
f"Industry: {info['Industry']}\n"
|
106 |
-
f"Sector: {info['Sector']}\n"
|
107 |
-
f"Market Cap: ${info['Market Cap']:,.2f}\n\n"
|
108 |
-
f"Financial Metrics:\n"
|
109 |
-
f"P/E Ratio: {ratios['P/E Ratio']:.2f}\n"
|
110 |
-
f"P/S Ratio: {ratios['P/S Ratio']:.2f}\n"
|
111 |
-
f"P/B Ratio: {ratios['P/B Ratio']:.2f}\n"
|
112 |
-
f"PEG Ratio: {ratios['PEG Ratio']:.2f}\n"
|
113 |
-
f"Dividend Yield: {ratios['Dividend Yield (%)']:.2f}%\n\n"
|
114 |
-
f"Recommended Investment Action: {recommendation}.\n\n"
|
115 |
-
f"Please provide a detailed financial analysis based on the information above."
|
116 |
-
)
|
117 |
-
summary = summarizer(report, max_length=250, min_length=100, do_sample=False)[0]['summary_text']
|
118 |
-
return summary
|
119 |
-
|
120 |
-
|
121 |
-
def answer_investing_question(question):
|
122 |
-
prompt = (
|
123 |
-
f"Someone asked: '{question}'. "
|
124 |
-
f"Please answer clearly, simply, and in a conversational tone without restating the question. "
|
125 |
-
f"Keep the answer beginner-friendly and encouraging."
|
126 |
-
)
|
127 |
-
response = summarizer(prompt, max_length=200, min_length=60, do_sample=False)[0]['summary_text']
|
128 |
-
return response
|
129 |
-
|
130 |
-
|
131 |
-
# (Rest of the app continues with stock_research, download_report, and Gradio UI, including improved Valuation Ratios with sector ideal comparison and polished UI.)
|
132 |
-
|
133 |
-
|
134 |
-
def calculate_ratios(market_cap, total_revenue, price, dividend_amount, assumed_eps=5.0, growth_rate=0.1, book_value=500000000):
|
135 |
-
pe_ratio = price / assumed_eps if assumed_eps else 0
|
136 |
-
ps_ratio = market_cap / total_revenue if total_revenue else 0
|
137 |
-
pb_ratio = market_cap / book_value if book_value else 0
|
138 |
-
peg_ratio = pe_ratio / (growth_rate * 100) if growth_rate else 0
|
139 |
-
dividend_yield = (dividend_amount / price) * 100 if price else 0
|
140 |
return {
|
141 |
-
'P/E Ratio':
|
142 |
-
'P/S Ratio':
|
143 |
-
'P/B Ratio':
|
144 |
-
'PEG Ratio':
|
145 |
-
'Dividend Yield
|
146 |
}
|
147 |
|
148 |
def compare_to_sector(sector, ratios):
|
@@ -158,7 +110,6 @@ def compare_to_sector(sector, ratios):
|
|
158 |
"Sector Average": [],
|
159 |
"Difference": []
|
160 |
}
|
161 |
-
|
162 |
for key in averages:
|
163 |
stock_value = ratios.get(key, 0)
|
164 |
sector_value = averages.get(key, 0)
|
@@ -167,59 +118,65 @@ def compare_to_sector(sector, ratios):
|
|
167 |
data["Stock Value"].append(round(stock_value, 2))
|
168 |
data["Sector Average"].append(round(sector_value, 2))
|
169 |
data["Difference"].append(round(diff, 2))
|
170 |
-
|
171 |
return pd.DataFrame(data)
|
172 |
|
173 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
info = get_company_info(symbol)
|
175 |
price = get_current_price(symbol)
|
176 |
dividends = get_dividends(symbol)
|
177 |
dates, prices = get_historical_prices(symbol)
|
178 |
|
179 |
if not info or not price:
|
180 |
-
return "⚠️ Error
|
181 |
|
182 |
-
ratios = calculate_ratios(info['Market Cap'], info['Total Revenue'], price, dividends
|
183 |
summary = generate_summary(info, ratios)
|
184 |
-
|
185 |
-
# Apply fallback for sector
|
186 |
-
sector = info.get('Sector', 'Technology')
|
187 |
-
sector_comp = compare_to_sector(sector, ratios)
|
188 |
|
189 |
fig, ax = plt.subplots()
|
190 |
-
ax.plot(dates, prices
|
191 |
-
ax.set_title(f"{symbol} Historical Price (
|
192 |
ax.set_xlabel("Date")
|
193 |
ax.set_ylabel("Price ($)")
|
194 |
-
ax.legend()
|
195 |
ax.grid(True)
|
196 |
|
197 |
-
info_table = pd.DataFrame(
|
198 |
-
|
199 |
-
"Value": [f"${v:,.0f}" if isinstance(v, (int, float)) and abs(v) > 1000 else v for v in info.values()]
|
200 |
-
})
|
201 |
-
ratios_table = pd.DataFrame({
|
202 |
-
"Ratio": list(ratios.keys()),
|
203 |
-
"Value": [f"{v:.3f}" if isinstance(v, float) else v for v in ratios.values()]
|
204 |
-
})
|
205 |
|
206 |
return summary, info_table, ratios_table, sector_comp, fig
|
207 |
|
208 |
-
def download_report(info_table, ratios_table, sector_comp, summary):
|
209 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode='w') as f:
|
210 |
-
info_table.to_csv(f, index=False)
|
211 |
-
f.write("\n")
|
212 |
-
ratios_table.to_csv(f, index=False)
|
213 |
-
f.write("\n")
|
214 |
-
sector_comp.to_csv(f, index=False)
|
215 |
-
f.write("\nSummary\n")
|
216 |
-
f.write(summary)
|
217 |
-
file_path = f.name
|
218 |
-
return file_path
|
219 |
-
|
220 |
# --- Gradio UI ---
|
221 |
-
|
222 |
-
with gr.Blocks() as iface:
|
223 |
with gr.Row():
|
224 |
symbol = gr.Textbox(label="Stock Symbol (e.g., AAPL)")
|
225 |
eps = gr.Number(label="Assumed EPS", value=5.0)
|
@@ -238,8 +195,8 @@ with gr.Blocks() as iface:
|
|
238 |
with gr.Tab("Historical Price Chart"):
|
239 |
output_chart = gr.Plot()
|
240 |
with gr.Tab("Ask About Investing"):
|
241 |
-
user_question = gr.Textbox(label="Ask
|
242 |
-
answer_box = gr.Textbox(
|
243 |
ask_button = gr.Button("Get Answer")
|
244 |
ask_button.click(fn=answer_investing_question, inputs=[user_question], outputs=[answer_box])
|
245 |
|
@@ -250,8 +207,17 @@ with gr.Blocks() as iface:
|
|
250 |
submit_btn.click(fn=stock_research, inputs=[symbol, eps, growth, book],
|
251 |
outputs=[output_summary, output_info, output_ratios, output_sector, output_chart])
|
252 |
|
253 |
-
|
254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
|
256 |
if __name__ == "__main__":
|
257 |
iface.launch()
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
import requests
|
|
|
|
|
4 |
import datetime
|
5 |
import tempfile
|
6 |
+
import os
|
7 |
+
from transformers import pipeline
|
8 |
|
9 |
+
# Initialize Models
|
10 |
summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
|
11 |
+
chat_model = pipeline("text-generation", model="google/flan-t5-large", max_length=256)
|
12 |
|
13 |
+
# API Key
|
14 |
POLYGON_API_KEY = os.getenv("POLYGON_API_KEY")
|
15 |
|
16 |
# Sector Averages
|
|
|
21 |
"Energy": {"P/E Ratio": 12, "P/S Ratio": 1.2, "P/B Ratio": 1.3},
|
22 |
}
|
23 |
|
24 |
+
# Tooltip dictionary
|
25 |
+
tooltips = {
|
26 |
+
"P/E Ratio": "Price/Earnings: Lower can indicate better value.",
|
27 |
+
"P/S Ratio": "Price/Sales: Lower can indicate better value relative to sales.",
|
28 |
+
"P/B Ratio": "Price/Book: Lower can indicate undervaluation.",
|
29 |
+
"PEG Ratio": "Price/Earnings to Growth: Closer to 1 is ideal.",
|
30 |
+
"Dividend Yield": "Annual dividend income relative to price."
|
31 |
+
}
|
32 |
|
33 |
+
# Helper Functions
|
34 |
def safe_request(url):
|
35 |
try:
|
36 |
response = requests.get(url)
|
37 |
response.raise_for_status()
|
38 |
return response
|
39 |
+
except:
|
40 |
+
return None
|
|
|
|
|
|
|
|
|
41 |
|
42 |
def get_company_info(symbol):
|
43 |
+
url = f"https://api.polygon.io/v3/reference/tickers/{symbol}?apiKey={POLYGON_API_KEY}"
|
|
|
|
|
|
|
|
|
44 |
response = safe_request(url)
|
45 |
if response:
|
46 |
data = response.json().get('results', {})
|
47 |
+
sector = data.get('market', 'Technology')
|
48 |
+
# Dynamic Guess
|
49 |
+
if sector.lower() == 'stocks':
|
50 |
+
sector = "Technology"
|
51 |
return {
|
52 |
'Name': data.get('name', 'N/A'),
|
53 |
'Industry': data.get('sic_description', 'N/A'),
|
54 |
+
'Sector': sector,
|
55 |
'Market Cap': data.get('market_cap', 0),
|
56 |
'Total Revenue': data.get('total_employees', 0) * 100000
|
57 |
}
|
58 |
return None
|
59 |
|
|
|
60 |
def get_current_price(symbol):
|
61 |
+
url = f"https://api.polygon.io/v2/aggs/ticker/{symbol}/prev?adjusted=true&apiKey={POLYGON_API_KEY}"
|
|
|
62 |
response = safe_request(url)
|
63 |
if response:
|
64 |
+
return response.json()['results'][0]['c']
|
|
|
65 |
return None
|
66 |
|
|
|
67 |
def get_dividends(symbol):
|
68 |
+
url = f"https://api.polygon.io/v3/reference/dividends?ticker={symbol}&apiKey={POLYGON_API_KEY}"
|
|
|
69 |
response = safe_request(url)
|
70 |
if response:
|
71 |
+
return response.json()['results'][0].get('cash_amount', 0)
|
72 |
+
return 0
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
def get_historical_prices(symbol):
|
|
|
75 |
end = datetime.date.today()
|
76 |
start = end - datetime.timedelta(days=365)
|
77 |
+
url = f"https://api.polygon.io/v2/aggs/ticker/{symbol}/range/1/day/{start}/{end}?adjusted=true&sort=asc&apiKey={POLYGON_API_KEY}"
|
78 |
response = safe_request(url)
|
79 |
if response:
|
80 |
results = response.json()['results']
|
|
|
83 |
return dates, prices
|
84 |
return [], []
|
85 |
|
86 |
+
def calculate_ratios(market_cap, total_revenue, price, dividend_amount, eps=5.0, growth=0.1, book_value=500000000):
|
87 |
+
pe = price / eps if eps else 0
|
88 |
+
ps = market_cap / total_revenue if total_revenue else 0
|
89 |
+
pb = market_cap / book_value if book_value else 0
|
90 |
+
peg = pe / (growth * 100) if growth else 0
|
91 |
+
div_yield = (dividend_amount / price) * 100 if price else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
return {
|
93 |
+
'P/E Ratio': pe,
|
94 |
+
'P/S Ratio': ps,
|
95 |
+
'P/B Ratio': pb,
|
96 |
+
'PEG Ratio': peg,
|
97 |
+
'Dividend Yield': div_yield
|
98 |
}
|
99 |
|
100 |
def compare_to_sector(sector, ratios):
|
|
|
110 |
"Sector Average": [],
|
111 |
"Difference": []
|
112 |
}
|
|
|
113 |
for key in averages:
|
114 |
stock_value = ratios.get(key, 0)
|
115 |
sector_value = averages.get(key, 0)
|
|
|
118 |
data["Stock Value"].append(round(stock_value, 2))
|
119 |
data["Sector Average"].append(round(sector_value, 2))
|
120 |
data["Difference"].append(round(diff, 2))
|
|
|
121 |
return pd.DataFrame(data)
|
122 |
|
123 |
+
def generate_summary(info, ratios):
|
124 |
+
recommendation = "Hold"
|
125 |
+
if ratios['P/E Ratio'] < 15 and ratios['P/B Ratio'] < 2 and ratios['PEG Ratio'] < 1.0 and ratios['Dividend Yield'] > 2:
|
126 |
+
recommendation = "Buy"
|
127 |
+
elif ratios['P/E Ratio'] > 30 and ratios['P/B Ratio'] > 5 and ratios['PEG Ratio'] > 2.0:
|
128 |
+
recommendation = "Sell"
|
129 |
+
report = (
|
130 |
+
f"Company Overview:\n"
|
131 |
+
f"Name: {info['Name']}\n"
|
132 |
+
f"Industry: {info['Industry']}\n"
|
133 |
+
f"Sector: {info['Sector']}\n"
|
134 |
+
f"Market Cap: ${info['Market Cap']:,.2f}\n\n"
|
135 |
+
f"Financial Metrics:\n"
|
136 |
+
f"P/E Ratio: {ratios['P/E Ratio']:.2f}\n"
|
137 |
+
f"P/S Ratio: {ratios['P/S Ratio']:.2f}\n"
|
138 |
+
f"P/B Ratio: {ratios['P/B Ratio']:.2f}\n"
|
139 |
+
f"PEG Ratio: {ratios['PEG Ratio']:.2f}\n"
|
140 |
+
f"Dividend Yield: {ratios['Dividend Yield']:.2f}%\n\n"
|
141 |
+
f"Recommended Investment Action: {recommendation}.\n"
|
142 |
+
)
|
143 |
+
return summarizer(report, max_length=250, min_length=100, do_sample=False)[0]['summary_text']
|
144 |
+
|
145 |
+
def answer_investing_question(question):
|
146 |
+
prompt = (
|
147 |
+
f"Explain the following investing question clearly and simply for a beginner:\n\n"
|
148 |
+
f"{question}\n\n"
|
149 |
+
f"Be conversational and encouraging."
|
150 |
+
)
|
151 |
+
return chat_model(prompt)[0]['generated_text']
|
152 |
+
|
153 |
+
def stock_research(symbol, eps=5.0, growth=0.1, book=500000000):
|
154 |
info = get_company_info(symbol)
|
155 |
price = get_current_price(symbol)
|
156 |
dividends = get_dividends(symbol)
|
157 |
dates, prices = get_historical_prices(symbol)
|
158 |
|
159 |
if not info or not price:
|
160 |
+
return "⚠️ Error fetching stock info", None, None, None, None
|
161 |
|
162 |
+
ratios = calculate_ratios(info['Market Cap'], info['Total Revenue'], price, dividends, eps, growth, book)
|
163 |
summary = generate_summary(info, ratios)
|
164 |
+
sector_comp = compare_to_sector(info['Sector'], ratios)
|
|
|
|
|
|
|
165 |
|
166 |
fig, ax = plt.subplots()
|
167 |
+
ax.plot(dates, prices)
|
168 |
+
ax.set_title(f"{symbol} Historical Price (1Y)")
|
169 |
ax.set_xlabel("Date")
|
170 |
ax.set_ylabel("Price ($)")
|
|
|
171 |
ax.grid(True)
|
172 |
|
173 |
+
info_table = pd.DataFrame(info.items(), columns=["Metric", "Value"])
|
174 |
+
ratios_table = pd.DataFrame(ratios.items(), columns=["Ratio", "Value"])
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
|
176 |
return summary, info_table, ratios_table, sector_comp, fig
|
177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
# --- Gradio UI ---
|
179 |
+
with gr.Blocks(theme="soft") as iface:
|
|
|
180 |
with gr.Row():
|
181 |
symbol = gr.Textbox(label="Stock Symbol (e.g., AAPL)")
|
182 |
eps = gr.Number(label="Assumed EPS", value=5.0)
|
|
|
195 |
with gr.Tab("Historical Price Chart"):
|
196 |
output_chart = gr.Plot()
|
197 |
with gr.Tab("Ask About Investing"):
|
198 |
+
user_question = gr.Textbox(label="Ask about investing...")
|
199 |
+
answer_box = gr.Textbox()
|
200 |
ask_button = gr.Button("Get Answer")
|
201 |
ask_button.click(fn=answer_investing_question, inputs=[user_question], outputs=[answer_box])
|
202 |
|
|
|
207 |
submit_btn.click(fn=stock_research, inputs=[symbol, eps, growth, book],
|
208 |
outputs=[output_summary, output_info, output_ratios, output_sector, output_chart])
|
209 |
|
210 |
+
# Sector Comparison Color Highlight
|
211 |
+
def style_sector(df):
|
212 |
+
def highlight(val):
|
213 |
+
if isinstance(val, (int, float)):
|
214 |
+
if val < 0:
|
215 |
+
return 'color: green'
|
216 |
+
elif val > 0:
|
217 |
+
return 'color: red'
|
218 |
+
return ''
|
219 |
+
return df.style.applymap(highlight, subset=['Difference'])
|
220 |
+
output_sector.style_fn = style_sector
|
221 |
|
222 |
if __name__ == "__main__":
|
223 |
iface.launch()
|