prernajeet14 commited on
Commit
62ca104
Β·
verified Β·
1 Parent(s): 4613dfd

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +488 -0
app.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import pandas as pd
5
+ from datetime import datetime, timedelta
6
+ import yfinance as yf
7
+ import numpy as np
8
+ from typing import Dict, List, Optional
9
+ import time
10
+ import os
11
+ import google.generativeai as genai
12
+ from textblob import TextBlob
13
+ import re
14
+ from concurrent.futures import ThreadPoolExecutor
15
+ import asyncio
16
+ import aiohttp
17
+
18
+ # Configure Gemini API
19
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
20
+ if GEMINI_API_KEY:
21
+ genai.configure(api_key=GEMINI_API_KEY)
22
+ model = genai.GenerativeModel('gemini-2.0-flash-exp')
23
+
24
+ class APIAgent:
25
+ """Handles real-time market data retrieval"""
26
+
27
+ def __init__(self):
28
+ self.base_url = "https://query1.finance.yahoo.com/v8/finance/chart/"
29
+ self.news_url = "https://feeds.finance.yahoo.com/rss/2.0/headline"
30
+
31
+ def get_stock_data(self, symbol: str, period: str = "1d") -> Dict:
32
+ """Fetch stock data using yfinance"""
33
+ try:
34
+ ticker = yf.Ticker(symbol)
35
+ hist = ticker.history(period=period)
36
+ info = ticker.info
37
+
38
+ current_price = hist['Close'].iloc[-1] if not hist.empty else 0
39
+ prev_close = info.get('previousClose', current_price)
40
+ change_percent = ((current_price - prev_close) / prev_close) * 100 if prev_close else 0
41
+
42
+ return {
43
+ 'symbol': symbol,
44
+ 'current_price': round(current_price, 2),
45
+ 'change_percent': round(change_percent, 2),
46
+ 'volume': int(hist['Volume'].iloc[-1]) if not hist.empty else 0,
47
+ 'market_cap': info.get('marketCap', 'N/A'),
48
+ 'pe_ratio': info.get('trailingPE', 'N/A')
49
+ }
50
+ except Exception as e:
51
+ return {'symbol': symbol, 'error': str(e)}
52
+
53
+ def get_multiple_stocks(self, symbols: List[str]) -> List[Dict]:
54
+ """Fetch data for multiple stocks concurrently"""
55
+ with ThreadPoolExecutor(max_workers=5) as executor:
56
+ results = list(executor.map(self.get_stock_data, symbols))
57
+ return results
58
+
59
+ class ScrapingAgent:
60
+ """Handles news and sentiment scraping"""
61
+
62
+ def __init__(self):
63
+ self.news_sources = [
64
+ "https://feeds.finance.yahoo.com/rss/2.0/headline",
65
+ "https://www.alphavantage.co/query"
66
+ ]
67
+
68
+ def get_market_news(self, query: str = "tech stocks") -> List[Dict]:
69
+ """Scrape recent market news"""
70
+ try:
71
+ # Simplified news gathering using yfinance news
72
+ search_terms = ["AAPL", "GOOGL", "MSFT", "TSMC", "NVDA"]
73
+ news_items = []
74
+
75
+ for symbol in search_terms[:3]: # Limit to avoid rate limits
76
+ try:
77
+ ticker = yf.Ticker(symbol)
78
+ news = ticker.news[:2] # Get latest 2 news items
79
+ for item in news:
80
+ news_items.append({
81
+ 'title': item.get('title', 'No title'),
82
+ 'summary': item.get('summary', 'No summary')[:200],
83
+ 'publisher': item.get('publisher', 'Unknown'),
84
+ 'symbol': symbol,
85
+ 'sentiment': self.analyze_sentiment(item.get('title', '') + ' ' + item.get('summary', ''))
86
+ })
87
+ except:
88
+ continue
89
+
90
+ return news_items[:5] # Return top 5 news items
91
+ except Exception as e:
92
+ return [{'error': f'News scraping failed: {str(e)}'}]
93
+
94
+ def analyze_sentiment(self, text: str) -> str:
95
+ """Basic sentiment analysis"""
96
+ try:
97
+ blob = TextBlob(text)
98
+ polarity = blob.sentiment.polarity
99
+ if polarity > 0.1:
100
+ return "Positive"
101
+ elif polarity < -0.1:
102
+ return "Negative"
103
+ else:
104
+ return "Neutral"
105
+ except:
106
+ return "Neutral"
107
+
108
+ class RetrieverAgent:
109
+ """Handles data indexing and retrieval"""
110
+
111
+ def __init__(self):
112
+ self.knowledge_base = {}
113
+ self.embeddings_cache = {}
114
+
115
+ def index_data(self, data: Dict, category: str):
116
+ """Simple in-memory indexing"""
117
+ if category not in self.knowledge_base:
118
+ self.knowledge_base[category] = []
119
+ self.knowledge_base[category].append({
120
+ 'timestamp': datetime.now(),
121
+ 'data': data
122
+ })
123
+ # Keep only last 100 entries per category
124
+ if len(self.knowledge_base[category]) > 100:
125
+ self.knowledge_base[category] = self.knowledge_base[category][-100:]
126
+
127
+ def retrieve_relevant_data(self, query: str, top_k: int = 5) -> List[Dict]:
128
+ """Retrieve relevant data based on query"""
129
+ relevant_data = []
130
+ query_lower = query.lower()
131
+
132
+ for category, entries in self.knowledge_base.items():
133
+ for entry in entries[-top_k:]: # Get recent entries
134
+ data_str = str(entry['data']).lower()
135
+ if any(keyword in data_str for keyword in query_lower.split()):
136
+ relevant_data.append({
137
+ 'category': category,
138
+ 'data': entry['data'],
139
+ 'timestamp': entry['timestamp']
140
+ })
141
+
142
+ return relevant_data[:top_k]
143
+
144
+ class AnalysisAgent:
145
+ """Handles quantitative analysis"""
146
+
147
+ def __init__(self):
148
+ self.metrics_cache = {}
149
+
150
+ def calculate_portfolio_metrics(self, stocks_data: List[Dict]) -> Dict:
151
+ """Calculate portfolio risk and performance metrics"""
152
+ try:
153
+ valid_stocks = [s for s in stocks_data if 'error' not in s]
154
+ if not valid_stocks:
155
+ return {'error': 'No valid stock data available'}
156
+
157
+ total_value = sum([s.get('current_price', 0) for s in valid_stocks])
158
+
159
+ # Calculate basic metrics
160
+ positive_movers = len([s for s in valid_stocks if s.get('change_percent', 0) > 0])
161
+ negative_movers = len([s for s in valid_stocks if s.get('change_percent', 0) < 0])
162
+
163
+ avg_change = np.mean([s.get('change_percent', 0) for s in valid_stocks])
164
+ volatility = np.std([s.get('change_percent', 0) for s in valid_stocks])
165
+
166
+ return {
167
+ 'total_stocks': len(valid_stocks),
168
+ 'positive_movers': positive_movers,
169
+ 'negative_movers': negative_movers,
170
+ 'avg_change_percent': round(avg_change, 2),
171
+ 'volatility': round(volatility, 2),
172
+ 'total_portfolio_value': round(total_value, 2),
173
+ 'risk_level': 'High' if volatility > 3 else 'Medium' if volatility > 1 else 'Low'
174
+ }
175
+ except Exception as e:
176
+ return {'error': f'Analysis failed: {str(e)}'}
177
+
178
+ def detect_earnings_surprises(self, stocks_data: List[Dict]) -> List[Dict]:
179
+ """Detect potential earnings surprises"""
180
+ surprises = []
181
+ for stock in stocks_data:
182
+ if 'error' not in stock:
183
+ change = stock.get('change_percent', 0)
184
+ if abs(change) > 5: # Significant move
185
+ surprises.append({
186
+ 'symbol': stock['symbol'],
187
+ 'change_percent': change,
188
+ 'type': 'Beat' if change > 0 else 'Miss'
189
+ })
190
+ return surprises
191
+
192
+ class LanguageAgent:
193
+ """Handles LLM-based synthesis and narrative generation"""
194
+
195
+ def __init__(self):
196
+ self.model = model if 'model' in globals() else None
197
+
198
+ def synthesize_market_brief(self, portfolio_data: Dict, news_data: List[Dict],
199
+ analysis_data: Dict, query: str) -> str:
200
+ """Generate comprehensive market brief using Gemini"""
201
+ if not self.model:
202
+ return "Gemini API not configured. Please set GEMINI_API_KEY environment variable."
203
+
204
+ try:
205
+ prompt = f"""
206
+ As a professional financial analyst, provide a concise market brief based on the following data:
207
+
208
+ User Query: {query}
209
+
210
+ Portfolio Analysis: {json.dumps(analysis_data, indent=2)}
211
+
212
+ Recent News Headlines: {json.dumps([n.get('title', 'N/A') for n in news_data[:3]], indent=2)}
213
+
214
+ Market Sentiment: {', '.join([n.get('sentiment', 'Neutral') for n in news_data[:3]])}
215
+
216
+ Please provide a professional, concise response that:
217
+ 1. Addresses the specific query
218
+ 2. Highlights key portfolio metrics
219
+ 3. Mentions significant market movements
220
+ 4. Provides actionable insights
221
+ 5. Keep it under 200 words
222
+
223
+ Format the response as a market brief suitable for a portfolio manager.
224
+ """
225
+
226
+ response = self.model.generate_content(prompt)
227
+ return response.text
228
+
229
+ except Exception as e:
230
+ return f"Error generating market brief: {str(e)}"
231
+
232
+ def generate_risk_assessment(self, analysis_data: Dict) -> str:
233
+ """Generate risk assessment narrative"""
234
+ if not self.model:
235
+ return "Risk assessment unavailable - Gemini API not configured."
236
+
237
+ try:
238
+ prompt = f"""
239
+ Based on this portfolio analysis data: {json.dumps(analysis_data, indent=2)}
240
+
241
+ Provide a brief risk assessment (2-3 sentences) focusing on:
242
+ - Current risk level
243
+ - Key concerns or opportunities
244
+ - Recommended actions
245
+ """
246
+
247
+ response = self.model.generate_content(prompt)
248
+ return response.text
249
+
250
+ except Exception as e:
251
+ return f"Risk assessment error: {str(e)}"
252
+
253
+ class VoiceAgent:
254
+ """Handles voice input/output (simplified for Gradio)"""
255
+
256
+ def __init__(self):
257
+ self.tts_enabled = False
258
+
259
+ def text_to_speech(self, text: str) -> str:
260
+ """Placeholder for TTS functionality"""
261
+ return f"πŸ”Š Voice Output Ready: {text[:100]}..."
262
+
263
+ def speech_to_text(self, audio_file) -> str:
264
+ """Placeholder for STT functionality"""
265
+ return "Voice input processed: What's our risk exposure in Asia tech stocks today?"
266
+
267
+ class MultiAgentOrchestrator:
268
+ """Main orchestrator that coordinates all agents"""
269
+
270
+ def __init__(self):
271
+ self.api_agent = APIAgent()
272
+ self.scraping_agent = ScrapingAgent()
273
+ self.retriever_agent = RetrieverAgent()
274
+ self.analysis_agent = AnalysisAgent()
275
+ self.language_agent = LanguageAgent()
276
+ self.voice_agent = VoiceAgent()
277
+
278
+ # Default Asia tech stocks
279
+ self.asia_tech_stocks = ["TSM", "NVDA", "AAPL", "GOOGL", "MSFT", "ASML"]
280
+
281
+ def process_market_query(self, query: str, include_voice: bool = False) -> Dict:
282
+ """Main processing pipeline"""
283
+ try:
284
+ # Step 1: Get market data
285
+ stocks_data = self.api_agent.get_multiple_stocks(self.asia_tech_stocks)
286
+
287
+ # Step 2: Get news and sentiment
288
+ news_data = self.scraping_agent.get_market_news("tech stocks")
289
+
290
+ # Step 3: Perform analysis
291
+ analysis_data = self.analysis_agent.calculate_portfolio_metrics(stocks_data)
292
+ earnings_surprises = self.analysis_agent.detect_earnings_surprises(stocks_data)
293
+
294
+ # Step 4: Index data for retrieval
295
+ self.retriever_agent.index_data(stocks_data, 'stocks')
296
+ self.retriever_agent.index_data(news_data, 'news')
297
+ self.retriever_agent.index_data(analysis_data, 'analysis')
298
+
299
+ # Step 5: Generate narrative
300
+ market_brief = self.language_agent.synthesize_market_brief(
301
+ stocks_data, news_data, analysis_data, query
302
+ )
303
+
304
+ risk_assessment = self.language_agent.generate_risk_assessment(analysis_data)
305
+
306
+ # Step 6: Prepare response
307
+ response = {
308
+ 'market_brief': market_brief,
309
+ 'risk_assessment': risk_assessment,
310
+ 'portfolio_metrics': analysis_data,
311
+ 'earnings_surprises': earnings_surprises,
312
+ 'recent_news': news_data[:3],
313
+ 'stock_data': stocks_data,
314
+ 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
315
+ }
316
+
317
+ if include_voice:
318
+ response['voice_output'] = self.voice_agent.text_to_speech(market_brief)
319
+
320
+ return response
321
+
322
+ except Exception as e:
323
+ return {'error': f'Processing failed: {str(e)}'}
324
+
325
+ # Initialize the orchestrator
326
+ orchestrator = MultiAgentOrchestrator()
327
+
328
+ def create_gradio_interface():
329
+ """Create the colorful Gradio interface"""
330
+
331
+ def process_query(query, include_voice, stock_symbols):
332
+ """Process user query and return formatted response"""
333
+ if stock_symbols:
334
+ # Update stock symbols if provided
335
+ symbols = [s.strip().upper() for s in stock_symbols.split(',')]
336
+ orchestrator.asia_tech_stocks = symbols
337
+
338
+ result = orchestrator.process_market_query(query, include_voice)
339
+
340
+ if 'error' in result:
341
+ return result['error'], "", "", "", ""
342
+
343
+ # Format the response for display
344
+ market_brief = result.get('market_brief', 'No brief available')
345
+ risk_assessment = result.get('risk_assessment', 'No risk assessment available')
346
+
347
+ # Format portfolio metrics
348
+ metrics = result.get('portfolio_metrics', {})
349
+ metrics_text = f"""
350
+ πŸ“Š **Portfolio Metrics:**
351
+ - Total Stocks Analyzed: {metrics.get('total_stocks', 'N/A')}
352
+ - Positive Movers: {metrics.get('positive_movers', 'N/A')}
353
+ - Negative Movers: {metrics.get('negative_movers', 'N/A')}
354
+ - Average Change: {metrics.get('avg_change_percent', 'N/A')}%
355
+ - Volatility: {metrics.get('volatility', 'N/A')}%
356
+ - Risk Level: {metrics.get('risk_level', 'N/A')}
357
+ """
358
+
359
+ # Format earnings surprises
360
+ surprises = result.get('earnings_surprises', [])
361
+ surprises_text = "πŸ“ˆ **Earnings Surprises:**\n"
362
+ if surprises:
363
+ for surprise in surprises:
364
+ surprises_text += f"- {surprise['symbol']}: {surprise['change_percent']}% ({surprise['type']})\n"
365
+ else:
366
+ surprises_text += "No significant earnings surprises detected."
367
+
368
+ # Format news
369
+ news = result.get('recent_news', [])
370
+ news_text = "πŸ“° **Recent News:**\n"
371
+ for item in news:
372
+ if 'error' not in item:
373
+ news_text += f"οΏ½οΏ½οΏ½ {item.get('title', 'No title')} ({item.get('sentiment', 'Neutral')})\n"
374
+
375
+ return market_brief, risk_assessment, metrics_text, surprises_text, news_text
376
+
377
+ # Custom CSS for colorful interface
378
+ css = """
379
+ .gradio-container {
380
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
381
+ font-family: 'Arial', sans-serif;
382
+ }
383
+ .gr-button {
384
+ background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
385
+ border: none;
386
+ color: white;
387
+ font-weight: bold;
388
+ }
389
+ .gr-input, .gr-textbox {
390
+ border-radius: 10px;
391
+ border: 2px solid #4ECDC4;
392
+ }
393
+ """
394
+
395
+ with gr.Blocks(css=css, title="πŸš€ Multi-Agent Finance Assistant") as interface:
396
+ gr.HTML("""
397
+ <div style='text-align: center; padding: 20px; background: linear-gradient(90deg, #FF6B6B, #4ECDC4, #45B7D1, #96CEB4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;'>
398
+ <h1 style='font-size: 3em; font-weight: bold; margin: 0;'>πŸš€ Multi-Agent Finance Assistant</h1>
399
+ <p style='font-size: 1.2em; color: #2C3E50;'>AI-Powered Market Intelligence with Real-Time Analysis</p>
400
+ </div>
401
+ """)
402
+
403
+ with gr.Row():
404
+ with gr.Column(scale=2):
405
+ query_input = gr.Textbox(
406
+ label="πŸ“ Market Query",
407
+ placeholder="What's our risk exposure in Asia tech stocks today?",
408
+ value="What's our risk exposure in Asia tech stocks today, and highlight any earnings surprises?",
409
+ lines=2
410
+ )
411
+
412
+ stock_symbols = gr.Textbox(
413
+ label="πŸ“ˆ Stock Symbols (comma-separated)",
414
+ placeholder="TSM, NVDA, AAPL, GOOGL, MSFT",
415
+ value="TSM, NVDA, AAPL, GOOGL, MSFT"
416
+ )
417
+
418
+ include_voice = gr.Checkbox(label="🎀 Include Voice Processing", value=False)
419
+
420
+ submit_btn = gr.Button("πŸ” Analyze Market", variant="primary", size="lg")
421
+
422
+ with gr.Row():
423
+ with gr.Column():
424
+ market_brief_output = gr.Textbox(
425
+ label="πŸ“Š Market Brief",
426
+ lines=8,
427
+ max_lines=15
428
+ )
429
+
430
+ risk_assessment_output = gr.Textbox(
431
+ label="⚠️ Risk Assessment",
432
+ lines=4,
433
+ max_lines=8
434
+ )
435
+
436
+ with gr.Row():
437
+ with gr.Column():
438
+ metrics_output = gr.Textbox(
439
+ label="πŸ“ˆ Portfolio Metrics",
440
+ lines=6
441
+ )
442
+
443
+ with gr.Column():
444
+ surprises_output = gr.Textbox(
445
+ label="🎯 Earnings Surprises",
446
+ lines=6
447
+ )
448
+
449
+ with gr.Row():
450
+ news_output = gr.Textbox(
451
+ label="πŸ“° Recent Market News",
452
+ lines=8
453
+ )
454
+
455
+ # Add sample queries
456
+ gr.HTML("""
457
+ <div style='margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.1); border-radius: 10px;'>
458
+ <h3 style='color: #2C3E50;'>πŸ’‘ Sample Queries:</h3>
459
+ <ul style='color: #34495E;'>
460
+ <li>"What's the current risk exposure in my tech portfolio?"</li>
461
+ <li>"Show me recent earnings surprises in Asia tech stocks"</li>
462
+ <li>"Analyze sentiment for NVIDIA and Taiwan Semiconductor"</li>
463
+ <li>"What are the top market movers today?"</li>
464
+ </ul>
465
+ </div>
466
+ """)
467
+
468
+ submit_btn.click(
469
+ fn=process_query,
470
+ inputs=[query_input, include_voice, stock_symbols],
471
+ outputs=[market_brief_output, risk_assessment_output, metrics_output,
472
+ surprises_output, news_output]
473
+ )
474
+
475
+ # Auto-run on load with default query
476
+ interface.load(
477
+ fn=process_query,
478
+ inputs=[query_input, include_voice, stock_symbols],
479
+ outputs=[market_brief_output, risk_assessment_output, metrics_output,
480
+ surprises_output, news_output]
481
+ )
482
+
483
+ return interface
484
+
485
+ # Launch the application
486
+ if __name__ == "__main__":
487
+ app = create_gradio_interface()
488
+ app.launch(server_name="0.0.0.0", server_port=7860, share=True)