dure-waseem commited on
Commit
a189e3e
·
1 Parent(s): a7d376f

initial code

Browse files
app.py ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import sys
3
+ import os
4
+ import traceback
5
+ from crew import PredictingStock
6
+ import json
7
+ import re
8
+ import tempfile
9
+ import subprocess
10
+ import pandas as pd
11
+ import numpy as np
12
+ import matplotlib.pyplot as plt
13
+ import plotly.graph_objects as go
14
+ import plotly.express as px
15
+ from plotly.subplots import make_subplots
16
+ from typing import Dict, Any
17
+ from datetime import datetime, timedelta
18
+
19
+ class StockDataProcessor:
20
+ """Handles stock data processing and visualization"""
21
+
22
+ def __init__(self):
23
+ self.data = None
24
+ self.df = None
25
+
26
+ def load_json_data(self, json_file_path: str) -> bool:
27
+ """Load stock data from JSON file"""
28
+ try:
29
+ with open(json_file_path, 'r') as f:
30
+ content = f.read()
31
+
32
+ # Handle double-encoded JSON if necessary
33
+ try:
34
+ self.data = json.loads(content)
35
+ if isinstance(self.data, str):
36
+ self.data = json.loads(self.data)
37
+ except:
38
+ self.data = json.loads(content)
39
+
40
+ # Convert to DataFrame
41
+ self.df = pd.DataFrame.from_dict(self.data, orient='index')
42
+ self.df.index = pd.to_datetime(self.df.index)
43
+ self.df = self.df.sort_index()
44
+
45
+ # Rename columns for easier access
46
+ self.df.columns = ['open', 'high', 'low', 'close', 'volume', 'dividends', 'stock_splits']
47
+
48
+ # Calculate technical indicators
49
+ self._calculate_technical_indicators()
50
+
51
+ return True
52
+ except Exception as e:
53
+ return False
54
+
55
+ def _calculate_technical_indicators(self):
56
+ """Calculate technical indicators"""
57
+ # Daily returns
58
+ self.df['daily_return'] = self.df['close'].pct_change()
59
+
60
+ # Moving averages
61
+ self.df['ma_20'] = self.df['close'].rolling(window=20).mean()
62
+ self.df['ma_50'] = self.df['close'].rolling(window=50).mean()
63
+
64
+ # RSI
65
+ self.df['rsi'] = self._calculate_rsi(self.df['close'])
66
+
67
+ # Bollinger Bands
68
+ self.df['bb_middle'] = self.df['ma_20']
69
+ bb_std = self.df['close'].rolling(window=20).std()
70
+ self.df['bb_upper'] = self.df['bb_middle'] + (bb_std * 2)
71
+ self.df['bb_lower'] = self.df['bb_middle'] - (bb_std * 2)
72
+
73
+ # Volume moving average
74
+ self.df['volume_ma'] = self.df['volume'].rolling(window=20).mean()
75
+
76
+ def _calculate_rsi(self, prices, window=14):
77
+ """Calculate RSI indicator"""
78
+ delta = prices.diff()
79
+ gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
80
+ loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
81
+ rs = gain / loss
82
+ return 100 - (100 / (1 + rs))
83
+
84
+ def create_interactive_chart(self, ticker: str):
85
+ """Create interactive Plotly chart with only stock price and technical analysis"""
86
+ if self.df is None or self.df.empty:
87
+ return None
88
+
89
+ # Create a single subplot for the stock price with technical analysis
90
+ fig = make_subplots(
91
+ rows=1, cols=1,
92
+ shared_xaxes=True,
93
+ subplot_titles=(f'{ticker} - Stock Price with Technical Analysis',)
94
+ )
95
+
96
+ # Price chart with technical indicators
97
+ fig.add_trace(
98
+ go.Scatter(x=self.df.index, y=self.df['close'],
99
+ name='Close Price', line=dict(color='#2E86C1', width=2)),
100
+ row=1, col=1
101
+ )
102
+
103
+ fig.add_trace(
104
+ go.Scatter(x=self.df.index, y=self.df['ma_20'],
105
+ name='20-day MA', line=dict(color='orange', width=1)),
106
+ row=1, col=1
107
+ )
108
+
109
+ fig.add_trace(
110
+ go.Scatter(x=self.df.index, y=self.df['ma_50'],
111
+ name='50-day MA', line=dict(color='red', width=1)),
112
+ row=1, col=1
113
+ )
114
+
115
+ # Bollinger Bands
116
+ fig.add_trace(
117
+ go.Scatter(x=self.df.index, y=self.df['bb_upper'],
118
+ line=dict(color='gray', width=1, dash='dash'),
119
+ name='BB Upper', showlegend=False),
120
+ row=1, col=1
121
+ )
122
+
123
+ fig.add_trace(
124
+ go.Scatter(x=self.df.index, y=self.df['bb_lower'],
125
+ line=dict(color='gray', width=1, dash='dash'),
126
+ fill='tonexty', fillcolor='rgba(128,128,128,0.1)',
127
+ name='Bollinger Bands'),
128
+ row=1, col=1
129
+ )
130
+
131
+ # Update layout
132
+ fig.update_layout(
133
+ height=600,
134
+ title_text=f"Stock Price with Technical Analysis for {ticker}",
135
+ showlegend=True,
136
+ xaxis_rangeslider_visible=True
137
+ )
138
+
139
+ # Update y-axes labels
140
+ fig.update_yaxes(title_text="Price ($)", row=1, col=1)
141
+ fig.update_xaxes(title_text="Date", row=1, col=1)
142
+
143
+ return fig
144
+
145
+ def create_default_chart(self):
146
+ """Create a default placeholder chart"""
147
+ fig = go.Figure()
148
+ fig.add_trace(go.Scatter(
149
+ x=[0, 1, 2, 3, 4],
150
+ y=[100, 110, 105, 115, 120],
151
+ mode='lines+markers',
152
+ name='Sample Stock Data',
153
+ line=dict(color='#2E86C1', width=2)
154
+ ))
155
+
156
+ fig.update_layout(
157
+ title="📈 Example Stock Chart Preview (Note: This is not the actual graph.)",
158
+ xaxis_title="Time Period",
159
+ yaxis_title="Price ($)",
160
+ height=400,
161
+ template="plotly_white"
162
+ )
163
+
164
+ return fig
165
+
166
+ def get_statistics(self, ticker: str) -> str:
167
+ """Generate key statistics"""
168
+ if self.df is None or self.df.empty:
169
+ return "No data available"
170
+
171
+ try:
172
+ current_price = self.df['close'].iloc[-1]
173
+ start_price = self.df['close'].iloc[0]
174
+
175
+ stats = f"""
176
+ 📊 **Key Statistics for {ticker}**
177
+
178
+ **Price Performance:**
179
+ • Current Price: ${current_price:.2f}
180
+
181
+ **Trading Activity:**
182
+ • Data Period: {self.df.index[0].strftime('%Y-%m-%d')} to {self.df.index[-1].strftime('%Y-%m-%d')}
183
+ • Total Trading Days: {len(self.df)}
184
+ """
185
+
186
+ return stats.strip()
187
+
188
+ except Exception as e:
189
+ return f"Error calculating statistics: {e}"
190
+
191
+ def get_default_statistics(self):
192
+ """Get default statistics for initial display"""
193
+ return """
194
+ 📊 **Stock Statistics Preview**
195
+
196
+ Welcome to AI Stock Analysis!
197
+
198
+ **What you'll get:**
199
+ • Real-time stock price data
200
+ • Technical analysis indicators
201
+ • AI-powered investment recommendations
202
+ • Risk assessment and insights
203
+
204
+ 📈 **Enter a stock ticker below to begin analysis**
205
+ """
206
+
207
+ class SecureAPIHandler:
208
+ """Handles API keys securely without permanent storage"""
209
+
210
+ def __init__(self):
211
+ self.session_keys = {}
212
+ # Use Hugging Face Secrets for Finnhub API key
213
+ self.finnhub_available = bool(os.getenv("FINNHUB_API_KEY"))
214
+
215
+ def validate_anthropic_key(self, key_value: str) -> tuple[bool, str]:
216
+ """Validate Anthropic API key format"""
217
+ if not key_value or not key_value.strip():
218
+ return False, "Anthropic API key is required"
219
+
220
+ key_value = key_value.strip()
221
+
222
+ if not key_value.startswith("sk-ant-"):
223
+ return False, "Anthropic API key should start with 'sk-ant-'"
224
+ if len(key_value) < 20:
225
+ return False, "Anthropic API key appears too short"
226
+
227
+ return True, "Valid"
228
+
229
+ def check_finnhub_env(self) -> tuple[bool, str]:
230
+ """Check if Finnhub key is properly set in environment"""
231
+ finnhub_key = os.getenv("FINNHUB_API_KEY")
232
+ if not finnhub_key:
233
+ return False, "Finnhub API key not found in environment variables"
234
+ if len(finnhub_key.strip()) < 10:
235
+ return False, "Finnhub API key appears invalid"
236
+ return True, "Finnhub API key loaded from environment"
237
+
238
+ def set_anthropic_key(self, anthropic_key: str) -> tuple[bool, str]:
239
+ """Securely set Anthropic API key for the session"""
240
+ try:
241
+ is_valid, message = self.validate_anthropic_key(anthropic_key)
242
+ if not is_valid:
243
+ return False, f"Validation Error: {message}"
244
+
245
+ finnhub_valid, finnhub_message = self.check_finnhub_env()
246
+ if not finnhub_valid:
247
+ return False, f"Environment Error: {finnhub_message}"
248
+
249
+ os.environ["ANTHROPIC_API_KEY"] = anthropic_key.strip()
250
+ self.session_keys["ANTHROPIC_API_KEY"] = "***" + anthropic_key[-4:]
251
+ self.session_keys["FINNHUB_API_KEY"] = "***" + os.getenv("FINNHUB_API_KEY")[-4:]
252
+
253
+ return True, "API keys validated and configured successfully"
254
+
255
+ except Exception as e:
256
+ return False, f"Error configuring API keys: {str(e)}"
257
+
258
+ def clear_session_keys(self):
259
+ """Clear Anthropic API key from environment"""
260
+ if "ANTHROPIC_API_KEY" in os.environ:
261
+ del os.environ["ANTHROPIC_API_KEY"]
262
+ self.session_keys.clear()
263
+
264
+ # Global instances
265
+ api_handler = SecureAPIHandler()
266
+ stock_processor = StockDataProcessor()
267
+
268
+ def run_chart_generation(company_ticker: str) -> tuple[bool, str]:
269
+ """Run chart.py to generate JSON data"""
270
+ try:
271
+ # Run chart.py script
272
+ result = subprocess.run([sys.executable, 'chart.py', company_ticker],
273
+ capture_output=True, text=True, timeout=60)
274
+
275
+ if result.returncode == 0:
276
+ return True, "Chart data generated successfully"
277
+ else:
278
+ return False, f"Chart generation failed: {result.stderr}"
279
+
280
+ except subprocess.TimeoutExpired:
281
+ return False, "Chart generation timed out"
282
+ except Exception as e:
283
+ return False, f"Error running chart.py: {str(e)}"
284
+
285
+ def load_stock_data_and_chart(ticker_symbol: str = "AAPL", progress=gr.Progress()):
286
+ """First function: Load stock data and create chart/stats"""
287
+ try:
288
+ if progress:
289
+ progress(0.1, desc="Generating stock data...")
290
+
291
+ ticker = ticker_symbol.strip().upper()
292
+
293
+ # Step 1: Run chart.py to generate JSON data
294
+ chart_success, chart_message = run_chart_generation(ticker)
295
+ if not chart_success:
296
+ return f"❌ Chart Generation Error: {chart_message}", stock_processor.get_default_statistics(), stock_processor.create_default_chart()
297
+
298
+ if progress:
299
+ progress(0.5, desc="Loading and processing stock data...")
300
+
301
+ # Step 2: Load the generated JSON data
302
+ json_file_path = f"{ticker}_financial_data.json"
303
+ if not os.path.exists(json_file_path):
304
+ return f"❌ Error: JSON file {json_file_path} not found", stock_processor.get_default_statistics(), stock_processor.create_default_chart()
305
+
306
+ # Load stock data for visualization
307
+ if not stock_processor.load_json_data(json_file_path):
308
+ return "❌ Please enter company ticker", stock_processor.get_default_statistics(), stock_processor.create_default_chart()
309
+
310
+ if progress:
311
+ progress(1.0, desc="Creating visualization...")
312
+
313
+ # Step 3: Create visualization and stats
314
+ chart_fig = stock_processor.create_interactive_chart(ticker)
315
+ stats = stock_processor.get_statistics(ticker)
316
+
317
+ success_msg = f"✅ Successfully loaded data for {ticker}"
318
+
319
+ return success_msg, stats, chart_fig
320
+
321
+ except Exception as e:
322
+ error_msg = f"❌ Error loading stock data: {str(e)}"
323
+ return error_msg, stock_processor.get_default_statistics(), stock_processor.create_default_chart()
324
+
325
+ def load_company_data(ticker: str):
326
+ """Load data for a specific company from navigation bar"""
327
+ # Load the stock data
328
+ status, stats, chart = load_stock_data_and_chart(ticker)
329
+ # Return the ticker value and the loaded data
330
+ return ticker, status, stats, chart
331
+
332
+ def run_ai_analysis(company_ticker: str, max_amount: str, anthropic_key: str, progress=gr.Progress()):
333
+ """Second function: Run AI crew analysis only"""
334
+ try:
335
+ progress(0.05, desc="Validating inputs...")
336
+
337
+ # Validate inputs
338
+ if not company_ticker or not company_ticker.strip():
339
+ return "❌ Error: Please enter a company ticker symbol"
340
+
341
+ if not max_amount or not max_amount.strip():
342
+ return "❌ Error: Please enter a maximum investment amount"
343
+
344
+ try:
345
+ float(max_amount)
346
+ except ValueError:
347
+ return "❌ Error: Maximum investment amount must be a valid number"
348
+
349
+ if not anthropic_key or not anthropic_key.strip():
350
+ return "❌ Error: Anthropic API key is required"
351
+
352
+ ticker = company_ticker.strip().upper()
353
+
354
+ progress(0.2, desc="Configuring API keys...")
355
+
356
+ # Set API keys securely
357
+ success, message = api_handler.set_anthropic_key(anthropic_key)
358
+ if not success:
359
+ return f"❌ {message}"
360
+
361
+ progress(0.3, desc="Preparing AI analysis...")
362
+
363
+ # Prepare inputs for CrewAI
364
+ inputs = {
365
+ "company_name": ticker,
366
+ "max_amount": max_amount
367
+ }
368
+
369
+ progress(0.4, desc="Initializing AI agents...")
370
+
371
+ # Run CrewAI analysis
372
+ crew_instance = PredictingStock()
373
+
374
+ progress(0.6, desc="AI agents are running now... This may take 2-5 minutes")
375
+
376
+ result = crew_instance.crew().kickoff(inputs=inputs)
377
+
378
+ progress(1.0, desc="Analysis complete!")
379
+
380
+ # Format results
381
+ analysis_result = f"""
382
+ 🎯 COMPREHENSIVE STOCK ANALYSIS FOR: {ticker}
383
+ {'='*60}
384
+
385
+ {result.raw}
386
+
387
+ """.strip()
388
+
389
+ return analysis_result
390
+
391
+ except Exception as e:
392
+ error_msg = f"""
393
+ ❌ ERROR DURING AI ANALYSIS:
394
+
395
+ {str(e)}
396
+
397
+ TROUBLESHOOTING TIPS:
398
+ 1. Verify your Anthropic API key is correct
399
+ 2. Check that FINNHUB_API_KEY is set in Hugging Face Secrets
400
+ 3. Check your internet connection
401
+ 4. Ensure the ticker symbol is valid
402
+ 5. Try again in a few moments
403
+
404
+ Full error details:
405
+ {traceback.format_exc()}
406
+ """.strip()
407
+ return error_msg
408
+
409
+ def create_secure_interface():
410
+ """Create the enhanced Gradio interface with rearranged layout"""
411
+ css = """
412
+ .gradio-container {
413
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
414
+ }
415
+ .header {
416
+ text-align: center;
417
+ padding: 20px;
418
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
419
+ color: white;
420
+ border-radius: 10px;
421
+ margin-bottom: 30px;
422
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
423
+ }
424
+ .nav-bar {
425
+ background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
426
+ padding: 15px;
427
+ border-radius: 10px;
428
+ margin-bottom: 20px;
429
+ text-align: center;
430
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
431
+ }
432
+ .nav-button {
433
+ margin: 5px !important;
434
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
435
+ border: none !important;
436
+ color: white !important;
437
+ font-weight: bold !important;
438
+ border-radius: 8px !important;
439
+ padding: 10px 20px !important;
440
+ transition: all 0.3s ease !important;
441
+ }
442
+ .nav-button:hover {
443
+ transform: translateY(-2px) !important;
444
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important;
445
+ }
446
+ .stats-box {
447
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
448
+ color: white;
449
+ padding: 15px;
450
+ border-radius: 10px;
451
+ margin: 10px 0;
452
+ }
453
+ .secure-input {
454
+ border: 2px solid #28a745 !important;
455
+ }
456
+ .section-header {
457
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
458
+ color: white;
459
+ padding: 10px;
460
+ border-radius: 8px;
461
+ margin: 15px 0 10px 0;
462
+ text-align: center;
463
+ }
464
+ """
465
+
466
+ with gr.Blocks(css=css, title="AI Stock Prediction with Visualization") as interface:
467
+ # Header
468
+ gr.HTML("""
469
+ <div class="header">
470
+ <h1>🚀 AI Stock Prediction & Visualization System</h1>
471
+ <p>Complete stock analysis with interactive charts and AI-powered insights</p>
472
+ </div>
473
+ """)
474
+
475
+ # NAVIGATION BAR
476
+ gr.HTML('<div class="nav-bar"><h3>🏢 Quick Access - Popular Companies</h3></div>')
477
+
478
+ with gr.Row():
479
+ apple_btn = gr.Button("🍎 Apple (AAPL)", elem_classes=["nav-button"], scale=1)
480
+ google_btn = gr.Button("🔍 Google (GOOGL)", elem_classes=["nav-button"], scale=1)
481
+ microsoft_btn = gr.Button("💻 Microsoft (MSFT)", elem_classes=["nav-button"], scale=1)
482
+ tesla_btn = gr.Button("⚡ Tesla (TSLA)", elem_classes=["nav-button"], scale=1)
483
+ amazon_btn = gr.Button("📦 Amazon (AMZN)", elem_classes=["nav-button"], scale=1)
484
+
485
+ # CUSTOM COMPANY SECTION
486
+ gr.HTML('<div class="nav-bar"><h4>✨ Add Any Company</h4></div>')
487
+
488
+ with gr.Row():
489
+ with gr.Column(scale=3):
490
+ custom_ticker_input = gr.Textbox(
491
+ label="Enter Any Stock Ticker",
492
+ placeholder="e.g., NVDA, META, NFLX, etc.",
493
+ value="",
494
+ info="Type any valid stock ticker symbol",
495
+ elem_classes=["secure-input"]
496
+ )
497
+ with gr.Column(scale=1):
498
+ custom_load_btn = gr.Button(
499
+ "🚀 Load Custom Stock",
500
+ elem_classes=["nav-button"],
501
+ variant="primary",
502
+ size="lg"
503
+ )
504
+
505
+ # TOP SECTION: Statistics and Chart (Displayed from start)
506
+ gr.HTML('<div class="section-header"><h2>📊 Live Statistics & Interactive Chart</h2></div>')
507
+
508
+ # Load default stock data (AAPL) on startup
509
+ default_message, default_stats, default_chart = load_stock_data_and_chart("AAPL")
510
+
511
+ with gr.Row():
512
+ with gr.Column(scale=1):
513
+ # Statistics box (shown from start with real stock data)
514
+ stats_output = gr.Markdown(
515
+ value=default_stats,
516
+ elem_classes=["stats-box"]
517
+ )
518
+
519
+ with gr.Column(scale=2):
520
+ # Interactive chart (shown from start with real stock data)
521
+ chart_output = gr.Plot(
522
+ value=default_chart,
523
+ label="Stock Price Chart",
524
+ show_label=True
525
+ )
526
+
527
+ # Status message for stock data loading
528
+ stock_status = gr.Markdown(value=default_message)
529
+
530
+ # MIDDLE SECTION: Input Configuration
531
+ gr.HTML('<div class="section-header"><h2>⚙️ Analysis Configuration</h2></div>')
532
+
533
+ with gr.Row():
534
+ with gr.Column(scale=1):
535
+ company_ticker = gr.Textbox(
536
+ label="Company Ticker Symbol",
537
+ placeholder="Here the selected company ticker will be shown",
538
+ value="",
539
+ info="Stock ticker symbol to analyze",
540
+ elem_classes=["secure-input"],
541
+ interactive=False
542
+ )
543
+
544
+ with gr.Column(scale=1):
545
+ max_amount = gr.Textbox(
546
+ label="Maximum Investment Amount (USD)",
547
+ placeholder="e.g., 1000, 5000.50",
548
+ value="",
549
+ info="The maximum amount you are willing to invest",
550
+ elem_classes=["secure-input"]
551
+ )
552
+
553
+ with gr.Column(scale=1):
554
+ anthropic_key = gr.Textbox(
555
+ label="Anthropic API Key (Claude)",
556
+ placeholder="sk-ant-api03-...",
557
+ type="password",
558
+ info="Get from console.anthropic.com - Required for AI analysis",
559
+ elem_classes=["secure-input"]
560
+ )
561
+
562
+ with gr.Row():
563
+ with gr.Column():
564
+ load_data_btn = gr.Button(
565
+ "📊 Load Stock Data",
566
+ variant="secondary",
567
+ size="lg",
568
+ scale=1
569
+ )
570
+ with gr.Column():
571
+ analyze_btn = gr.Button(
572
+ "🤖 Run AI Analysis",
573
+ variant="primary",
574
+ size="lg",
575
+ scale=2
576
+ )
577
+
578
+ # BOTTOM SECTION: AI Analysis Results
579
+ gr.HTML('<div class="section-header"><h2>🤖 AI Agent Analysis & Recommendations</h2></div>')
580
+
581
+ result_output = gr.TextArea(
582
+ label="AI Analysis Report",
583
+ lines=25,
584
+ max_lines=35,
585
+ interactive=False,
586
+ show_copy_button=True,
587
+ placeholder="🤖 AI analysis results and investment recommendations will appear here after running the analysis...\n\nThe AI agents will provide:\n• Comprehensive stock analysis\n• Risk assessment\n• Investment recommendations\n• Market insights\n• Technical analysis summary"
588
+ )
589
+
590
+ # Event handlers
591
+
592
+ # Navigation bar buttons
593
+ apple_btn.click(
594
+ fn=lambda: load_company_data("AAPL"),
595
+ outputs=[company_ticker, stock_status, stats_output, chart_output]
596
+ )
597
+
598
+ google_btn.click(
599
+ fn=lambda: load_company_data("GOOGL"),
600
+ outputs=[company_ticker, stock_status, stats_output, chart_output]
601
+ )
602
+
603
+ microsoft_btn.click(
604
+ fn=lambda: load_company_data("MSFT"),
605
+ outputs=[company_ticker, stock_status, stats_output, chart_output]
606
+ )
607
+
608
+ tesla_btn.click(
609
+ fn=lambda: load_company_data("TSLA"),
610
+ outputs=[company_ticker, stock_status, stats_output, chart_output]
611
+ )
612
+
613
+ amazon_btn.click(
614
+ fn=lambda: load_company_data("AMZN"),
615
+ outputs=[company_ticker, stock_status, stats_output, chart_output]
616
+ )
617
+
618
+ # Custom ticker loading functionality
619
+ custom_load_btn.click(
620
+ fn=lambda ticker: load_company_data(ticker),
621
+ inputs=[custom_ticker_input],
622
+ outputs=[company_ticker, stock_status, stats_output, chart_output]
623
+ )
624
+
625
+ # Load stock data when load button is clicked
626
+ load_data_btn.click(
627
+ fn=load_stock_data_and_chart,
628
+ inputs=[company_ticker],
629
+ outputs=[stock_status, stats_output, chart_output]
630
+ )
631
+
632
+ # Run AI analysis when button is clicked
633
+ analyze_btn.click(
634
+ fn=run_ai_analysis,
635
+ inputs=[company_ticker, max_amount, anthropic_key],
636
+ outputs=[result_output]
637
+ )
638
+
639
+ # Footer
640
+ gr.HTML("""
641
+ <div style="text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #dee2e6;">
642
+ <p>🔐 <strong>Secure AI Stock Analysis with Interactive Visualization</strong></p>
643
+ <p style="font-size: 12px; color: #6c757d;">
644
+ Features: Real-time data • Technical indicators • AI analysis • Interactive charts
645
+ </p>
646
+ </div>
647
+ """)
648
+
649
+ return interface
650
+
651
+ # Launch the application
652
+ if __name__ == "__main__":
653
+ interface = create_secure_interface()
654
+ # Modified for Hugging Face Spaces deployment
655
+ interface.launch(
656
+ share=False,
657
+ debug=False,
658
+ show_error=True
659
+ )
chart.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+ from datetime import datetime, timedelta
3
+ import json
4
+ import sys
5
+ import os
6
+ import time
7
+ import requests
8
+ from requests.adapters import HTTPAdapter
9
+ from urllib3.util.retry import Retry
10
+
11
+ def setup_session_with_retries():
12
+ """Setup a requests session with retry logic for better reliability"""
13
+ session = requests.Session()
14
+
15
+ retry_strategy = Retry(
16
+ total=3,
17
+ backoff_factor=1,
18
+ status_forcelist=[429, 500, 502, 503, 504],
19
+ )
20
+
21
+ adapter = HTTPAdapter(max_retries=retry_strategy)
22
+ session.mount("http://", adapter)
23
+ session.mount("https://", adapter)
24
+
25
+ return session
26
+
27
+ def retrieve_financial_data(company_name: str) -> str:
28
+ """
29
+ It returns the last 6 months of daily stock Open, High, Low, Close, Volume, Dividends, Stock Splits for the specified company.
30
+
31
+ Args:
32
+ company_name: str -> the ticker symbol of the company (e.g., 'AAPL', 'GOOGL')
33
+
34
+ Returns:
35
+ str: JSON string containing the financial data or error message
36
+ """
37
+ try:
38
+ # Validate ticker symbol
39
+ if not company_name or not company_name.strip():
40
+ return "Error: Empty ticker symbol provided"
41
+
42
+ company_name = company_name.strip().upper()
43
+
44
+ # Calculate dates for last 6 months
45
+ end_date = datetime.now()
46
+ start_date = end_date - timedelta(days=180) # Approximately 6 months
47
+
48
+ # Setup session with retries for better reliability
49
+ session = setup_session_with_retries()
50
+
51
+ # Get the data with custom session
52
+ ticker = yf.Ticker(company_name, session=session)
53
+
54
+ # Add a small delay to be respectful to the API
55
+ time.sleep(0.1)
56
+
57
+ # Fetch historical data
58
+ hist = ticker.history(
59
+ start=start_date.strftime("%Y-%m-%d"),
60
+ end=end_date.strftime("%Y-%m-%d"),
61
+ auto_adjust=True,
62
+ prepost=True,
63
+ threads=True
64
+ )
65
+
66
+ if hist.empty:
67
+ return f"Error: No data found for ticker symbol: {company_name}. Please verify the ticker symbol is correct."
68
+
69
+ # Validate that we have sufficient data
70
+ if len(hist) < 10:
71
+ return f"Error: Insufficient data found for {company_name}. Only {len(hist)} days of data available."
72
+
73
+ # Clean the data - remove any NaN values and ensure proper formatting
74
+ hist = hist.dropna()
75
+
76
+ if hist.empty:
77
+ return f"Error: No valid data remaining for {company_name} after cleaning."
78
+
79
+ # Convert to JSON and return
80
+ json_data = hist.to_json(date_format='iso', orient='index')
81
+
82
+ # Validate JSON was created successfully
83
+ try:
84
+ json.loads(json_data) # Test if it's valid JSON
85
+ except json.JSONDecodeError:
86
+ return f"Error: Failed to convert data to JSON format for {company_name}"
87
+
88
+ return json_data
89
+
90
+ except requests.exceptions.RequestException as e:
91
+ return f"Error: Network issue while retrieving data for {company_name}: {str(e)}"
92
+ except yf.exceptions.YFinanceException as e:
93
+ return f"Error: Yahoo Finance API issue for {company_name}: {str(e)}"
94
+ except Exception as e:
95
+ return f"Error: Unexpected error retrieving data for {company_name}: {str(e)}"
96
+
97
+ def save_financial_data(company_name: str, data: str) -> bool:
98
+ """
99
+ Save financial data to a JSON file with error handling
100
+
101
+ Args:
102
+ company_name: str -> the ticker symbol
103
+ data: str -> JSON string to save
104
+
105
+ Returns:
106
+ bool: True if successful, False otherwise
107
+ """
108
+ try:
109
+ filename = f"{company_name}_financial_data.json"
110
+
111
+ # Check if data is an error message
112
+ if data.startswith("Error:"):
113
+ print(f"❌ Data retrieval failed: {data}")
114
+ return False
115
+
116
+ # Validate JSON before saving
117
+ try:
118
+ json.loads(data)
119
+ except json.JSONDecodeError:
120
+ print(f"❌ Invalid JSON data for {company_name}")
121
+ return False
122
+
123
+ # Ensure directory exists (important for cloud environments)
124
+ os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True)
125
+
126
+ # Save to file
127
+ with open(filename, "w", encoding='utf-8') as file:
128
+ json.dump(data, file, ensure_ascii=False, indent=2)
129
+
130
+ # Verify file was created and has content
131
+ if os.path.exists(filename) and os.path.getsize(filename) > 0:
132
+ print(f"✅ Successfully saved data for {company_name} to {filename}")
133
+ return True
134
+ else:
135
+ print(f"❌ Failed to save data for {company_name} - file not created or empty")
136
+ return False
137
+
138
+ except PermissionError:
139
+ print(f"❌ Permission denied: Cannot write to file for {company_name}")
140
+ return False
141
+ except OSError as e:
142
+ print(f"❌ OS Error saving data for {company_name}: {str(e)}")
143
+ return False
144
+ except Exception as e:
145
+ print(f"❌ Unexpected error saving data for {company_name}: {str(e)}")
146
+ return False
147
+
148
+ def validate_ticker_symbol(symbol: str) -> tuple[bool, str]:
149
+ """
150
+ Basic validation of ticker symbol format
151
+
152
+ Args:
153
+ symbol: str -> ticker symbol to validate
154
+
155
+ Returns:
156
+ tuple: (is_valid, message)
157
+ """
158
+ if not symbol or not symbol.strip():
159
+ return False, "Ticker symbol cannot be empty"
160
+
161
+ symbol = symbol.strip().upper()
162
+
163
+ # Basic format validation
164
+ if len(symbol) < 1 or len(symbol) > 10:
165
+ return False, "Ticker symbol must be 1-10 characters long"
166
+
167
+ if not symbol.isalnum():
168
+ return False, "Ticker symbol must contain only letters and numbers"
169
+
170
+ return True, "Valid ticker symbol"
171
+
172
+ if __name__ == "__main__":
173
+ try:
174
+ # Check command line arguments
175
+ if len(sys.argv) != 2:
176
+ print("❌ Usage: python chart.py <TICKER_SYMBOL>")
177
+ print("Example: python chart.py AAPL")
178
+ sys.exit(1)
179
+
180
+ symbol = sys.argv[1].strip().upper()
181
+
182
+ # Validate ticker symbol
183
+ is_valid, validation_message = validate_ticker_symbol(symbol)
184
+ if not is_valid:
185
+ print(f"❌ Invalid ticker symbol: {validation_message}")
186
+ sys.exit(1)
187
+
188
+ print(f"📊 Retrieving financial data for {symbol}...")
189
+
190
+ # Retrieve financial data
191
+ financial_data = retrieve_financial_data(symbol)
192
+
193
+ # Save the data
194
+ success = save_financial_data(symbol, financial_data)
195
+
196
+ if success:
197
+ print(f"🎯 Operation completed successfully for {symbol}")
198
+ sys.exit(0)
199
+ else:
200
+ print(f"❌ Failed to complete operation for {symbol}")
201
+ sys.exit(1)
202
+
203
+ except KeyboardInterrupt:
204
+ print("\n❌ Operation cancelled by user")
205
+ sys.exit(1)
206
+ except Exception as e:
207
+ print(f"❌ Unexpected error: {e}")
208
+ sys.exit(1)
config/agents.yaml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # config/agents.yaml
2
+ stock_information_specialist:
3
+ role: >
4
+ Stock Information Specialist
5
+ goal: >
6
+ To provide accurate and timely stock information using the tool by gathering comprehensive historical stock Open, High, Low, Close, Volume, Dividends, Stock Splits for a specified company.
7
+ backstory: >
8
+ You are a meticulous financial data analyst with an unparalleled ability to extract,
9
+ process, and interpret historical stock market information. Your expertise lies
10
+ in identifying trends, patterns, and anomalies within vast datasets of stock prices,
11
+ volume, and financial statements. You are crucial for providing the foundational
12
+ numerical context for predicting future stock movements.
13
+
14
+ company_profile_specialist:
15
+ role: >
16
+ Company Profile Analyst
17
+ goal: >
18
+ To gather and analyze key company information including market capitalization,
19
+ industry classification, exchange listing, geographic location, and corporate
20
+ fundamentals to assess the company's market position and characteristics.
21
+ backstory: >
22
+ You are an experienced company research analyst with deep expertise in analyzing
23
+ corporate fundamentals and market positioning. Your strength lies in interpreting
24
+ company profiles, market capitalizations, industry classifications, and geographic
25
+ factors that influence stock performance. You excel at understanding how company
26
+ size, listing exchange, industry sector, and regional factors impact investment potential.
27
+
28
+ news_analyst:
29
+ role: >
30
+ News and Sentiment Analyst
31
+ goal: >
32
+ To gather, analyze, and interpret the latest news, market sentiment, and external
33
+ factors that could impact the stock's short-term and long-term performance.
34
+ backstory: >
35
+ You are a seasoned market news analyst with an exceptional ability to quickly
36
+ process and interpret financial news, earnings reports, regulatory changes, and
37
+ market sentiment. Your expertise lies in identifying how external events and
38
+ market psychology influence stock prices, providing crucial insights into timing
39
+ and market dynamics.
40
+
41
+ investment_advisor:
42
+ role: >
43
+ Senior Investment Advisor
44
+ goal: >
45
+ To synthesize all available information from technical analysis, fundamental
46
+ analysis, and market sentiment to make a well-informed investment recommendation along with how many to invest from {max_amount}
47
+ with clear reasoning and risk assessment.
48
+ backstory: >
49
+ You are a seasoned investment advisor with over 15 years of experience in making
50
+ investment decisions. Your expertise lies in combining technical analysis,
51
+ fundamental analysis, and market sentiment to provide comprehensive investment
52
+ recommendations. You have a proven track record of successful stock picks and
53
+ risk management, always considering both potential returns and downside risks.
config/tasks.yaml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ generate_stock_information:
2
+ description: >
3
+ You are a 'Stock Information Specialist' and fetch the last 6 months of daily
4
+ stock prices using the retrieve_financial_data tool for the company {company_name}.
5
+ Analyze the data for Open, High, Low, Close, Volume, Dividends, Stock Splits.
6
+ Ensure the data is clean and ready for analysis. Provide insights on trends,
7
+ volatility, and any notable patterns in the stock performance.
8
+ expected_output: >
9
+ A comprehensive analysis of the stock data including:
10
+ 1. Raw historical data for the last 6 months
11
+ 2. Key statistics (average price, volatility, volume trends)
12
+ 3. Notable patterns or anomalies in the data
13
+ 4. Technical indicators and trends
14
+ 5. Summary of stock performance trends
15
+
16
+ analyze_company_profile:
17
+ description: >
18
+ You are a 'Company Profile Analyst' and use the get_company_profile tool to gather
19
+ key company information for {company_name}. Analyze the company's market
20
+ capitalization, industry classification, exchange listing, geographic headquarters,
21
+ shares outstanding, IPO history, and other corporate fundamentals. Focus on how
22
+ these factors influence the company's market position and investment attractiveness.
23
+ expected_output: >
24
+ A detailed company profile analysis including:
25
+ 1. Company overview (name, ticker, headquarters country)
26
+ 2. Market metrics (market cap, shares outstanding, currency)
27
+ 3. Exchange and listing information
28
+ 4. Industry classification and sector analysis
29
+ 5. IPO date and company maturity assessment
30
+ 6. Geographic and regulatory environment considerations
31
+ 7. Company size classification (large-cap, mid-cap, etc.)
32
+ 8. Overall corporate profile assessment for investment purposes
33
+ gather_news_sentiment:
34
+ description: >
35
+ You are a 'News and Sentiment Analyst' and use the get_stock_news tool to gather
36
+ comprehensive company news for {company_name} from the last 30 days. Analyze
37
+ the news headlines, summaries, categories, sources, related companies, and publication
38
+ frequency to determine market sentiment and identify factors impacting stock performance.
39
+ Pay special attention to news categories, source credibility, and any mentions of
40
+ related companies that might affect the target stock.
41
+ expected_output: >
42
+ A comprehensive news and sentiment analysis including:
43
+ 1. Executive summary of news sentiment (bullish/bearish/neutral)
44
+ 2. Analysis of news frequency and intensity (high/medium/low news coverage)
45
+ 3. Breakdown of news categories and their sentiment implications
46
+ 4. Assessment of source credibility and news quality
47
+ 5. Identification of key catalysts, risks, or market-moving events
48
+ 6. Analysis of related companies/stocks mentioned and their potential impact
49
+ 7. Overall sentiment score (1-10 scale) with directional bias
50
+ 8. Key headlines that could significantly impact stock price
51
+
52
+
53
+ make_investment_decision:
54
+ description: >
55
+ You are a 'Senior Investment Advisor'. Based on the comprehensive analysis provided
56
+ by the Stock Information Specialist, Company Profile Analyst, and News Analyst,
57
+ make a final investment recommendation for {company_name} and how much to invest from {max_amount}. Consider technical
58
+ analysis, fundamental analysis, and market sentiment to determine whether to BUY,
59
+ HOLD, or SELL the stock and if BUY then how much to invest from {max_amount}. Provide clear reasoning, risk assessment, and confidence level.
60
+ expected_output: >
61
+ A final investment recommendation report including:
62
+ 1. Clear investment decision: BUY/HOLD/SELL
63
+ 2. Recommended investment amount from {max_amount} if BUY
64
+ 3. Detailed reasoning combining all three analyses
65
+ 4. Risk assessment and potential downside
66
+ 5. Expected timeline and price targets
67
+ 6. Confidence level (1-10 scale), Bar chart information
68
+ 7. Alternative scenarios consideration
69
+ 8. Executive summary for quick decision making
crew.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # crew.py
3
+ from crewai import Agent, Crew, Process, Task, LLM
4
+ from crewai.project import CrewBase, agent, crew, task
5
+ from dotenv import load_dotenv
6
+ import os
7
+
8
+ from tools.retrieve_financial_data import retrieve_financial_data
9
+ from tools.company_profile_tool import get_company_profile
10
+ from tools.news_tool import get_stock_news
11
+
12
+ # Load environment variables
13
+ load_dotenv()
14
+
15
+ @CrewBase
16
+ class PredictingStock():
17
+ """PredictingStock crew"""
18
+
19
+ agents_config = 'config/agents.yaml'
20
+ tasks_config = 'config/tasks.yaml'
21
+
22
+ def __init__(self):
23
+ self.llm = LLM(
24
+ model="claude-3-haiku-20240307",
25
+ api_key=os.getenv("ANTHROPIC_API_KEY")
26
+ )
27
+
28
+ @agent
29
+ def stock_information_specialist(self) -> Agent:
30
+ return Agent(
31
+ config=self.agents_config['stock_information_specialist'],
32
+ llm=self.llm,
33
+ tools=[retrieve_financial_data],
34
+ verbose=True
35
+ )
36
+
37
+ @agent
38
+ def company_profile_specialist(self) -> Agent:
39
+ return Agent(
40
+ config=self.agents_config['company_profile_specialist'],
41
+ llm=self.llm,
42
+ tools=[get_company_profile],
43
+ verbose=True
44
+ )
45
+
46
+ @agent
47
+ def news_analyst(self) -> Agent:
48
+ return Agent(
49
+ config=self.agents_config['news_analyst'],
50
+ llm=self.llm,
51
+ tools=[get_stock_news],
52
+ verbose=True
53
+ )
54
+
55
+ @agent
56
+ def investment_advisor(self) -> Agent:
57
+ return Agent(
58
+ config=self.agents_config['investment_advisor'],
59
+ llm=self.llm,
60
+ verbose=True
61
+ )
62
+
63
+ @task
64
+ def generate_stock_information(self) -> Task:
65
+ return Task(
66
+ config=self.tasks_config['generate_stock_information'],
67
+ agent=self.stock_information_specialist()
68
+ )
69
+
70
+ @task
71
+ def analyze_company_profile(self) -> Task:
72
+ return Task(
73
+ config=self.tasks_config['analyze_company_profile'],
74
+ agent=self.company_profile_specialist()
75
+ )
76
+
77
+ @task
78
+ def gather_news_sentiment(self) -> Task:
79
+ return Task(
80
+ config=self.tasks_config['gather_news_sentiment'],
81
+ agent=self.news_analyst()
82
+ )
83
+
84
+ @task
85
+ def make_investment_decision(self) -> Task:
86
+ return Task(
87
+ config=self.tasks_config['make_investment_decision'],
88
+ agent=self.investment_advisor(),
89
+ context=[self.generate_stock_information(), self.analyze_company_profile(), self.gather_news_sentiment()]
90
+ )
91
+
92
+ @crew
93
+ def crew(self) -> Crew:
94
+ """Creates the PredictingStock crew"""
95
+ return Crew(
96
+ agents=self.agents, # Automatically created by the @agent decorator
97
+ tasks=self.tasks, # Automatically created by the @task decorator
98
+ process=Process.sequential,
99
+ verbose=True,
100
+ )
tools/__pycache__/company_profile_tool.cpython-312.pyc ADDED
Binary file (2.5 kB). View file
 
tools/__pycache__/file_read.cpython-312.pyc ADDED
Binary file (761 Bytes). View file
 
tools/__pycache__/news_tool.cpython-312.pyc ADDED
Binary file (4.71 kB). View file
 
tools/__pycache__/retrieve_financial_data.cpython-312.pyc ADDED
Binary file (1.59 kB). View file
 
tools/company_profile_tool.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from crewai.tools import tool
2
+ import finnhub
3
+ import os
4
+ import json
5
+
6
+ @tool("get_company_profile")
7
+ def get_company_profile(company_symbol: str) -> str:
8
+ """
9
+ Retrieves company profile information from Finnhub including country, currency,
10
+ exchange, industry, IPO date, market cap, name, phone, shares outstanding,
11
+ ticker, and website.
12
+
13
+ Args:
14
+ company_symbol: str -> the ticker symbol of the company (e.g., 'AAPL', 'GOOGL')
15
+ """
16
+ try:
17
+ # Initialize Finnhub client
18
+ finnhub_client = finnhub.Client(api_key=os.getenv("FINNHUB_API_KEY"))
19
+
20
+ # Get company profile
21
+ profile = finnhub_client.company_profile2(symbol=company_symbol)
22
+
23
+ if not profile:
24
+ return f"No company profile found for symbol: {company_symbol}"
25
+
26
+ # Format the profile data for better readability
27
+ formatted_profile = {
28
+ "Company Name": profile.get("name", "N/A"),
29
+ "Ticker Symbol": profile.get("ticker", "N/A"),
30
+ "Country": profile.get("country", "N/A"),
31
+ "Currency": profile.get("currency", "N/A"),
32
+ "Exchange": profile.get("exchange", "N/A"),
33
+ "Industry": profile.get("finnhubIndustry", "N/A"),
34
+ "IPO Date": profile.get("ipo", "N/A"),
35
+ "Market Capitalization": f"${profile.get('marketCapitalization', 0):,}" if profile.get('marketCapitalization') else "N/A",
36
+ "Shares Outstanding": f"{profile.get('shareOutstanding', 0):,}" if profile.get('shareOutstanding') else "N/A",
37
+ "Phone": profile.get("phone", "N/A"),
38
+ "Website": profile.get("weburl", "N/A"),
39
+ "Logo": profile.get("logo", "N/A")
40
+ }
41
+
42
+ # Convert to JSON string for better formatting
43
+ return json.dumps(formatted_profile, indent=2)
44
+
45
+ except Exception as e:
46
+ return f"Error retrieving company profile for {company_symbol}: {str(e)}"
tools/news_tool.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # from crewai.tools import tool
2
+ # from langchain_community.tools.yahoo_finance_news import YahooFinanceNewsTool
3
+ # import json
4
+
5
+ # @tool("get_stock_news")
6
+ # def get_stock_news(company_ticker: str) -> str:
7
+ # """
8
+ # Retrieves the latest news and market sentiment for a given stock ticker
9
+ # from Yahoo Finance News.
10
+
11
+ # Args:
12
+ # company_ticker: str -> the ticker symbol of the company (e.g., 'AAPL', 'GOOGL')
13
+ # """
14
+ # try:
15
+ # # Initialize Yahoo Finance News tool
16
+ # yahoo_news_tool = YahooFinanceNewsTool()
17
+
18
+ # # Get news for the company
19
+ # news_result = yahoo_news_tool.run(company_ticker)
20
+
21
+ # if not news_result:
22
+ # return f"No news found for ticker: {company_ticker}"
23
+
24
+ # # If the result is already a string, return it
25
+ # if isinstance(news_result, str):
26
+ # return news_result
27
+
28
+ # # If it's a dict or list, format it as JSON
29
+ # return json.dumps(news_result, indent=2, ensure_ascii=False)
30
+
31
+ # except Exception as e:
32
+ # return f"Error retrieving news for {company_ticker}: {str(e)}"
33
+
34
+
35
+ from crewai.tools import tool
36
+ import finnhub
37
+ import os
38
+ import json
39
+ from datetime import datetime, timedelta
40
+
41
+ @tool("get_stock_news")
42
+ def get_stock_news(company_ticker: str) -> str:
43
+ """
44
+ Retrieves the latest company news for a given stock ticker from Finnhub.
45
+ Gets news from the last 30 days to capture recent developments and market sentiment.
46
+
47
+ Args:
48
+ company_ticker: str -> the ticker symbol of the company (e.g., 'AAPL', 'GOOGL')
49
+ """
50
+ try:
51
+ # Initialize Finnhub client
52
+ finnhub_client = finnhub.Client(api_key=os.getenv("FINNHUB_API_KEY"))
53
+
54
+ # Calculate date range (last 30 days)
55
+ end_date = datetime.now()
56
+ start_date = end_date - timedelta(days=30)
57
+
58
+ # Format dates as required by Finnhub API
59
+ from_date = start_date.strftime("%Y-%m-%d")
60
+ to_date = end_date.strftime("%Y-%m-%d")
61
+
62
+ # Get company news
63
+ news_data = finnhub_client.company_news(
64
+ symbol=company_ticker,
65
+ _from=from_date,
66
+ to=to_date
67
+ )
68
+
69
+ if not news_data:
70
+ return f"No recent news found for ticker: {company_ticker} in the last 30 days"
71
+
72
+ # Format the news data using all available Finnhub fields
73
+ formatted_news = []
74
+ for article in news_data[:20]: # Analyze top 15 most recent articles
75
+ formatted_article = {
76
+ "id": article.get("id", "unknown"),
77
+ "headline": article.get("headline", "No headline available"),
78
+ "summary": article.get("summary", "No summary available"),
79
+ "category": article.get("category", "General"),
80
+ "source": article.get("source", "Unknown source"),
81
+ "url": article.get("url", ""),
82
+ "image": article.get("image", ""),
83
+ "datetime": datetime.fromtimestamp(article.get("datetime", 0)).strftime("%Y-%m-%d %H:%M:%S") if article.get("datetime") else "Unknown date",
84
+ "unix_timestamp": article.get("datetime", 0),
85
+ "related_stocks": article.get("related", []) if article.get("related") else []
86
+ }
87
+ formatted_news.append(formatted_article)
88
+
89
+ # Sort by datetime (most recent first)
90
+ formatted_news.sort(key=lambda x: x["unix_timestamp"], reverse=True)
91
+
92
+ # Analyze news categories and sources
93
+ categories = {}
94
+ sources = {}
95
+ related_companies = set()
96
+
97
+ for article in formatted_news:
98
+ # Count categories
99
+ category = article["category"]
100
+ categories[category] = categories.get(category, 0) + 1
101
+
102
+ # Count sources
103
+ source = article["source"]
104
+ sources[source] = sources.get(source, 0) + 1
105
+
106
+ # Collect related companies/stocks
107
+ if article["related_stocks"]:
108
+ related_companies.update(article["related_stocks"])
109
+
110
+ # Create comprehensive news analysis
111
+ news_analysis = {
112
+ "company_ticker": company_ticker,
113
+ "analysis_period": {
114
+ "from_date": from_date,
115
+ "to_date": to_date,
116
+ "days_analyzed": 30
117
+ },
118
+ "news_metrics": {
119
+ "total_articles_found": len(news_data),
120
+ "articles_analyzed": len(formatted_news),
121
+ "average_articles_per_day": round(len(news_data) / 30, 2)
122
+ },
123
+ "news_breakdown": {
124
+ "categories": dict(sorted(categories.items(), key=lambda x: x[1], reverse=True)),
125
+ "top_sources": dict(sorted(sources.items(), key=lambda x: x[1], reverse=True)[:5]),
126
+ "related_companies_mentioned": list(related_companies)[:10] if related_companies else []
127
+ },
128
+ "recent_articles": formatted_news
129
+ }
130
+
131
+ return json.dumps(news_analysis, indent=2, ensure_ascii=False)
132
+
133
+ except Exception as e:
134
+ return f"Error retrieving news for {company_ticker}: {str(e)}\nPlease ensure FINNHUB_API_KEY is properly set in .env file"
135
+
tools/retrieve_financial_data.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from crewai.tools import tool
2
+ import yfinance as yf
3
+ from datetime import datetime, timedelta
4
+
5
+ @tool("retrieve_financial_data")
6
+ def retrieve_financial_data(company_name: str) -> str:
7
+ """
8
+ It returns the last 6 months of daily stock Open, High, Low, Close, Volume, Dividends, Stock Splits for the specified company.
9
+
10
+ Args:
11
+ company_name: str -> the ticker symbol of the company (e.g., 'AAPL', 'GOOGL')
12
+ """
13
+ try:
14
+ # Calculate dates for last 6 months
15
+ end_date = datetime.now()
16
+ start_date = end_date - timedelta(days=180) # Approximately 6 months
17
+
18
+ # Get the data
19
+ ticker = yf.Ticker(company_name)
20
+ hist = ticker.history(start=start_date.strftime("%Y-%m-%d"),
21
+ end=end_date.strftime("%Y-%m-%d"))
22
+
23
+ if hist.empty:
24
+ return f"No data found for ticker symbol: {company_name}"
25
+
26
+ # Convert to JSON and return
27
+ return hist.to_json(date_format='iso', orient='index')
28
+
29
+ except Exception as e:
30
+ return f"Error retrieving data for {company_name}: {str(e)}"