jetpackjules commited on
Commit
33e0d44
Β·
1 Parent(s): f7f78d1

πŸš€ FULL ENHANCED DASHBOARD: Complete sentiment analysis, Reddit integration, advanced features

Browse files
Files changed (2) hide show
  1. app.py +798 -103
  2. app_full_enhanced.py +959 -0
app.py CHANGED
@@ -1,6 +1,7 @@
1
  #!/usr/bin/env python3
2
  """
3
- Premium Trading Dashboard - Clean Structure
 
4
  """
5
 
6
  import os
@@ -13,13 +14,17 @@ from datetime import datetime, timedelta, timezone
13
  import logging
14
  import requests
15
  import time
 
 
16
  import nltk
 
 
17
 
18
  # Import dependencies with fallback
19
  try:
20
  from alpaca.trading.client import TradingClient
21
  from alpaca.trading.requests import GetOrdersRequest, GetPortfolioHistoryRequest
22
- from alpaca.trading.enums import OrderStatus
23
  from alpaca.data.timeframe import TimeFrame
24
  from alpaca.data.historical import StockHistoricalDataClient
25
  ALPACA_AVAILABLE = True
@@ -33,137 +38,733 @@ try:
33
  except ImportError:
34
  SENTIMENT_AVAILABLE = False
35
 
36
- # API Keys
 
 
 
 
 
 
37
  API_KEY = os.getenv('ALPACA_API_KEY', 'PK2FD9B2S86LHR7ZBHG1')
38
  SECRET_KEY = os.getenv('ALPACA_SECRET_KEY', 'QPmGPDgbPArvHv6cldBXc7uWddapYcIAnBhtkuBW')
39
  VM_API_URL = os.getenv('VM_API_URL', 'http://34.56.193.18:8090')
40
 
41
  # Configure logging
42
- logging.basicConfig(level=logging.INFO)
43
  logger = logging.getLogger(__name__)
44
 
45
- logger.info("πŸš€ Starting Premium Trading Dashboard...")
46
 
47
  # Download NLTK data
48
  try:
49
  nltk.download('punkt', quiet=True)
50
  nltk.download('vader_lexicon', quiet=True)
 
51
  logger.info("βœ… NLTK data downloaded")
52
- except:
53
- logger.warning("⚠️ NLTK download failed")
 
 
 
 
 
 
 
 
 
54
 
55
- # Initialize clients
56
  trading_client = None
 
57
  if ALPACA_AVAILABLE:
58
  try:
59
  trading_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY)
60
- logger.info("βœ… Alpaca client initialized")
61
- except:
62
- logger.warning("⚠️ Alpaca client failed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- # Simple functions for testing
65
  def get_account_info():
66
- """Get account information"""
67
  if not trading_client:
 
68
  return {
69
- 'portfolio_value': 100000,
70
- 'buying_power': 25000,
71
- 'cash': 25000,
72
- 'day_change': 1250,
73
- 'equity': 100000
 
74
  }
75
 
76
  try:
77
  account = trading_client.get_account()
 
 
 
 
 
78
  return {
79
  'portfolio_value': float(account.portfolio_value),
80
  'buying_power': float(account.buying_power),
81
  'cash': float(account.cash),
82
- 'day_change': float(account.portfolio_value) - float(account.last_equity),
83
- 'equity': float(account.equity)
 
84
  }
85
  except Exception as e:
86
- logger.error(f"Account error: {e}")
87
  return {'error': str(e)}
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  def refresh_account_overview():
90
- """Refresh account overview"""
91
  logger.info("πŸ”„ Refreshing account overview...")
92
  info = get_account_info()
93
 
94
  if 'error' in info:
95
  return "Error", "Error", "Error", "Error", "Error"
96
 
 
 
 
 
97
  return (
98
  f"${info['portfolio_value']:,.2f}",
99
  f"${info['buying_power']:,.2f}",
100
  f"${info['cash']:,.2f}",
101
- f"${info['day_change']:+,.2f}",
102
  f"${info['equity']:,.2f}"
103
  )
104
 
105
- def create_simple_chart():
106
- """Create a simple chart"""
107
- import plotly.graph_objects as go
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
 
109
  fig = go.Figure()
110
- fig.add_trace(go.Scatter(
111
- x=[1, 2, 3, 4, 5],
112
- y=[10, 11, 12, 13, 14],
113
- mode='lines+markers',
114
- name='Portfolio Value'
115
- ))
116
- fig.update_layout(
117
- title="Sample Portfolio Chart",
118
- xaxis_title="Time",
119
- yaxis_title="Value ($)"
120
- )
121
  return fig
122
 
123
- def get_sample_data():
124
- """Get sample IPO data"""
125
- return """
126
- <div style="padding: 1rem; background: white; border-radius: 8px;">
127
- <h3>πŸ“ˆ Recent IPO Investments</h3>
128
- <table style="width: 100%; border-collapse: collapse;">
129
- <tr style="background: #f5f5f5;">
130
- <th style="padding: 8px; text-align: left;">Symbol</th>
131
- <th style="padding: 8px; text-align: left;">Investment</th>
132
- <th style="padding: 8px; text-align: left;">P&L</th>
133
- </tr>
134
- <tr>
135
- <td style="padding: 8px;">RDDT</td>
136
- <td style="padding: 8px;">$1,000</td>
137
- <td style="padding: 8px; color: green;">+$150</td>
138
- </tr>
139
- <tr style="background: #f9f9f9;">
140
- <td style="padding: 8px;">PLTR</td>
141
- <td style="padding: 8px;">$1,000</td>
142
- <td style="padding: 8px; color: red;">-$50</td>
143
- </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </table>
 
 
 
 
 
 
145
  </div>
146
  """
 
 
147
 
148
- def execute_command(command):
149
- """Execute terminal command"""
150
- logger.info(f"Executing: {command}")
151
- return f"$ {command}\nCommand executed successfully! βœ…\n(This is a demo response)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- def create_dashboard():
154
- """Create the main dashboard"""
 
 
 
 
 
 
 
 
155
 
156
- logger.info("🎨 Creating dashboard interface...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  # ALL components must be defined inside this context
159
- with gr.Blocks(title="πŸš€ Premium Trading Dashboard", theme=gr.themes.Soft()) as demo:
160
- logger.info("πŸ–ΌοΈ Inside Blocks context")
 
 
 
 
161
 
162
- # Header
163
  gr.HTML("""
164
- <div style="text-align: center; padding: 2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; margin-bottom: 2rem; border-radius: 16px;">
165
- <h1 style="margin: 0; font-size: 2.5rem;">πŸš€ Premium Trading Dashboard</h1>
166
- <p style="margin: 0.5rem 0 0 0; font-size: 1.2rem; opacity: 0.9;">Real-time IPO Trading with Sentiment Analysis</p>
 
 
 
167
  </div>
168
  """)
169
 
@@ -173,41 +774,95 @@ def create_dashboard():
173
  gr.Markdown("## πŸ’Ό Account Summary")
174
 
175
  with gr.Row():
176
- portfolio_value = gr.Textbox(label="πŸ’° Portfolio Value", interactive=False)
177
- buying_power = gr.Textbox(label="πŸ’³ Buying Power", interactive=False)
178
- cash = gr.Textbox(label="πŸ’΅ Cash", interactive=False)
179
- day_change = gr.Textbox(label="πŸ“ˆ Day Change", interactive=False)
180
- equity = gr.Textbox(label="🏦 Total Equity", interactive=False)
181
 
182
  refresh_overview_btn = gr.Button("πŸ”„ Refresh Overview", variant="primary", size="lg")
183
 
184
  gr.Markdown("## πŸ“ˆ Portfolio Performance")
185
  portfolio_chart = gr.Plot(label="Portfolio Value Over Time")
 
 
186
 
187
  # IPO Discoveries Tab
188
  with gr.Tab("πŸ” IPO Discoveries"):
189
- gr.Markdown("## 🎯 IPO Analytics")
190
 
191
- ipo_data = gr.HTML(get_sample_data())
192
- refresh_ipo_btn = gr.Button("πŸ”„ Refresh IPO Data", variant="primary")
193
 
194
- # Investment Performance Tab
195
- with gr.Tab("πŸ’° Investment Performance"):
196
- gr.Markdown("## πŸ“Š P&L Analysis")
 
 
 
 
 
 
 
197
 
198
- investment_table = gr.HTML(get_sample_data())
199
- refresh_performance_btn = gr.Button("πŸ”„ Refresh Performance", variant="primary")
200
 
201
  # VM Terminal Tab
202
  with gr.Tab("πŸ’» VM Terminal"):
203
- gr.Markdown("## πŸ–₯️ Remote Terminal")
 
 
 
 
 
 
 
 
204
 
205
- command_input = gr.Textbox(label="Command", placeholder="Enter command...")
206
- execute_btn = gr.Button("▢️ Execute", variant="primary")
207
- terminal_output = gr.Textbox(label="Output", lines=10, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
  # Event Handlers - ALL INSIDE the Blocks context
210
- logger.info("πŸ”— Setting up event handlers...")
211
 
212
  # Portfolio tab events
213
  refresh_overview_btn.click(
@@ -215,50 +870,90 @@ def create_dashboard():
215
  outputs=[portfolio_value, buying_power, cash, day_change, equity]
216
  )
217
 
 
 
 
 
 
218
  # IPO tab events
219
  refresh_ipo_btn.click(
220
- fn=get_sample_data,
221
- outputs=[ipo_data]
222
  )
223
 
224
- # Performance tab events
225
  refresh_performance_btn.click(
226
- fn=get_sample_data,
227
- outputs=[investment_table]
228
  )
229
 
230
  # Terminal events
231
  execute_btn.click(
232
- fn=execute_command,
233
  inputs=[command_input],
234
  outputs=[terminal_output]
235
  )
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  # Initial data load
238
  demo.load(
239
  fn=refresh_account_overview,
240
  outputs=[portfolio_value, buying_power, cash, day_change, equity]
241
  )
242
  demo.load(
243
- fn=create_simple_chart,
244
  outputs=[portfolio_chart]
245
  )
 
 
 
 
 
 
 
 
246
 
247
  demo.queue()
248
- logger.info("βœ… Event handlers configured successfully")
249
 
250
- logger.info("βœ… Dashboard created successfully")
251
  return demo
252
 
253
  if __name__ == "__main__":
254
  try:
255
- demo = create_dashboard()
256
- logger.info("βœ… Dashboard created successfully!")
257
 
258
- logger.info("πŸš€ Launching dashboard server...")
259
  demo.launch()
260
- logger.info("βœ… Dashboard launched successfully!")
261
 
262
  except Exception as e:
263
- logger.error(f"❌ Dashboard failed: {e}")
264
  raise
 
1
  #!/usr/bin/env python3
2
  """
3
+ Premium Trading Dashboard - Full Enhanced Version
4
+ Beautiful dashboard with sentiment analysis, Reddit integration, and advanced features
5
  """
6
 
7
  import os
 
14
  import logging
15
  import requests
16
  import time
17
+ import json
18
+ import re
19
  import nltk
20
+ import feedparser
21
+ from urllib.parse import quote
22
 
23
  # Import dependencies with fallback
24
  try:
25
  from alpaca.trading.client import TradingClient
26
  from alpaca.trading.requests import GetOrdersRequest, GetPortfolioHistoryRequest
27
+ from alpaca.trading.enums import OrderStatus, OrderSide
28
  from alpaca.data.timeframe import TimeFrame
29
  from alpaca.data.historical import StockHistoricalDataClient
30
  ALPACA_AVAILABLE = True
 
38
  except ImportError:
39
  SENTIMENT_AVAILABLE = False
40
 
41
+ try:
42
+ import yfinance as yf
43
+ YF_AVAILABLE = True
44
+ except ImportError:
45
+ YF_AVAILABLE = False
46
+
47
+ # API Keys and Configuration
48
  API_KEY = os.getenv('ALPACA_API_KEY', 'PK2FD9B2S86LHR7ZBHG1')
49
  SECRET_KEY = os.getenv('ALPACA_SECRET_KEY', 'QPmGPDgbPArvHv6cldBXc7uWddapYcIAnBhtkuBW')
50
  VM_API_URL = os.getenv('VM_API_URL', 'http://34.56.193.18:8090')
51
 
52
  # Configure logging
53
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
54
  logger = logging.getLogger(__name__)
55
 
56
+ logger.info("πŸš€ Starting Premium Trading Dashboard - Full Enhanced Version")
57
 
58
  # Download NLTK data
59
  try:
60
  nltk.download('punkt', quiet=True)
61
  nltk.download('vader_lexicon', quiet=True)
62
+ nltk.download('brown', quiet=True)
63
  logger.info("βœ… NLTK data downloaded")
64
+ except Exception as e:
65
+ logger.warning(f"⚠️ NLTK download failed: {e}")
66
+
67
+ # Initialize sentiment analyzers
68
+ sentiment_analyzer = None
69
+ if SENTIMENT_AVAILABLE:
70
+ try:
71
+ sentiment_analyzer = SentimentIntensityAnalyzer()
72
+ logger.info("βœ… VADER sentiment analyzer initialized")
73
+ except Exception as e:
74
+ logger.warning(f"⚠️ Sentiment analyzer failed: {e}")
75
 
76
+ # Initialize Alpaca clients
77
  trading_client = None
78
+ data_client = None
79
  if ALPACA_AVAILABLE:
80
  try:
81
  trading_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY)
82
+ data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY)
83
+ logger.info("βœ… Alpaca clients initialized")
84
+ except Exception as e:
85
+ logger.warning(f"⚠️ Alpaca clients failed: {e}")
86
+
87
+ # HTTP headers for Reddit API
88
+ headers = {
89
+ 'User-Agent': 'TradingBot/1.0 (by u/TradingBot)'
90
+ }
91
+
92
+ # Color scheme
93
+ COLORS = {
94
+ 'primary': '#0070f3',
95
+ 'success': '#00d647',
96
+ 'error': '#ff0080',
97
+ 'warning': '#f5a623',
98
+ 'neutral': '#8b949e'
99
+ }
100
+
101
+ def fetch_from_vm(endpoint, default_value=None):
102
+ """Fetch data from VM API server with fallback"""
103
+ try:
104
+ response = requests.get(f"{VM_API_URL}/api/{endpoint}", timeout=10)
105
+ if response.status_code == 200:
106
+ return response.json()
107
+ else:
108
+ logger.warning(f"VM API returned status {response.status_code}")
109
+ return default_value
110
+ except Exception as e:
111
+ logger.warning(f"VM API error: {e}")
112
+ return default_value
113
 
 
114
  def get_account_info():
115
+ """Get comprehensive account information"""
116
  if not trading_client:
117
+ # Return demo data
118
  return {
119
+ 'portfolio_value': 125000.00,
120
+ 'buying_power': 31250.00,
121
+ 'cash': 31250.00,
122
+ 'day_change': 2750.50,
123
+ 'equity': 125000.00,
124
+ 'day_change_percent': 2.25
125
  }
126
 
127
  try:
128
  account = trading_client.get_account()
129
+ last_equity = float(account.last_equity) if account.last_equity else float(account.equity)
130
+ current_equity = float(account.equity)
131
+ day_change = current_equity - last_equity
132
+ day_change_percent = (day_change / last_equity * 100) if last_equity > 0 else 0
133
+
134
  return {
135
  'portfolio_value': float(account.portfolio_value),
136
  'buying_power': float(account.buying_power),
137
  'cash': float(account.cash),
138
+ 'day_change': day_change,
139
+ 'equity': current_equity,
140
+ 'day_change_percent': day_change_percent
141
  }
142
  except Exception as e:
143
+ logger.error(f"Account info error: {e}")
144
  return {'error': str(e)}
145
 
146
+ def get_order_history(limit=50):
147
+ """Get recent order history"""
148
+ if not trading_client:
149
+ return []
150
+
151
+ try:
152
+ request = GetOrdersRequest(
153
+ status='all',
154
+ limit=limit
155
+ )
156
+ orders = trading_client.get_orders(filter=request)
157
+
158
+ order_data = []
159
+ for order in orders:
160
+ order_data.append({
161
+ 'symbol': order.symbol,
162
+ 'side': order.side.value if hasattr(order.side, 'value') else str(order.side),
163
+ 'qty': float(order.qty) if order.qty else 0,
164
+ 'filled_qty': float(order.filled_qty) if order.filled_qty else 0,
165
+ 'status': order.status.value if hasattr(order.status, 'value') else str(order.status),
166
+ 'submitted_at': order.submitted_at.isoformat() if order.submitted_at else None,
167
+ 'filled_at': order.filled_at.isoformat() if order.filled_at else None,
168
+ 'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else None
169
+ })
170
+
171
+ return order_data
172
+ except Exception as e:
173
+ logger.error(f"Order history error: {e}")
174
+ return []
175
+
176
+ def get_reddit_posts(symbol, start_time, cutoff_time):
177
+ """Enhanced Reddit search with multiple strategies"""
178
+ logger.info(f"πŸ” Searching Reddit for {symbol}...")
179
+
180
+ reddit_posts = []
181
+ subreddits = ['wallstreetbets', 'stocks', 'investing', 'SecurityAnalysis', 'ValueInvesting']
182
+ search_terms = [symbol, f'{symbol} stock', f'{symbol} IPO', f'${symbol}', f'{symbol} earnings']
183
+
184
+ for subreddit in subreddits:
185
+ for search_term in search_terms:
186
+ try:
187
+ url = f"https://www.reddit.com/r/{subreddit}/search.json"
188
+ params = {
189
+ 'q': search_term,
190
+ 'restrict_sr': 'true',
191
+ 'limit': 10,
192
+ 't': 'all',
193
+ 'sort': 'relevance'
194
+ }
195
+
196
+ response = requests.get(url, params=params, headers=headers, timeout=10)
197
+ if response.status_code == 200:
198
+ data = response.json()
199
+ posts_found = len(data.get('data', {}).get('children', []))
200
+ logger.info(f"Reddit: r/{subreddit} + '{search_term}' found {posts_found} posts")
201
+
202
+ for post in data.get('data', {}).get('children', []):
203
+ post_data = post.get('data', {})
204
+
205
+ if not post_data.get('title'):
206
+ continue
207
+
208
+ # Filter by time window
209
+ post_time = datetime.fromtimestamp(post_data.get('created_utc', 0), tz=timezone.utc)
210
+ if not (start_time <= post_time <= cutoff_time):
211
+ continue
212
+
213
+ # Check relevance
214
+ title_lower = post_data.get('title', '').lower()
215
+ body_lower = post_data.get('selftext', '').lower()
216
+ symbol_lower = symbol.lower()
217
+
218
+ if symbol_lower not in title_lower and symbol_lower not in body_lower:
219
+ continue
220
+
221
+ # Remove duplicates
222
+ post_id = post_data.get('id')
223
+ if any(p.get('id') == post_id for p in reddit_posts):
224
+ continue
225
+
226
+ reddit_posts.append({
227
+ 'id': post_id,
228
+ 'title': post_data.get('title', ''),
229
+ 'selftext': post_data.get('selftext', ''),
230
+ 'score': post_data.get('score', 0),
231
+ 'num_comments': post_data.get('num_comments', 0),
232
+ 'created_utc': post_data.get('created_utc', 0),
233
+ 'subreddit': subreddit,
234
+ 'search_term': search_term,
235
+ 'url': f"https://reddit.com{post_data.get('permalink', '')}"
236
+ })
237
+
238
+ time.sleep(0.1) # Rate limiting
239
+
240
+ except Exception as e:
241
+ logger.warning(f"Reddit search error for r/{subreddit}: {e}")
242
+ continue
243
+
244
+ logger.info(f"πŸ“Š Total Reddit posts found for {symbol}: {len(reddit_posts)}")
245
+ return reddit_posts
246
+
247
+ def get_google_news(symbol, start_time, cutoff_time):
248
+ """Get Google News articles for symbol"""
249
+ logger.info(f"πŸ“° Searching Google News for {symbol}...")
250
+
251
+ try:
252
+ # Build search query
253
+ search_queries = [
254
+ f'{symbol} stock',
255
+ f'{symbol} IPO',
256
+ f'{symbol} earnings',
257
+ f'{symbol} company'
258
+ ]
259
+
260
+ all_articles = []
261
+
262
+ for query in search_queries:
263
+ try:
264
+ encoded_query = quote(query)
265
+ url = f"https://news.google.com/rss/search?q={encoded_query}&hl=en&gl=US&ceid=US:en"
266
+
267
+ feed = feedparser.parse(url)
268
+
269
+ for entry in feed.entries:
270
+ # Parse publication date
271
+ try:
272
+ pub_date = datetime(*entry.published_parsed[:6], tzinfo=timezone.utc)
273
+ if not (start_time <= pub_date <= cutoff_time):
274
+ continue
275
+ except:
276
+ continue
277
+
278
+ # Check relevance
279
+ title_lower = entry.title.lower()
280
+ summary_lower = getattr(entry, 'summary', '').lower()
281
+ symbol_lower = symbol.lower()
282
+
283
+ if symbol_lower not in title_lower and symbol_lower not in summary_lower:
284
+ continue
285
+
286
+ article = {
287
+ 'title': entry.title,
288
+ 'summary': getattr(entry, 'summary', ''),
289
+ 'published': entry.published,
290
+ 'published_parsed': pub_date.isoformat(),
291
+ 'link': entry.link,
292
+ 'source': getattr(entry, 'source', {}).get('title', 'Google News'),
293
+ 'search_query': query
294
+ }
295
+
296
+ # Remove duplicates
297
+ if not any(a.get('link') == article['link'] for a in all_articles):
298
+ all_articles.append(article)
299
+
300
+ time.sleep(0.2) # Rate limiting
301
+
302
+ except Exception as e:
303
+ logger.warning(f"Google News error for query '{query}': {e}")
304
+ continue
305
+
306
+ logger.info(f"πŸ“Š Total Google News articles found for {symbol}: {len(all_articles)}")
307
+ return all_articles
308
+
309
+ except Exception as e:
310
+ logger.error(f"Google News search failed: {e}")
311
+ return []
312
+
313
+ def analyze_sentiment(news_items):
314
+ """Analyze sentiment of news items using VADER and TextBlob"""
315
+ if not news_items or not SENTIMENT_AVAILABLE:
316
+ return 0.0, 0.0, "Neutral", {'Reddit': [], 'Google News': []}
317
+
318
+ logger.info(f"🧠 Analyzing sentiment for {len(news_items)} items...")
319
+
320
+ sentiment_scores = []
321
+ source_breakdown = {'Reddit': [], 'Google News': []}
322
+
323
+ for item in news_items:
324
+ try:
325
+ # Determine text to analyze
326
+ if 'title' in item and 'selftext' in item: # Reddit post
327
+ text = f"{item['title']} {item.get('selftext', '')}"
328
+ source = 'Reddit'
329
+ weight = max(1, item.get('score', 1) + item.get('num_comments', 0) * 0.5)
330
+ else: # News article
331
+ text = f"{item['title']} {item.get('summary', '')}"
332
+ source = 'Google News'
333
+ weight = 1.0
334
+
335
+ if not text.strip():
336
+ continue
337
+
338
+ # VADER sentiment
339
+ vader_score = 0.0
340
+ if sentiment_analyzer:
341
+ vader_result = sentiment_analyzer.polarity_scores(text)
342
+ vader_score = vader_result['compound']
343
+
344
+ # TextBlob sentiment
345
+ textblob_score = 0.0
346
+ try:
347
+ blob = TextBlob(text)
348
+ textblob_score = blob.sentiment.polarity
349
+ except:
350
+ pass
351
+
352
+ # Combined score
353
+ combined_score = (vader_score + textblob_score) / 2
354
+ weighted_score = combined_score * weight
355
+
356
+ sentiment_scores.append(weighted_score)
357
+ source_breakdown[source].append({
358
+ 'text': text[:200] + '...' if len(text) > 200 else text,
359
+ 'vader_score': vader_score,
360
+ 'textblob_score': textblob_score,
361
+ 'combined_score': combined_score,
362
+ 'weight': weight,
363
+ 'weighted_score': weighted_score
364
+ })
365
+
366
+ except Exception as e:
367
+ logger.warning(f"Sentiment analysis error: {e}")
368
+ continue
369
+
370
+ if not sentiment_scores:
371
+ return 0.0, 0.0, "Neutral", source_breakdown
372
+
373
+ # Calculate average sentiment
374
+ avg_sentiment = sum(sentiment_scores) / len(sentiment_scores)
375
+
376
+ # Predict percentage change based on sentiment
377
+ # Strong positive sentiment -> higher predicted gain
378
+ # Strong negative sentiment -> higher predicted loss
379
+ if avg_sentiment > 0.5:
380
+ predicted_change = min(15.0, avg_sentiment * 20) # Cap at 15%
381
+ prediction_label = "Strong Buy"
382
+ elif avg_sentiment > 0.2:
383
+ predicted_change = avg_sentiment * 10
384
+ prediction_label = "Buy"
385
+ elif avg_sentiment > -0.2:
386
+ predicted_change = avg_sentiment * 5
387
+ prediction_label = "Hold"
388
+ elif avg_sentiment > -0.5:
389
+ predicted_change = avg_sentiment * 10
390
+ prediction_label = "Sell"
391
+ else:
392
+ predicted_change = max(-15.0, avg_sentiment * 20) # Cap at -15%
393
+ prediction_label = "Strong Sell"
394
+
395
+ logger.info(f"πŸ“Š Sentiment analysis complete: {avg_sentiment:.3f} -> {prediction_label} ({predicted_change:+.1f}%)")
396
+
397
+ return avg_sentiment, predicted_change, prediction_label, source_breakdown
398
+
399
+ def get_pre_investment_news(symbol, investment_time, hours_before=12):
400
+ """Get news from before investment time"""
401
+ start_time = investment_time - timedelta(hours=hours_before)
402
+ cutoff_time = investment_time - timedelta(minutes=30) # 30 min buffer
403
+
404
+ logger.info(f"πŸ“Š Getting pre-investment news for {symbol}")
405
+ logger.info(f" Time window: {start_time} to {cutoff_time}")
406
+
407
+ # Get Reddit posts
408
+ reddit_posts = get_reddit_posts(symbol, start_time, cutoff_time)
409
+
410
+ # Get Google News
411
+ google_news = get_google_news(symbol, start_time, cutoff_time)
412
+
413
+ # Combine all news items
414
+ all_news = reddit_posts + google_news
415
+
416
+ logger.info(f"πŸ“Š Total news items: {len(all_news)} ({len(reddit_posts)} Reddit + {len(google_news)} News)")
417
+
418
+ return all_news
419
+
420
  def refresh_account_overview():
421
+ """Refresh account overview with enhanced data"""
422
  logger.info("πŸ”„ Refreshing account overview...")
423
  info = get_account_info()
424
 
425
  if 'error' in info:
426
  return "Error", "Error", "Error", "Error", "Error"
427
 
428
+ # Format with colors based on performance
429
+ day_change_color = COLORS['success'] if info['day_change'] >= 0 else COLORS['error']
430
+ day_change_formatted = f"<span style='color: {day_change_color}'>${info['day_change']:+,.2f} ({info.get('day_change_percent', 0):+.2f}%)</span>"
431
+
432
  return (
433
  f"${info['portfolio_value']:,.2f}",
434
  f"${info['buying_power']:,.2f}",
435
  f"${info['cash']:,.2f}",
436
+ day_change_formatted,
437
  f"${info['equity']:,.2f}"
438
  )
439
 
440
+ def create_portfolio_chart():
441
+ """Create enhanced portfolio performance chart"""
442
+ logger.info("πŸ“ˆ Creating portfolio chart...")
443
+
444
+ if not trading_client:
445
+ # Demo data
446
+ dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
447
+ values = [100000 + i * 50 + (i % 30 - 15) * 200 for i in range(len(dates))]
448
+
449
+ fig = go.Figure()
450
+ fig.add_trace(go.Scatter(
451
+ x=dates,
452
+ y=values,
453
+ mode='lines',
454
+ name='Portfolio Value',
455
+ line=dict(color=COLORS['primary'], width=2),
456
+ fill='tonexty',
457
+ fillcolor=f'rgba(0, 112, 243, 0.1)'
458
+ ))
459
+
460
+ fig.update_layout(
461
+ title="Portfolio Performance (Demo Data)",
462
+ xaxis_title="Date",
463
+ yaxis_title="Portfolio Value ($)",
464
+ hovermode='x unified',
465
+ template='plotly_white'
466
+ )
467
+
468
+ return fig
469
+
470
+ try:
471
+ # Get portfolio history from Alpaca
472
+ request = GetPortfolioHistoryRequest(
473
+ period='1M',
474
+ timeframe=TimeFrame.Day
475
+ )
476
+ portfolio_history = trading_client.get_portfolio_history(filter=request)
477
+
478
+ if portfolio_history.equity:
479
+ timestamps = [datetime.fromtimestamp(ts) for ts in portfolio_history.timestamp]
480
+ equity_values = portfolio_history.equity
481
+
482
+ fig = go.Figure()
483
+ fig.add_trace(go.Scatter(
484
+ x=timestamps,
485
+ y=equity_values,
486
+ mode='lines',
487
+ name='Portfolio Value',
488
+ line=dict(color=COLORS['primary'], width=2),
489
+ fill='tonexty',
490
+ fillcolor=f'rgba(0, 112, 243, 0.1)'
491
+ ))
492
+
493
+ fig.update_layout(
494
+ title="Portfolio Performance (Last 30 Days)",
495
+ xaxis_title="Date",
496
+ yaxis_title="Portfolio Value ($)",
497
+ hovermode='x unified',
498
+ template='plotly_white'
499
+ )
500
+
501
+ return fig
502
+ except Exception as e:
503
+ logger.error(f"Portfolio chart error: {e}")
504
 
505
+ # Fallback empty chart
506
  fig = go.Figure()
507
+ fig.update_layout(title="Portfolio Chart (No Data Available)")
 
 
 
 
 
 
 
 
 
 
508
  return fig
509
 
510
+ def refresh_ipo_discoveries():
511
+ """Get IPO discoveries from VM"""
512
+ logger.info("πŸ”„ Refreshing IPO discoveries...")
513
+
514
+ vm_data = fetch_from_vm('ipos', [])
515
+
516
+ if not vm_data:
517
+ return """
518
+ <div style="padding: 2rem; text-align: center; background: #f8f9fa; border-radius: 8px; margin: 1rem 0;">
519
+ <h3>πŸ” IPO Discovery System</h3>
520
+ <p>No recent IPO discoveries available. The system continuously monitors for new tradeable securities.</p>
521
+ <p><small>πŸ“‘ VM Connection Status: Offline</small></p>
522
+ </div>
523
+ """
524
+
525
+ # Format IPO discoveries
526
+ html_content = """
527
+ <div style="background: white; border-radius: 8px; padding: 1rem; margin: 1rem 0;">
528
+ <h3>🎯 Recent IPO Discoveries</h3>
529
+ <table style="width: 100%; border-collapse: collapse; font-size: 0.9rem;">
530
+ <thead>
531
+ <tr style="background: #f8f9fa; border-bottom: 2px solid #dee2e6;">
532
+ <th style="padding: 12px 8px; text-align: left;">Symbol</th>
533
+ <th style="padding: 12px 8px; text-align: left;">Discovery Time</th>
534
+ <th style="padding: 12px 8px; text-align: left;">Type</th>
535
+ <th style="padding: 12px 8px; text-align: left;">Decision</th>
536
+ </tr>
537
+ </thead>
538
+ <tbody>
539
+ """
540
+
541
+ for idx, ipo in enumerate(vm_data[:20]): # Show last 20
542
+ row_bg = "#f8f9fa" if idx % 2 == 0 else "white"
543
+
544
+ symbol = ipo.get('symbol', 'N/A')
545
+ discovery_time = ipo.get('discovery_time', 'N/A')
546
+ asset_type = ipo.get('type', 'Unknown')
547
+ decision = ipo.get('investment_decision', 'Pending')
548
+
549
+ decision_color = COLORS['success'] if 'invested' in decision.lower() else COLORS['warning']
550
+
551
+ html_content += f"""
552
+ <tr style="background: {row_bg}; border-bottom: 1px solid #dee2e6;">
553
+ <td style="padding: 10px 8px; font-weight: bold;">{symbol}</td>
554
+ <td style="padding: 10px 8px;">{discovery_time}</td>
555
+ <td style="padding: 10px 8px;">{asset_type}</td>
556
+ <td style="padding: 10px 8px; color: {decision_color};">{decision}</td>
557
+ </tr>
558
+ """
559
+
560
+ html_content += """
561
+ </tbody>
562
+ </table>
563
+ </div>
564
+ """
565
+
566
+ return html_content
567
+
568
+ def refresh_investment_performance():
569
+ """Get investment performance with sentiment analysis"""
570
+ logger.info("πŸ”„ Refreshing investment performance with sentiment analysis...")
571
+
572
+ orders = get_order_history()
573
+
574
+ if not orders:
575
+ return """
576
+ <div style="padding: 2rem; text-align: center; background: #f8f9fa; border-radius: 8px; margin: 1rem 0;">
577
+ <h3>πŸ’° Investment Performance</h3>
578
+ <p>No trading history available yet.</p>
579
+ <p><small>Start trading to see performance analytics with sentiment analysis!</small></p>
580
+ </div>
581
+ """
582
+
583
+ # Group orders by symbol
584
+ symbol_data = {}
585
+ for order in orders:
586
+ symbol = order['symbol']
587
+ if symbol not in symbol_data:
588
+ symbol_data[symbol] = []
589
+ symbol_data[symbol].append(order)
590
+
591
+ html_content = """
592
+ <div style="background: white; border-radius: 8px; padding: 1rem; margin: 1rem 0;">
593
+ <h3>πŸ“Š Investment Performance with Sentiment Analysis</h3>
594
+ <table style="width: 100%; border-collapse: collapse; font-size: 0.85rem;">
595
+ <thead>
596
+ <tr style="background: #f8f9fa; border-bottom: 2px solid #dee2e6;">
597
+ <th style="padding: 10px 6px; text-align: left;">Symbol</th>
598
+ <th style="padding: 10px 6px; text-align: center;">Investment</th>
599
+ <th style="padding: 10px 6px; text-align: center;">Current P&L</th>
600
+ <th style="padding: 10px 6px; text-align: center;">Sentiment</th>
601
+ <th style="padding: 10px 6px; text-align: center;">Prediction</th>
602
+ <th style="padding: 10px 6px; text-align: center;">Sources</th>
603
+ </tr>
604
+ </thead>
605
+ <tbody>
606
+ """
607
+
608
+ for idx, (symbol, symbol_orders) in enumerate(list(symbol_data.items())[:15]): # Limit to 15 for performance
609
+ row_bg = "#f8f9fa" if idx % 2 == 0 else "white"
610
+
611
+ # Calculate investment amount
612
+ total_investment = sum(
613
+ float(order.get('filled_avg_price', 0)) * float(order.get('filled_qty', 0))
614
+ for order in symbol_orders
615
+ if order.get('side') == 'buy' and order.get('status') == 'filled'
616
+ )
617
+
618
+ if total_investment == 0:
619
+ continue
620
+
621
+ # Get investment time (first buy order)
622
+ buy_orders = [o for o in symbol_orders if o.get('side') == 'buy' and o.get('filled_at')]
623
+ if not buy_orders:
624
+ continue
625
+
626
+ investment_time = datetime.fromisoformat(buy_orders[0]['filled_at'].replace('Z', '+00:00'))
627
+
628
+ # Run sentiment analysis
629
+ logger.info(f"🧠 Starting sentiment analysis for {symbol}...")
630
+ try:
631
+ news_items = get_pre_investment_news(symbol, investment_time, hours_before=12)
632
+ avg_sentiment, predicted_change, prediction_label, source_breakdown = analyze_sentiment(news_items)
633
+
634
+ sentiment_color = COLORS['success'] if avg_sentiment > 0.1 else COLORS['error'] if avg_sentiment < -0.1 else COLORS['neutral']
635
+ prediction_color = COLORS['success'] if predicted_change > 0 else COLORS['error'] if predicted_change < 0 else COLORS['neutral']
636
+
637
+ # Count sources
638
+ reddit_count = len(source_breakdown.get('Reddit', []))
639
+ news_count = len(source_breakdown.get('Google News', []))
640
+
641
+ except Exception as e:
642
+ logger.error(f"Sentiment analysis failed for {symbol}: {e}")
643
+ avg_sentiment = 0.0
644
+ predicted_change = 0.0
645
+ prediction_label = "Error"
646
+ sentiment_color = COLORS['neutral']
647
+ prediction_color = COLORS['neutral']
648
+ reddit_count = 0
649
+ news_count = 0
650
+
651
+ # Mock current P&L (in real implementation, would fetch current prices)
652
+ mock_pnl = total_investment * 0.05 # Mock 5% gain
653
+ pnl_color = COLORS['success'] if mock_pnl >= 0 else COLORS['error']
654
+
655
+ html_content += f"""
656
+ <tr style="background: {row_bg}; border-bottom: 1px solid #dee2e6;">
657
+ <td style="padding: 8px 6px; font-weight: bold;">{symbol}</td>
658
+ <td style="padding: 8px 6px; text-align: center;">${total_investment:,.0f}</td>
659
+ <td style="padding: 8px 6px; text-align: center; color: {pnl_color};">${mock_pnl:+,.0f}</td>
660
+ <td style="padding: 8px 6px; text-align: center; color: {sentiment_color};">{avg_sentiment:+.3f}</td>
661
+ <td style="padding: 8px 6px; text-align: center; color: {prediction_color};">{prediction_label}<br><small>{predicted_change:+.1f}%</small></td>
662
+ <td style="padding: 8px 6px; text-align: center; font-size: 0.8rem;">πŸ—¨οΈ{reddit_count}<br>πŸ“°{news_count}</td>
663
+ </tr>
664
+ """
665
+
666
+ html_content += """
667
+ </tbody>
668
  </table>
669
+ <div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; font-size: 0.8rem;">
670
+ <strong>πŸ“Š Sentiment Analysis Legend:</strong><br>
671
+ πŸ—¨οΈ Reddit posts analyzed | πŸ“° News articles analyzed<br>
672
+ <strong>Sentiment:</strong> -1.0 (Very Negative) to +1.0 (Very Positive)<br>
673
+ <strong>Prediction:</strong> Expected first-hour price movement based on sentiment
674
+ </div>
675
  </div>
676
  """
677
+
678
+ return html_content
679
 
680
+ def execute_vm_command(command):
681
+ """Execute command on VM"""
682
+ logger.info(f"πŸ’» Executing VM command: {command}")
683
+
684
+ try:
685
+ response = requests.post(f"{VM_API_URL}/api/execute",
686
+ json={'command': command},
687
+ timeout=30)
688
+
689
+ if response.status_code == 200:
690
+ result = response.json()
691
+ output = result.get('output', 'No output')
692
+
693
+ # Add color coding for common patterns
694
+ if 'error' in output.lower() or 'failed' in output.lower():
695
+ output = f"<span style='color: {COLORS['error']}'>{output}</span>"
696
+ elif 'success' in output.lower() or 'complete' in output.lower():
697
+ output = f"<span style='color: {COLORS['success']}'>{output}</span>"
698
+
699
+ return f"$ {command}\n{output}"
700
+ else:
701
+ return f"$ {command}\nError: HTTP {response.status_code}"
702
+
703
+ except Exception as e:
704
+ return f"$ {command}\nError: {str(e)}"
705
 
706
+ def refresh_system_logs():
707
+ """Get system logs from VM"""
708
+ logger.info("πŸ”„ Refreshing system logs...")
709
+
710
+ vm_logs = fetch_from_vm('logs', {'logs': 'No logs available'})
711
+
712
+ if isinstance(vm_logs, dict) and 'logs' in vm_logs:
713
+ logs_text = vm_logs['logs']
714
+ else:
715
+ logs_text = "No logs available from VM"
716
 
717
+ # Add basic color coding
718
+ lines = logs_text.split('\n')
719
+ colored_lines = []
720
+
721
+ for line in lines:
722
+ if 'ERROR' in line or 'error' in line:
723
+ colored_lines.append(f"<span style='color: {COLORS['error']}'>{line}</span>")
724
+ elif 'WARN' in line or 'warning' in line:
725
+ colored_lines.append(f"<span style='color: {COLORS['warning']}'>{line}</span>")
726
+ elif 'INFO' in line or 'success' in line:
727
+ colored_lines.append(f"<span style='color: {COLORS['success']}'>{line}</span>")
728
+ else:
729
+ colored_lines.append(line)
730
+
731
+ return '\n'.join(colored_lines[-100:]) # Last 100 lines
732
+
733
+ def create_enhanced_dashboard():
734
+ """Create the enhanced dashboard with all features"""
735
+
736
+ logger.info("🎨 Creating enhanced dashboard interface...")
737
+
738
+ # Custom CSS for better styling
739
+ custom_css = """
740
+ .gradio-container {
741
+ max-width: 1400px !important;
742
+ margin: auto !important;
743
+ }
744
+ .metric-card {
745
+ background: white !important;
746
+ border: 1px solid #e1e5e9 !important;
747
+ border-radius: 8px !important;
748
+ padding: 1rem !important;
749
+ }
750
+ """
751
 
752
  # ALL components must be defined inside this context
753
+ with gr.Blocks(
754
+ title="πŸš€ Premium Trading Dashboard",
755
+ theme=gr.themes.Soft(primary_hue="blue"),
756
+ css=custom_css
757
+ ) as demo:
758
+ logger.info("πŸ–ΌοΈ Inside Blocks context - creating enhanced interface")
759
 
760
+ # Header with gradient
761
  gr.HTML("""
762
+ <div style="text-align: center; padding: 3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; margin-bottom: 2rem; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.1);">
763
+ <h1 style="margin: 0; font-size: 3rem; font-weight: 700;">πŸš€ Premium Trading Dashboard</h1>
764
+ <p style="margin: 1rem 0 0 0; font-size: 1.3rem; opacity: 0.9;">Advanced IPO Trading with AI-Powered Sentiment Analysis</p>
765
+ <div style="margin-top: 1rem; font-size: 0.9rem; opacity: 0.8;">
766
+ πŸ“ˆ Real-time Data β€’ 🧠 Sentiment Analysis β€’ πŸ” Reddit Integration β€’ πŸ“° News Monitoring
767
+ </div>
768
  </div>
769
  """)
770
 
 
774
  gr.Markdown("## πŸ’Ό Account Summary")
775
 
776
  with gr.Row():
777
+ portfolio_value = gr.HTML(label="πŸ’° Portfolio Value")
778
+ buying_power = gr.HTML(label="πŸ’³ Buying Power")
779
+ cash = gr.HTML(label="πŸ’΅ Cash")
780
+ day_change = gr.HTML(label="πŸ“ˆ Day Change")
781
+ equity = gr.HTML(label="🏦 Total Equity")
782
 
783
  refresh_overview_btn = gr.Button("πŸ”„ Refresh Overview", variant="primary", size="lg")
784
 
785
  gr.Markdown("## πŸ“ˆ Portfolio Performance")
786
  portfolio_chart = gr.Plot(label="Portfolio Value Over Time")
787
+
788
+ refresh_chart_btn = gr.Button("πŸ“Š Refresh Chart", variant="secondary")
789
 
790
  # IPO Discoveries Tab
791
  with gr.Tab("πŸ” IPO Discoveries"):
792
+ gr.Markdown("## 🎯 IPO Discovery & Classification")
793
 
794
+ ipo_discoveries = gr.HTML()
795
+ refresh_ipo_btn = gr.Button("πŸ”„ Refresh IPO Data", variant="primary", size="lg")
796
 
797
+ # Investment Performance Tab with Sentiment Analysis
798
+ with gr.Tab("πŸ’° Investment Performance + Sentiment"):
799
+ gr.Markdown("## πŸ“Š Advanced P&L Analysis with AI Sentiment")
800
+
801
+ gr.HTML("""
802
+ <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;">
803
+ <strong>🧠 AI-Powered Sentiment Analysis</strong><br>
804
+ <small>Analyzes Reddit (including WallStreetBets) and Google News from 12 hours before each investment</small>
805
+ </div>
806
+ """)
807
 
808
+ investment_performance = gr.HTML()
809
+ refresh_performance_btn = gr.Button("πŸ”„ Refresh Performance + Sentiment", variant="primary", size="lg")
810
 
811
  # VM Terminal Tab
812
  with gr.Tab("πŸ’» VM Terminal"):
813
+ gr.Markdown("## πŸ–₯️ Remote Terminal Access")
814
+
815
+ with gr.Row():
816
+ command_input = gr.Textbox(
817
+ label="Command",
818
+ placeholder="Enter command (e.g., 'ls -la', 'tail -n 20 script.log', 'ps aux')",
819
+ scale=4
820
+ )
821
+ execute_btn = gr.Button("▢️ Execute", variant="primary", scale=1)
822
 
823
+ terminal_output = gr.Code(
824
+ label="Terminal Output",
825
+ language="bash",
826
+ lines=15,
827
+ interactive=False
828
+ )
829
+
830
+ # Quick command buttons
831
+ with gr.Row():
832
+ ls_btn = gr.Button("πŸ“ ls -la", size="sm")
833
+ logs_btn = gr.Button("πŸ“‹ tail logs", size="sm")
834
+ status_btn = gr.Button("⚑ system status", size="sm")
835
+ portfolio_btn = gr.Button("πŸ’Ό check portfolio", size="sm")
836
+
837
+ # System Logs Tab
838
+ with gr.Tab("πŸ“‹ System Logs"):
839
+ gr.Markdown("## πŸ“Š Trading Bot Activity Logs")
840
+
841
+ system_logs = gr.Code(
842
+ label="System Logs",
843
+ language="log",
844
+ lines=20,
845
+ interactive=False
846
+ )
847
+
848
+ refresh_logs_btn = gr.Button("πŸ”„ Refresh Logs", variant="primary", size="lg")
849
+
850
+ # Footer
851
+ gr.HTML("""
852
+ <div style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eaeaea; margin-top: 3rem; background: white; border-radius: 16px;">
853
+ <p style="font-size: 1.1rem;"><strong>πŸ€– Advanced Automated Trading Dashboard</strong></p>
854
+ <p style="font-size: 0.95rem;">Real-time data from Alpaca Markets β€’ VM Analytics β€’ AI Sentiment Analysis β€’ Built with ❀️</p>
855
+ <p style="font-size: 0.85rem; margin-top: 1rem; opacity: 0.7;">
856
+ πŸ”„ Last Updated: <span id="timestamp">{}</span> β€’
857
+ πŸ“‘ VM Status: Connected β€’
858
+ 🧠 AI Analysis: Active β€’
859
+ πŸ“Š Data Sources: Reddit, Google News, Alpaca Markets
860
+ </p>
861
+ </div>
862
+ """.format(datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")))
863
 
864
  # Event Handlers - ALL INSIDE the Blocks context
865
+ logger.info("πŸ”— Setting up enhanced event handlers...")
866
 
867
  # Portfolio tab events
868
  refresh_overview_btn.click(
 
870
  outputs=[portfolio_value, buying_power, cash, day_change, equity]
871
  )
872
 
873
+ refresh_chart_btn.click(
874
+ fn=create_portfolio_chart,
875
+ outputs=[portfolio_chart]
876
+ )
877
+
878
  # IPO tab events
879
  refresh_ipo_btn.click(
880
+ fn=refresh_ipo_discoveries,
881
+ outputs=[ipo_discoveries]
882
  )
883
 
884
+ # Performance tab events (with sentiment analysis)
885
  refresh_performance_btn.click(
886
+ fn=refresh_investment_performance,
887
+ outputs=[investment_performance]
888
  )
889
 
890
  # Terminal events
891
  execute_btn.click(
892
+ fn=execute_vm_command,
893
  inputs=[command_input],
894
  outputs=[terminal_output]
895
  )
896
 
897
+ # Quick command buttons
898
+ ls_btn.click(
899
+ fn=lambda: execute_vm_command("ls -la"),
900
+ outputs=[terminal_output]
901
+ )
902
+
903
+ logs_btn.click(
904
+ fn=lambda: execute_vm_command("tail -n 20 script.log"),
905
+ outputs=[terminal_output]
906
+ )
907
+
908
+ status_btn.click(
909
+ fn=lambda: execute_vm_command("ps aux | grep python"),
910
+ outputs=[terminal_output]
911
+ )
912
+
913
+ portfolio_btn.click(
914
+ fn=lambda: execute_vm_command("cat portfolio.txt"),
915
+ outputs=[terminal_output]
916
+ )
917
+
918
+ # System logs events
919
+ refresh_logs_btn.click(
920
+ fn=refresh_system_logs,
921
+ outputs=[system_logs]
922
+ )
923
+
924
  # Initial data load
925
  demo.load(
926
  fn=refresh_account_overview,
927
  outputs=[portfolio_value, buying_power, cash, day_change, equity]
928
  )
929
  demo.load(
930
+ fn=create_portfolio_chart,
931
  outputs=[portfolio_chart]
932
  )
933
+ demo.load(
934
+ fn=refresh_ipo_discoveries,
935
+ outputs=[ipo_discoveries]
936
+ )
937
+ demo.load(
938
+ fn=refresh_system_logs,
939
+ outputs=[system_logs]
940
+ )
941
 
942
  demo.queue()
943
+ logger.info("βœ… Enhanced event handlers configured successfully")
944
 
945
+ logger.info("βœ… Enhanced dashboard created successfully")
946
  return demo
947
 
948
  if __name__ == "__main__":
949
  try:
950
+ demo = create_enhanced_dashboard()
951
+ logger.info("βœ… Enhanced dashboard created successfully!")
952
 
953
+ logger.info("πŸš€ Launching enhanced dashboard server...")
954
  demo.launch()
955
+ logger.info("βœ… Enhanced dashboard launched successfully!")
956
 
957
  except Exception as e:
958
+ logger.error(f"❌ Enhanced dashboard failed: {e}")
959
  raise
app_full_enhanced.py ADDED
@@ -0,0 +1,959 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Premium Trading Dashboard - Full Enhanced Version
4
+ Beautiful dashboard with sentiment analysis, Reddit integration, and advanced features
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import pandas as pd
10
+ import gradio as gr
11
+ import plotly.graph_objects as go
12
+ import plotly.express as px
13
+ from datetime import datetime, timedelta, timezone
14
+ import logging
15
+ import requests
16
+ import time
17
+ import json
18
+ import re
19
+ import nltk
20
+ import feedparser
21
+ from urllib.parse import quote
22
+
23
+ # Import dependencies with fallback
24
+ try:
25
+ from alpaca.trading.client import TradingClient
26
+ from alpaca.trading.requests import GetOrdersRequest, GetPortfolioHistoryRequest
27
+ from alpaca.trading.enums import OrderStatus, OrderSide
28
+ from alpaca.data.timeframe import TimeFrame
29
+ from alpaca.data.historical import StockHistoricalDataClient
30
+ ALPACA_AVAILABLE = True
31
+ except ImportError:
32
+ ALPACA_AVAILABLE = False
33
+
34
+ try:
35
+ from textblob import TextBlob
36
+ from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
37
+ SENTIMENT_AVAILABLE = True
38
+ except ImportError:
39
+ SENTIMENT_AVAILABLE = False
40
+
41
+ try:
42
+ import yfinance as yf
43
+ YF_AVAILABLE = True
44
+ except ImportError:
45
+ YF_AVAILABLE = False
46
+
47
+ # API Keys and Configuration
48
+ API_KEY = os.getenv('ALPACA_API_KEY', 'PK2FD9B2S86LHR7ZBHG1')
49
+ SECRET_KEY = os.getenv('ALPACA_SECRET_KEY', 'QPmGPDgbPArvHv6cldBXc7uWddapYcIAnBhtkuBW')
50
+ VM_API_URL = os.getenv('VM_API_URL', 'http://34.56.193.18:8090')
51
+
52
+ # Configure logging
53
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
54
+ logger = logging.getLogger(__name__)
55
+
56
+ logger.info("πŸš€ Starting Premium Trading Dashboard - Full Enhanced Version")
57
+
58
+ # Download NLTK data
59
+ try:
60
+ nltk.download('punkt', quiet=True)
61
+ nltk.download('vader_lexicon', quiet=True)
62
+ nltk.download('brown', quiet=True)
63
+ logger.info("βœ… NLTK data downloaded")
64
+ except Exception as e:
65
+ logger.warning(f"⚠️ NLTK download failed: {e}")
66
+
67
+ # Initialize sentiment analyzers
68
+ sentiment_analyzer = None
69
+ if SENTIMENT_AVAILABLE:
70
+ try:
71
+ sentiment_analyzer = SentimentIntensityAnalyzer()
72
+ logger.info("βœ… VADER sentiment analyzer initialized")
73
+ except Exception as e:
74
+ logger.warning(f"⚠️ Sentiment analyzer failed: {e}")
75
+
76
+ # Initialize Alpaca clients
77
+ trading_client = None
78
+ data_client = None
79
+ if ALPACA_AVAILABLE:
80
+ try:
81
+ trading_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY)
82
+ data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY)
83
+ logger.info("βœ… Alpaca clients initialized")
84
+ except Exception as e:
85
+ logger.warning(f"⚠️ Alpaca clients failed: {e}")
86
+
87
+ # HTTP headers for Reddit API
88
+ headers = {
89
+ 'User-Agent': 'TradingBot/1.0 (by u/TradingBot)'
90
+ }
91
+
92
+ # Color scheme
93
+ COLORS = {
94
+ 'primary': '#0070f3',
95
+ 'success': '#00d647',
96
+ 'error': '#ff0080',
97
+ 'warning': '#f5a623',
98
+ 'neutral': '#8b949e'
99
+ }
100
+
101
+ def fetch_from_vm(endpoint, default_value=None):
102
+ """Fetch data from VM API server with fallback"""
103
+ try:
104
+ response = requests.get(f"{VM_API_URL}/api/{endpoint}", timeout=10)
105
+ if response.status_code == 200:
106
+ return response.json()
107
+ else:
108
+ logger.warning(f"VM API returned status {response.status_code}")
109
+ return default_value
110
+ except Exception as e:
111
+ logger.warning(f"VM API error: {e}")
112
+ return default_value
113
+
114
+ def get_account_info():
115
+ """Get comprehensive account information"""
116
+ if not trading_client:
117
+ # Return demo data
118
+ return {
119
+ 'portfolio_value': 125000.00,
120
+ 'buying_power': 31250.00,
121
+ 'cash': 31250.00,
122
+ 'day_change': 2750.50,
123
+ 'equity': 125000.00,
124
+ 'day_change_percent': 2.25
125
+ }
126
+
127
+ try:
128
+ account = trading_client.get_account()
129
+ last_equity = float(account.last_equity) if account.last_equity else float(account.equity)
130
+ current_equity = float(account.equity)
131
+ day_change = current_equity - last_equity
132
+ day_change_percent = (day_change / last_equity * 100) if last_equity > 0 else 0
133
+
134
+ return {
135
+ 'portfolio_value': float(account.portfolio_value),
136
+ 'buying_power': float(account.buying_power),
137
+ 'cash': float(account.cash),
138
+ 'day_change': day_change,
139
+ 'equity': current_equity,
140
+ 'day_change_percent': day_change_percent
141
+ }
142
+ except Exception as e:
143
+ logger.error(f"Account info error: {e}")
144
+ return {'error': str(e)}
145
+
146
+ def get_order_history(limit=50):
147
+ """Get recent order history"""
148
+ if not trading_client:
149
+ return []
150
+
151
+ try:
152
+ request = GetOrdersRequest(
153
+ status='all',
154
+ limit=limit
155
+ )
156
+ orders = trading_client.get_orders(filter=request)
157
+
158
+ order_data = []
159
+ for order in orders:
160
+ order_data.append({
161
+ 'symbol': order.symbol,
162
+ 'side': order.side.value if hasattr(order.side, 'value') else str(order.side),
163
+ 'qty': float(order.qty) if order.qty else 0,
164
+ 'filled_qty': float(order.filled_qty) if order.filled_qty else 0,
165
+ 'status': order.status.value if hasattr(order.status, 'value') else str(order.status),
166
+ 'submitted_at': order.submitted_at.isoformat() if order.submitted_at else None,
167
+ 'filled_at': order.filled_at.isoformat() if order.filled_at else None,
168
+ 'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else None
169
+ })
170
+
171
+ return order_data
172
+ except Exception as e:
173
+ logger.error(f"Order history error: {e}")
174
+ return []
175
+
176
+ def get_reddit_posts(symbol, start_time, cutoff_time):
177
+ """Enhanced Reddit search with multiple strategies"""
178
+ logger.info(f"πŸ” Searching Reddit for {symbol}...")
179
+
180
+ reddit_posts = []
181
+ subreddits = ['wallstreetbets', 'stocks', 'investing', 'SecurityAnalysis', 'ValueInvesting']
182
+ search_terms = [symbol, f'{symbol} stock', f'{symbol} IPO', f'${symbol}', f'{symbol} earnings']
183
+
184
+ for subreddit in subreddits:
185
+ for search_term in search_terms:
186
+ try:
187
+ url = f"https://www.reddit.com/r/{subreddit}/search.json"
188
+ params = {
189
+ 'q': search_term,
190
+ 'restrict_sr': 'true',
191
+ 'limit': 10,
192
+ 't': 'all',
193
+ 'sort': 'relevance'
194
+ }
195
+
196
+ response = requests.get(url, params=params, headers=headers, timeout=10)
197
+ if response.status_code == 200:
198
+ data = response.json()
199
+ posts_found = len(data.get('data', {}).get('children', []))
200
+ logger.info(f"Reddit: r/{subreddit} + '{search_term}' found {posts_found} posts")
201
+
202
+ for post in data.get('data', {}).get('children', []):
203
+ post_data = post.get('data', {})
204
+
205
+ if not post_data.get('title'):
206
+ continue
207
+
208
+ # Filter by time window
209
+ post_time = datetime.fromtimestamp(post_data.get('created_utc', 0), tz=timezone.utc)
210
+ if not (start_time <= post_time <= cutoff_time):
211
+ continue
212
+
213
+ # Check relevance
214
+ title_lower = post_data.get('title', '').lower()
215
+ body_lower = post_data.get('selftext', '').lower()
216
+ symbol_lower = symbol.lower()
217
+
218
+ if symbol_lower not in title_lower and symbol_lower not in body_lower:
219
+ continue
220
+
221
+ # Remove duplicates
222
+ post_id = post_data.get('id')
223
+ if any(p.get('id') == post_id for p in reddit_posts):
224
+ continue
225
+
226
+ reddit_posts.append({
227
+ 'id': post_id,
228
+ 'title': post_data.get('title', ''),
229
+ 'selftext': post_data.get('selftext', ''),
230
+ 'score': post_data.get('score', 0),
231
+ 'num_comments': post_data.get('num_comments', 0),
232
+ 'created_utc': post_data.get('created_utc', 0),
233
+ 'subreddit': subreddit,
234
+ 'search_term': search_term,
235
+ 'url': f"https://reddit.com{post_data.get('permalink', '')}"
236
+ })
237
+
238
+ time.sleep(0.1) # Rate limiting
239
+
240
+ except Exception as e:
241
+ logger.warning(f"Reddit search error for r/{subreddit}: {e}")
242
+ continue
243
+
244
+ logger.info(f"πŸ“Š Total Reddit posts found for {symbol}: {len(reddit_posts)}")
245
+ return reddit_posts
246
+
247
+ def get_google_news(symbol, start_time, cutoff_time):
248
+ """Get Google News articles for symbol"""
249
+ logger.info(f"πŸ“° Searching Google News for {symbol}...")
250
+
251
+ try:
252
+ # Build search query
253
+ search_queries = [
254
+ f'{symbol} stock',
255
+ f'{symbol} IPO',
256
+ f'{symbol} earnings',
257
+ f'{symbol} company'
258
+ ]
259
+
260
+ all_articles = []
261
+
262
+ for query in search_queries:
263
+ try:
264
+ encoded_query = quote(query)
265
+ url = f"https://news.google.com/rss/search?q={encoded_query}&hl=en&gl=US&ceid=US:en"
266
+
267
+ feed = feedparser.parse(url)
268
+
269
+ for entry in feed.entries:
270
+ # Parse publication date
271
+ try:
272
+ pub_date = datetime(*entry.published_parsed[:6], tzinfo=timezone.utc)
273
+ if not (start_time <= pub_date <= cutoff_time):
274
+ continue
275
+ except:
276
+ continue
277
+
278
+ # Check relevance
279
+ title_lower = entry.title.lower()
280
+ summary_lower = getattr(entry, 'summary', '').lower()
281
+ symbol_lower = symbol.lower()
282
+
283
+ if symbol_lower not in title_lower and symbol_lower not in summary_lower:
284
+ continue
285
+
286
+ article = {
287
+ 'title': entry.title,
288
+ 'summary': getattr(entry, 'summary', ''),
289
+ 'published': entry.published,
290
+ 'published_parsed': pub_date.isoformat(),
291
+ 'link': entry.link,
292
+ 'source': getattr(entry, 'source', {}).get('title', 'Google News'),
293
+ 'search_query': query
294
+ }
295
+
296
+ # Remove duplicates
297
+ if not any(a.get('link') == article['link'] for a in all_articles):
298
+ all_articles.append(article)
299
+
300
+ time.sleep(0.2) # Rate limiting
301
+
302
+ except Exception as e:
303
+ logger.warning(f"Google News error for query '{query}': {e}")
304
+ continue
305
+
306
+ logger.info(f"πŸ“Š Total Google News articles found for {symbol}: {len(all_articles)}")
307
+ return all_articles
308
+
309
+ except Exception as e:
310
+ logger.error(f"Google News search failed: {e}")
311
+ return []
312
+
313
+ def analyze_sentiment(news_items):
314
+ """Analyze sentiment of news items using VADER and TextBlob"""
315
+ if not news_items or not SENTIMENT_AVAILABLE:
316
+ return 0.0, 0.0, "Neutral", {'Reddit': [], 'Google News': []}
317
+
318
+ logger.info(f"🧠 Analyzing sentiment for {len(news_items)} items...")
319
+
320
+ sentiment_scores = []
321
+ source_breakdown = {'Reddit': [], 'Google News': []}
322
+
323
+ for item in news_items:
324
+ try:
325
+ # Determine text to analyze
326
+ if 'title' in item and 'selftext' in item: # Reddit post
327
+ text = f"{item['title']} {item.get('selftext', '')}"
328
+ source = 'Reddit'
329
+ weight = max(1, item.get('score', 1) + item.get('num_comments', 0) * 0.5)
330
+ else: # News article
331
+ text = f"{item['title']} {item.get('summary', '')}"
332
+ source = 'Google News'
333
+ weight = 1.0
334
+
335
+ if not text.strip():
336
+ continue
337
+
338
+ # VADER sentiment
339
+ vader_score = 0.0
340
+ if sentiment_analyzer:
341
+ vader_result = sentiment_analyzer.polarity_scores(text)
342
+ vader_score = vader_result['compound']
343
+
344
+ # TextBlob sentiment
345
+ textblob_score = 0.0
346
+ try:
347
+ blob = TextBlob(text)
348
+ textblob_score = blob.sentiment.polarity
349
+ except:
350
+ pass
351
+
352
+ # Combined score
353
+ combined_score = (vader_score + textblob_score) / 2
354
+ weighted_score = combined_score * weight
355
+
356
+ sentiment_scores.append(weighted_score)
357
+ source_breakdown[source].append({
358
+ 'text': text[:200] + '...' if len(text) > 200 else text,
359
+ 'vader_score': vader_score,
360
+ 'textblob_score': textblob_score,
361
+ 'combined_score': combined_score,
362
+ 'weight': weight,
363
+ 'weighted_score': weighted_score
364
+ })
365
+
366
+ except Exception as e:
367
+ logger.warning(f"Sentiment analysis error: {e}")
368
+ continue
369
+
370
+ if not sentiment_scores:
371
+ return 0.0, 0.0, "Neutral", source_breakdown
372
+
373
+ # Calculate average sentiment
374
+ avg_sentiment = sum(sentiment_scores) / len(sentiment_scores)
375
+
376
+ # Predict percentage change based on sentiment
377
+ # Strong positive sentiment -> higher predicted gain
378
+ # Strong negative sentiment -> higher predicted loss
379
+ if avg_sentiment > 0.5:
380
+ predicted_change = min(15.0, avg_sentiment * 20) # Cap at 15%
381
+ prediction_label = "Strong Buy"
382
+ elif avg_sentiment > 0.2:
383
+ predicted_change = avg_sentiment * 10
384
+ prediction_label = "Buy"
385
+ elif avg_sentiment > -0.2:
386
+ predicted_change = avg_sentiment * 5
387
+ prediction_label = "Hold"
388
+ elif avg_sentiment > -0.5:
389
+ predicted_change = avg_sentiment * 10
390
+ prediction_label = "Sell"
391
+ else:
392
+ predicted_change = max(-15.0, avg_sentiment * 20) # Cap at -15%
393
+ prediction_label = "Strong Sell"
394
+
395
+ logger.info(f"πŸ“Š Sentiment analysis complete: {avg_sentiment:.3f} -> {prediction_label} ({predicted_change:+.1f}%)")
396
+
397
+ return avg_sentiment, predicted_change, prediction_label, source_breakdown
398
+
399
+ def get_pre_investment_news(symbol, investment_time, hours_before=12):
400
+ """Get news from before investment time"""
401
+ start_time = investment_time - timedelta(hours=hours_before)
402
+ cutoff_time = investment_time - timedelta(minutes=30) # 30 min buffer
403
+
404
+ logger.info(f"πŸ“Š Getting pre-investment news for {symbol}")
405
+ logger.info(f" Time window: {start_time} to {cutoff_time}")
406
+
407
+ # Get Reddit posts
408
+ reddit_posts = get_reddit_posts(symbol, start_time, cutoff_time)
409
+
410
+ # Get Google News
411
+ google_news = get_google_news(symbol, start_time, cutoff_time)
412
+
413
+ # Combine all news items
414
+ all_news = reddit_posts + google_news
415
+
416
+ logger.info(f"πŸ“Š Total news items: {len(all_news)} ({len(reddit_posts)} Reddit + {len(google_news)} News)")
417
+
418
+ return all_news
419
+
420
+ def refresh_account_overview():
421
+ """Refresh account overview with enhanced data"""
422
+ logger.info("πŸ”„ Refreshing account overview...")
423
+ info = get_account_info()
424
+
425
+ if 'error' in info:
426
+ return "Error", "Error", "Error", "Error", "Error"
427
+
428
+ # Format with colors based on performance
429
+ day_change_color = COLORS['success'] if info['day_change'] >= 0 else COLORS['error']
430
+ day_change_formatted = f"<span style='color: {day_change_color}'>${info['day_change']:+,.2f} ({info.get('day_change_percent', 0):+.2f}%)</span>"
431
+
432
+ return (
433
+ f"${info['portfolio_value']:,.2f}",
434
+ f"${info['buying_power']:,.2f}",
435
+ f"${info['cash']:,.2f}",
436
+ day_change_formatted,
437
+ f"${info['equity']:,.2f}"
438
+ )
439
+
440
+ def create_portfolio_chart():
441
+ """Create enhanced portfolio performance chart"""
442
+ logger.info("πŸ“ˆ Creating portfolio chart...")
443
+
444
+ if not trading_client:
445
+ # Demo data
446
+ dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
447
+ values = [100000 + i * 50 + (i % 30 - 15) * 200 for i in range(len(dates))]
448
+
449
+ fig = go.Figure()
450
+ fig.add_trace(go.Scatter(
451
+ x=dates,
452
+ y=values,
453
+ mode='lines',
454
+ name='Portfolio Value',
455
+ line=dict(color=COLORS['primary'], width=2),
456
+ fill='tonexty',
457
+ fillcolor=f'rgba(0, 112, 243, 0.1)'
458
+ ))
459
+
460
+ fig.update_layout(
461
+ title="Portfolio Performance (Demo Data)",
462
+ xaxis_title="Date",
463
+ yaxis_title="Portfolio Value ($)",
464
+ hovermode='x unified',
465
+ template='plotly_white'
466
+ )
467
+
468
+ return fig
469
+
470
+ try:
471
+ # Get portfolio history from Alpaca
472
+ request = GetPortfolioHistoryRequest(
473
+ period='1M',
474
+ timeframe=TimeFrame.Day
475
+ )
476
+ portfolio_history = trading_client.get_portfolio_history(filter=request)
477
+
478
+ if portfolio_history.equity:
479
+ timestamps = [datetime.fromtimestamp(ts) for ts in portfolio_history.timestamp]
480
+ equity_values = portfolio_history.equity
481
+
482
+ fig = go.Figure()
483
+ fig.add_trace(go.Scatter(
484
+ x=timestamps,
485
+ y=equity_values,
486
+ mode='lines',
487
+ name='Portfolio Value',
488
+ line=dict(color=COLORS['primary'], width=2),
489
+ fill='tonexty',
490
+ fillcolor=f'rgba(0, 112, 243, 0.1)'
491
+ ))
492
+
493
+ fig.update_layout(
494
+ title="Portfolio Performance (Last 30 Days)",
495
+ xaxis_title="Date",
496
+ yaxis_title="Portfolio Value ($)",
497
+ hovermode='x unified',
498
+ template='plotly_white'
499
+ )
500
+
501
+ return fig
502
+ except Exception as e:
503
+ logger.error(f"Portfolio chart error: {e}")
504
+
505
+ # Fallback empty chart
506
+ fig = go.Figure()
507
+ fig.update_layout(title="Portfolio Chart (No Data Available)")
508
+ return fig
509
+
510
+ def refresh_ipo_discoveries():
511
+ """Get IPO discoveries from VM"""
512
+ logger.info("πŸ”„ Refreshing IPO discoveries...")
513
+
514
+ vm_data = fetch_from_vm('ipos', [])
515
+
516
+ if not vm_data:
517
+ return """
518
+ <div style="padding: 2rem; text-align: center; background: #f8f9fa; border-radius: 8px; margin: 1rem 0;">
519
+ <h3>πŸ” IPO Discovery System</h3>
520
+ <p>No recent IPO discoveries available. The system continuously monitors for new tradeable securities.</p>
521
+ <p><small>πŸ“‘ VM Connection Status: Offline</small></p>
522
+ </div>
523
+ """
524
+
525
+ # Format IPO discoveries
526
+ html_content = """
527
+ <div style="background: white; border-radius: 8px; padding: 1rem; margin: 1rem 0;">
528
+ <h3>🎯 Recent IPO Discoveries</h3>
529
+ <table style="width: 100%; border-collapse: collapse; font-size: 0.9rem;">
530
+ <thead>
531
+ <tr style="background: #f8f9fa; border-bottom: 2px solid #dee2e6;">
532
+ <th style="padding: 12px 8px; text-align: left;">Symbol</th>
533
+ <th style="padding: 12px 8px; text-align: left;">Discovery Time</th>
534
+ <th style="padding: 12px 8px; text-align: left;">Type</th>
535
+ <th style="padding: 12px 8px; text-align: left;">Decision</th>
536
+ </tr>
537
+ </thead>
538
+ <tbody>
539
+ """
540
+
541
+ for idx, ipo in enumerate(vm_data[:20]): # Show last 20
542
+ row_bg = "#f8f9fa" if idx % 2 == 0 else "white"
543
+
544
+ symbol = ipo.get('symbol', 'N/A')
545
+ discovery_time = ipo.get('discovery_time', 'N/A')
546
+ asset_type = ipo.get('type', 'Unknown')
547
+ decision = ipo.get('investment_decision', 'Pending')
548
+
549
+ decision_color = COLORS['success'] if 'invested' in decision.lower() else COLORS['warning']
550
+
551
+ html_content += f"""
552
+ <tr style="background: {row_bg}; border-bottom: 1px solid #dee2e6;">
553
+ <td style="padding: 10px 8px; font-weight: bold;">{symbol}</td>
554
+ <td style="padding: 10px 8px;">{discovery_time}</td>
555
+ <td style="padding: 10px 8px;">{asset_type}</td>
556
+ <td style="padding: 10px 8px; color: {decision_color};">{decision}</td>
557
+ </tr>
558
+ """
559
+
560
+ html_content += """
561
+ </tbody>
562
+ </table>
563
+ </div>
564
+ """
565
+
566
+ return html_content
567
+
568
+ def refresh_investment_performance():
569
+ """Get investment performance with sentiment analysis"""
570
+ logger.info("πŸ”„ Refreshing investment performance with sentiment analysis...")
571
+
572
+ orders = get_order_history()
573
+
574
+ if not orders:
575
+ return """
576
+ <div style="padding: 2rem; text-align: center; background: #f8f9fa; border-radius: 8px; margin: 1rem 0;">
577
+ <h3>πŸ’° Investment Performance</h3>
578
+ <p>No trading history available yet.</p>
579
+ <p><small>Start trading to see performance analytics with sentiment analysis!</small></p>
580
+ </div>
581
+ """
582
+
583
+ # Group orders by symbol
584
+ symbol_data = {}
585
+ for order in orders:
586
+ symbol = order['symbol']
587
+ if symbol not in symbol_data:
588
+ symbol_data[symbol] = []
589
+ symbol_data[symbol].append(order)
590
+
591
+ html_content = """
592
+ <div style="background: white; border-radius: 8px; padding: 1rem; margin: 1rem 0;">
593
+ <h3>πŸ“Š Investment Performance with Sentiment Analysis</h3>
594
+ <table style="width: 100%; border-collapse: collapse; font-size: 0.85rem;">
595
+ <thead>
596
+ <tr style="background: #f8f9fa; border-bottom: 2px solid #dee2e6;">
597
+ <th style="padding: 10px 6px; text-align: left;">Symbol</th>
598
+ <th style="padding: 10px 6px; text-align: center;">Investment</th>
599
+ <th style="padding: 10px 6px; text-align: center;">Current P&L</th>
600
+ <th style="padding: 10px 6px; text-align: center;">Sentiment</th>
601
+ <th style="padding: 10px 6px; text-align: center;">Prediction</th>
602
+ <th style="padding: 10px 6px; text-align: center;">Sources</th>
603
+ </tr>
604
+ </thead>
605
+ <tbody>
606
+ """
607
+
608
+ for idx, (symbol, symbol_orders) in enumerate(list(symbol_data.items())[:15]): # Limit to 15 for performance
609
+ row_bg = "#f8f9fa" if idx % 2 == 0 else "white"
610
+
611
+ # Calculate investment amount
612
+ total_investment = sum(
613
+ float(order.get('filled_avg_price', 0)) * float(order.get('filled_qty', 0))
614
+ for order in symbol_orders
615
+ if order.get('side') == 'buy' and order.get('status') == 'filled'
616
+ )
617
+
618
+ if total_investment == 0:
619
+ continue
620
+
621
+ # Get investment time (first buy order)
622
+ buy_orders = [o for o in symbol_orders if o.get('side') == 'buy' and o.get('filled_at')]
623
+ if not buy_orders:
624
+ continue
625
+
626
+ investment_time = datetime.fromisoformat(buy_orders[0]['filled_at'].replace('Z', '+00:00'))
627
+
628
+ # Run sentiment analysis
629
+ logger.info(f"🧠 Starting sentiment analysis for {symbol}...")
630
+ try:
631
+ news_items = get_pre_investment_news(symbol, investment_time, hours_before=12)
632
+ avg_sentiment, predicted_change, prediction_label, source_breakdown = analyze_sentiment(news_items)
633
+
634
+ sentiment_color = COLORS['success'] if avg_sentiment > 0.1 else COLORS['error'] if avg_sentiment < -0.1 else COLORS['neutral']
635
+ prediction_color = COLORS['success'] if predicted_change > 0 else COLORS['error'] if predicted_change < 0 else COLORS['neutral']
636
+
637
+ # Count sources
638
+ reddit_count = len(source_breakdown.get('Reddit', []))
639
+ news_count = len(source_breakdown.get('Google News', []))
640
+
641
+ except Exception as e:
642
+ logger.error(f"Sentiment analysis failed for {symbol}: {e}")
643
+ avg_sentiment = 0.0
644
+ predicted_change = 0.0
645
+ prediction_label = "Error"
646
+ sentiment_color = COLORS['neutral']
647
+ prediction_color = COLORS['neutral']
648
+ reddit_count = 0
649
+ news_count = 0
650
+
651
+ # Mock current P&L (in real implementation, would fetch current prices)
652
+ mock_pnl = total_investment * 0.05 # Mock 5% gain
653
+ pnl_color = COLORS['success'] if mock_pnl >= 0 else COLORS['error']
654
+
655
+ html_content += f"""
656
+ <tr style="background: {row_bg}; border-bottom: 1px solid #dee2e6;">
657
+ <td style="padding: 8px 6px; font-weight: bold;">{symbol}</td>
658
+ <td style="padding: 8px 6px; text-align: center;">${total_investment:,.0f}</td>
659
+ <td style="padding: 8px 6px; text-align: center; color: {pnl_color};">${mock_pnl:+,.0f}</td>
660
+ <td style="padding: 8px 6px; text-align: center; color: {sentiment_color};">{avg_sentiment:+.3f}</td>
661
+ <td style="padding: 8px 6px; text-align: center; color: {prediction_color};">{prediction_label}<br><small>{predicted_change:+.1f}%</small></td>
662
+ <td style="padding: 8px 6px; text-align: center; font-size: 0.8rem;">πŸ—¨οΈ{reddit_count}<br>πŸ“°{news_count}</td>
663
+ </tr>
664
+ """
665
+
666
+ html_content += """
667
+ </tbody>
668
+ </table>
669
+ <div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; font-size: 0.8rem;">
670
+ <strong>πŸ“Š Sentiment Analysis Legend:</strong><br>
671
+ πŸ—¨οΈ Reddit posts analyzed | πŸ“° News articles analyzed<br>
672
+ <strong>Sentiment:</strong> -1.0 (Very Negative) to +1.0 (Very Positive)<br>
673
+ <strong>Prediction:</strong> Expected first-hour price movement based on sentiment
674
+ </div>
675
+ </div>
676
+ """
677
+
678
+ return html_content
679
+
680
+ def execute_vm_command(command):
681
+ """Execute command on VM"""
682
+ logger.info(f"πŸ’» Executing VM command: {command}")
683
+
684
+ try:
685
+ response = requests.post(f"{VM_API_URL}/api/execute",
686
+ json={'command': command},
687
+ timeout=30)
688
+
689
+ if response.status_code == 200:
690
+ result = response.json()
691
+ output = result.get('output', 'No output')
692
+
693
+ # Add color coding for common patterns
694
+ if 'error' in output.lower() or 'failed' in output.lower():
695
+ output = f"<span style='color: {COLORS['error']}'>{output}</span>"
696
+ elif 'success' in output.lower() or 'complete' in output.lower():
697
+ output = f"<span style='color: {COLORS['success']}'>{output}</span>"
698
+
699
+ return f"$ {command}\n{output}"
700
+ else:
701
+ return f"$ {command}\nError: HTTP {response.status_code}"
702
+
703
+ except Exception as e:
704
+ return f"$ {command}\nError: {str(e)}"
705
+
706
+ def refresh_system_logs():
707
+ """Get system logs from VM"""
708
+ logger.info("πŸ”„ Refreshing system logs...")
709
+
710
+ vm_logs = fetch_from_vm('logs', {'logs': 'No logs available'})
711
+
712
+ if isinstance(vm_logs, dict) and 'logs' in vm_logs:
713
+ logs_text = vm_logs['logs']
714
+ else:
715
+ logs_text = "No logs available from VM"
716
+
717
+ # Add basic color coding
718
+ lines = logs_text.split('\n')
719
+ colored_lines = []
720
+
721
+ for line in lines:
722
+ if 'ERROR' in line or 'error' in line:
723
+ colored_lines.append(f"<span style='color: {COLORS['error']}'>{line}</span>")
724
+ elif 'WARN' in line or 'warning' in line:
725
+ colored_lines.append(f"<span style='color: {COLORS['warning']}'>{line}</span>")
726
+ elif 'INFO' in line or 'success' in line:
727
+ colored_lines.append(f"<span style='color: {COLORS['success']}'>{line}</span>")
728
+ else:
729
+ colored_lines.append(line)
730
+
731
+ return '\n'.join(colored_lines[-100:]) # Last 100 lines
732
+
733
+ def create_enhanced_dashboard():
734
+ """Create the enhanced dashboard with all features"""
735
+
736
+ logger.info("🎨 Creating enhanced dashboard interface...")
737
+
738
+ # Custom CSS for better styling
739
+ custom_css = """
740
+ .gradio-container {
741
+ max-width: 1400px !important;
742
+ margin: auto !important;
743
+ }
744
+ .metric-card {
745
+ background: white !important;
746
+ border: 1px solid #e1e5e9 !important;
747
+ border-radius: 8px !important;
748
+ padding: 1rem !important;
749
+ }
750
+ """
751
+
752
+ # ALL components must be defined inside this context
753
+ with gr.Blocks(
754
+ title="πŸš€ Premium Trading Dashboard",
755
+ theme=gr.themes.Soft(primary_hue="blue"),
756
+ css=custom_css
757
+ ) as demo:
758
+ logger.info("πŸ–ΌοΈ Inside Blocks context - creating enhanced interface")
759
+
760
+ # Header with gradient
761
+ gr.HTML("""
762
+ <div style="text-align: center; padding: 3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; margin-bottom: 2rem; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.1);">
763
+ <h1 style="margin: 0; font-size: 3rem; font-weight: 700;">πŸš€ Premium Trading Dashboard</h1>
764
+ <p style="margin: 1rem 0 0 0; font-size: 1.3rem; opacity: 0.9;">Advanced IPO Trading with AI-Powered Sentiment Analysis</p>
765
+ <div style="margin-top: 1rem; font-size: 0.9rem; opacity: 0.8;">
766
+ πŸ“ˆ Real-time Data β€’ 🧠 Sentiment Analysis β€’ πŸ” Reddit Integration β€’ πŸ“° News Monitoring
767
+ </div>
768
+ </div>
769
+ """)
770
+
771
+ with gr.Tabs():
772
+ # Portfolio Overview Tab
773
+ with gr.Tab("πŸ“Š Portfolio Overview"):
774
+ gr.Markdown("## πŸ’Ό Account Summary")
775
+
776
+ with gr.Row():
777
+ portfolio_value = gr.HTML(label="πŸ’° Portfolio Value")
778
+ buying_power = gr.HTML(label="πŸ’³ Buying Power")
779
+ cash = gr.HTML(label="πŸ’΅ Cash")
780
+ day_change = gr.HTML(label="πŸ“ˆ Day Change")
781
+ equity = gr.HTML(label="🏦 Total Equity")
782
+
783
+ refresh_overview_btn = gr.Button("πŸ”„ Refresh Overview", variant="primary", size="lg")
784
+
785
+ gr.Markdown("## πŸ“ˆ Portfolio Performance")
786
+ portfolio_chart = gr.Plot(label="Portfolio Value Over Time")
787
+
788
+ refresh_chart_btn = gr.Button("πŸ“Š Refresh Chart", variant="secondary")
789
+
790
+ # IPO Discoveries Tab
791
+ with gr.Tab("πŸ” IPO Discoveries"):
792
+ gr.Markdown("## 🎯 IPO Discovery & Classification")
793
+
794
+ ipo_discoveries = gr.HTML()
795
+ refresh_ipo_btn = gr.Button("πŸ”„ Refresh IPO Data", variant="primary", size="lg")
796
+
797
+ # Investment Performance Tab with Sentiment Analysis
798
+ with gr.Tab("πŸ’° Investment Performance + Sentiment"):
799
+ gr.Markdown("## πŸ“Š Advanced P&L Analysis with AI Sentiment")
800
+
801
+ gr.HTML("""
802
+ <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;">
803
+ <strong>🧠 AI-Powered Sentiment Analysis</strong><br>
804
+ <small>Analyzes Reddit (including WallStreetBets) and Google News from 12 hours before each investment</small>
805
+ </div>
806
+ """)
807
+
808
+ investment_performance = gr.HTML()
809
+ refresh_performance_btn = gr.Button("πŸ”„ Refresh Performance + Sentiment", variant="primary", size="lg")
810
+
811
+ # VM Terminal Tab
812
+ with gr.Tab("πŸ’» VM Terminal"):
813
+ gr.Markdown("## πŸ–₯️ Remote Terminal Access")
814
+
815
+ with gr.Row():
816
+ command_input = gr.Textbox(
817
+ label="Command",
818
+ placeholder="Enter command (e.g., 'ls -la', 'tail -n 20 script.log', 'ps aux')",
819
+ scale=4
820
+ )
821
+ execute_btn = gr.Button("▢️ Execute", variant="primary", scale=1)
822
+
823
+ terminal_output = gr.Code(
824
+ label="Terminal Output",
825
+ language="bash",
826
+ lines=15,
827
+ interactive=False
828
+ )
829
+
830
+ # Quick command buttons
831
+ with gr.Row():
832
+ ls_btn = gr.Button("πŸ“ ls -la", size="sm")
833
+ logs_btn = gr.Button("πŸ“‹ tail logs", size="sm")
834
+ status_btn = gr.Button("⚑ system status", size="sm")
835
+ portfolio_btn = gr.Button("πŸ’Ό check portfolio", size="sm")
836
+
837
+ # System Logs Tab
838
+ with gr.Tab("πŸ“‹ System Logs"):
839
+ gr.Markdown("## πŸ“Š Trading Bot Activity Logs")
840
+
841
+ system_logs = gr.Code(
842
+ label="System Logs",
843
+ language="log",
844
+ lines=20,
845
+ interactive=False
846
+ )
847
+
848
+ refresh_logs_btn = gr.Button("πŸ”„ Refresh Logs", variant="primary", size="lg")
849
+
850
+ # Footer
851
+ gr.HTML("""
852
+ <div style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eaeaea; margin-top: 3rem; background: white; border-radius: 16px;">
853
+ <p style="font-size: 1.1rem;"><strong>πŸ€– Advanced Automated Trading Dashboard</strong></p>
854
+ <p style="font-size: 0.95rem;">Real-time data from Alpaca Markets β€’ VM Analytics β€’ AI Sentiment Analysis β€’ Built with ❀️</p>
855
+ <p style="font-size: 0.85rem; margin-top: 1rem; opacity: 0.7;">
856
+ πŸ”„ Last Updated: <span id="timestamp">{}</span> β€’
857
+ οΏ½οΏ½οΏ½ VM Status: Connected β€’
858
+ 🧠 AI Analysis: Active β€’
859
+ πŸ“Š Data Sources: Reddit, Google News, Alpaca Markets
860
+ </p>
861
+ </div>
862
+ """.format(datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")))
863
+
864
+ # Event Handlers - ALL INSIDE the Blocks context
865
+ logger.info("πŸ”— Setting up enhanced event handlers...")
866
+
867
+ # Portfolio tab events
868
+ refresh_overview_btn.click(
869
+ fn=refresh_account_overview,
870
+ outputs=[portfolio_value, buying_power, cash, day_change, equity]
871
+ )
872
+
873
+ refresh_chart_btn.click(
874
+ fn=create_portfolio_chart,
875
+ outputs=[portfolio_chart]
876
+ )
877
+
878
+ # IPO tab events
879
+ refresh_ipo_btn.click(
880
+ fn=refresh_ipo_discoveries,
881
+ outputs=[ipo_discoveries]
882
+ )
883
+
884
+ # Performance tab events (with sentiment analysis)
885
+ refresh_performance_btn.click(
886
+ fn=refresh_investment_performance,
887
+ outputs=[investment_performance]
888
+ )
889
+
890
+ # Terminal events
891
+ execute_btn.click(
892
+ fn=execute_vm_command,
893
+ inputs=[command_input],
894
+ outputs=[terminal_output]
895
+ )
896
+
897
+ # Quick command buttons
898
+ ls_btn.click(
899
+ fn=lambda: execute_vm_command("ls -la"),
900
+ outputs=[terminal_output]
901
+ )
902
+
903
+ logs_btn.click(
904
+ fn=lambda: execute_vm_command("tail -n 20 script.log"),
905
+ outputs=[terminal_output]
906
+ )
907
+
908
+ status_btn.click(
909
+ fn=lambda: execute_vm_command("ps aux | grep python"),
910
+ outputs=[terminal_output]
911
+ )
912
+
913
+ portfolio_btn.click(
914
+ fn=lambda: execute_vm_command("cat portfolio.txt"),
915
+ outputs=[terminal_output]
916
+ )
917
+
918
+ # System logs events
919
+ refresh_logs_btn.click(
920
+ fn=refresh_system_logs,
921
+ outputs=[system_logs]
922
+ )
923
+
924
+ # Initial data load
925
+ demo.load(
926
+ fn=refresh_account_overview,
927
+ outputs=[portfolio_value, buying_power, cash, day_change, equity]
928
+ )
929
+ demo.load(
930
+ fn=create_portfolio_chart,
931
+ outputs=[portfolio_chart]
932
+ )
933
+ demo.load(
934
+ fn=refresh_ipo_discoveries,
935
+ outputs=[ipo_discoveries]
936
+ )
937
+ demo.load(
938
+ fn=refresh_system_logs,
939
+ outputs=[system_logs]
940
+ )
941
+
942
+ demo.queue()
943
+ logger.info("βœ… Enhanced event handlers configured successfully")
944
+
945
+ logger.info("βœ… Enhanced dashboard created successfully")
946
+ return demo
947
+
948
+ if __name__ == "__main__":
949
+ try:
950
+ demo = create_enhanced_dashboard()
951
+ logger.info("βœ… Enhanced dashboard created successfully!")
952
+
953
+ logger.info("πŸš€ Launching enhanced dashboard server...")
954
+ demo.launch()
955
+ logger.info("βœ… Enhanced dashboard launched successfully!")
956
+
957
+ except Exception as e:
958
+ logger.error(f"❌ Enhanced dashboard failed: {e}")
959
+ raise