mcli-framework 7.6.0__py3-none-any.whl → 7.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcli-framework might be problematic. Click here for more details.

Files changed (49) hide show
  1. mcli/app/commands_cmd.py +51 -39
  2. mcli/app/main.py +10 -2
  3. mcli/app/model_cmd.py +1 -1
  4. mcli/lib/custom_commands.py +4 -10
  5. mcli/ml/api/app.py +1 -5
  6. mcli/ml/dashboard/app.py +2 -2
  7. mcli/ml/dashboard/app_integrated.py +168 -116
  8. mcli/ml/dashboard/app_supabase.py +7 -3
  9. mcli/ml/dashboard/app_training.py +3 -6
  10. mcli/ml/dashboard/components/charts.py +74 -115
  11. mcli/ml/dashboard/components/metrics.py +24 -44
  12. mcli/ml/dashboard/components/tables.py +32 -40
  13. mcli/ml/dashboard/overview.py +102 -78
  14. mcli/ml/dashboard/pages/cicd.py +103 -56
  15. mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
  16. mcli/ml/dashboard/pages/gravity_viz.py +374 -313
  17. mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
  18. mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
  19. mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
  20. mcli/ml/dashboard/pages/test_portfolio.py +153 -121
  21. mcli/ml/dashboard/pages/trading.py +238 -169
  22. mcli/ml/dashboard/pages/workflows.py +129 -84
  23. mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
  24. mcli/ml/dashboard/utils.py +24 -21
  25. mcli/ml/dashboard/warning_suppression.py +6 -4
  26. mcli/ml/database/session.py +16 -5
  27. mcli/ml/mlops/pipeline_orchestrator.py +1 -3
  28. mcli/ml/predictions/monte_carlo.py +6 -18
  29. mcli/ml/trading/alpaca_client.py +95 -96
  30. mcli/ml/trading/migrations.py +76 -40
  31. mcli/ml/trading/models.py +78 -60
  32. mcli/ml/trading/paper_trading.py +92 -74
  33. mcli/ml/trading/risk_management.py +106 -85
  34. mcli/ml/trading/trading_service.py +155 -110
  35. mcli/ml/training/train_model.py +1 -3
  36. mcli/self/self_cmd.py +71 -57
  37. mcli/workflow/daemon/daemon.py +2 -0
  38. mcli/workflow/model_service/openai_adapter.py +6 -2
  39. mcli/workflow/politician_trading/models.py +6 -2
  40. mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
  41. mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
  42. mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
  43. mcli/workflow/politician_trading/seed_database.py +70 -89
  44. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
  45. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +49 -49
  46. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
  47. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
  48. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
  49. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/top_level.txt +0 -0
@@ -1,55 +1,61 @@
1
1
  """Enhanced Predictions Dashboard with Interactive Features - REAL DATA"""
2
2
 
3
- import streamlit as st
4
- import pandas as pd
3
+ import os
4
+ from datetime import datetime, timedelta
5
+ from typing import Dict, List, Optional
6
+
5
7
  import numpy as np
8
+ import pandas as pd
6
9
  import plotly.express as px
7
10
  import plotly.graph_objects as go
11
+ import streamlit as st
8
12
  from plotly.subplots import make_subplots
9
- from datetime import datetime, timedelta
10
- from typing import Optional, Dict, List
11
- import os
12
13
 
13
14
  # Import components
14
15
  try:
15
- from ..components.metrics import display_kpi_row, display_status_badge
16
16
  from ..components.charts import create_timeline_chart, render_chart
17
+ from ..components.metrics import display_kpi_row, display_status_badge
17
18
  from ..components.tables import display_filterable_dataframe, export_dataframe
18
19
  except ImportError:
19
20
  # Fallback for when imported outside package context
20
- from components.metrics import display_kpi_row, display_status_badge
21
21
  from components.charts import create_timeline_chart, render_chart
22
+ from components.metrics import display_kpi_row, display_status_badge
22
23
  from components.tables import display_filterable_dataframe, export_dataframe
23
24
 
24
25
  # Import real data functions from utils
25
26
  try:
26
27
  from ..utils import (
27
- get_supabase_client,
28
28
  get_disclosures_data,
29
29
  get_politician_names,
30
30
  get_politician_trading_history,
31
+ get_supabase_client,
31
32
  )
33
+
32
34
  HAS_REAL_DATA = True
33
35
  except ImportError:
34
36
  HAS_REAL_DATA = False
35
37
  st.warning("⚠️ Real data functions not available. Using fallback mode.")
36
38
 
39
+
37
40
  # Fallback functions for missing imports
38
41
  def run_ml_pipeline(df_disclosures):
39
42
  """Fallback ML pipeline function"""
40
43
  return df_disclosures
41
44
 
45
+
42
46
  def engineer_features(df):
43
47
  """Fallback feature engineering function"""
44
48
  return df
45
49
 
50
+
46
51
  def generate_production_prediction(df, features, trading_history):
47
52
  """Fallback prediction function"""
48
53
  import random
54
+
49
55
  return {
50
- 'predicted_return': random.uniform(-0.1, 0.1),
51
- 'confidence': random.uniform(0.5, 0.9),
52
- 'recommendation': random.choice(['BUY', 'SELL', 'HOLD'])
56
+ "predicted_return": random.uniform(-0.1, 0.1),
57
+ "confidence": random.uniform(0.5, 0.9),
58
+ "recommendation": random.choice(["BUY", "SELL", "HOLD"]),
53
59
  }
54
60
 
55
61
 
@@ -57,11 +63,39 @@ def generate_mock_predictions(num_predictions: int = 50) -> pd.DataFrame:
57
63
  """Generate mock prediction data for demonstration"""
58
64
  import random
59
65
 
60
- tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'NVDA', 'TSLA', 'META', 'AMD', 'NFLX', 'INTC',
61
- 'JPM', 'BAC', 'WFC', 'GS', 'MS', 'V', 'MA', 'PYPL', 'SQ', 'COIN']
62
- politicians = ['Nancy Pelosi', 'Paul Pelosi', 'Dan Crenshaw', 'Josh Gottheimer',
63
- 'Susie Lee', 'Brian Higgins', 'Mark Green', 'Tommy Tuberville']
64
- sectors = ['Technology', 'Finance', 'Healthcare', 'Energy', 'Consumer', 'Industrial']
66
+ tickers = [
67
+ "AAPL",
68
+ "MSFT",
69
+ "GOOGL",
70
+ "AMZN",
71
+ "NVDA",
72
+ "TSLA",
73
+ "META",
74
+ "AMD",
75
+ "NFLX",
76
+ "INTC",
77
+ "JPM",
78
+ "BAC",
79
+ "WFC",
80
+ "GS",
81
+ "MS",
82
+ "V",
83
+ "MA",
84
+ "PYPL",
85
+ "SQ",
86
+ "COIN",
87
+ ]
88
+ politicians = [
89
+ "Nancy Pelosi",
90
+ "Paul Pelosi",
91
+ "Dan Crenshaw",
92
+ "Josh Gottheimer",
93
+ "Susie Lee",
94
+ "Brian Higgins",
95
+ "Mark Green",
96
+ "Tommy Tuberville",
97
+ ]
98
+ sectors = ["Technology", "Finance", "Healthcare", "Energy", "Consumer", "Industrial"]
65
99
 
66
100
  predictions = []
67
101
  for i in range(num_predictions):
@@ -72,50 +106,54 @@ def generate_mock_predictions(num_predictions: int = 50) -> pd.DataFrame:
72
106
 
73
107
  # Recommendation logic
74
108
  if predicted_return > 0.10 and confidence > 0.7:
75
- recommendation = 'BUY'
109
+ recommendation = "BUY"
76
110
  elif predicted_return < -0.05 and confidence > 0.7:
77
- recommendation = 'SELL'
111
+ recommendation = "SELL"
78
112
  else:
79
- recommendation = 'HOLD'
80
-
81
- predictions.append({
82
- 'ticker': ticker,
83
- 'company_name': f'{ticker} Inc.',
84
- 'predicted_return': predicted_return,
85
- 'confidence': confidence,
86
- 'risk_score': risk_score,
87
- 'recommendation': recommendation,
88
- 'sector': random.choice(sectors),
89
- 'politician': random.choice(politicians),
90
- 'transaction_type': random.choice(['Purchase', 'Sale', 'Exchange']),
91
- 'transaction_date': (datetime.now() - timedelta(days=random.randint(0, 30))).date(),
92
- 'current_price': random.uniform(50, 500),
93
- 'target_price': random.uniform(50, 500),
94
- 'time_horizon_days': random.choice([30, 60, 90, 180]),
95
- 'historical_accuracy': random.uniform(0.6, 0.9),
96
- 'similar_trades_count': random.randint(1, 20)
97
- })
113
+ recommendation = "HOLD"
114
+
115
+ predictions.append(
116
+ {
117
+ "ticker": ticker,
118
+ "company_name": f"{ticker} Inc.",
119
+ "predicted_return": predicted_return,
120
+ "confidence": confidence,
121
+ "risk_score": risk_score,
122
+ "recommendation": recommendation,
123
+ "sector": random.choice(sectors),
124
+ "politician": random.choice(politicians),
125
+ "transaction_type": random.choice(["Purchase", "Sale", "Exchange"]),
126
+ "transaction_date": (datetime.now() - timedelta(days=random.randint(0, 30))).date(),
127
+ "current_price": random.uniform(50, 500),
128
+ "target_price": random.uniform(50, 500),
129
+ "time_horizon_days": random.choice([30, 60, 90, 180]),
130
+ "historical_accuracy": random.uniform(0.6, 0.9),
131
+ "similar_trades_count": random.randint(1, 20),
132
+ }
133
+ )
98
134
 
99
135
  return pd.DataFrame(predictions)
100
136
 
101
137
 
102
138
  def generate_mock_historical_performance() -> pd.DataFrame:
103
139
  """Generate mock historical prediction performance"""
104
- dates = pd.date_range(end=datetime.now(), periods=90, freq='D')
140
+ dates = pd.date_range(end=datetime.now(), periods=90, freq="D")
105
141
 
106
142
  performance = []
107
143
  for date in dates:
108
- performance.append({
109
- 'date': date,
110
- 'accuracy': 0.65 + np.random.normal(0, 0.05),
111
- 'predictions_made': np.random.randint(10, 50),
112
- 'successful_predictions': np.random.randint(15, 40),
113
- 'avg_return': np.random.uniform(-0.02, 0.08),
114
- 'sharpe_ratio': np.random.uniform(0.5, 2.0)
115
- })
144
+ performance.append(
145
+ {
146
+ "date": date,
147
+ "accuracy": 0.65 + np.random.normal(0, 0.05),
148
+ "predictions_made": np.random.randint(10, 50),
149
+ "successful_predictions": np.random.randint(15, 40),
150
+ "avg_return": np.random.uniform(-0.02, 0.08),
151
+ "sharpe_ratio": np.random.uniform(0.5, 2.0),
152
+ }
153
+ )
116
154
 
117
155
  df = pd.DataFrame(performance)
118
- df['accuracy'] = df['accuracy'].clip(0.5, 0.95)
156
+ df["accuracy"] = df["accuracy"].clip(0.5, 0.95)
119
157
  return df
120
158
 
121
159
 
@@ -130,7 +168,9 @@ def get_real_predictions() -> pd.DataFrame:
130
168
  disclosures = get_disclosures_data()
131
169
 
132
170
  if disclosures.empty:
133
- st.info("No disclosure data available. Click 'Run ML Pipeline' in sidebar to generate predictions.")
171
+ st.info(
172
+ "No disclosure data available. Click 'Run ML Pipeline' in sidebar to generate predictions."
173
+ )
134
174
  return generate_mock_predictions()
135
175
 
136
176
  # Run ML pipeline to generate predictions
@@ -138,17 +178,24 @@ def get_real_predictions() -> pd.DataFrame:
138
178
 
139
179
  if predictions is not None and not predictions.empty:
140
180
  # Ensure all required columns exist
141
- required_cols = ['ticker', 'predicted_return', 'confidence', 'risk_score',
142
- 'recommendation', 'sector', 'politician']
181
+ required_cols = [
182
+ "ticker",
183
+ "predicted_return",
184
+ "confidence",
185
+ "risk_score",
186
+ "recommendation",
187
+ "sector",
188
+ "politician",
189
+ ]
143
190
 
144
191
  for col in required_cols:
145
192
  if col not in predictions.columns:
146
- if col == 'sector':
147
- predictions[col] = 'Technology' # Default
148
- elif col == 'politician':
149
- predictions[col] = 'Unknown'
150
- elif col == 'ticker':
151
- predictions[col] = 'UNK'
193
+ if col == "sector":
194
+ predictions[col] = "Technology" # Default
195
+ elif col == "politician":
196
+ predictions[col] = "Unknown"
197
+ elif col == "ticker":
198
+ predictions[col] = "UNK"
152
199
 
153
200
  return predictions
154
201
  else:
@@ -176,13 +223,15 @@ def show_predictions_enhanced():
176
223
  st.warning("🟡 Demo Mode")
177
224
 
178
225
  # Tabs for different views
179
- tab1, tab2, tab3, tab4, tab5 = st.tabs([
180
- "📊 Active Predictions",
181
- "🎯 Prediction Generator",
182
- "📈 Performance Tracker",
183
- "👥 Politician Analysis",
184
- "💼 Portfolio Builder"
185
- ])
226
+ tab1, tab2, tab3, tab4, tab5 = st.tabs(
227
+ [
228
+ "📊 Active Predictions",
229
+ "🎯 Prediction Generator",
230
+ "📈 Performance Tracker",
231
+ "👥 Politician Analysis",
232
+ "💼 Portfolio Builder",
233
+ ]
234
+ )
186
235
 
187
236
  # Get REAL predictions data
188
237
  predictions_df = get_real_predictions()
@@ -210,18 +259,21 @@ def show_active_predictions(predictions_df: pd.DataFrame):
210
259
 
211
260
  # KPIs
212
261
  total_preds = len(predictions_df)
213
- buy_preds = len(predictions_df[predictions_df['recommendation'] == 'BUY'])
214
- sell_preds = len(predictions_df[predictions_df['recommendation'] == 'SELL'])
215
- avg_confidence = predictions_df['confidence'].mean()
216
- avg_return = predictions_df['predicted_return'].mean()
262
+ buy_preds = len(predictions_df[predictions_df["recommendation"] == "BUY"])
263
+ sell_preds = len(predictions_df[predictions_df["recommendation"] == "SELL"])
264
+ avg_confidence = predictions_df["confidence"].mean()
265
+ avg_return = predictions_df["predicted_return"].mean()
217
266
 
218
267
  metrics = {
219
268
  "Total Predictions": {"value": total_preds, "icon": "📊"},
220
269
  "BUY Signals": {"value": buy_preds, "icon": "📈", "delta": "+12"},
221
270
  "SELL Signals": {"value": sell_preds, "icon": "📉", "delta": "-3"},
222
271
  "Avg Confidence": {"value": f"{avg_confidence*100:.1f}%", "icon": "🎯"},
223
- "Avg Return": {"value": f"{avg_return*100:+.1f}%", "icon": "💰",
224
- "delta_color": "normal" if avg_return > 0 else "inverse"}
272
+ "Avg Return": {
273
+ "value": f"{avg_return*100:+.1f}%",
274
+ "icon": "💰",
275
+ "delta_color": "normal" if avg_return > 0 else "inverse",
276
+ },
225
277
  }
226
278
 
227
279
  display_kpi_row(metrics, columns=5)
@@ -237,30 +289,28 @@ def show_active_predictions(predictions_df: pd.DataFrame):
237
289
 
238
290
  with col2:
239
291
  recommendation_filter = st.multiselect(
240
- "Recommendation",
241
- options=['BUY', 'SELL', 'HOLD'],
242
- default=['BUY', 'SELL']
292
+ "Recommendation", options=["BUY", "SELL", "HOLD"], default=["BUY", "SELL"]
243
293
  )
244
294
 
245
295
  with col3:
246
296
  sector_filter = st.multiselect(
247
297
  "Sector",
248
- options=predictions_df['sector'].unique().tolist(),
249
- default=predictions_df['sector'].unique().tolist()
298
+ options=predictions_df["sector"].unique().tolist(),
299
+ default=predictions_df["sector"].unique().tolist(),
250
300
  )
251
301
 
252
302
  with col4:
253
303
  sort_by = st.selectbox(
254
304
  "Sort By",
255
305
  ["predicted_return", "confidence", "risk_score"],
256
- format_func=lambda x: x.replace('_', ' ').title()
306
+ format_func=lambda x: x.replace("_", " ").title(),
257
307
  )
258
308
 
259
309
  # Apply filters
260
310
  filtered_df = predictions_df[
261
- (predictions_df['confidence'] >= min_confidence) &
262
- (predictions_df['recommendation'].isin(recommendation_filter)) &
263
- (predictions_df['sector'].isin(sector_filter))
311
+ (predictions_df["confidence"] >= min_confidence)
312
+ & (predictions_df["recommendation"].isin(recommendation_filter))
313
+ & (predictions_df["sector"].isin(sector_filter))
264
314
  ].sort_values(sort_by, ascending=False)
265
315
 
266
316
  st.caption(f"Showing {len(filtered_df)} of {len(predictions_df)} predictions")
@@ -272,26 +322,26 @@ def show_active_predictions(predictions_df: pd.DataFrame):
272
322
  st.markdown("#### 📊 Risk-Return Analysis")
273
323
  fig = px.scatter(
274
324
  filtered_df,
275
- x='risk_score',
276
- y='predicted_return',
277
- color='recommendation',
278
- size='confidence',
279
- hover_data=['ticker', 'sector'],
325
+ x="risk_score",
326
+ y="predicted_return",
327
+ color="recommendation",
328
+ size="confidence",
329
+ hover_data=["ticker", "sector"],
280
330
  title="Risk vs Expected Return",
281
- labels={'risk_score': 'Risk Score', 'predicted_return': 'Expected Return'},
282
- color_discrete_map={'BUY': '#10b981', 'SELL': '#ef4444', 'HOLD': '#6b7280'}
331
+ labels={"risk_score": "Risk Score", "predicted_return": "Expected Return"},
332
+ color_discrete_map={"BUY": "#10b981", "SELL": "#ef4444", "HOLD": "#6b7280"},
283
333
  )
284
334
  fig.update_layout(height=400)
285
335
  render_chart(fig)
286
336
 
287
337
  with col2:
288
338
  st.markdown("#### 🥧 Sector Distribution")
289
- sector_counts = filtered_df['sector'].value_counts()
339
+ sector_counts = filtered_df["sector"].value_counts()
290
340
  fig = px.pie(
291
341
  values=sector_counts.values,
292
342
  names=sector_counts.index,
293
343
  title="Predictions by Sector",
294
- hole=0.4
344
+ hole=0.4,
295
345
  )
296
346
  fig.update_layout(height=400)
297
347
  render_chart(fig)
@@ -299,8 +349,8 @@ def show_active_predictions(predictions_df: pd.DataFrame):
299
349
  # Top predictions
300
350
  st.markdown("### 🏆 Top Predictions")
301
351
 
302
- top_buy = filtered_df[filtered_df['recommendation'] == 'BUY'].head(5)
303
- top_sell = filtered_df[filtered_df['recommendation'] == 'SELL'].head(5)
352
+ top_buy = filtered_df[filtered_df["recommendation"] == "BUY"].head(5)
353
+ top_sell = filtered_df[filtered_df["recommendation"] == "SELL"].head(5)
304
354
 
305
355
  col1, col2 = st.columns(2)
306
356
 
@@ -317,9 +367,15 @@ def show_active_predictions(predictions_df: pd.DataFrame):
317
367
  with cols[2]:
318
368
  st.metric("Confidence", f"{row['confidence']*100:.0f}%")
319
369
  with cols[3]:
320
- risk_color = "🟢" if row['risk_score'] < 0.4 else "🟡" if row['risk_score'] < 0.7 else "🔴"
370
+ risk_color = (
371
+ "🟢"
372
+ if row["risk_score"] < 0.4
373
+ else "🟡" if row["risk_score"] < 0.7 else "🔴"
374
+ )
321
375
  st.markdown(f"{risk_color} Risk: {row['risk_score']:.2f}")
322
- st.caption(f"Based on {row['politician']}'s {row['transaction_type'].lower()} on {row['transaction_date']}")
376
+ st.caption(
377
+ f"Based on {row['politician']}'s {row['transaction_type'].lower()} on {row['transaction_date']}"
378
+ )
323
379
  st.divider()
324
380
 
325
381
  with col2:
@@ -335,9 +391,15 @@ def show_active_predictions(predictions_df: pd.DataFrame):
335
391
  with cols[2]:
336
392
  st.metric("Confidence", f"{row['confidence']*100:.0f}%")
337
393
  with cols[3]:
338
- risk_color = "🟢" if row['risk_score'] < 0.4 else "🟡" if row['risk_score'] < 0.7 else "🔴"
394
+ risk_color = (
395
+ "🟢"
396
+ if row["risk_score"] < 0.4
397
+ else "🟡" if row["risk_score"] < 0.7 else "🔴"
398
+ )
339
399
  st.markdown(f"{risk_color} Risk: {row['risk_score']:.2f}")
340
- st.caption(f"Based on {row['politician']}'s {row['transaction_type'].lower()} on {row['transaction_date']}")
400
+ st.caption(
401
+ f"Based on {row['politician']}'s {row['transaction_type'].lower()} on {row['transaction_date']}"
402
+ )
341
403
  st.divider()
342
404
 
343
405
  # Export
@@ -347,11 +409,18 @@ def show_active_predictions(predictions_df: pd.DataFrame):
347
409
  # Detailed table
348
410
  with st.expander("📋 View All Predictions (Table)"):
349
411
  st.dataframe(
350
- filtered_df[[
351
- 'ticker', 'recommendation', 'predicted_return', 'confidence',
352
- 'risk_score', 'sector', 'politician'
353
- ]],
354
- width="stretch"
412
+ filtered_df[
413
+ [
414
+ "ticker",
415
+ "recommendation",
416
+ "predicted_return",
417
+ "confidence",
418
+ "risk_score",
419
+ "sector",
420
+ "politician",
421
+ ]
422
+ ],
423
+ width="stretch",
355
424
  )
356
425
 
357
426
 
@@ -362,7 +431,7 @@ def show_prediction_generator():
362
431
  st.markdown("Get AI-powered predictions for specific stock/politician combinations")
363
432
 
364
433
  # Get REAL politician names from database
365
- politician_list = ['Nancy Pelosi', 'Paul Pelosi', 'Dan Crenshaw'] # Fallback
434
+ politician_list = ["Nancy Pelosi", "Paul Pelosi", "Dan Crenshaw"] # Fallback
366
435
  if HAS_REAL_DATA:
367
436
  try:
368
437
  politician_list = get_politician_names()
@@ -374,23 +443,26 @@ def show_prediction_generator():
374
443
  with col1:
375
444
  st.markdown("#### Stock Selection")
376
445
  ticker = st.text_input("Stock Ticker", placeholder="e.g., AAPL", value="NVDA")
377
- sector = st.selectbox("Sector", ['Technology', 'Finance', 'Healthcare', 'Energy', 'Consumer', 'Industrial'])
446
+ sector = st.selectbox(
447
+ "Sector", ["Technology", "Finance", "Healthcare", "Energy", "Consumer", "Industrial"]
448
+ )
378
449
  current_price = st.number_input("Current Price ($)", min_value=1.0, value=450.0, step=10.0)
379
450
 
380
451
  with col2:
381
452
  st.markdown("#### Context")
382
- politician = st.selectbox(
383
- "Politician",
384
- politician_list
453
+ politician = st.selectbox("Politician", politician_list)
454
+ transaction_type = st.selectbox("Transaction Type", ["Purchase", "Sale", "Exchange"])
455
+ transaction_amount = st.number_input(
456
+ "Transaction Amount ($)", min_value=1000, value=100000, step=10000
385
457
  )
386
- transaction_type = st.selectbox("Transaction Type", ['Purchase', 'Sale', 'Exchange'])
387
- transaction_amount = st.number_input("Transaction Amount ($)", min_value=1000, value=100000, step=10000)
388
458
 
389
459
  col1, col2, col3 = st.columns(3)
390
460
  with col1:
391
- time_horizon = st.selectbox("Time Horizon", ['30 days', '60 days', '90 days', '180 days'])
461
+ time_horizon = st.selectbox("Time Horizon", ["30 days", "60 days", "90 days", "180 days"])
392
462
  with col2:
393
- risk_tolerance = st.select_slider("Risk Tolerance", options=['Low', 'Medium', 'High'], value='Medium')
463
+ risk_tolerance = st.select_slider(
464
+ "Risk Tolerance", options=["Low", "Medium", "High"], value="Medium"
465
+ )
394
466
  with col3:
395
467
  use_historical = st.checkbox("Use Historical Patterns", value=True)
396
468
 
@@ -413,31 +485,43 @@ def show_prediction_generator():
413
485
  market_cap="Large Cap", # Could be fetched from API
414
486
  sector=sector,
415
487
  sentiment=0.2, # Could be fetched from sentiment API
416
- volatility=0.3 if risk_tolerance == 'Low' else 0.5 if risk_tolerance == 'Medium' else 0.7,
417
- trading_history=trading_history
488
+ volatility=(
489
+ 0.3
490
+ if risk_tolerance == "Low"
491
+ else 0.5 if risk_tolerance == "Medium" else 0.7
492
+ ),
493
+ trading_history=trading_history,
418
494
  )
419
495
 
420
496
  # Generate REAL prediction
421
497
  prediction_result = generate_production_prediction(features)
422
498
 
423
- predicted_return = prediction_result['predicted_return']
424
- confidence = prediction_result['confidence']
425
- risk_score = prediction_result['risk_score']
499
+ predicted_return = prediction_result["predicted_return"]
500
+ confidence = prediction_result["confidence"]
501
+ risk_score = prediction_result["risk_score"]
426
502
 
427
503
  st.success("✅ Prediction Generated from Real Data & ML Model!")
428
504
 
429
505
  except Exception as e:
430
506
  st.warning(f"Could not use real model: {e}. Using demo prediction.")
431
507
  # Fallback to demo
432
- predicted_return = np.random.uniform(0.05, 0.25) if transaction_type == 'Purchase' else np.random.uniform(-0.15, -0.05)
508
+ predicted_return = (
509
+ np.random.uniform(0.05, 0.25)
510
+ if transaction_type == "Purchase"
511
+ else np.random.uniform(-0.15, -0.05)
512
+ )
433
513
  confidence = np.random.uniform(0.7, 0.95)
434
- risk_score = {'Low': 0.3, 'Medium': 0.5, 'High': 0.7}[risk_tolerance]
514
+ risk_score = {"Low": 0.3, "Medium": 0.5, "High": 0.7}[risk_tolerance]
435
515
  else:
436
516
  # Demo mode
437
517
  st.info("Using demo prediction (Supabase not connected)")
438
- predicted_return = np.random.uniform(0.05, 0.25) if transaction_type == 'Purchase' else np.random.uniform(-0.15, -0.05)
518
+ predicted_return = (
519
+ np.random.uniform(0.05, 0.25)
520
+ if transaction_type == "Purchase"
521
+ else np.random.uniform(-0.15, -0.05)
522
+ )
439
523
  confidence = np.random.uniform(0.7, 0.95)
440
- risk_score = {'Low': 0.3, 'Medium': 0.5, 'High': 0.7}[risk_tolerance]
524
+ risk_score = {"Low": 0.3, "Medium": 0.5, "High": 0.7}[risk_tolerance]
441
525
 
442
526
  st.success("✅ Prediction Generated!")
443
527
 
@@ -446,16 +530,26 @@ def show_prediction_generator():
446
530
 
447
531
  col1, col2, col3, col4 = st.columns(4)
448
532
  with col1:
449
- st.metric("Predicted Return", f"{predicted_return*100:+.1f}%",
450
- delta=f"{predicted_return*transaction_amount:+,.0f}")
533
+ st.metric(
534
+ "Predicted Return",
535
+ f"{predicted_return*100:+.1f}%",
536
+ delta=f"{predicted_return*transaction_amount:+,.0f}",
537
+ )
451
538
  with col2:
452
539
  st.metric("Confidence", f"{confidence*100:.0f}%")
453
540
  with col3:
454
- recommendation = 'BUY' if predicted_return > 0.1 else 'SELL' if predicted_return < -0.05 else 'HOLD'
541
+ recommendation = (
542
+ "BUY"
543
+ if predicted_return > 0.1
544
+ else "SELL" if predicted_return < -0.05 else "HOLD"
545
+ )
455
546
  st.metric("Recommendation", recommendation)
456
547
  with col4:
457
- st.metric("Risk Score", f"{risk_score:.2f}",
458
- delta="Low" if risk_score < 0.4 else "High" if risk_score > 0.7 else "Medium")
548
+ st.metric(
549
+ "Risk Score",
550
+ f"{risk_score:.2f}",
551
+ delta="Low" if risk_score < 0.4 else "High" if risk_score > 0.7 else "Medium",
552
+ )
459
553
 
460
554
  # Detailed analysis
461
555
  st.markdown("### 🔍 Detailed Analysis")
@@ -463,7 +557,8 @@ def show_prediction_generator():
463
557
  tab1, tab2, tab3 = st.tabs(["💡 Key Insights", "📈 Price Forecast", "⚠️ Risk Factors"])
464
558
 
465
559
  with tab1:
466
- st.markdown(f"""
560
+ st.markdown(
561
+ f"""
467
562
  **Trading Pattern Analysis:**
468
563
  - {politician} has a historical accuracy of **{np.random.uniform(0.65, 0.85):.0%}** on {sector} stocks
469
564
  - Similar transactions by {politician} resulted in average returns of **{np.random.uniform(0.05, 0.20):.1%}**
@@ -473,12 +568,13 @@ def show_prediction_generator():
473
568
  - Pattern match: {confidence*100:.0f}%
474
569
  - Data recency: {np.random.uniform(0.7, 0.95)*100:.0f}%
475
570
  - Market alignment: {np.random.uniform(0.6, 0.9)*100:.0f}%
476
- """)
571
+ """
572
+ )
477
573
 
478
574
  with tab2:
479
575
  # Price forecast chart
480
576
  days = int(time_horizon.split()[0])
481
- dates = pd.date_range(start=datetime.now(), periods=days, freq='D')
577
+ dates = pd.date_range(start=datetime.now(), periods=days, freq="D")
482
578
 
483
579
  # Generate forecast
484
580
  base_trend = predicted_return / days
@@ -491,45 +587,58 @@ def show_prediction_generator():
491
587
  lower_bound = forecast_prices * 0.9
492
588
 
493
589
  fig = go.Figure()
494
- fig.add_trace(go.Scatter(
495
- x=dates, y=forecast_prices,
496
- mode='lines',
497
- name='Forecast',
498
- line=dict(color='#3b82f6', width=3)
499
- ))
500
- fig.add_trace(go.Scatter(
501
- x=dates, y=upper_bound,
502
- mode='lines',
503
- name='Upper Bound',
504
- line=dict(width=0),
505
- showlegend=False
506
- ))
507
- fig.add_trace(go.Scatter(
508
- x=dates, y=lower_bound,
509
- mode='lines',
510
- name='Lower Bound',
511
- fill='tonexty',
512
- line=dict(width=0),
513
- fillcolor='rgba(59, 130, 246, 0.2)',
514
- showlegend=True
515
- ))
590
+ fig.add_trace(
591
+ go.Scatter(
592
+ x=dates,
593
+ y=forecast_prices,
594
+ mode="lines",
595
+ name="Forecast",
596
+ line=dict(color="#3b82f6", width=3),
597
+ )
598
+ )
599
+ fig.add_trace(
600
+ go.Scatter(
601
+ x=dates,
602
+ y=upper_bound,
603
+ mode="lines",
604
+ name="Upper Bound",
605
+ line=dict(width=0),
606
+ showlegend=False,
607
+ )
608
+ )
609
+ fig.add_trace(
610
+ go.Scatter(
611
+ x=dates,
612
+ y=lower_bound,
613
+ mode="lines",
614
+ name="Lower Bound",
615
+ fill="tonexty",
616
+ line=dict(width=0),
617
+ fillcolor="rgba(59, 130, 246, 0.2)",
618
+ showlegend=True,
619
+ )
620
+ )
516
621
 
517
622
  fig.update_layout(
518
623
  title=f"{ticker} Price Forecast ({time_horizon})",
519
624
  xaxis_title="Date",
520
625
  yaxis_title="Price ($)",
521
626
  height=400,
522
- hovermode='x unified'
627
+ hovermode="x unified",
523
628
  )
524
629
 
525
630
  render_chart(fig)
526
631
 
527
632
  target_price = forecast_prices[-1]
528
- st.metric("Target Price", f"${target_price:.2f}",
529
- delta=f"{(target_price/current_price - 1)*100:+.1f}%")
633
+ st.metric(
634
+ "Target Price",
635
+ f"${target_price:.2f}",
636
+ delta=f"{(target_price/current_price - 1)*100:+.1f}%",
637
+ )
530
638
 
531
639
  with tab3:
532
- st.markdown(f"""
640
+ st.markdown(
641
+ f"""
533
642
  **Risk Assessment:**
534
643
 
535
644
  🎯 **Overall Risk Level:** {risk_score:.2f}/1.00 ({'Low' if risk_score < 0.4 else 'High' if risk_score > 0.7 else 'Medium'})
@@ -545,7 +654,8 @@ def show_prediction_generator():
545
654
  - Set stop-loss at {(1 - np.random.uniform(0.05, 0.15))*100:.1f}% of entry price
546
655
  - Monitor for changes in trading patterns
547
656
  - Review prediction after 30 days
548
- """)
657
+ """
658
+ )
549
659
 
550
660
 
551
661
  def show_performance_tracker():
@@ -558,18 +668,18 @@ def show_performance_tracker():
558
668
  performance_df = generate_mock_historical_performance()
559
669
 
560
670
  # KPIs
561
- recent_accuracy = performance_df.tail(30)['accuracy'].mean()
562
- total_predictions = performance_df['predictions_made'].sum()
563
- total_successful = performance_df['successful_predictions'].sum()
564
- avg_return = performance_df['avg_return'].mean()
565
- avg_sharpe = performance_df['sharpe_ratio'].mean()
671
+ recent_accuracy = performance_df.tail(30)["accuracy"].mean()
672
+ total_predictions = performance_df["predictions_made"].sum()
673
+ total_successful = performance_df["successful_predictions"].sum()
674
+ avg_return = performance_df["avg_return"].mean()
675
+ avg_sharpe = performance_df["sharpe_ratio"].mean()
566
676
 
567
677
  metrics = {
568
678
  "30-Day Accuracy": {"value": f"{recent_accuracy*100:.1f}%", "icon": "🎯", "delta": "+2.3%"},
569
679
  "Total Predictions": {"value": f"{total_predictions:,}", "icon": "📊"},
570
680
  "Successful": {"value": f"{total_successful:,}", "icon": "✅"},
571
681
  "Avg Return": {"value": f"{avg_return*100:+.1f}%", "icon": "💰"},
572
- "Sharpe Ratio": {"value": f"{avg_sharpe:.2f}", "icon": "📈"}
682
+ "Sharpe Ratio": {"value": f"{avg_sharpe:.2f}", "icon": "📈"},
573
683
  }
574
684
 
575
685
  display_kpi_row(metrics, columns=5)
@@ -580,23 +690,27 @@ def show_performance_tracker():
580
690
  st.markdown("### 📊 Accuracy Trend")
581
691
 
582
692
  fig = go.Figure()
583
- fig.add_trace(go.Scatter(
584
- x=performance_df['date'],
585
- y=performance_df['accuracy'] * 100,
586
- mode='lines+markers',
587
- name='Daily Accuracy',
588
- line=dict(color='#10b981', width=2)
589
- ))
693
+ fig.add_trace(
694
+ go.Scatter(
695
+ x=performance_df["date"],
696
+ y=performance_df["accuracy"] * 100,
697
+ mode="lines+markers",
698
+ name="Daily Accuracy",
699
+ line=dict(color="#10b981", width=2),
700
+ )
701
+ )
590
702
 
591
703
  # Add rolling average
592
- rolling_avg = performance_df['accuracy'].rolling(window=7).mean() * 100
593
- fig.add_trace(go.Scatter(
594
- x=performance_df['date'],
595
- y=rolling_avg,
596
- mode='lines',
597
- name='7-Day Average',
598
- line=dict(color='#3b82f6', width=3, dash='dash')
599
- ))
704
+ rolling_avg = performance_df["accuracy"].rolling(window=7).mean() * 100
705
+ fig.add_trace(
706
+ go.Scatter(
707
+ x=performance_df["date"],
708
+ y=rolling_avg,
709
+ mode="lines",
710
+ name="7-Day Average",
711
+ line=dict(color="#3b82f6", width=3, dash="dash"),
712
+ )
713
+ )
600
714
 
601
715
  # Target line
602
716
  fig.add_hline(y=70, line_dash="dot", line_color="red", annotation_text="Target: 70%")
@@ -606,7 +720,7 @@ def show_performance_tracker():
606
720
  xaxis_title="Date",
607
721
  yaxis_title="Accuracy (%)",
608
722
  height=400,
609
- hovermode='x unified'
723
+ hovermode="x unified",
610
724
  )
611
725
 
612
726
  render_chart(fig)
@@ -624,7 +738,7 @@ def show_performance_tracker():
624
738
  x=returns * 100,
625
739
  nbins=50,
626
740
  title="Distribution of Predicted Returns",
627
- labels={'x': 'Return (%)', 'y': 'Frequency'}
741
+ labels={"x": "Return (%)", "y": "Frequency"},
628
742
  )
629
743
  fig.add_vline(x=0, line_dash="dash", line_color="red")
630
744
  fig.update_layout(height=350)
@@ -633,23 +747,25 @@ def show_performance_tracker():
633
747
  with col2:
634
748
  st.markdown("### 📊 Win Rate by Recommendation")
635
749
 
636
- win_rates = pd.DataFrame({
637
- 'Recommendation': ['BUY', 'SELL', 'HOLD'],
638
- 'Win Rate': [0.68, 0.62, 0.71],
639
- 'Count': [450, 280, 320]
640
- })
750
+ win_rates = pd.DataFrame(
751
+ {
752
+ "Recommendation": ["BUY", "SELL", "HOLD"],
753
+ "Win Rate": [0.68, 0.62, 0.71],
754
+ "Count": [450, 280, 320],
755
+ }
756
+ )
641
757
 
642
758
  fig = px.bar(
643
759
  win_rates,
644
- x='Recommendation',
645
- y='Win Rate',
646
- text='Win Rate',
760
+ x="Recommendation",
761
+ y="Win Rate",
762
+ text="Win Rate",
647
763
  title="Success Rate by Recommendation Type",
648
- color='Win Rate',
649
- color_continuous_scale='RdYlGn',
650
- range_color=[0.5, 0.8]
764
+ color="Win Rate",
765
+ color_continuous_scale="RdYlGn",
766
+ range_color=[0.5, 0.8],
651
767
  )
652
- fig.update_traces(texttemplate='%{text:.1%}', textposition='outside')
768
+ fig.update_traces(texttemplate="%{text:.1%}", textposition="outside")
653
769
  fig.update_layout(height=350)
654
770
  render_chart(fig)
655
771
 
@@ -661,14 +777,26 @@ def show_politician_analysis(predictions_df: pd.DataFrame):
661
777
  st.markdown("Analyze prediction patterns by politician trading activity")
662
778
 
663
779
  # Group by politician
664
- politician_stats = predictions_df.groupby('politician').agg({
665
- 'ticker': 'count',
666
- 'predicted_return': 'mean',
667
- 'confidence': 'mean',
668
- 'risk_score': 'mean'
669
- }).reset_index()
670
- politician_stats.columns = ['Politician', 'Predictions', 'Avg Return', 'Avg Confidence', 'Avg Risk']
671
- politician_stats = politician_stats.sort_values('Avg Return', ascending=False)
780
+ politician_stats = (
781
+ predictions_df.groupby("politician")
782
+ .agg(
783
+ {
784
+ "ticker": "count",
785
+ "predicted_return": "mean",
786
+ "confidence": "mean",
787
+ "risk_score": "mean",
788
+ }
789
+ )
790
+ .reset_index()
791
+ )
792
+ politician_stats.columns = [
793
+ "Politician",
794
+ "Predictions",
795
+ "Avg Return",
796
+ "Avg Confidence",
797
+ "Avg Risk",
798
+ ]
799
+ politician_stats = politician_stats.sort_values("Avg Return", ascending=False)
672
800
 
673
801
  # Top politicians
674
802
  col1, col2 = st.columns(2)
@@ -678,15 +806,15 @@ def show_politician_analysis(predictions_df: pd.DataFrame):
678
806
 
679
807
  fig = px.bar(
680
808
  politician_stats.head(5),
681
- x='Avg Return',
682
- y='Politician',
683
- orientation='h',
809
+ x="Avg Return",
810
+ y="Politician",
811
+ orientation="h",
684
812
  title="Politicians with Highest Predicted Returns",
685
- text='Avg Return',
686
- color='Avg Return',
687
- color_continuous_scale='RdYlGn'
813
+ text="Avg Return",
814
+ color="Avg Return",
815
+ color_continuous_scale="RdYlGn",
688
816
  )
689
- fig.update_traces(texttemplate='%{text:.1%}', textposition='outside')
817
+ fig.update_traces(texttemplate="%{text:.1%}", textposition="outside")
690
818
  fig.update_layout(height=350)
691
819
  render_chart(fig)
692
820
 
@@ -695,10 +823,10 @@ def show_politician_analysis(predictions_df: pd.DataFrame):
695
823
 
696
824
  fig = px.pie(
697
825
  politician_stats,
698
- values='Predictions',
699
- names='Politician',
826
+ values="Predictions",
827
+ names="Politician",
700
828
  title="Prediction Distribution by Politician",
701
- hole=0.4
829
+ hole=0.4,
702
830
  )
703
831
  fig.update_layout(height=350)
704
832
  render_chart(fig)
@@ -707,11 +835,10 @@ def show_politician_analysis(predictions_df: pd.DataFrame):
707
835
  st.markdown("### 🔍 Detailed Analysis")
708
836
 
709
837
  selected_politician = st.selectbox(
710
- "Select Politician",
711
- options=predictions_df['politician'].unique().tolist()
838
+ "Select Politician", options=predictions_df["politician"].unique().tolist()
712
839
  )
713
840
 
714
- pol_data = predictions_df[predictions_df['politician'] == selected_politician]
841
+ pol_data = predictions_df[predictions_df["politician"] == selected_politician]
715
842
 
716
843
  col1, col2, col3, col4 = st.columns(4)
717
844
  with col1:
@@ -721,29 +848,30 @@ def show_politician_analysis(predictions_df: pd.DataFrame):
721
848
  with col3:
722
849
  st.metric("Avg Confidence", f"{pol_data['confidence'].mean()*100:.0f}%")
723
850
  with col4:
724
- buy_rate = len(pol_data[pol_data['recommendation'] == 'BUY']) / len(pol_data) * 100
851
+ buy_rate = len(pol_data[pol_data["recommendation"] == "BUY"]) / len(pol_data) * 100
725
852
  st.metric("BUY Rate", f"{buy_rate:.0f}%")
726
853
 
727
854
  # Sector breakdown
728
855
  st.markdown(f"#### Sector Preferences - {selected_politician}")
729
856
 
730
- sector_breakdown = pol_data.groupby('sector').agg({
731
- 'ticker': 'count',
732
- 'predicted_return': 'mean'
733
- }).reset_index()
734
- sector_breakdown.columns = ['Sector', 'Count', 'Avg Return']
857
+ sector_breakdown = (
858
+ pol_data.groupby("sector")
859
+ .agg({"ticker": "count", "predicted_return": "mean"})
860
+ .reset_index()
861
+ )
862
+ sector_breakdown.columns = ["Sector", "Count", "Avg Return"]
735
863
 
736
864
  fig = px.scatter(
737
865
  sector_breakdown,
738
- x='Count',
739
- y='Avg Return',
740
- size='Count',
741
- color='Sector',
866
+ x="Count",
867
+ y="Avg Return",
868
+ size="Count",
869
+ color="Sector",
742
870
  title=f"{selected_politician}'s Sector Performance",
743
- labels={'Count': 'Number of Trades', 'Avg Return': 'Average Predicted Return'},
744
- text='Sector'
871
+ labels={"Count": "Number of Trades", "Avg Return": "Average Predicted Return"},
872
+ text="Sector",
745
873
  )
746
- fig.update_traces(textposition='top center')
874
+ fig.update_traces(textposition="top center")
747
875
  fig.update_layout(height=400)
748
876
  render_chart(fig)
749
877
 
@@ -761,68 +889,88 @@ def show_portfolio_builder(predictions_df: pd.DataFrame):
761
889
 
762
890
  portfolio_size = st.slider("Number of Stocks", 3, 15, 8)
763
891
  risk_preference = st.select_slider(
764
- "Risk Preference",
765
- options=['Conservative', 'Balanced', 'Aggressive'],
766
- value='Balanced'
892
+ "Risk Preference", options=["Conservative", "Balanced", "Aggressive"], value="Balanced"
767
893
  )
768
894
  min_confidence = st.slider("Min Confidence", 0.5, 0.95, 0.7, 0.05)
769
- total_capital = st.number_input("Total Capital ($)", min_value=1000, value=100000, step=10000)
895
+ total_capital = st.number_input(
896
+ "Total Capital ($)", min_value=1000, value=100000, step=10000
897
+ )
770
898
 
771
899
  if st.button("🎯 Build Portfolio", type="primary", width="stretch"):
772
- st.session_state['portfolio_built'] = True
900
+ st.session_state["portfolio_built"] = True
773
901
 
774
902
  with col2:
775
- if st.session_state.get('portfolio_built'):
903
+ if st.session_state.get("portfolio_built"):
776
904
  st.markdown("#### 🎉 Recommended Portfolio")
777
905
 
778
906
  # Filter and select stocks
779
- risk_thresholds = {
780
- 'Conservative': 0.4,
781
- 'Balanced': 0.6,
782
- 'Aggressive': 0.8
783
- }
907
+ risk_thresholds = {"Conservative": 0.4, "Balanced": 0.6, "Aggressive": 0.8}
784
908
 
785
909
  filtered = predictions_df[
786
- (predictions_df['confidence'] >= min_confidence) &
787
- (predictions_df['risk_score'] <= risk_thresholds[risk_preference]) &
788
- (predictions_df['recommendation'] == 'BUY')
789
- ].nlargest(portfolio_size, 'predicted_return')
910
+ (predictions_df["confidence"] >= min_confidence)
911
+ & (predictions_df["risk_score"] <= risk_thresholds[risk_preference])
912
+ & (predictions_df["recommendation"] == "BUY")
913
+ ].nlargest(portfolio_size, "predicted_return")
790
914
 
791
915
  if len(filtered) > 0:
792
916
  # Calculate allocations
793
- total_score = filtered['confidence'].sum()
794
- filtered['allocation'] = filtered['confidence'] / total_score
795
- filtered['investment'] = filtered['allocation'] * total_capital
917
+ total_score = filtered["confidence"].sum()
918
+ filtered["allocation"] = filtered["confidence"] / total_score
919
+ filtered["investment"] = filtered["allocation"] * total_capital
796
920
 
797
921
  # Portfolio metrics
798
922
  col_a, col_b, col_c, col_d = st.columns(4)
799
923
  with col_a:
800
924
  st.metric("Stocks", len(filtered))
801
925
  with col_b:
802
- st.metric("Expected Return", f"{(filtered['predicted_return'] * filtered['allocation']).sum()*100:+.1f}%")
926
+ st.metric(
927
+ "Expected Return",
928
+ f"{(filtered['predicted_return'] * filtered['allocation']).sum()*100:+.1f}%",
929
+ )
803
930
  with col_c:
804
931
  st.metric("Avg Confidence", f"{filtered['confidence'].mean()*100:.0f}%")
805
932
  with col_d:
806
- st.metric("Portfolio Risk", f"{(filtered['risk_score'] * filtered['allocation']).sum():.2f}")
933
+ st.metric(
934
+ "Portfolio Risk",
935
+ f"{(filtered['risk_score'] * filtered['allocation']).sum():.2f}",
936
+ )
807
937
 
808
938
  # Allocation pie chart
809
939
  fig = px.pie(
810
940
  filtered,
811
- values='allocation',
812
- names='ticker',
941
+ values="allocation",
942
+ names="ticker",
813
943
  title="Portfolio Allocation",
814
- hole=0.4
944
+ hole=0.4,
815
945
  )
816
946
  fig.update_layout(height=350)
817
947
  render_chart(fig)
818
948
 
819
949
  # Detailed table
820
950
  st.markdown("#### 📋 Portfolio Details")
821
- portfolio_display = filtered[['ticker', 'sector', 'predicted_return', 'confidence', 'risk_score', 'allocation', 'investment']].copy()
822
- portfolio_display['predicted_return'] = portfolio_display['predicted_return'].apply(lambda x: f"{x*100:+.1f}%")
823
- portfolio_display['confidence'] = portfolio_display['confidence'].apply(lambda x: f"{x*100:.0f}%")
824
- portfolio_display['allocation'] = portfolio_display['allocation'].apply(lambda x: f"{x*100:.1f}%")
825
- portfolio_display['investment'] = portfolio_display['investment'].apply(lambda x: f"${x:,.0f}")
951
+ portfolio_display = filtered[
952
+ [
953
+ "ticker",
954
+ "sector",
955
+ "predicted_return",
956
+ "confidence",
957
+ "risk_score",
958
+ "allocation",
959
+ "investment",
960
+ ]
961
+ ].copy()
962
+ portfolio_display["predicted_return"] = portfolio_display["predicted_return"].apply(
963
+ lambda x: f"{x*100:+.1f}%"
964
+ )
965
+ portfolio_display["confidence"] = portfolio_display["confidence"].apply(
966
+ lambda x: f"{x*100:.0f}%"
967
+ )
968
+ portfolio_display["allocation"] = portfolio_display["allocation"].apply(
969
+ lambda x: f"{x*100:.1f}%"
970
+ )
971
+ portfolio_display["investment"] = portfolio_display["investment"].apply(
972
+ lambda x: f"${x:,.0f}"
973
+ )
826
974
 
827
975
  st.dataframe(portfolio_display, width="stretch")
828
976