mr601s commited on
Commit
657d8f8
Β·
verified Β·
1 Parent(s): 10976c8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +368 -18
app.py CHANGED
@@ -1,8 +1,332 @@
1
  import gradio as gr
2
- from utils.sec_utils import SECUtils
3
- from utils.news_utils import NewsUtils
4
- from utils.finance_utils import FinanceUtils
5
- from utils.chart_utils import ChartUtils
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  # Initialize utility classes
8
  sec_utils = SECUtils()
@@ -12,17 +336,28 @@ chart_utils = ChartUtils()
12
 
13
  def update_stock_info(ticker):
14
  """Update all sections when ticker changes"""
15
- if not ticker:
16
  empty_msg = "Enter a ticker symbol to see data"
17
  return empty_msg, empty_msg, empty_msg, empty_msg, None
18
 
19
  ticker = ticker.upper().strip()
20
 
21
- # Get data for each section
 
 
 
22
  quote_data = finance_utils.get_stock_quote(ticker)
 
 
23
  news_data = news_utils.get_yahoo_news(ticker)
 
 
24
  filings_data = sec_utils.get_recent_filings(ticker)
 
 
25
  financial_data = finance_utils.get_financial_summary(ticker)
 
 
26
  chart = chart_utils.create_price_chart(ticker)
27
 
28
  return quote_data, news_data, filings_data, financial_data, chart
@@ -30,28 +365,36 @@ def update_stock_info(ticker):
30
  # Custom CSS
31
  css = """
32
  .gradio-container {
33
- font-family: 'Arial', sans-serif;
 
 
34
  }
35
  .tab-nav button {
36
  font-size: 16px;
37
- font-weight: bold;
38
  }
39
  """
40
 
41
  # Create Gradio interface
42
- with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
43
  gr.Markdown("""
44
  # πŸ“ˆ Stock Research Platform MVP
45
- Enter a stock ticker symbol to explore comprehensive information, news, SEC filings, and financial data.
 
 
 
 
46
  """)
47
 
48
  with gr.Row():
49
- ticker_input = gr.Textbox(
50
- label="Stock Ticker",
51
- placeholder="Enter ticker (e.g., AAPL, TSLA, MSFT)",
52
- value="AAPL"
53
- )
54
- refresh_btn = gr.Button("πŸ”„ Refresh Data", variant="primary")
 
 
55
 
56
  with gr.Tabs():
57
  with gr.TabItem("πŸ’° Quote & Overview"):
@@ -69,7 +412,14 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
69
  with gr.TabItem("πŸ“ˆ Price Chart"):
70
  chart_output = gr.Plot(value=None)
71
 
72
- # Event handlers
 
 
 
 
 
 
 
73
  ticker_input.change(
74
  fn=update_stock_info,
75
  inputs=[ticker_input],
@@ -84,4 +434,4 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
84
 
85
  # Launch the app
86
  if __name__ == "__main__":
87
- demo.launch()
 
1
  import gradio as gr
2
+ import requests
3
+ import yfinance as yf
4
+ import plotly.graph_objects as go
5
+ import feedparser
6
+ import json
7
+ import time
8
+ import random
9
+ from datetime import datetime, timedelta
10
+ import pandas as pd
11
+
12
+ # SEC Utilities
13
+ class SECUtils:
14
+ def __init__(self):
15
+ self.cik_lookup_url = "https://www.sec.gov/files/company_tickers.json"
16
+ self.edgar_search_url = "https://data.sec.gov/submissions/CIK{cik}.json"
17
+ self.headers = {
18
+ "User-Agent": "StockResearchMVP/1.0 (educational@example.com)"
19
+ }
20
+
21
+ def get_cik(self, ticker):
22
+ try:
23
+ # Add small delay to avoid rate limiting
24
+ time.sleep(0.5)
25
+ response = requests.get(self.cik_lookup_url, headers=self.headers, timeout=20)
26
+ if response.status_code != 200:
27
+ return None
28
+ data = response.json()
29
+
30
+ for k, v in data.items():
31
+ if isinstance(v, dict) and v.get('ticker', '').upper() == ticker.upper():
32
+ return str(v['cik_str']).zfill(10)
33
+ return None
34
+ except Exception as e:
35
+ print(f"CIK lookup error: {e}")
36
+ return None
37
+
38
+ def get_recent_filings(self, ticker):
39
+ try:
40
+ cik = self.get_cik(ticker)
41
+ if not cik:
42
+ return f"πŸ“„ **SEC Filings for {ticker}**\n\n❌ CIK not found. This may be a newer company or ticker not in SEC database.\n\nπŸ’‘ Try checking [SEC EDGAR directly](https://www.sec.gov/edgar/search/) for this company."
43
+
44
+ # Add delay for rate limiting
45
+ time.sleep(0.5)
46
+ url = self.edgar_search_url.format(cik=cik)
47
+ response = requests.get(url, headers=self.headers, timeout=20)
48
+
49
+ if response.status_code != 200:
50
+ return f"πŸ“„ **SEC Filings for {ticker}**\n\n❌ Unable to fetch SEC data (Status: {response.status_code}).\n\nπŸ’‘ Try [SEC EDGAR search](https://www.sec.gov/edgar/search/) directly."
51
+
52
+ data = response.json()
53
+ filings = data.get('filings', {}).get('recent', {})
54
+
55
+ if not filings or not filings.get('form'):
56
+ return f"πŸ“„ **SEC Filings for {ticker}**\n\n❌ No recent filings found for CIK: {cik}."
57
+
58
+ result = f"πŸ“„ **SEC Filings for {ticker}** (CIK: {cik})\n\n"
59
+
60
+ # Get last 5 filings
61
+ forms = filings.get('form', [])
62
+ dates = filings.get('filingDate', [])
63
+ accessions = filings.get('accessionNumber', [])
64
+
65
+ for i in range(min(5, len(forms))):
66
+ if i < len(dates) and i < len(accessions):
67
+ form = forms[i]
68
+ filing_date = dates[i]
69
+ accession_num = accessions[i].replace('-', '')
70
+
71
+ filing_url = f"https://www.sec.gov/Archives/edgar/data/{int(cik)}/{accession_num}/"
72
+ result += f"β€’ **{form}** - {filing_date}\n"
73
+ result += f" πŸ“Ž [View Filing]({filing_url})\n\n"
74
+
75
+ return result
76
+
77
+ except Exception as e:
78
+ return f"πŸ“„ **SEC Filings for {ticker}**\n\n❌ Error fetching SEC filings: {str(e)}\n\nπŸ’‘ Try [SEC EDGAR search](https://www.sec.gov/edgar/search/) directly."
79
+
80
+ # News Utilities with better error handling
81
+ class NewsUtils:
82
+ def __init__(self):
83
+ self.headers = {"User-Agent": "StockResearchMVP/1.0 (educational@example.com)"}
84
+
85
+ def get_yahoo_news(self, ticker):
86
+ try:
87
+ # Add delay to avoid rate limiting
88
+ time.sleep(random.uniform(0.5, 1.0))
89
+ url = f"https://feeds.finance.yahoo.com/rss/2.0/headline?s={ticker}&region=US&lang=en-US"
90
+ feed = feedparser.parse(url)
91
+
92
+ if not feed.entries:
93
+ return f"πŸ“° **Latest News for {ticker}**\n\n❌ No recent news found via RSS feed.\n\nπŸ’‘ Try these alternatives:\nβ€’ [Yahoo Finance News](https://finance.yahoo.com/quote/{ticker}/news)\nβ€’ [Google Finance](https://www.google.com/finance/quote/{ticker}:NASDAQ)\nβ€’ [MarketWatch](https://www.marketwatch.com/investing/stock/{ticker})"
94
+
95
+ result = f"πŸ“° **Latest News for {ticker}**\n\n"
96
+
97
+ for i, entry in enumerate(feed.entries[:5]):
98
+ title = getattr(entry, 'title', 'No title')[:100] + '...' if len(getattr(entry, 'title', '')) > 100 else getattr(entry, 'title', 'No title')
99
+ link = getattr(entry, 'link', '#')
100
+ pub_date = getattr(entry, 'published', 'Unknown date')
101
+
102
+ result += f"{i+1}. **{title}**\n"
103
+ result += f" πŸ“… {pub_date}\n"
104
+ result += f" πŸ”— [Read More]({link})\n\n"
105
+
106
+ return result
107
+
108
+ except Exception as e:
109
+ return f"πŸ“° **Latest News for {ticker}**\n\n❌ Error fetching news: {str(e)}\n\nπŸ’‘ Try these alternatives:\nβ€’ [Yahoo Finance News](https://finance.yahoo.com/quote/{ticker}/news)\nβ€’ [Google Finance](https://www.google.com/finance/quote/{ticker}:NASDAQ)\nβ€’ [MarketWatch](https://www.marketwatch.com/investing/stock/{ticker})"
110
+
111
+ # Finance Utilities with retry logic
112
+ class FinanceUtils:
113
+ def __init__(self):
114
+ self.session = requests.Session()
115
+ self.session.headers.update({'User-Agent': 'StockResearchMVP/1.0'})
116
+
117
+ def get_stock_quote_with_retry(self, ticker, max_retries=2):
118
+ """Get stock quote with retry logic and delays"""
119
+ for attempt in range(max_retries):
120
+ try:
121
+ # Add increasing delays between attempts
122
+ if attempt > 0:
123
+ delay = random.uniform(1, 3) * attempt
124
+ time.sleep(delay)
125
+
126
+ stock = yf.Ticker(ticker)
127
+
128
+ # Try to get basic info first
129
+ info = stock.info
130
+
131
+ if info and len(info) > 5:
132
+ return info
133
+
134
+ except Exception as e:
135
+ if attempt == max_retries - 1: # Last attempt
136
+ print(f"Final attempt failed for {ticker}: {e}")
137
+ return None
138
+ else:
139
+ print(f"Attempt {attempt + 1} failed for {ticker}: {e}")
140
+ continue
141
+
142
+ return None
143
+
144
+ def get_stock_quote(self, ticker):
145
+ try:
146
+ info = self.get_stock_quote_with_retry(ticker)
147
+
148
+ if not info:
149
+ return f"πŸ’° **Stock Quote for {ticker}**\n\n❌ Unable to fetch stock data (likely rate limited).\n\nπŸ’‘ Try these alternatives:\nβ€’ [Yahoo Finance](https://finance.yahoo.com/quote/{ticker})\nβ€’ [Google Finance](https://www.google.com/finance/quote/{ticker}:NASDAQ)\nβ€’ [MarketWatch](https://www.marketwatch.com/investing/stock/{ticker})\nβ€’ Wait a moment and try again"
150
+
151
+ # Get current price with multiple fallbacks
152
+ price = (info.get('currentPrice') or
153
+ info.get('regularMarketPrice') or
154
+ info.get('previousClose') or
155
+ info.get('ask') or
156
+ info.get('bid'))
157
+
158
+ prev_close = info.get('previousClose')
159
+
160
+ result = f"πŸ’° **Stock Quote for {ticker}**\n\n"
161
+
162
+ # Company name
163
+ company_name = info.get('longName', info.get('shortName', ticker))
164
+ if company_name and company_name != ticker:
165
+ result += f"**{company_name}**\n\n"
166
+
167
+ if price is not None and price != 'N/A':
168
+ result += f"β€’ **Current Price**: ${price:.2f}\n"
169
+
170
+ if prev_close is not None and prev_close != 'N/A' and price != prev_close:
171
+ change = price - prev_close
172
+ change_pct = (change / prev_close * 100) if prev_close != 0 else 0
173
+ change_emoji = "πŸ“ˆ" if change >= 0 else "πŸ“‰"
174
+ result += f"β€’ **Previous Close**: ${prev_close:.2f}\n"
175
+ result += f"β€’ **Change**: {change_emoji} ${change:.2f} ({change_pct:+.2f}%)\n"
176
+
177
+ # Additional info with null checks
178
+ volume = info.get('volume') or info.get('regularMarketVolume')
179
+ if volume is not None:
180
+ result += f"β€’ **Volume**: {volume:,}\n"
181
+
182
+ market_cap = info.get('marketCap')
183
+ if market_cap is not None:
184
+ if market_cap >= 1e12:
185
+ result += f"β€’ **Market Cap**: ${market_cap/1e12:.2f}T\n"
186
+ elif market_cap >= 1e9:
187
+ result += f"β€’ **Market Cap**: ${market_cap/1e9:.2f}B\n"
188
+ elif market_cap >= 1e6:
189
+ result += f"β€’ **Market Cap**: ${market_cap/1e6:.2f}M\n"
190
+ else:
191
+ result += f"β€’ **Market Cap**: ${market_cap:,}\n"
192
+
193
+ high_52w = info.get('fiftyTwoWeekHigh')
194
+ low_52w = info.get('fiftyTwoWeekLow')
195
+ if high_52w is not None:
196
+ result += f"β€’ **52W High**: ${high_52w:.2f}\n"
197
+ if low_52w is not None:
198
+ result += f"β€’ **52W Low**: ${low_52w:.2f}\n"
199
+
200
+ # Exchange info
201
+ exchange = info.get('exchange', info.get('market', ''))
202
+ if exchange:
203
+ result += f"β€’ **Exchange**: {exchange}\n"
204
+
205
+ result += f"\nπŸ’‘ [More details on Yahoo Finance](https://finance.yahoo.com/quote/{ticker})"
206
+
207
+ return result
208
+
209
+ except Exception as e:
210
+ return f"πŸ’° **Stock Quote for {ticker}**\n\n❌ Error: {str(e)}\n\nπŸ’‘ This might be due to:\nβ€’ Rate limiting (try again in a moment)\nβ€’ Invalid ticker symbol\nβ€’ Network issues\n\nTry [Yahoo Finance](https://finance.yahoo.com/quote/{ticker}) directly."
211
+
212
+ def get_financial_summary(self, ticker):
213
+ try:
214
+ info = self.get_stock_quote_with_retry(ticker)
215
+
216
+ if not info:
217
+ return f"πŸ“Š **Financial Summary for {ticker}**\n\n❌ Unable to fetch financial data (likely rate limited).\n\nπŸ’‘ Try [Yahoo Finance Financials](https://finance.yahoo.com/quote/{ticker}/financials) directly."
218
+
219
+ result = f"πŸ“Š **Financial Summary for {ticker}**\n\n"
220
+
221
+ # Company basics
222
+ company_name = info.get('longName', info.get('shortName', ''))
223
+ if company_name:
224
+ result += f"**{company_name}**\n\n"
225
+
226
+ sector = info.get('sector')
227
+ industry = info.get('industry')
228
+ if sector:
229
+ result += f"β€’ **Sector**: {sector}\n"
230
+ if industry:
231
+ result += f"β€’ **Industry**: {industry}\n"
232
+
233
+ if sector or industry:
234
+ result += "\n"
235
+
236
+ # Financial metrics
237
+ revenue = info.get('totalRevenue')
238
+ if revenue is not None:
239
+ if revenue >= 1e12:
240
+ result += f"β€’ **Revenue (TTM)**: ${revenue/1e12:.2f}T\n"
241
+ elif revenue >= 1e9:
242
+ result += f"β€’ **Revenue (TTM)**: ${revenue/1e9:.2f}B\n"
243
+ elif revenue >= 1e6:
244
+ result += f"β€’ **Revenue (TTM)**: ${revenue/1e6:.2f}M\n"
245
+ else:
246
+ result += f"β€’ **Revenue (TTM)**: ${revenue:,}\n"
247
+
248
+ # Profitability
249
+ profit_margins = info.get('profitMargins')
250
+ if profit_margins is not None:
251
+ result += f"β€’ **Profit Margin**: {profit_margins:.2%}\n"
252
+
253
+ # Valuation ratios
254
+ pe_ratio = info.get('trailingPE')
255
+ if pe_ratio is not None and pe_ratio > 0:
256
+ result += f"β€’ **P/E Ratio**: {pe_ratio:.2f}\n"
257
+
258
+ pb_ratio = info.get('priceToBook')
259
+ if pb_ratio is not None and pb_ratio > 0:
260
+ result += f"β€’ **P/B Ratio**: {pb_ratio:.2f}\n"
261
+
262
+ # Dividend info
263
+ dividend_yield = info.get('dividendYield')
264
+ if dividend_yield is not None:
265
+ result += f"β€’ **Dividend Yield**: {dividend_yield:.2%}\n"
266
+
267
+ # Beta (risk measure)
268
+ beta = info.get('beta')
269
+ if beta is not None:
270
+ result += f"β€’ **Beta**: {beta:.2f}\n"
271
+
272
+ # If very limited data, show what we can
273
+ if result == f"πŸ“Š **Financial Summary for {ticker}**\n\n":
274
+ result += "Limited financial data available for this ticker.\n"
275
+ if company_name:
276
+ result += f"β€’ **Company**: {company_name}\n"
277
+ if sector:
278
+ result += f"β€’ **Sector**: {sector}\n"
279
+ if industry:
280
+ result += f"β€’ **Industry**: {industry}\n"
281
+
282
+ result += f"\nπŸ’‘ [More financials on Yahoo Finance](https://finance.yahoo.com/quote/{ticker}/financials)"
283
+
284
+ return result
285
+
286
+ except Exception as e:
287
+ return f"πŸ“Š **Financial Summary for {ticker}**\n\n❌ Error: {str(e)}\n\nTry [Yahoo Finance Financials](https://finance.yahoo.com/quote/{ticker}/financials) directly."
288
+
289
+ # Chart Utilities with error handling
290
+ class ChartUtils:
291
+ def create_price_chart(self, ticker, period="3mo"): # Shorter period for faster loading
292
+ try:
293
+ # Add delay for rate limiting
294
+ time.sleep(random.uniform(0.5, 1.5))
295
+
296
+ stock = yf.Ticker(ticker)
297
+ hist = stock.history(period=period)
298
+
299
+ if hist.empty:
300
+ return None
301
+
302
+ fig = go.Figure()
303
+
304
+ fig.add_trace(go.Scatter(
305
+ x=hist.index,
306
+ y=hist['Close'],
307
+ mode='lines',
308
+ name=f'{ticker} Close Price',
309
+ line=dict(color='#00d4aa', width=2),
310
+ hovertemplate='Date: %{x}<br>Price: $%{y:.2f}<extra></extra>'
311
+ ))
312
+
313
+ fig.update_layout(
314
+ title=f'{ticker} Stock Price - Last {period}',
315
+ xaxis_title='Date',
316
+ yaxis_title='Price ($)',
317
+ hovermode='x unified',
318
+ showlegend=False,
319
+ height=400,
320
+ margin=dict(l=50, r=20, t=50, b=50),
321
+ plot_bgcolor='rgba(0,0,0,0)',
322
+ paper_bgcolor='rgba(0,0,0,0)',
323
+ )
324
+
325
+ return fig
326
+
327
+ except Exception as e:
328
+ print(f"Chart error for {ticker}: {e}")
329
+ return None
330
 
331
  # Initialize utility classes
332
  sec_utils = SECUtils()
 
336
 
337
  def update_stock_info(ticker):
338
  """Update all sections when ticker changes"""
339
+ if not ticker or not ticker.strip():
340
  empty_msg = "Enter a ticker symbol to see data"
341
  return empty_msg, empty_msg, empty_msg, empty_msg, None
342
 
343
  ticker = ticker.upper().strip()
344
 
345
+ # Show loading message
346
+ loading_msg = f"⏳ Loading data for {ticker}..."
347
+
348
+ # Get data for each section with delays between calls
349
  quote_data = finance_utils.get_stock_quote(ticker)
350
+ time.sleep(0.3) # Small delay between different API calls
351
+
352
  news_data = news_utils.get_yahoo_news(ticker)
353
+ time.sleep(0.3)
354
+
355
  filings_data = sec_utils.get_recent_filings(ticker)
356
+ time.sleep(0.3)
357
+
358
  financial_data = finance_utils.get_financial_summary(ticker)
359
+ time.sleep(0.3)
360
+
361
  chart = chart_utils.create_price_chart(ticker)
362
 
363
  return quote_data, news_data, filings_data, financial_data, chart
 
365
  # Custom CSS
366
  css = """
367
  .gradio-container {
368
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
369
+ max-width: 1400px;
370
+ margin: 0 auto;
371
  }
372
  .tab-nav button {
373
  font-size: 16px;
374
+ font-weight: 600;
375
  }
376
  """
377
 
378
  # Create Gradio interface
379
+ with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Stock Research Platform") as demo:
380
  gr.Markdown("""
381
  # πŸ“ˆ Stock Research Platform MVP
382
+ **Comprehensive stock analysis with real-time data, news, SEC filings, and financial summaries.**
383
+
384
+ 🎯 Enter a stock ticker symbol (like **AAPL**, **TSLA**, **MSFT**, **GOOGL**) to explore detailed information.
385
+
386
+ ⚠️ **Note**: If you see rate limiting errors, wait 30 seconds and try again. This is a free service with usage limits.
387
  """)
388
 
389
  with gr.Row():
390
+ with gr.Column(scale=3):
391
+ ticker_input = gr.Textbox(
392
+ label="Stock Ticker",
393
+ placeholder="Enter ticker (e.g., AAPL, TSLA, MSFT)",
394
+ value="AAPL"
395
+ )
396
+ with gr.Column(scale=1):
397
+ refresh_btn = gr.Button("πŸ”„ Refresh Data", variant="primary", size="lg")
398
 
399
  with gr.Tabs():
400
  with gr.TabItem("πŸ’° Quote & Overview"):
 
412
  with gr.TabItem("πŸ“ˆ Price Chart"):
413
  chart_output = gr.Plot(value=None)
414
 
415
+ gr.Markdown("""
416
+ ---
417
+ **Data Sources:** Yahoo Finance, SEC EDGAR, Public RSS Feeds | **Educational Tool - Not Investment Advice**
418
+
419
+ **Troubleshooting:** If you encounter errors, try waiting 30-60 seconds between requests to avoid rate limits.
420
+ """)
421
+
422
+ # Event handlers with rate limiting protection
423
  ticker_input.change(
424
  fn=update_stock_info,
425
  inputs=[ticker_input],
 
434
 
435
  # Launch the app
436
  if __name__ == "__main__":
437
+ demo.launch(show_error=True)