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.
- mcli/app/commands_cmd.py +51 -39
- mcli/app/main.py +10 -2
- mcli/app/model_cmd.py +1 -1
- mcli/lib/custom_commands.py +4 -10
- mcli/ml/api/app.py +1 -5
- mcli/ml/dashboard/app.py +2 -2
- mcli/ml/dashboard/app_integrated.py +168 -116
- mcli/ml/dashboard/app_supabase.py +7 -3
- mcli/ml/dashboard/app_training.py +3 -6
- mcli/ml/dashboard/components/charts.py +74 -115
- mcli/ml/dashboard/components/metrics.py +24 -44
- mcli/ml/dashboard/components/tables.py +32 -40
- mcli/ml/dashboard/overview.py +102 -78
- mcli/ml/dashboard/pages/cicd.py +103 -56
- mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
- mcli/ml/dashboard/pages/gravity_viz.py +374 -313
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
- mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
- mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
- mcli/ml/dashboard/pages/test_portfolio.py +153 -121
- mcli/ml/dashboard/pages/trading.py +238 -169
- mcli/ml/dashboard/pages/workflows.py +129 -84
- mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
- mcli/ml/dashboard/utils.py +24 -21
- mcli/ml/dashboard/warning_suppression.py +6 -4
- mcli/ml/database/session.py +16 -5
- mcli/ml/mlops/pipeline_orchestrator.py +1 -3
- mcli/ml/predictions/monte_carlo.py +6 -18
- mcli/ml/trading/alpaca_client.py +95 -96
- mcli/ml/trading/migrations.py +76 -40
- mcli/ml/trading/models.py +78 -60
- mcli/ml/trading/paper_trading.py +92 -74
- mcli/ml/trading/risk_management.py +106 -85
- mcli/ml/trading/trading_service.py +155 -110
- mcli/ml/training/train_model.py +1 -3
- mcli/self/self_cmd.py +71 -57
- mcli/workflow/daemon/daemon.py +2 -0
- mcli/workflow/model_service/openai_adapter.py +6 -2
- mcli/workflow/politician_trading/models.py +6 -2
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
- mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
- mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
- mcli/workflow/politician_trading/seed_database.py +70 -89
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +49 -49
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
4
|
-
import
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 = [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 =
|
|
109
|
+
recommendation = "BUY"
|
|
76
110
|
elif predicted_return < -0.05 and confidence > 0.7:
|
|
77
|
-
recommendation =
|
|
111
|
+
recommendation = "SELL"
|
|
78
112
|
else:
|
|
79
|
-
recommendation =
|
|
80
|
-
|
|
81
|
-
predictions.append(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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=
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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[
|
|
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(
|
|
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 = [
|
|
142
|
-
|
|
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 ==
|
|
147
|
-
predictions[col] =
|
|
148
|
-
elif col ==
|
|
149
|
-
predictions[col] =
|
|
150
|
-
elif col ==
|
|
151
|
-
predictions[col] =
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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[
|
|
214
|
-
sell_preds = len(predictions_df[predictions_df[
|
|
215
|
-
avg_confidence = predictions_df[
|
|
216
|
-
avg_return = predictions_df[
|
|
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": {
|
|
224
|
-
|
|
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[
|
|
249
|
-
default=predictions_df[
|
|
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(
|
|
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[
|
|
262
|
-
(predictions_df[
|
|
263
|
-
(predictions_df[
|
|
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=
|
|
276
|
-
y=
|
|
277
|
-
color=
|
|
278
|
-
size=
|
|
279
|
-
hover_data=[
|
|
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={
|
|
282
|
-
color_discrete_map={
|
|
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[
|
|
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[
|
|
303
|
-
top_sell = filtered_df[filtered_df[
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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 = [
|
|
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(
|
|
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
|
-
|
|
384
|
-
|
|
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", [
|
|
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(
|
|
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=
|
|
417
|
-
|
|
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[
|
|
424
|
-
confidence = prediction_result[
|
|
425
|
-
risk_score = prediction_result[
|
|
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 =
|
|
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 = {
|
|
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 =
|
|
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 = {
|
|
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(
|
|
450
|
-
|
|
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 =
|
|
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(
|
|
458
|
-
|
|
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(
|
|
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=
|
|
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(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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=
|
|
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(
|
|
529
|
-
|
|
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(
|
|
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)[
|
|
562
|
-
total_predictions = performance_df[
|
|
563
|
-
total_successful = performance_df[
|
|
564
|
-
avg_return = performance_df[
|
|
565
|
-
avg_sharpe = performance_df[
|
|
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(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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[
|
|
593
|
-
fig.add_trace(
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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=
|
|
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={
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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=
|
|
645
|
-
y=
|
|
646
|
-
text=
|
|
760
|
+
x="Recommendation",
|
|
761
|
+
y="Win Rate",
|
|
762
|
+
text="Win Rate",
|
|
647
763
|
title="Success Rate by Recommendation Type",
|
|
648
|
-
color=
|
|
649
|
-
color_continuous_scale=
|
|
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=
|
|
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 =
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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=
|
|
682
|
-
y=
|
|
683
|
-
orientation=
|
|
809
|
+
x="Avg Return",
|
|
810
|
+
y="Politician",
|
|
811
|
+
orientation="h",
|
|
684
812
|
title="Politicians with Highest Predicted Returns",
|
|
685
|
-
text=
|
|
686
|
-
color=
|
|
687
|
-
color_continuous_scale=
|
|
813
|
+
text="Avg Return",
|
|
814
|
+
color="Avg Return",
|
|
815
|
+
color_continuous_scale="RdYlGn",
|
|
688
816
|
)
|
|
689
|
-
fig.update_traces(texttemplate=
|
|
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=
|
|
699
|
-
names=
|
|
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[
|
|
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[
|
|
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 =
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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=
|
|
739
|
-
y=
|
|
740
|
-
size=
|
|
741
|
-
color=
|
|
866
|
+
x="Count",
|
|
867
|
+
y="Avg Return",
|
|
868
|
+
size="Count",
|
|
869
|
+
color="Sector",
|
|
742
870
|
title=f"{selected_politician}'s Sector Performance",
|
|
743
|
-
labels={
|
|
744
|
-
text=
|
|
871
|
+
labels={"Count": "Number of Trades", "Avg Return": "Average Predicted Return"},
|
|
872
|
+
text="Sector",
|
|
745
873
|
)
|
|
746
|
-
fig.update_traces(textposition=
|
|
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(
|
|
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[
|
|
900
|
+
st.session_state["portfolio_built"] = True
|
|
773
901
|
|
|
774
902
|
with col2:
|
|
775
|
-
if st.session_state.get(
|
|
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[
|
|
787
|
-
(predictions_df[
|
|
788
|
-
(predictions_df[
|
|
789
|
-
].nlargest(portfolio_size,
|
|
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[
|
|
794
|
-
filtered[
|
|
795
|
-
filtered[
|
|
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(
|
|
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(
|
|
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=
|
|
812
|
-
names=
|
|
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[
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
|