Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -57,19 +57,9 @@ class FinancialSpacesAgent:
|
|
| 57 |
device_map="auto" if torch.cuda.is_available() else "cpu"
|
| 58 |
)
|
| 59 |
|
| 60 |
-
# 2. Mini summarizer
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
"summarization",
|
| 64 |
-
model="sshleifer/distilbart-cnn-12-6",
|
| 65 |
-
device_map="auto" if torch.cuda.is_available() else "cpu",
|
| 66 |
-
max_length=100,
|
| 67 |
-
min_length=30
|
| 68 |
-
)
|
| 69 |
-
print("✅ Summarization model loaded")
|
| 70 |
-
except:
|
| 71 |
-
self.summarizer = None
|
| 72 |
-
print("⚠️ Summarization model skipped")
|
| 73 |
|
| 74 |
# 3. Lightweight embeddings
|
| 75 |
try:
|
|
@@ -78,8 +68,8 @@ class FinancialSpacesAgent:
|
|
| 78 |
except:
|
| 79 |
self.embedder = None
|
| 80 |
|
| 81 |
-
# 4. Text generation (
|
| 82 |
-
self.text_generator = None
|
| 83 |
|
| 84 |
self.models_loaded = True
|
| 85 |
print("🎉 All models loaded successfully!")
|
|
@@ -113,7 +103,7 @@ class FinancialSpacesAgent:
|
|
| 113 |
'history_1y': stock.history(period="1y"),
|
| 114 |
'history_5y': stock.history(period="5y"),
|
| 115 |
'dividends': stock.dividends,
|
| 116 |
-
'news': getattr(stock, 'news', [])[:
|
| 117 |
}
|
| 118 |
|
| 119 |
# Calculate key metrics
|
|
@@ -184,24 +174,21 @@ class FinancialSpacesAgent:
|
|
| 184 |
|
| 185 |
# Create subplots
|
| 186 |
from plotly.subplots import make_subplots
|
| 187 |
-
import plotly.graph_objects as go
|
| 188 |
|
| 189 |
fig = make_subplots(
|
| 190 |
-
rows=2, cols=
|
| 191 |
subplot_titles=(
|
| 192 |
'📈 Price Trend',
|
| 193 |
'💰 Key Metrics',
|
| 194 |
'📊 Financial Health',
|
| 195 |
-
'📰 News Sentiment',
|
| 196 |
-
'📅 Historical Performance',
|
| 197 |
'🎯 Recommendation'
|
| 198 |
),
|
| 199 |
specs=[
|
| 200 |
-
[{"type": "scatter"}, {"type": "bar"}
|
| 201 |
-
[{"type": "
|
| 202 |
],
|
| 203 |
-
vertical_spacing=0.
|
| 204 |
-
horizontal_spacing=0.
|
| 205 |
)
|
| 206 |
|
| 207 |
# 1. Price Trend
|
|
@@ -232,6 +219,9 @@ class FinancialSpacesAgent:
|
|
| 232 |
),
|
| 233 |
row=1, col=1
|
| 234 |
)
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
# 2. Key Metrics Bar Chart
|
| 237 |
if metrics:
|
|
@@ -242,29 +232,36 @@ class FinancialSpacesAgent:
|
|
| 242 |
|
| 243 |
for key in metric_keys:
|
| 244 |
val = metrics.get(key, 0)
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
fig.add_trace(
|
| 258 |
go.Bar(
|
| 259 |
x=metric_labels,
|
| 260 |
y=metric_values,
|
| 261 |
marker_color=metric_colors,
|
| 262 |
-
text=[f'{v:.1f}' for v in metric_values],
|
| 263 |
textposition='auto',
|
| 264 |
name='Metrics'
|
| 265 |
),
|
| 266 |
row=1, col=2
|
| 267 |
)
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
# 3. Financial Health Gauge
|
| 270 |
health_score = self.calculate_health_score(metrics)
|
|
@@ -288,50 +285,10 @@ class FinancialSpacesAgent:
|
|
| 288 |
}
|
| 289 |
}
|
| 290 |
),
|
| 291 |
-
row=
|
| 292 |
)
|
| 293 |
|
| 294 |
-
# 4.
|
| 295 |
-
news = data.get('news', [])
|
| 296 |
-
if news and self.models_loaded:
|
| 297 |
-
sentiments = []
|
| 298 |
-
for item in news[:5]:
|
| 299 |
-
title = item.get('title', '')
|
| 300 |
-
if title:
|
| 301 |
-
try:
|
| 302 |
-
sentiment = self.sentiment_model(title[:100])[0]
|
| 303 |
-
sentiments.append(1 if sentiment['label'] == 'positive' else -1 if sentiment['label'] == 'negative' else 0)
|
| 304 |
-
except:
|
| 305 |
-
sentiments.append(0)
|
| 306 |
-
|
| 307 |
-
if sentiments:
|
| 308 |
-
fig.add_trace(
|
| 309 |
-
go.Bar(
|
| 310 |
-
x=['News 1', 'News 2', 'News 3', 'News 4', 'News 5'][:len(sentiments)],
|
| 311 |
-
y=sentiments,
|
| 312 |
-
marker_color=['green' if s > 0 else 'red' if s < 0 else 'gray' for s in sentiments],
|
| 313 |
-
name='Sentiment'
|
| 314 |
-
),
|
| 315 |
-
row=2, col=1
|
| 316 |
-
)
|
| 317 |
-
|
| 318 |
-
# 5. Historical Returns
|
| 319 |
-
if not history.empty and len(history) > 20:
|
| 320 |
-
returns = history['Close'].pct_change().dropna()
|
| 321 |
-
fig.add_trace(
|
| 322 |
-
go.Scatter(
|
| 323 |
-
x=returns.index,
|
| 324 |
-
y=returns * 100,
|
| 325 |
-
mode='lines',
|
| 326 |
-
name='Daily Returns %',
|
| 327 |
-
line=dict(color='#6BAA75', width=1),
|
| 328 |
-
fill='tozeroy',
|
| 329 |
-
fillcolor='rgba(107, 170, 117, 0.2)'
|
| 330 |
-
),
|
| 331 |
-
row=2, col=2
|
| 332 |
-
)
|
| 333 |
-
|
| 334 |
-
# 6. Recommendation Indicator
|
| 335 |
recommendation = self.generate_recommendation(metrics)
|
| 336 |
color_map = {
|
| 337 |
'Strong Buy': 'green',
|
|
@@ -343,18 +300,18 @@ class FinancialSpacesAgent:
|
|
| 343 |
|
| 344 |
fig.add_trace(
|
| 345 |
go.Indicator(
|
| 346 |
-
mode="
|
| 347 |
value=health_score,
|
| 348 |
title={'text': f"Recommendation: {recommendation}"},
|
| 349 |
-
|
| 350 |
-
domain={'row':
|
| 351 |
),
|
| 352 |
-
row=2, col=
|
| 353 |
)
|
| 354 |
|
| 355 |
# Update layout
|
| 356 |
fig.update_layout(
|
| 357 |
-
height=
|
| 358 |
showlegend=True,
|
| 359 |
title_text=f"📊 Financial Dashboard - {ticker}",
|
| 360 |
title_font_size=20,
|
|
@@ -368,11 +325,16 @@ class FinancialSpacesAgent:
|
|
| 368 |
# Return simple figure
|
| 369 |
fig = go.Figure()
|
| 370 |
fig.add_annotation(
|
| 371 |
-
text="Dashboard generation failed",
|
| 372 |
xref="paper", yref="paper",
|
| 373 |
x=0.5, y=0.5,
|
| 374 |
showarrow=False,
|
| 375 |
-
font_size=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
)
|
| 377 |
return fig
|
| 378 |
|
|
@@ -443,90 +405,59 @@ class FinancialSpacesAgent:
|
|
| 443 |
def generate_ai_analysis(self, ticker, data):
|
| 444 |
"""Generate AI analysis of company"""
|
| 445 |
try:
|
| 446 |
-
if not self.models_loaded:
|
| 447 |
-
return self.generate_basic_analysis(ticker, data)
|
| 448 |
-
|
| 449 |
metrics = data.get('metrics', {})
|
| 450 |
|
| 451 |
-
# Create analysis
|
| 452 |
-
|
| 453 |
-
|
| 454 |
|
| 455 |
-
|
| 456 |
"""
|
| 457 |
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
|
| 462 |
-
|
|
|
|
|
|
|
|
|
|
| 463 |
|
| 464 |
-
#
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
**Recommendation:** {self.generate_recommendation(metrics)}
|
| 488 |
-
|
| 489 |
-
*Note: AI analysis is for educational purposes only.*
|
| 490 |
-
"""
|
| 491 |
-
|
| 492 |
-
except:
|
| 493 |
-
return self.generate_basic_analysis(ticker, data)
|
| 494 |
|
| 495 |
except Exception as e:
|
| 496 |
return f"Error in AI analysis: {str(e)}"
|
| 497 |
|
| 498 |
-
def generate_basic_analysis(self, ticker, data):
|
| 499 |
-
"""Generate basic analysis when AI models aren't available"""
|
| 500 |
-
metrics = data.get('metrics', {})
|
| 501 |
-
|
| 502 |
-
analysis = f"""
|
| 503 |
-
## 📊 Basic Analysis for {ticker}
|
| 504 |
-
|
| 505 |
-
### Key Metrics:
|
| 506 |
-
"""
|
| 507 |
-
|
| 508 |
-
for key, value in metrics.items():
|
| 509 |
-
if isinstance(value, (int, float)):
|
| 510 |
-
if 'margin' in key or 'roe' in key:
|
| 511 |
-
analysis += f"• {key.replace('_', ' ').title()}: **{value:.1f}%**\n"
|
| 512 |
-
else:
|
| 513 |
-
analysis += f"• {key.replace('_', ' ').title()}: **{value:.2f}**\n"
|
| 514 |
-
|
| 515 |
-
analysis += f"""
|
| 516 |
-
|
| 517 |
-
### Financial Health Score: {self.calculate_health_score(metrics)}/10
|
| 518 |
-
### Recommendation: {self.generate_recommendation(metrics)}
|
| 519 |
-
|
| 520 |
-
---
|
| 521 |
-
*Data Source: Yahoo Finance*
|
| 522 |
-
"""
|
| 523 |
-
|
| 524 |
-
return analysis
|
| 525 |
-
|
| 526 |
def create_metrics_table(self, metrics):
|
| 527 |
"""Create a nice HTML table for metrics"""
|
| 528 |
if not metrics:
|
| 529 |
-
return "No metrics available"
|
| 530 |
|
| 531 |
table_html = """
|
| 532 |
<style>
|
|
@@ -535,12 +466,14 @@ class FinancialSpacesAgent:
|
|
| 535 |
border-collapse: collapse;
|
| 536 |
margin: 10px 0;
|
| 537 |
font-family: Arial, sans-serif;
|
|
|
|
| 538 |
}
|
| 539 |
.metrics-table th {
|
| 540 |
background-color: #2E86AB;
|
| 541 |
color: white;
|
| 542 |
padding: 10px;
|
| 543 |
text-align: left;
|
|
|
|
| 544 |
}
|
| 545 |
.metrics-table td {
|
| 546 |
padding: 8px 10px;
|
|
@@ -549,9 +482,9 @@ class FinancialSpacesAgent:
|
|
| 549 |
.metrics-table tr:hover {
|
| 550 |
background-color: #f5f5f5;
|
| 551 |
}
|
| 552 |
-
.good { color:
|
| 553 |
-
.warning { color:
|
| 554 |
-
.poor { color:
|
| 555 |
</style>
|
| 556 |
|
| 557 |
<table class="metrics-table">
|
|
@@ -565,10 +498,12 @@ class FinancialSpacesAgent:
|
|
| 565 |
for key, value in metrics.items():
|
| 566 |
if isinstance(value, (int, float)):
|
| 567 |
# Format value
|
| 568 |
-
if 'margin' in key or 'roe' in key:
|
| 569 |
display_value = f"{value:.1f}%"
|
| 570 |
-
|
| 571 |
display_value = f"{value:.2f}"
|
|
|
|
|
|
|
| 572 |
|
| 573 |
# Determine assessment
|
| 574 |
assessment = ""
|
|
@@ -581,9 +516,12 @@ class FinancialSpacesAgent:
|
|
| 581 |
elif value > 8:
|
| 582 |
assessment = "Good"
|
| 583 |
css_class = "good"
|
| 584 |
-
|
| 585 |
-
assessment = "
|
| 586 |
css_class = "warning"
|
|
|
|
|
|
|
|
|
|
| 587 |
|
| 588 |
elif key == 'net_margin':
|
| 589 |
if value > 15:
|
|
@@ -591,9 +529,13 @@ class FinancialSpacesAgent:
|
|
| 591 |
css_class = "good"
|
| 592 |
elif value > 5:
|
| 593 |
assessment = "Good"
|
| 594 |
-
|
|
|
|
| 595 |
assessment = "Low"
|
| 596 |
css_class = "warning"
|
|
|
|
|
|
|
|
|
|
| 597 |
|
| 598 |
elif key == 'current_ratio':
|
| 599 |
if value > 2:
|
|
@@ -601,6 +543,7 @@ class FinancialSpacesAgent:
|
|
| 601 |
css_class = "good"
|
| 602 |
elif value > 1:
|
| 603 |
assessment = "Adequate"
|
|
|
|
| 604 |
else:
|
| 605 |
assessment = "Risk"
|
| 606 |
css_class = "warning"
|
|
@@ -611,10 +554,32 @@ class FinancialSpacesAgent:
|
|
| 611 |
css_class = "good"
|
| 612 |
elif value < 1:
|
| 613 |
assessment = "Moderate"
|
|
|
|
|
|
|
|
|
|
| 614 |
else:
|
| 615 |
-
assessment = "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
css_class = "warning"
|
| 617 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
table_html += f"""
|
| 619 |
<tr>
|
| 620 |
<td>{key.replace('_', ' ').title()}</td>
|
|
@@ -629,7 +594,7 @@ class FinancialSpacesAgent:
|
|
| 629 |
# Initialize agent
|
| 630 |
agent = FinancialSpacesAgent()
|
| 631 |
|
| 632 |
-
# Create Gradio interface
|
| 633 |
def analyze_company_gradio(ticker):
|
| 634 |
"""Main Gradio analysis function"""
|
| 635 |
if not ticker:
|
|
@@ -663,6 +628,7 @@ def analyze_company_gradio(ticker):
|
|
| 663 |
**Current Price:** ${info.get('currentPrice', 'N/A')}
|
| 664 |
**52W High:** ${info.get('fiftyTwoWeekHigh', 'N/A')}
|
| 665 |
**52W Low:** ${info.get('fiftyTwoWeekLow', 'N/A')}
|
|
|
|
| 666 |
"""
|
| 667 |
|
| 668 |
return analysis, dashboard, metrics_table, basic_info
|
|
@@ -682,7 +648,7 @@ def compare_companies_gradio(ticker1, ticker2):
|
|
| 682 |
metrics2 = data2.get('metrics', {})
|
| 683 |
|
| 684 |
# Create comparison table
|
| 685 |
-
comparison_metrics = ['roe', 'net_margin', 'current_ratio', 'debt_to_equity', 'pe_ratio']
|
| 686 |
|
| 687 |
comparison_data = []
|
| 688 |
for metric in comparison_metrics:
|
|
@@ -696,36 +662,52 @@ def compare_companies_gradio(ticker1, ticker2):
|
|
| 696 |
})
|
| 697 |
|
| 698 |
# Create comparison chart
|
| 699 |
-
import plotly.graph_objects as go
|
| 700 |
-
|
| 701 |
fig = go.Figure()
|
| 702 |
|
| 703 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 704 |
val1 = metrics1.get(metric, 0)
|
| 705 |
val2 = metrics2.get(metric, 0)
|
| 706 |
|
| 707 |
if isinstance(val1, (int, float)) and isinstance(val2, (int, float)):
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
|
| 722 |
# Create comparison text
|
| 723 |
comparison_text = f"## ⚖️ {ticker1} vs {ticker2}\n\n"
|
| 724 |
comparison_text += "| Metric | " + ticker1 + " | " + ticker2 + " |\n"
|
| 725 |
comparison_text += "|--------|" + "-" * (len(ticker1) + 2) + "|" + "-" * (len(ticker2) + 2) + "|\n"
|
| 726 |
|
| 727 |
-
for
|
| 728 |
-
comparison_text += f"| {
|
| 729 |
|
| 730 |
return comparison_text, fig
|
| 731 |
|
|
@@ -763,12 +745,14 @@ with gr.Blocks(
|
|
| 763 |
|
| 764 |
gr.Markdown("### 📊 Popular Companies")
|
| 765 |
with gr.Row():
|
| 766 |
-
popular_tickers = ["AAPL", "MSFT", "GOOGL", "TSLA"
|
| 767 |
for t in popular_tickers:
|
| 768 |
-
gr.Button(t, size="sm")
|
| 769 |
-
|
|
|
|
|
|
|
| 770 |
inputs=None,
|
| 771 |
-
outputs=
|
| 772 |
)
|
| 773 |
|
| 774 |
with gr.Column(scale=2):
|
|
@@ -782,7 +766,7 @@ with gr.Blocks(
|
|
| 782 |
metrics_output = gr.HTML(label="Key Metrics")
|
| 783 |
|
| 784 |
with gr.Row():
|
| 785 |
-
analysis_output = gr.Markdown(label="AI Analysis Report"
|
| 786 |
|
| 787 |
with gr.TabItem("⚖️ Compare Companies"):
|
| 788 |
with gr.Row():
|
|
@@ -899,7 +883,7 @@ with gr.Blocks(
|
|
| 899 |
outputs=[comparison_output, comparison_plot]
|
| 900 |
)
|
| 901 |
|
| 902 |
-
# Auto-load example
|
| 903 |
demo.load(
|
| 904 |
lambda: analyze_company_gradio("AAPL"),
|
| 905 |
inputs=None,
|
|
@@ -912,6 +896,6 @@ if __name__ == "__main__":
|
|
| 912 |
server_name="0.0.0.0",
|
| 913 |
server_port=7860,
|
| 914 |
share=False,
|
| 915 |
-
|
| 916 |
-
|
| 917 |
)
|
|
|
|
| 57 |
device_map="auto" if torch.cuda.is_available() else "cpu"
|
| 58 |
)
|
| 59 |
|
| 60 |
+
# 2. Mini summarizer (skip to save memory)
|
| 61 |
+
self.summarizer = None
|
| 62 |
+
print("⚠️ Summarization model skipped to save memory")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
# 3. Lightweight embeddings
|
| 65 |
try:
|
|
|
|
| 68 |
except:
|
| 69 |
self.embedder = None
|
| 70 |
|
| 71 |
+
# 4. Text generation (skip to save memory)
|
| 72 |
+
self.text_generator = None
|
| 73 |
|
| 74 |
self.models_loaded = True
|
| 75 |
print("🎉 All models loaded successfully!")
|
|
|
|
| 103 |
'history_1y': stock.history(period="1y"),
|
| 104 |
'history_5y': stock.history(period="5y"),
|
| 105 |
'dividends': stock.dividends,
|
| 106 |
+
'news': getattr(stock, 'news', [])[:5] # Limit news to save memory
|
| 107 |
}
|
| 108 |
|
| 109 |
# Calculate key metrics
|
|
|
|
| 174 |
|
| 175 |
# Create subplots
|
| 176 |
from plotly.subplots import make_subplots
|
|
|
|
| 177 |
|
| 178 |
fig = make_subplots(
|
| 179 |
+
rows=2, cols=2,
|
| 180 |
subplot_titles=(
|
| 181 |
'📈 Price Trend',
|
| 182 |
'💰 Key Metrics',
|
| 183 |
'📊 Financial Health',
|
|
|
|
|
|
|
| 184 |
'🎯 Recommendation'
|
| 185 |
),
|
| 186 |
specs=[
|
| 187 |
+
[{"type": "scatter"}, {"type": "bar"}],
|
| 188 |
+
[{"type": "indicator"}, {"type": "indicator"}]
|
| 189 |
],
|
| 190 |
+
vertical_spacing=0.2,
|
| 191 |
+
horizontal_spacing=0.15
|
| 192 |
)
|
| 193 |
|
| 194 |
# 1. Price Trend
|
|
|
|
| 219 |
),
|
| 220 |
row=1, col=1
|
| 221 |
)
|
| 222 |
+
|
| 223 |
+
fig.update_xaxes(title_text="Date", row=1, col=1)
|
| 224 |
+
fig.update_yaxes(title_text="Price ($)", row=1, col=1)
|
| 225 |
|
| 226 |
# 2. Key Metrics Bar Chart
|
| 227 |
if metrics:
|
|
|
|
| 232 |
|
| 233 |
for key in metric_keys:
|
| 234 |
val = metrics.get(key, 0)
|
| 235 |
+
if isinstance(val, (int, float)):
|
| 236 |
+
metric_values.append(val)
|
| 237 |
+
|
| 238 |
+
# Color coding
|
| 239 |
+
if key == 'roe':
|
| 240 |
+
metric_colors.append('#2E86AB' if val > 15 else '#A23B72' if val > 8 else '#C73E1D')
|
| 241 |
+
elif key == 'net_margin':
|
| 242 |
+
metric_colors.append('#2E86AB' if val > 10 else '#A23B72' if val > 5 else '#C73E1D')
|
| 243 |
+
elif key == 'current_ratio':
|
| 244 |
+
metric_colors.append('#2E86AB' if val > 1.5 else '#A23B72' if val > 1 else '#C73E1D')
|
| 245 |
+
elif key == 'debt_to_equity':
|
| 246 |
+
metric_colors.append('#2E86AB' if val < 1 else '#A23B72' if val < 2 else '#C73E1D')
|
| 247 |
+
else:
|
| 248 |
+
metric_values.append(0)
|
| 249 |
+
metric_colors.append('#CCCCCC')
|
| 250 |
|
| 251 |
fig.add_trace(
|
| 252 |
go.Bar(
|
| 253 |
x=metric_labels,
|
| 254 |
y=metric_values,
|
| 255 |
marker_color=metric_colors,
|
| 256 |
+
text=[f'{v:.1f}' if isinstance(v, (int, float)) else 'N/A' for v in metric_values],
|
| 257 |
textposition='auto',
|
| 258 |
name='Metrics'
|
| 259 |
),
|
| 260 |
row=1, col=2
|
| 261 |
)
|
| 262 |
+
|
| 263 |
+
fig.update_xaxes(title_text="Metrics", row=1, col=2, tickangle=45)
|
| 264 |
+
fig.update_yaxes(title_text="Value", row=1, col=2)
|
| 265 |
|
| 266 |
# 3. Financial Health Gauge
|
| 267 |
health_score = self.calculate_health_score(metrics)
|
|
|
|
| 285 |
}
|
| 286 |
}
|
| 287 |
),
|
| 288 |
+
row=2, col=1
|
| 289 |
)
|
| 290 |
|
| 291 |
+
# 4. Recommendation Indicator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
recommendation = self.generate_recommendation(metrics)
|
| 293 |
color_map = {
|
| 294 |
'Strong Buy': 'green',
|
|
|
|
| 300 |
|
| 301 |
fig.add_trace(
|
| 302 |
go.Indicator(
|
| 303 |
+
mode="number",
|
| 304 |
value=health_score,
|
| 305 |
title={'text': f"Recommendation: {recommendation}"},
|
| 306 |
+
number={'font': {'color': color_map.get(recommendation, 'black'), 'size': 24}},
|
| 307 |
+
domain={'row': 2, 'column': 2}
|
| 308 |
),
|
| 309 |
+
row=2, col=2
|
| 310 |
)
|
| 311 |
|
| 312 |
# Update layout
|
| 313 |
fig.update_layout(
|
| 314 |
+
height=700,
|
| 315 |
showlegend=True,
|
| 316 |
title_text=f"📊 Financial Dashboard - {ticker}",
|
| 317 |
title_font_size=20,
|
|
|
|
| 325 |
# Return simple figure
|
| 326 |
fig = go.Figure()
|
| 327 |
fig.add_annotation(
|
| 328 |
+
text="Dashboard generation failed<br>Try a different ticker",
|
| 329 |
xref="paper", yref="paper",
|
| 330 |
x=0.5, y=0.5,
|
| 331 |
showarrow=False,
|
| 332 |
+
font_size=16,
|
| 333 |
+
align="center"
|
| 334 |
+
)
|
| 335 |
+
fig.update_layout(
|
| 336 |
+
title=f"Error for {ticker}",
|
| 337 |
+
height=400
|
| 338 |
)
|
| 339 |
return fig
|
| 340 |
|
|
|
|
| 405 |
def generate_ai_analysis(self, ticker, data):
|
| 406 |
"""Generate AI analysis of company"""
|
| 407 |
try:
|
|
|
|
|
|
|
|
|
|
| 408 |
metrics = data.get('metrics', {})
|
| 409 |
|
| 410 |
+
# Create analysis
|
| 411 |
+
analysis = f"""
|
| 412 |
+
## 🤖 AI Analysis for {ticker}
|
| 413 |
|
| 414 |
+
### 📊 Key Metrics Analysis:
|
| 415 |
"""
|
| 416 |
|
| 417 |
+
# Add metric analysis
|
| 418 |
+
metric_analysis = {
|
| 419 |
+
'roe': "Return on Equity measures how effectively management is using shareholders' funds.",
|
| 420 |
+
'net_margin': "Net Profit Margin shows what percentage of revenue is actual profit.",
|
| 421 |
+
'current_ratio': "Current Ratio indicates short-term liquidity and ability to pay debts.",
|
| 422 |
+
'debt_to_equity': "Debt-to-Equity ratio shows the company's financial leverage."
|
| 423 |
+
}
|
| 424 |
|
| 425 |
+
for key, desc in metric_analysis.items():
|
| 426 |
+
val = metrics.get(key, 'N/A')
|
| 427 |
+
if isinstance(val, (int, float)):
|
| 428 |
+
analysis += f"**{key.replace('_', ' ').title()}**: {val:.1f} - {desc}\n\n"
|
| 429 |
|
| 430 |
+
# Add overall assessment
|
| 431 |
+
health_score = self.calculate_health_score(metrics)
|
| 432 |
+
recommendation = self.generate_recommendation(metrics)
|
| 433 |
+
|
| 434 |
+
analysis += f"""
|
| 435 |
+
### 🏥 Financial Health Score: **{health_score}/10**
|
| 436 |
+
|
| 437 |
+
### 🎯 Recommendation: **{recommendation}**
|
| 438 |
+
|
| 439 |
+
### 📈 Market Outlook:
|
| 440 |
+
"""
|
| 441 |
+
|
| 442 |
+
# Add simple outlook based on metrics
|
| 443 |
+
if health_score >= 7:
|
| 444 |
+
analysis += "The company shows strong financial fundamentals with good profitability and manageable leverage."
|
| 445 |
+
elif health_score >= 5:
|
| 446 |
+
analysis += "The company has average financial health with room for improvement in some areas."
|
| 447 |
+
else:
|
| 448 |
+
analysis += "The company shows concerning financial metrics that warrant caution."
|
| 449 |
+
|
| 450 |
+
analysis += "\n\n*Note: This analysis is AI-generated for educational purposes only.*"
|
| 451 |
+
|
| 452 |
+
return analysis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
|
| 454 |
except Exception as e:
|
| 455 |
return f"Error in AI analysis: {str(e)}"
|
| 456 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
def create_metrics_table(self, metrics):
|
| 458 |
"""Create a nice HTML table for metrics"""
|
| 459 |
if not metrics:
|
| 460 |
+
return "<p>No metrics available</p>"
|
| 461 |
|
| 462 |
table_html = """
|
| 463 |
<style>
|
|
|
|
| 466 |
border-collapse: collapse;
|
| 467 |
margin: 10px 0;
|
| 468 |
font-family: Arial, sans-serif;
|
| 469 |
+
font-size: 14px;
|
| 470 |
}
|
| 471 |
.metrics-table th {
|
| 472 |
background-color: #2E86AB;
|
| 473 |
color: white;
|
| 474 |
padding: 10px;
|
| 475 |
text-align: left;
|
| 476 |
+
font-weight: bold;
|
| 477 |
}
|
| 478 |
.metrics-table td {
|
| 479 |
padding: 8px 10px;
|
|
|
|
| 482 |
.metrics-table tr:hover {
|
| 483 |
background-color: #f5f5f5;
|
| 484 |
}
|
| 485 |
+
.good { color: #28a745; font-weight: bold; }
|
| 486 |
+
.warning { color: #ffc107; font-weight: bold; }
|
| 487 |
+
.poor { color: #dc3545; font-weight: bold; }
|
| 488 |
</style>
|
| 489 |
|
| 490 |
<table class="metrics-table">
|
|
|
|
| 498 |
for key, value in metrics.items():
|
| 499 |
if isinstance(value, (int, float)):
|
| 500 |
# Format value
|
| 501 |
+
if 'margin' in key or 'roe' in key or 'yield' in key:
|
| 502 |
display_value = f"{value:.1f}%"
|
| 503 |
+
elif 'ratio' in key or 'beta' in key:
|
| 504 |
display_value = f"{value:.2f}"
|
| 505 |
+
else:
|
| 506 |
+
display_value = f"{value:,.0f}" if value > 1000 else f"{value:.1f}"
|
| 507 |
|
| 508 |
# Determine assessment
|
| 509 |
assessment = ""
|
|
|
|
| 516 |
elif value > 8:
|
| 517 |
assessment = "Good"
|
| 518 |
css_class = "good"
|
| 519 |
+
elif value > 0:
|
| 520 |
+
assessment = "Poor"
|
| 521 |
css_class = "warning"
|
| 522 |
+
else:
|
| 523 |
+
assessment = "Negative"
|
| 524 |
+
css_class = "poor"
|
| 525 |
|
| 526 |
elif key == 'net_margin':
|
| 527 |
if value > 15:
|
|
|
|
| 529 |
css_class = "good"
|
| 530 |
elif value > 5:
|
| 531 |
assessment = "Good"
|
| 532 |
+
css_class = "good"
|
| 533 |
+
elif value > 0:
|
| 534 |
assessment = "Low"
|
| 535 |
css_class = "warning"
|
| 536 |
+
else:
|
| 537 |
+
assessment = "Negative"
|
| 538 |
+
css_class = "poor"
|
| 539 |
|
| 540 |
elif key == 'current_ratio':
|
| 541 |
if value > 2:
|
|
|
|
| 543 |
css_class = "good"
|
| 544 |
elif value > 1:
|
| 545 |
assessment = "Adequate"
|
| 546 |
+
css_class = "good"
|
| 547 |
else:
|
| 548 |
assessment = "Risk"
|
| 549 |
css_class = "warning"
|
|
|
|
| 554 |
css_class = "good"
|
| 555 |
elif value < 1:
|
| 556 |
assessment = "Moderate"
|
| 557 |
+
elif value < 2:
|
| 558 |
+
assessment = "High"
|
| 559 |
+
css_class = "warning"
|
| 560 |
else:
|
| 561 |
+
assessment = "Very High"
|
| 562 |
+
css_class = "poor"
|
| 563 |
+
|
| 564 |
+
elif key == 'pe_ratio' and isinstance(value, (int, float)):
|
| 565 |
+
if value < 15:
|
| 566 |
+
assessment = "Undervalued"
|
| 567 |
+
css_class = "good"
|
| 568 |
+
elif value < 25:
|
| 569 |
+
assessment = "Fair"
|
| 570 |
+
else:
|
| 571 |
+
assessment = "Overvalued"
|
| 572 |
css_class = "warning"
|
| 573 |
|
| 574 |
+
elif key == 'dividend_yield' and isinstance(value, (int, float)):
|
| 575 |
+
if value > 4:
|
| 576 |
+
assessment = "High Yield"
|
| 577 |
+
css_class = "good"
|
| 578 |
+
elif value > 2:
|
| 579 |
+
assessment = "Moderate"
|
| 580 |
+
else:
|
| 581 |
+
assessment = "Low"
|
| 582 |
+
|
| 583 |
table_html += f"""
|
| 584 |
<tr>
|
| 585 |
<td>{key.replace('_', ' ').title()}</td>
|
|
|
|
| 594 |
# Initialize agent
|
| 595 |
agent = FinancialSpacesAgent()
|
| 596 |
|
| 597 |
+
# Create Gradio interface functions
|
| 598 |
def analyze_company_gradio(ticker):
|
| 599 |
"""Main Gradio analysis function"""
|
| 600 |
if not ticker:
|
|
|
|
| 628 |
**Current Price:** ${info.get('currentPrice', 'N/A')}
|
| 629 |
**52W High:** ${info.get('fiftyTwoWeekHigh', 'N/A')}
|
| 630 |
**52W Low:** ${info.get('fiftyTwoWeekLow', 'N/A')}
|
| 631 |
+
**Beta:** {info.get('beta', 'N/A')}
|
| 632 |
"""
|
| 633 |
|
| 634 |
return analysis, dashboard, metrics_table, basic_info
|
|
|
|
| 648 |
metrics2 = data2.get('metrics', {})
|
| 649 |
|
| 650 |
# Create comparison table
|
| 651 |
+
comparison_metrics = ['roe', 'net_margin', 'current_ratio', 'debt_to_equity', 'pe_ratio', 'dividend_yield']
|
| 652 |
|
| 653 |
comparison_data = []
|
| 654 |
for metric in comparison_metrics:
|
|
|
|
| 662 |
})
|
| 663 |
|
| 664 |
# Create comparison chart
|
|
|
|
|
|
|
| 665 |
fig = go.Figure()
|
| 666 |
|
| 667 |
+
# Get values for first 4 metrics
|
| 668 |
+
metric_keys = comparison_metrics[:4]
|
| 669 |
+
ticker1_vals = []
|
| 670 |
+
ticker2_vals = []
|
| 671 |
+
|
| 672 |
+
for metric in metric_keys:
|
| 673 |
val1 = metrics1.get(metric, 0)
|
| 674 |
val2 = metrics2.get(metric, 0)
|
| 675 |
|
| 676 |
if isinstance(val1, (int, float)) and isinstance(val2, (int, float)):
|
| 677 |
+
ticker1_vals.append(val1)
|
| 678 |
+
ticker2_vals.append(val2)
|
| 679 |
+
|
| 680 |
+
if ticker1_vals and ticker2_vals:
|
| 681 |
+
x_labels = [m.replace('_', ' ').title() for m in metric_keys[:len(ticker1_vals)]]
|
| 682 |
+
|
| 683 |
+
fig.add_trace(go.Bar(
|
| 684 |
+
name=ticker1,
|
| 685 |
+
x=x_labels,
|
| 686 |
+
y=ticker1_vals,
|
| 687 |
+
marker_color='#2E86AB'
|
| 688 |
+
))
|
| 689 |
+
|
| 690 |
+
fig.add_trace(go.Bar(
|
| 691 |
+
name=ticker2,
|
| 692 |
+
x=x_labels,
|
| 693 |
+
y=ticker2_vals,
|
| 694 |
+
marker_color='#F18F01'
|
| 695 |
+
))
|
| 696 |
+
|
| 697 |
+
fig.update_layout(
|
| 698 |
+
title=f"{ticker1} vs {ticker2} - Key Metrics Comparison",
|
| 699 |
+
barmode='group',
|
| 700 |
+
template="plotly_white",
|
| 701 |
+
height=400
|
| 702 |
+
)
|
| 703 |
|
| 704 |
# Create comparison text
|
| 705 |
comparison_text = f"## ⚖️ {ticker1} vs {ticker2}\n\n"
|
| 706 |
comparison_text += "| Metric | " + ticker1 + " | " + ticker2 + " |\n"
|
| 707 |
comparison_text += "|--------|" + "-" * (len(ticker1) + 2) + "|" + "-" * (len(ticker2) + 2) + "|\n"
|
| 708 |
|
| 709 |
+
for comp in comparison_data:
|
| 710 |
+
comparison_text += f"| {comp['Metric']} | {comp[ticker1]} | {comp[ticker2]} |\n"
|
| 711 |
|
| 712 |
return comparison_text, fig
|
| 713 |
|
|
|
|
| 745 |
|
| 746 |
gr.Markdown("### 📊 Popular Companies")
|
| 747 |
with gr.Row():
|
| 748 |
+
popular_tickers = ["AAPL", "MSFT", "GOOGL", "TSLA"]
|
| 749 |
for t in popular_tickers:
|
| 750 |
+
btn = gr.Button(t, size="sm")
|
| 751 |
+
# FIXED: Only update the ticker input, not other outputs
|
| 752 |
+
btn.click(
|
| 753 |
+
lambda t=t: t,
|
| 754 |
inputs=None,
|
| 755 |
+
outputs=ticker_input
|
| 756 |
)
|
| 757 |
|
| 758 |
with gr.Column(scale=2):
|
|
|
|
| 766 |
metrics_output = gr.HTML(label="Key Metrics")
|
| 767 |
|
| 768 |
with gr.Row():
|
| 769 |
+
analysis_output = gr.Markdown(label="AI Analysis Report")
|
| 770 |
|
| 771 |
with gr.TabItem("⚖️ Compare Companies"):
|
| 772 |
with gr.Row():
|
|
|
|
| 883 |
outputs=[comparison_output, comparison_plot]
|
| 884 |
)
|
| 885 |
|
| 886 |
+
# Auto-load example on startup
|
| 887 |
demo.load(
|
| 888 |
lambda: analyze_company_gradio("AAPL"),
|
| 889 |
inputs=None,
|
|
|
|
| 896 |
server_name="0.0.0.0",
|
| 897 |
server_port=7860,
|
| 898 |
share=False,
|
| 899 |
+
show_error=True,
|
| 900 |
+
debug=False # Disable debug mode for production
|
| 901 |
)
|