#!/usr/bin/env python3 """ Premium Trading Dashboard - Full Featured Beautiful Vercel-style dashboard with VM data integration """ import os import sys import pandas as pd import gradio as gr import plotly.graph_objects as go import plotly.express as px from datetime import datetime, timedelta, timezone import logging import requests import time from alpaca.trading.client import TradingClient from alpaca.trading.requests import GetOrdersRequest, GetPortfolioHistoryRequest from alpaca.trading.enums import OrderStatus from alpaca.data.timeframe import TimeFrame from alpaca.data.historical import StockHistoricalDataClient from textblob import TextBlob from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer import yfinance as yf # Get API keys and VM URL from environment variables API_KEY = os.getenv('ALPACA_API_KEY', 'PK2FD9B2S86LHR7ZBHG1') SECRET_KEY = os.getenv('ALPACA_SECRET_KEY', 'QPmGPDgbPArvHv6cldBXc7uWddapYcIAnBhtkuBW') VM_API_URL = os.getenv('VM_API_URL', 'http://34.56.193.18:8090') # Set this in Hugging Face # Configure detailed logging for debugging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(), ] ) logger = logging.getLogger(__name__) # Log startup information logger.info("๐Ÿš€ Starting Premium Trading Dashboard... (Build: 2025-07-29 05:15 - Fixed directory structure)") logger.info(f"Python version: {sys.version}") logger.info(f"Working directory: {os.getcwd()}") # Initialize Alpaca clients logger.info("๐Ÿ”Œ Initializing Alpaca trading client...") try: trading_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY) logger.info("โœ… Alpaca trading client initialized successfully") except Exception as e: logger.error(f"โŒ Failed to initialize Alpaca trading client: {e}") raise logger.info("๐Ÿ“Š Initializing Alpaca data client...") try: data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) logger.info("โœ… Alpaca data client initialized successfully") except Exception as e: logger.error(f"โŒ Failed to initialize Alpaca data client: {e}") raise # Initialize sentiment analyzers logger.info("๐Ÿง  Initializing sentiment analysis engines...") try: vader = SentimentIntensityAnalyzer() logger.info("โœ… VADER sentiment analyzer initialized") except Exception as e: logger.error(f"โŒ Failed to initialize VADER: {e}") raise try: from textblob import TextBlob # Test TextBlob test_blob = TextBlob("test") logger.info("โœ… TextBlob sentiment analyzer initialized") except Exception as e: logger.error(f"โŒ Failed to initialize TextBlob: {e}") raise headers = {'User-Agent': 'TradingHistoryBacktester/1.0'} logger.info("โœ… HTTP headers configured") # Modern color scheme COLORS = { 'primary': '#0070f3', 'success': '#00d647', 'error': '#ff0080', 'warning': '#f5a623', 'neutral': '#8b949e', 'background': '#fafafa', 'surface': '#ffffff', 'text': '#000000', 'text_secondary': '#666666', 'border': '#eaeaea' } def fetch_from_vm(endpoint, default_value=None): """Fetch data from VM API server""" try: response = requests.get(f"{VM_API_URL}/api/{endpoint}", timeout=10) if response.status_code == 200: return response.json() else: logger.warning(f"VM API {endpoint} returned {response.status_code}") return default_value except Exception as e: logger.error(f"Error fetching from VM {endpoint}: {e}") return default_value def get_account_info(): """Get current account information from Alpaca""" try: account = trading_client.get_account() return { 'portfolio_value': float(account.portfolio_value), 'buying_power': float(account.buying_power), 'cash': float(account.cash), 'equity': float(account.equity), 'day_change': float(getattr(account, 'unrealized_pl', 0)) if hasattr(account, 'unrealized_pl') else 0, 'day_change_percent': float(getattr(account, 'unrealized_plpc', 0)) * 100 if hasattr(account, 'unrealized_plpc') else 0, 'last_equity': float(account.last_equity) if account.last_equity else 0 } except Exception as e: logger.error(f"Error fetching account info: {e}") return { 'portfolio_value': 0, 'buying_power': 0, 'cash': 0, 'equity': 0, 'day_change': 0, 'day_change_percent': 0, 'last_equity': 0 } def get_portfolio_history(): """Get portfolio value history from Alpaca""" try: portfolio_history_request = GetPortfolioHistoryRequest( period="1M", timeframe="1D", extended_hours=False ) portfolio_history = trading_client.get_portfolio_history(portfolio_history_request) timestamps = [datetime.fromtimestamp(ts, tz=timezone.utc) for ts in portfolio_history.timestamp] equity_values = portfolio_history.equity df = pd.DataFrame({ 'timestamp': timestamps, 'equity': equity_values }) return df.dropna() except Exception as e: logger.error(f"Error fetching portfolio history: {e}") return pd.DataFrame() def get_current_positions(): """Get current positions""" try: positions = trading_client.get_all_positions() position_data = [] for position in positions: position_data.append({ 'symbol': position.symbol, 'qty': float(position.qty), 'market_value': float(position.market_value), 'cost_basis': float(position.cost_basis), 'unrealized_pl': float(position.unrealized_pl), 'unrealized_plpc': float(position.unrealized_plpc) * 100, 'current_price': float(position.current_price) if position.current_price else 0 }) return position_data except Exception as e: logger.error(f"Error fetching positions: {e}") return [] def create_portfolio_chart(): """Create beautiful portfolio value chart""" portfolio_df = get_portfolio_history() if portfolio_df.empty: fig = go.Figure() fig.add_annotation( text="No portfolio history available", x=0.5, y=0.5, xref="paper", yref="paper", showarrow=False, font=dict(size=16, color=COLORS['text_secondary']) ) else: fig = go.Figure() fig.add_trace(go.Scatter( x=portfolio_df['timestamp'], y=portfolio_df['equity'], mode='lines', name='Portfolio Value', line=dict(color=COLORS['primary'], width=3), fill='tonexty', fillcolor=f"rgba(0, 112, 243, 0.1)", hovertemplate='%{y:$,.2f}
%{x}' )) if len(portfolio_df) > 0: current_value = portfolio_df['equity'].iloc[-1] fig.add_annotation( x=portfolio_df['timestamp'].iloc[-1], y=current_value, text=f"${current_value:,.2f}", showarrow=True, arrowhead=2, arrowcolor=COLORS['primary'], bgcolor="white", bordercolor=COLORS['primary'], borderwidth=2, font=dict(size=12, color=COLORS['text']) ) fig.update_layout( title=dict( text="Portfolio Value (Last 30 Days)", font=dict(size=24, color=COLORS['text'], family="Inter"), x=0.02 ), xaxis=dict( title="Date", showgrid=True, gridcolor=COLORS['border'], color=COLORS['text_secondary'] ), yaxis=dict( title="Portfolio Value ($)", showgrid=True, gridcolor=COLORS['border'], color=COLORS['text_secondary'], tickformat='$,.0f' ), plot_bgcolor='white', paper_bgcolor='white', height=400, margin=dict(l=60, r=40, t=60, b=60), hovermode='x unified', showlegend=False ) return fig def create_ipo_discovery_chart(): """Create IPO discovery chart with investment decisions""" ipos = fetch_from_vm('ipos?limit=100', []) if not ipos: fig = go.Figure() fig.add_annotation( text="No IPO data available from VM", x=0.5, y=0.5, xref="paper", yref="paper", showarrow=False, font=dict(size=16, color=COLORS['text_secondary']) ) else: # Count by status status_counts = {} for ipo in ipos: status = ipo.get('investment_status', 'UNKNOWN') status_counts[status] = status_counts.get(status, 0) + 1 # Create pie chart labels = list(status_counts.keys()) values = list(status_counts.values()) # Map status to colors color_map = { 'INVESTED': COLORS['success'], 'ELIGIBLE_NOT_INVESTED': COLORS['warning'], 'WRONG_TYPE': COLORS['neutral'], 'UNKNOWN': COLORS['error'] } colors = [color_map.get(label, COLORS['neutral']) for label in labels] fig = go.Figure(data=[go.Pie( labels=labels, values=values, hole=0.4, marker=dict(colors=colors), textinfo='label+percent', textposition='outside' )]) fig.update_layout( title=dict( text="IPO Investment Decisions", font=dict(size=24, color=COLORS['text'], family="Inter"), x=0.5 ), plot_bgcolor='white', paper_bgcolor='white', height=400, margin=dict(l=60, r=60, t=60, b=60), showlegend=True ) return fig def refresh_account_overview(): """Refresh account overview display""" account = get_account_info() portfolio_value = f"${account['portfolio_value']:,.2f}" buying_power = f"${account['buying_power']:,.2f}" cash = f"${account['cash']:,.2f}" day_change_value = account['day_change'] day_change_percent = account['day_change_percent'] if day_change_value > 0: day_change = f"โ†—๏ธ +${day_change_value:,.2f} (+{day_change_percent:.2f}%)" elif day_change_value < 0: day_change = f"โ†˜๏ธ ${day_change_value:,.2f} ({day_change_percent:.2f}%)" else: day_change = f"โžก๏ธ ${day_change_value:,.2f} ({day_change_percent:.2f}%)" equity = f"${account['equity']:,.2f}" return portfolio_value, buying_power, cash, day_change, equity def refresh_positions_table(): """Refresh current positions table""" positions = get_current_positions() if not positions: return pd.DataFrame(columns=['Symbol', 'Quantity', 'Market Value', 'Unrealized P&L', 'Unrealized %']) df_data = [] for pos in positions: pnl_indicator = "๐ŸŸข" if pos['unrealized_pl'] > 0 else "๐Ÿ”ด" if pos['unrealized_pl'] < 0 else "โšช" df_data.append({ 'Symbol': f"{pnl_indicator} {pos['symbol']}", 'Quantity': f"{pos['qty']:.0f}", 'Market Value': f"${pos['market_value']:,.2f}", 'Unrealized P&L': f"${pos['unrealized_pl']:,.2f}", 'Unrealized %': f"{pos['unrealized_plpc']:.2f}%" }) return pd.DataFrame(df_data) def refresh_ipo_discoveries_table(): """Refresh IPO discoveries table with investment decisions""" ipos = fetch_from_vm('ipos?limit=100', []) if not ipos: return pd.DataFrame(columns=['Status', 'Symbol', 'Security Type', 'Price', 'Detected At']) df_data = [] for ipo in ipos: status_emoji = ipo.get('status_emoji', 'โšช') status = ipo.get('investment_status', 'UNKNOWN') # Clean up status for display display_status = { 'INVESTED': '๐ŸŸข INVESTED', 'ELIGIBLE_NOT_INVESTED': '๐ŸŸก ELIGIBLE', 'WRONG_TYPE': 'โšช WRONG TYPE', 'UNKNOWN': '๐Ÿ”ด UNKNOWN' }.get(status, 'โšช UNKNOWN') df_data.append({ 'Status': display_status, 'Symbol': ipo.get('symbol', 'N/A'), 'Security Type': ipo.get('security_type', 'N/A'), 'Price': f"${ipo.get('trading_price', 0)}" if ipo.get('trading_price') != 'N/A' else 'N/A', 'Detected At': ipo.get('detected_at', 'N/A') }) return pd.DataFrame(df_data) def get_order_history(): """Get order history from Alpaca""" try: # Use Method 2 which works: 1 year with CLOSED status end_date = datetime.now(timezone.utc) start_date = end_date - timedelta(days=365) order_request = GetOrdersRequest( status="closed", # This is the key - use "closed" status limit=500, after=start_date, until=end_date ) orders = trading_client.get_orders(order_request) logger.info(f"Successfully fetched {len(orders)} orders using closed status filter") return orders except Exception as e: logger.error(f"Error fetching order history: {e}") return [] def refresh_investment_performance_table(): """Refresh investment performance table with P&L and sentiment analysis for all trading symbols""" logger.info("๐Ÿ“Š Starting investment performance table refresh...") # Get IPO data and orders logger.info("๐Ÿ”Œ Fetching IPO data from VM...") ipos = fetch_from_vm('ipos?limit=100', []) logger.info(f"๐Ÿ“ˆ Retrieved {len(ipos)} IPO records from VM") logger.info("๐Ÿ“‹ Fetching order history from Alpaca...") orders = get_order_history() logger.info(f"๐Ÿ“ Retrieved {len(orders)} orders from Alpaca") logger.info("๐Ÿ’ผ Fetching current positions from Alpaca...") positions = get_current_positions() logger.info(f"๐Ÿฆ Retrieved {len(positions)} current positions") # Create proper empty DataFrame with correct column names columns = ['Symbol', 'Status', 'IPO Price', 'Buy Price', 'Sell Price', 'Investment', 'P&L ($)', 'P&L (%)', 'Sentiment', 'Predicted', 'Date'] logger.info(f"Found {len(orders)} total orders for performance analysis") if not orders: return pd.DataFrame(columns=columns) # Get all unique symbols from order history symbols_traded = set() for order in orders: if hasattr(order, 'symbol') and order.symbol: symbols_traded.add(order.symbol) logger.info(f"Found {len(symbols_traded)} unique symbols traded: {list(symbols_traded)}") # Create IPO price lookup from VM data ipo_price_lookup = {} for ipo in ipos: symbol = ipo.get('symbol', '') if symbol: try: price = float(ipo.get('trading_price', 0)) if price > 0: ipo_price_lookup[symbol] = price except (ValueError, TypeError): pass invested_data = [] # Process each symbol that was traded for symbol in sorted(symbols_traded): # Get all orders for this symbol symbol_orders = [o for o in orders if o.symbol == symbol] if symbol_orders: # Calculate from actual orders buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] sell_orders = [o for o in symbol_orders if o.side.value == 'sell'] if buy_orders: total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) avg_buy_price = total_cost / total_bought if total_bought > 0 else 0 total_sold = sum(float(o.filled_qty or 0) for o in sell_orders) current_qty = total_bought - total_sold # Get IPO price if available ipo_price = ipo_price_lookup.get(symbol, 0) # Get first buy date and time for sentiment analysis first_buy_order = min(buy_orders, key=lambda x: x.filled_at) first_buy_date = first_buy_order.filled_at.strftime('%Y-%m-%d') investment_time = first_buy_order.filled_at logger.info(f"Date for {symbol}: {first_buy_date} (from {first_buy_order.filled_at})") # Calculate sell price (average of all sells) if sell_orders: avg_sell_price = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) / sum(float(o.filled_qty or 0) for o in sell_orders) else: avg_sell_price = 0 current_qty = total_bought - total_sold if current_qty > 0: # Still holding - use current position for P&L status = "๐ŸŸฆ HOLDING" pos = next((p for p in positions if p['symbol'] == symbol), None) if pos: current_price = pos['current_price'] current_value = current_qty * current_price investment = current_qty * avg_buy_price pl_dollars = current_value - investment pl_percent = (pl_dollars / investment * 100) if investment > 0 else 0 else: # No current position data investment = current_qty * avg_buy_price pl_dollars = 0 pl_percent = 0 else: # Sold all - calculate realized P&L status = "๐ŸŸจ SOLD" investment = total_cost sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) pl_dollars = sold_value - investment pl_percent = (pl_dollars / investment * 100) if investment > 0 else 0 # Format P&L with arrows and colors if pl_dollars > 0: pl_arrow = "โ–ฒ" pl_color = "#00d647" row_bg = "rgba(0, 214, 71, 0.1)" elif pl_dollars < 0: pl_arrow = "โ–ผ" pl_color = "#ff0080" row_bg = "rgba(255, 0, 128, 0.1)" else: pl_arrow = "" pl_color = "#8b949e" row_bg = "rgba(139, 148, 158, 0.05)" # Format P&L values with styled arrows pl_dollar_str = f"{pl_arrow} ${abs(pl_dollars):.2f}" pl_percent_str = f"{pl_arrow} {abs(pl_percent):.2f}%" # ADD SENTIMENT ANALYSIS FOR EACH STOCK logger.info(f"๐Ÿง  Starting sentiment analysis for {symbol}...") start_time = time.time() try: # Get pre-investment news (quick version) logger.info(f"๐Ÿ“ฐ Gathering pre-investment news for {symbol}...") news_items = get_pre_investment_news(symbol, investment_time, hours_before=12) logger.info(f"๐Ÿ“‘ Found {len(news_items)} total news items for {symbol}") # Analyze sentiment logger.info(f"๐Ÿ” Analyzing sentiment for {symbol}...") avg_sentiment, predicted_change, prediction_label, source_breakdown = analyze_pre_investment_sentiment(news_items) analysis_time = time.time() - start_time logger.info(f"โšก Sentiment analysis for {symbol} completed in {analysis_time:.1f}s") # Format sentiment display if prediction_label == "bullish": sentiment_display = f"๐Ÿš€ {prediction_label.title()}" elif prediction_label == "bearish": sentiment_display = f"๐Ÿ“‰ {prediction_label.title()}" else: sentiment_display = f"๐Ÿ˜ {prediction_label.title()}" # Format prediction if predicted_change > 0: predicted_display = f"+{predicted_change:.1f}%" elif predicted_change < 0: predicted_display = f"{predicted_change:.1f}%" else: predicted_display = f"{predicted_change:.1f}%" reddit_count = len(source_breakdown.get('Reddit', [])) news_count = len(source_breakdown.get('Google News', [])) logger.info(f"๐ŸŽฏ {symbol} RESULTS: {prediction_label.upper()} ({predicted_change:+.1f}%) | Reddit: {reddit_count} posts | News: {news_count} articles") # Log sample titles for debugging if reddit_count > 0: sample_reddit = source_breakdown['Reddit'][0]['title'][:50] logger.info(f"๐Ÿ“ฑ Sample Reddit: {sample_reddit}...") if news_count > 0: sample_news = source_breakdown['Google News'][0]['title'][:50] logger.info(f"๐Ÿ“ฐ Sample News: {sample_news}...") except Exception as e: analysis_time = time.time() - start_time logger.error(f"โŒ Sentiment analysis failed for {symbol} after {analysis_time:.1f}s: {str(e)}") logger.error(f"๐Ÿ” Error type: {type(e).__name__}") import traceback logger.error(f"๐Ÿ“‹ Traceback: {traceback.format_exc()[:200]}...") sentiment_display = "โ“ Error" predicted_display = "N/A" # Continue with next stock instead of failing completely pass invested_data.append({ 'Symbol': symbol, 'Status': status, 'IPO Price': f"${ipo_price:.2f}" if ipo_price > 0 else 'N/A', 'Buy Price': f"${avg_buy_price:.2f}", 'Sell Price': f"${avg_sell_price:.2f}" if avg_sell_price > 0 else 'N/A', 'Investment': f"${investment:.2f}", 'P&L ($)': pl_dollar_str, 'P&L (%)': pl_percent_str, 'Sentiment': sentiment_display, 'Predicted': predicted_display, 'Date': first_buy_date, '_row_bg': row_bg, # Store background color for styling '_sort_date': first_buy_order.filled_at # Store datetime for sorting }) # Sort by date (most recent first) invested_data.sort(key=lambda x: x['_sort_date'], reverse=True) logger.info(f"๐Ÿ“‹ Processed {len(invested_data)} investments with sentiment analysis") df = pd.DataFrame(invested_data) logger.info(f"โœ… Investment performance table refresh completed - {len(df)} rows") return df def refresh_investment_performance_html(): """Return styled HTML table for investment performance""" df = refresh_investment_performance_table() if df.empty: return "
No trading data available
" # Build HTML table html = '' # Header html += '' for col in df.columns: if not col.startswith('_'): # Skip internal columns html += f'' html += '' # Body html += '' for _, row in df.iterrows(): # Determine row class based on P&L row_class = "" pl_str = str(row.get('P&L ($)', '')) if 'โ–ฒ' in pl_str: row_class = "profit-row" elif 'โ–ผ' in pl_str: row_class = "loss-row" else: row_class = "neutral-row" html += f'' for col in df.columns: if not col.startswith('_'): # Skip internal columns html += f'' html += '' html += '
{col}
{row[col]}
' return html def refresh_vm_stats(): """Refresh VM statistics""" stats = fetch_from_vm('stats', {}) if not stats: return "0", "0", "0", "0%", "No data" return ( str(stats.get('total_ipos_detected', 0)), str(stats.get('ipos_invested', 0)), str(stats.get('cs_stocks_detected', 0)), f"{stats.get('investment_rate', 0):.1f}%", stats.get('last_updated', 'N/A') ) def refresh_system_logs(): """Refresh system logs from VM""" logs = fetch_from_vm('logs', []) if not logs: return "No logs available from VM" # Format logs for display formatted_logs = [] for log in logs: emoji = log.get('emoji', 'โšช') timestamp = log.get('timestamp', 'N/A') message = log.get('message', '') formatted_logs.append(f"{emoji} {timestamp} | {message}") return '\n'.join(formatted_logs) def refresh_raw_logs(): """Refresh raw logs from VM""" raw_data = fetch_from_vm('logs/raw?lines=1000', {}) if not raw_data: return "No raw logs available from VM" content = raw_data.get('content', 'No content') total_lines = raw_data.get('total_lines', 0) showing_lines = raw_data.get('showing_lines', 0) header = f"=== RAW CRON LOGS ===\nShowing last {showing_lines} of {total_lines} total lines\n\n" return header + content def run_vm_command(command, current_output="", command_history=""): """Execute command on VM and return output""" try: if not command.strip(): return current_output, "", command_history # Add command to history history_list = command_history.split("|||") if command_history else [] if command not in history_list: history_list.append(command) # Keep last 50 commands history_list = history_list[-50:] new_history = "|||".join(history_list) response = requests.post(f"{VM_API_URL}/api/execute", json={"command": command}, timeout=10) if response.status_code == 200: data = response.json() output = data.get('output', '') exit_code = data.get('exit_code', 0) # Add color coding for common patterns colored_output = colorize_output(output) # Format terminal-style output with clean spacing # Clean up output to avoid weird quote formatting clean_output = colored_output.strip().replace('\r', '') new_line = f"$ {command}\n{clean_output}" if exit_code != 0: new_line += f"\n[Exit code: {exit_code}]" new_line += "\n$ " # RADICAL FIX: Put newest content at TOP instead of bottom! if current_output.strip(): full_output = new_line + "\n" + current_output.rstrip() else: full_output = new_line return full_output, "", new_history else: error_line = f"\n$ {command}\nError: VM API returned {response.status_code}\n$ " return current_output + error_line, "", new_history except Exception as e: error_line = f"\n$ {command}\nError: {str(e)}\n$ " return current_output + error_line, "", new_history def colorize_output(output): """Add basic color coding to terminal output""" import re # Color patterns (using ANSI-like styling for web) colored = output # File permissions and directories (ls output) colored = re.sub(r'^(d)([rwx-]{9})', r'\1\2', colored, flags=re.MULTILINE) colored = re.sub(r'^(-)([rwx-]{9})', r'\1\2', colored, flags=re.MULTILINE) # Error messages colored = re.sub(r'(ERROR|Error|error)', r'\1', colored) colored = re.sub(r'(WARNING|Warning|warning)', r'\1', colored) # Success indicators colored = re.sub(r'(SUCCESS|Success|success)', r'\1', colored) # File extensions colored = re.sub(r'(\w+\.(py|log|csv|json|txt))', r'\1', colored) # Numbers and timestamps colored = re.sub(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})', r'\1', colored) return colored def debug_order_history(): """Debug function to show raw order history data""" try: # Try multiple approaches to get orders debug_info = f"=== ORDER HISTORY DEBUG ===\n" # Approach 1: All orders, last 6 months (DEPRECATED - doesn't work) try: end_date = datetime.now(timezone.utc) start_date = end_date - timedelta(days=180) old_request = GetOrdersRequest(limit=500, after=start_date, until=end_date) old_orders = trading_client.get_orders(old_request) debug_info += f"Method 1 (6 months, all statuses): {len(old_orders)} orders [DEPRECATED]\n" except Exception as e: debug_info += f"Method 1 failed: {str(e)}\n" # Approach 1B: Current working method used by Investment Performance orders = get_order_history() debug_info += f"Method 1B (PRIMARY - 1 year, CLOSED): {len(orders)} orders [CURRENTLY USED]\n" # Approach 2: Just filled orders, last year try: end_date = datetime.now(timezone.utc) start_date = end_date - timedelta(days=365) filled_request = GetOrdersRequest( status="closed", # Use string instead of enum limit=500, after=start_date, until=end_date ) filled_orders = trading_client.get_orders(filled_request) debug_info += f"Method 2 (1 year, CLOSED orders): {len(filled_orders)} orders\n" except Exception as e: debug_info += f"Method 2 failed: {str(e)}\n" # Approach 3: No date filter, just get recent orders try: recent_request = GetOrdersRequest(limit=100) recent_orders = trading_client.get_orders(recent_request) debug_info += f"Method 3 (recent 100, no date filter): {len(recent_orders)} orders\n" except Exception as e: debug_info += f"Method 3 failed: {str(e)}\n" debug_info += "\n" # Show any orders we found all_orders = orders if orders else (filled_orders if 'filled_orders' in locals() else (recent_orders if 'recent_orders' in locals() else [])) if all_orders: debug_info += f"Sample orders (showing first 10):\n" for i, order in enumerate(all_orders[:10]): debug_info += f"{i+1}. Symbol: {order.symbol}, Side: {order.side}, " debug_info += f"Qty: {order.filled_qty}, Price: {order.filled_avg_price}, " debug_info += f"Status: {order.status}, Time: {order.filled_at}, " debug_info += f"Created: {order.created_at}\n" else: debug_info += "โŒ NO ORDERS FOUND WITH ANY METHOD!\n" debug_info += "\nLet's check account details:\n" # Check account info try: account = trading_client.get_account() debug_info += f"Account ID: {account.account_number}\n" debug_info += f"Account Status: {account.status}\n" debug_info += f"Trading Blocked: {account.trading_blocked}\n" debug_info += f"Pattern Day Trader: {account.pattern_day_trader}\n" debug_info += f"Cash: ${float(account.cash):,.2f}\n" debug_info += f"Portfolio Value: ${float(account.portfolio_value):,.2f}\n" # Check if this is paper trading debug_info += f"\nAPI Keys being used:\n" debug_info += f"API Key: {API_KEY[:8]}...{API_KEY[-4:]}\n" if "PK" in API_KEY: debug_info += "๐ŸŸข This appears to be PAPER TRADING (PK prefix)\n" elif "AK" in API_KEY: debug_info += "๐Ÿ”ด This appears to be LIVE TRADING (AK prefix)\n" else: debug_info += "โ“ Unknown API key type\n" except Exception as e: debug_info += f"โŒ Error getting account info: {str(e)}\n" debug_info += "\nPossible issues:\n" debug_info += "- No actual trading activity on this account\n" debug_info += "- Using paper trading account (no real orders)\n" debug_info += "- Orders are older than 1 year\n" debug_info += "- API key permissions issue\n" debug_info += "- Different Alpaca account than expected\n" return debug_info except Exception as e: return f"ERROR getting order history: {str(e)}" def debug_current_positions(): """Debug function to show current positions""" try: positions = get_current_positions() debug_info = f"=== CURRENT POSITIONS DEBUG ===\n" debug_info += f"Total positions: {len(positions)}\n\n" for pos in positions: debug_info += f"Symbol: {pos['symbol']}, Qty: {pos['qty']}, " debug_info += f"Market Value: ${pos['market_value']:.2f}, " debug_info += f"P&L: ${pos['unrealized_pl']:.2f}\n" return debug_info except Exception as e: return f"ERROR getting positions: {str(e)}" def debug_ipo_data(): """Debug function to show IPO data from VM""" try: ipos = fetch_from_vm('ipos?limit=20', []) debug_info = f"=== IPO DATA DEBUG ===\n" debug_info += f"Total IPOs: {len(ipos)}\n\n" invested_count = 0 for ipo in ipos: status = ipo.get('investment_status', 'UNKNOWN') if status == 'INVESTED': invested_count += 1 debug_info += f"INVESTED: {ipo.get('symbol')} - Price: ${ipo.get('trading_price')}\n" debug_info += f"\nTotal INVESTED IPOs: {invested_count}\n" return debug_info except Exception as e: return f"ERROR getting IPO data: {str(e)}" def debug_account_info(): """Debug function to show account info""" try: account = get_account_info() debug_info = f"=== ACCOUNT INFO DEBUG ===\n" for key, value in account.items(): debug_info += f"{key}: {value}\n" return debug_info except Exception as e: return f"ERROR getting account info: {str(e)}" def calculate_sequential_reinvestment(): """Calculate P&L% if reinvesting same amount sequentially in each stock""" try: orders = get_order_history() if not orders: return "No order data available for calculation" # Get unique symbols with their first buy date symbols_by_date = {} for order in orders: if order.side.value == 'buy' and order.status.value == 'filled': symbol = order.symbol fill_date = order.filled_at if symbol not in symbols_by_date or fill_date < symbols_by_date[symbol]: symbols_by_date[symbol] = fill_date # Sort by first buy date sorted_symbols = sorted(symbols_by_date.items(), key=lambda x: x[1]) # Calculate sequential reinvestment returns initial_investment = 1000 # Start with $1000 current_value = initial_investment results = [] total_return = 0 for symbol, first_date in sorted_symbols: # Get all orders for this symbol symbol_orders = [o for o in orders if o.symbol == symbol] buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] sell_orders = [o for o in symbol_orders if o.side.value == 'sell'] if buy_orders: # Calculate actual P&L for this symbol total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) if sell_orders: # Sold - use actual sell proceeds sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0 else: # Still holding - estimate current value positions = get_current_positions() pos = next((p for p in positions if p['symbol'] == symbol), None) if pos: current_price = pos['current_price'] current_symbol_value = total_bought * current_price pl_percent = ((current_symbol_value - total_cost) / total_cost) if total_cost > 0 else 0 else: pl_percent = 0 # Apply return to current value new_value = current_value * (1 + pl_percent) gain_loss = new_value - current_value results.append(f"{symbol}: {pl_percent*100:+.2f}% | ${current_value:.2f} โ†’ ${new_value:.2f} ({gain_loss:+.2f})") current_value = new_value total_return += pl_percent final_return_pct = ((current_value - initial_investment) / initial_investment) * 100 output = f"๐Ÿงฎ SEQUENTIAL REINVESTMENT ANALYSIS\n" output += f"Starting Investment: ${initial_investment:.2f}\n" output += f"Final Value: ${current_value:.2f}\n" output += f"Total Return: {final_return_pct:+.2f}%\n" output += f"Number of Trades: {len(sorted_symbols)}\n\n" output += "Trade Sequence:\n" output += "\n".join(results) return output except Exception as e: return f"ERROR calculating sequential reinvestment: {str(e)}" def calculate_equal_weight_portfolio(): """Calculate P&L% if investing equal amounts in all stocks simultaneously""" try: orders = get_order_history() if not orders: return "No order data available for calculation" # Get unique symbols symbols = set() for order in orders: if order.side.value == 'buy': symbols.add(order.symbol) total_pl = 0 valid_symbols = 0 results = [] for symbol in sorted(symbols): symbol_orders = [o for o in orders if o.symbol == symbol] buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] sell_orders = [o for o in symbol_orders if o.side.value == 'sell'] if buy_orders: total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) avg_buy_price = total_cost / total_bought if total_bought > 0 else 0 if sell_orders: # Sold sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0 status = "SOLD" else: # Still holding positions = get_current_positions() pos = next((p for p in positions if p['symbol'] == symbol), None) if pos: current_price = pos['current_price'] current_value = total_bought * current_price pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0 status = "HOLDING" else: pl_percent = 0 status = "UNKNOWN" total_pl += pl_percent valid_symbols += 1 results.append(f"{symbol}: {pl_percent*100:+.2f}% ({status})") avg_return = (total_pl / valid_symbols) * 100 if valid_symbols > 0 else 0 output = f"โš–๏ธ EQUAL WEIGHT PORTFOLIO ANALYSIS\n" output += f"Total Symbols: {valid_symbols}\n" output += f"Average Return per Symbol: {avg_return:+.2f}%\n" output += f"Portfolio Return (equal weights): {avg_return:+.2f}%\n\n" output += "Individual Returns:\n" output += "\n".join(results) return output except Exception as e: return f"ERROR calculating equal weight portfolio: {str(e)}" def calculate_best_worst_performers(): """Find best and worst performing stocks""" try: orders = get_order_history() if not orders: return "No order data available for calculation" symbols = set() for order in orders: if order.side.value == 'buy': symbols.add(order.symbol) performance = [] for symbol in symbols: symbol_orders = [o for o in orders if o.symbol == symbol] buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] sell_orders = [o for o in symbol_orders if o.side.value == 'sell'] if buy_orders: total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) if sell_orders: sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0 pl_dollars = sold_value - total_cost status = "SOLD" else: positions = get_current_positions() pos = next((p for p in positions if p['symbol'] == symbol), None) if pos: current_price = pos['current_price'] current_value = total_bought * current_price pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0 pl_dollars = current_value - total_cost status = "HOLDING" else: pl_percent = 0 pl_dollars = 0 status = "UNKNOWN" performance.append({ 'symbol': symbol, 'pl_percent': pl_percent, 'pl_dollars': pl_dollars, 'investment': total_cost, 'status': status }) # Sort by percentage return performance.sort(key=lambda x: x['pl_percent'], reverse=True) output = f"๐Ÿ† BEST vs WORST PERFORMERS\n\n" if performance: output += "๐Ÿฅ‡ TOP 5 PERFORMERS:\n" for i, perf in enumerate(performance[:5]): output += f"{i+1}. {perf['symbol']}: {perf['pl_percent']*100:+.2f}% (${perf['pl_dollars']:+.2f}) - {perf['status']}\n" output += "\n๐Ÿฅ‰ BOTTOM 5 PERFORMERS:\n" for i, perf in enumerate(performance[-5:]): rank = len(performance) - 4 + i output += f"{rank}. {perf['symbol']}: {perf['pl_percent']*100:+.2f}% (${perf['pl_dollars']:+.2f}) - {perf['status']}\n" # Calculate some stats total_winners = len([p for p in performance if p['pl_percent'] > 0]) total_losers = len([p for p in performance if p['pl_percent'] < 0]) output += f"\n๐Ÿ“Š SUMMARY:\n" output += f"Winners: {total_winners}/{len(performance)} ({total_winners/len(performance)*100:.1f}%)\n" output += f"Losers: {total_losers}/{len(performance)} ({total_losers/len(performance)*100:.1f}%)\n" return output except Exception as e: return f"ERROR calculating best/worst performers: {str(e)}" def calculate_win_rate_metrics(): """Calculate win rate and average returns""" try: orders = get_order_history() if not orders: return "No order data available for calculation" symbols = set() for order in orders: if order.side.value == 'buy': symbols.add(order.symbol) performance = [] total_investment = 0 for symbol in symbols: symbol_orders = [o for o in orders if o.symbol == symbol] buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] sell_orders = [o for o in symbol_orders if o.side.value == 'sell'] if buy_orders: total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) total_investment += total_cost if sell_orders: sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0 pl_dollars = sold_value - total_cost else: positions = get_current_positions() pos = next((p for p in positions if p['symbol'] == symbol), None) if pos: current_price = pos['current_price'] current_value = total_bought * current_price pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0 pl_dollars = current_value - total_cost else: pl_percent = 0 pl_dollars = 0 performance.append({ 'symbol': symbol, 'pl_percent': pl_percent, 'pl_dollars': pl_dollars, 'investment': total_cost }) if not performance: return "No performance data available" # Calculate metrics winners = [p for p in performance if p['pl_percent'] > 0] losers = [p for p in performance if p['pl_percent'] < 0] breakeven = [p for p in performance if p['pl_percent'] == 0] win_rate = len(winners) / len(performance) * 100 avg_win = sum(p['pl_percent'] for p in winners) / len(winners) * 100 if winners else 0 avg_loss = sum(p['pl_percent'] for p in losers) / len(losers) * 100 if losers else 0 total_pl_dollars = sum(p['pl_dollars'] for p in performance) total_pl_percent = (total_pl_dollars / total_investment) * 100 if total_investment > 0 else 0 # Risk/Reward ratio risk_reward = abs(avg_win / avg_loss) if avg_loss != 0 else float('inf') output = f"๐ŸŽฏ WIN RATE & AVERAGE RETURNS\n\n" output += f"Total Trades: {len(performance)}\n" output += f"Win Rate: {win_rate:.1f}% ({len(winners)} winners)\n" output += f"Loss Rate: {len(losers)/len(performance)*100:.1f}% ({len(losers)} losers)\n" output += f"Breakeven: {len(breakeven)} trades\n\n" output += f"๐Ÿ“ˆ AVERAGE PERFORMANCE:\n" output += f"Average Winner: +{avg_win:.2f}%\n" output += f"Average Loser: {avg_loss:.2f}%\n" output += f"Risk/Reward Ratio: {risk_reward:.2f}:1\n\n" output += f"๐Ÿ’ฐ TOTAL PERFORMANCE:\n" output += f"Total Invested: ${total_investment:.2f}\n" output += f"Total P&L: ${total_pl_dollars:+.2f}\n" output += f"Total Return: {total_pl_percent:+.2f}%\n" return output except Exception as e: return f"ERROR calculating win rate metrics: {str(e)}" def calculate_risk_metrics(): """Calculate risk metrics and volatility""" try: orders = get_order_history() if not orders: return "No order data available for calculation" symbols = set() for order in orders: if order.side.value == 'buy': symbols.add(order.symbol) returns = [] investments = [] for symbol in symbols: symbol_orders = [o for o in orders if o.symbol == symbol] buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] sell_orders = [o for o in symbol_orders if o.side.value == 'sell'] if buy_orders: total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) investments.append(total_cost) if sell_orders: sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0 else: positions = get_current_positions() pos = next((p for p in positions if p['symbol'] == symbol), None) if pos: current_price = pos['current_price'] current_value = total_bought * current_price pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0 else: pl_percent = 0 returns.append(pl_percent) if not returns: return "No return data available" # Calculate statistics import statistics avg_return = statistics.mean(returns) * 100 median_return = statistics.median(returns) * 100 volatility = statistics.stdev(returns) * 100 if len(returns) > 1 else 0 # Sharpe-like ratio (assuming risk-free rate = 0) sharpe = avg_return / volatility if volatility > 0 else 0 # Max drawdown max_return = max(returns) * 100 min_return = min(returns) * 100 max_drawdown = max_return - min_return # Portfolio concentration total_investment = sum(investments) avg_position_size = statistics.mean(investments) largest_position = max(investments) concentration = (largest_position / total_investment) * 100 if total_investment > 0 else 0 output = f"โš ๏ธ RISK METRICS & VOLATILITY\n\n" output += f"๐Ÿ“Š RETURN STATISTICS:\n" output += f"Average Return: {avg_return:+.2f}%\n" output += f"Median Return: {median_return:+.2f}%\n" output += f"Volatility (StdDev): {volatility:.2f}%\n" output += f"Sharpe-like Ratio: {sharpe:.2f}\n\n" output += f"๐Ÿ“‰ RISK MEASURES:\n" output += f"Best Trade: +{max_return:.2f}%\n" output += f"Worst Trade: {min_return:.2f}%\n" output += f"Max Range: {max_drawdown:.2f}%\n\n" output += f"๐ŸŽฏ POSITION SIZING:\n" output += f"Average Position: ${avg_position_size:.2f}\n" output += f"Largest Position: ${largest_position:.2f}\n" output += f"Concentration Risk: {concentration:.1f}% in largest\n" return output except Exception as e: return f"ERROR calculating risk metrics: {str(e)}" def calculate_time_analysis(): """Analyze performance by time periods""" try: orders = get_order_history() if not orders: return "No order data available for calculation" from datetime import datetime, timezone # Group orders by month monthly_performance = {} for order in orders: if order.side.value == 'buy' and order.status.value == 'filled': month_key = order.filled_at.strftime('%Y-%m') if month_key not in monthly_performance: monthly_performance[month_key] = {'symbols': set(), 'investment': 0, 'returns': []} symbol = order.symbol monthly_performance[month_key]['symbols'].add(symbol) # Calculate returns for each month symbols = set() for order in orders: if order.side.value == 'buy': symbols.add(order.symbol) symbol_performance = {} for symbol in symbols: symbol_orders = [o for o in orders if o.symbol == symbol] buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] sell_orders = [o for o in symbol_orders if o.side.value == 'sell'] if buy_orders: first_buy = min(buy_orders, key=lambda x: x.filled_at) month_key = first_buy.filled_at.strftime('%Y-%m') total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) if sell_orders: sold_value = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in sell_orders) pl_percent = ((sold_value - total_cost) / total_cost) if total_cost > 0 else 0 else: positions = get_current_positions() pos = next((p for p in positions if p['symbol'] == symbol), None) if pos: current_price = pos['current_price'] current_value = total_bought * current_price pl_percent = ((current_value - total_cost) / total_cost) if total_cost > 0 else 0 else: pl_percent = 0 if month_key in monthly_performance: monthly_performance[month_key]['investment'] += total_cost monthly_performance[month_key]['returns'].append(pl_percent) output = f"โฐ TIME-BASED PERFORMANCE ANALYSIS\n\n" for month in sorted(monthly_performance.keys()): data = monthly_performance[month] if data['returns']: avg_return = sum(data['returns']) / len(data['returns']) * 100 total_investment = data['investment'] num_trades = len(data['returns']) output += f"๐Ÿ“… {month}: {avg_return:+.2f}% avg return\n" output += f" โ€ข {num_trades} trades, ${total_investment:.2f} invested\n" # Calculate recent vs early performance sorted_months = sorted(monthly_performance.keys()) if len(sorted_months) >= 2: early_months = sorted_months[:len(sorted_months)//2] recent_months = sorted_months[len(sorted_months)//2:] early_returns = [] recent_returns = [] for month in early_months: early_returns.extend(monthly_performance[month]['returns']) for month in recent_months: recent_returns.extend(monthly_performance[month]['returns']) if early_returns and recent_returns: early_avg = sum(early_returns) / len(early_returns) * 100 recent_avg = sum(recent_returns) / len(recent_returns) * 100 output += f"\n๐Ÿ“ˆ TREND ANALYSIS:\n" output += f"Early Period Avg: {early_avg:+.2f}% ({len(early_returns)} trades)\n" output += f"Recent Period Avg: {recent_avg:+.2f}% ({len(recent_returns)} trades)\n" output += f"Improvement: {recent_avg - early_avg:+.2f}% difference\n" return output except Exception as e: return f"ERROR calculating time analysis: {str(e)}" # Trading History Backtesting Functions def get_pre_investment_news(symbol, investment_time, hours_before=12): """Get news from 12 hours before we invested""" cutoff_time = investment_time - timedelta(minutes=30) # 30 min buffer search_start = investment_time - timedelta(hours=hours_before) logger.info(f"๐Ÿ” NEWS SEARCH for {symbol}:") logger.info(f" ๐Ÿ“… Time window: {search_start.strftime('%Y-%m-%d %H:%M')} โ†’ {cutoff_time.strftime('%Y-%m-%d %H:%M')}") logger.info(f" โฐ Search duration: {hours_before} hours before investment") all_news = [] # Get Reddit posts logger.info(f"๐Ÿงต Starting Reddit search for {symbol}...") reddit_start = time.time() reddit_posts = get_reddit_pre_investment(symbol, search_start, cutoff_time) reddit_time = time.time() - reddit_start logger.info(f"โœ… Reddit search completed in {reddit_time:.1f}s - found {len(reddit_posts)} posts") all_news.extend(reddit_posts) # Get Google News logger.info(f"๐Ÿ“ฐ Starting Google News search for {symbol}...") news_start = time.time() google_news = get_google_news_pre_investment(symbol, search_start, cutoff_time) news_time = time.time() - news_start logger.info(f"โœ… Google News search completed in {news_time:.1f}s - found {len(google_news)} articles") all_news.extend(google_news) logger.info(f"๐Ÿ“Š TOTAL NEWS GATHERED for {symbol}: {len(all_news)} items ({len(reddit_posts)} Reddit + {len(google_news)} News)") return all_news def get_reddit_pre_investment(symbol, start_time, cutoff_time): """Get Reddit posts from before our investment""" reddit_posts = [] # Search key subreddits including WSB with multiple search strategies subreddits = ['wallstreetbets', 'stocks', 'investing'] search_terms = [symbol, f'{symbol} stock', f'{symbol} IPO', f'${symbol}'] for subreddit in subreddits: for search_term in search_terms: try: url = f"https://www.reddit.com/r/{subreddit}/search.json" params = { 'q': search_term, 'restrict_sr': 'true', 'limit': 5, # Reduced to avoid duplicates 't': 'all', # Search all time instead of just week 'sort': 'relevance' } response = requests.get(url, params=params, headers=headers, timeout=10) if response.status_code == 200: data = response.json() posts_found = len(data.get('data', {}).get('children', [])) logger.info(f"Reddit search: r/{subreddit} + '{search_term}' found {posts_found} posts") for post in data.get('data', {}).get('children', []): post_data = post.get('data', {}) if not post_data.get('title'): continue # Check if we already have this post (avoid duplicates) title = post_data.get('title', '') if any(existing['title'] == title for existing in reddit_posts): continue # Only include posts that actually mention the symbol title_text = f"{title} {post_data.get('selftext', '')}".upper() if symbol.upper() in title_text or f'${symbol.upper()}' in title_text: reddit_post = { 'title': title, 'selftext': post_data.get('selftext', '')[:300], 'score': post_data.get('score', 0), 'num_comments': post_data.get('num_comments', 0), 'subreddit': subreddit, 'source': 'Reddit', 'url': f"https://reddit.com{post_data.get('permalink', '')}", 'search_term': search_term } reddit_posts.append(reddit_post) logger.info(f"Added Reddit post: {title[:50]}... (score: {post_data.get('score', 0)})") time.sleep(0.5) # Reduced rate limiting except Exception as e: logger.warning(f"Reddit error for r/{subreddit} + '{search_term}': {e}") logger.info(f"Total Reddit posts found for {symbol}: {len(reddit_posts)}") return reddit_posts def get_google_news_pre_investment(symbol, start_time, cutoff_time): """Get Google News from before our investment""" google_news = [] try: # Search for IPO-related news search_queries = [ f'{symbol} IPO', f'{symbol} stock', f'{symbol} public offering' ] for query in search_queries: url = "https://news.google.com/rss/search" params = { 'q': query, 'hl': 'en-US', 'gl': 'US', 'ceid': 'US:en' } response = requests.get(url, params=params, headers=headers, timeout=10) if response.status_code == 200: # Parse RSS from xml.etree import ElementTree as ET root = ET.fromstring(response.content) for item in root.findall('.//item')[:5]: # Limit per query title_elem = item.find('title') link_elem = item.find('link') description_elem = item.find('description') if title_elem is not None: description = description_elem.text if description_elem is not None else "" # Clean HTML import re description = re.sub(r'<[^>]+>', '', description) news_item = { 'title': title_elem.text, 'description': description, 'source': 'Google News', 'url': link_elem.text if link_elem is not None else '' } google_news.append(news_item) time.sleep(0.5) except Exception as e: logger.warning(f"Google News error: {e}") return google_news def analyze_pre_investment_sentiment(news_items): """Analyze sentiment from news before our investment""" if not news_items: return 0.0, 0.0, "neutral", {} sentiments = [] source_breakdown = {'Reddit': [], 'Google News': []} for item in news_items: # Combine title and description/selftext if item['source'] == 'Reddit': text = f"{item['title']} {item.get('selftext', '')}" else: text = f"{item['title']} {item.get('description', '')}" # Sentiment analysis vader_scores = vader.polarity_scores(text) blob = TextBlob(text) combined_sentiment = (vader_scores['compound'] * 0.6) + (blob.sentiment.polarity * 0.4) # Weight by engagement for Reddit if item['source'] == 'Reddit': engagement = item.get('score', 0) + item.get('num_comments', 0) weight = min(engagement / 100.0, 2.0) if engagement > 0 else 0.5 else: weight = 1.0 weighted_sentiment = combined_sentiment * weight sentiments.append(weighted_sentiment) # Track by source source_breakdown[item['source']].append({ 'sentiment': weighted_sentiment, 'title': item['title'][:80], 'weight': weight }) # Calculate overall metrics avg_sentiment = sum(sentiments) / len(sentiments) # Convert to predicted change predicted_change = avg_sentiment * 25.0 # Add confidence based on source agreement reddit_sentiments = [s['sentiment'] for s in source_breakdown['Reddit']] news_sentiments = [s['sentiment'] for s in source_breakdown['Google News']] reddit_avg = sum(reddit_sentiments) / len(reddit_sentiments) if reddit_sentiments else 0 news_avg = sum(news_sentiments) / len(news_sentiments) if news_sentiments else 0 # Boost prediction if sources agree if (reddit_avg > 0 and news_avg > 0) or (reddit_avg < 0 and news_avg < 0): predicted_change *= 1.2 # Classify prediction if predicted_change >= 5.0: prediction_label = "bullish" elif predicted_change <= -5.0: prediction_label = "bearish" else: prediction_label = "neutral" return avg_sentiment, predicted_change, prediction_label, source_breakdown def get_actual_performance(symbol, investment_time, investment_price): """Get actual stock performance after our investment""" try: ticker = yf.Ticker(symbol) # Get data from investment day start_date = investment_time.date() end_date = start_date + timedelta(days=5) # Get a few days hist = ticker.history(start=start_date, end=end_date, interval='1h') if hist.empty: return None, None, None # Find first hour performance (approximate) day_data = hist[hist.index.date == start_date] if len(day_data) > 0: first_price = day_data.iloc[0]['Open'] # First hour high (if we have hourly data) if len(day_data) >= 2: first_hour_high = day_data.iloc[0:2]['High'].max() first_hour_change = ((first_hour_high - first_price) / first_price) * 100 else: # Fall back to first day first_day_close = day_data.iloc[-1]['Close'] first_hour_change = ((first_day_close - first_price) / first_price) * 100 # End of day performance end_of_day_close = day_data.iloc[-1]['Close'] day_change = ((end_of_day_close - first_price) / first_price) * 100 return first_hour_change, day_change, first_price except Exception as e: logger.warning(f"Error getting {symbol} performance: {e}") return None, None, None def run_trading_history_backtest(): """Run backtest on all our actual investments""" logger.info("Starting trading history backtesting...") try: # Get our trading history orders = get_order_history() if not orders: return "โŒ No trading history found", pd.DataFrame() # Get all unique symbols from order history symbols_traded = set() for order in orders: if hasattr(order, 'symbol') and order.symbol and order.side.value == 'buy': symbols_traded.add(order.symbol) logger.info(f"Found {len(symbols_traded)} unique symbols traded") results = [] total_error = 0 correct_directions = 0 valid_results = 0 summary_text = f"๐ŸŽฏ TRADING HISTORY BACKTESTING\n" summary_text += f"Testing sentiment analysis on {len(symbols_traded)} IPOs we actually invested in...\n" summary_text += f"Using news from 12 hours before our investment time\n\n" # Process each symbol that was traded for symbol in sorted(symbols_traded): # Get all orders for this symbol symbol_orders = [o for o in orders if o.symbol == symbol] buy_orders = [o for o in symbol_orders if o.side.value == 'buy'] if buy_orders: # Get first buy order details first_buy_order = min(buy_orders, key=lambda x: x.filled_at) investment_time = first_buy_order.filled_at total_bought = sum(float(o.filled_qty or 0) for o in buy_orders) total_cost = sum(float(o.filled_qty or 0) * float(o.filled_avg_price or 0) for o in buy_orders) avg_buy_price = total_cost / total_bought if total_bought > 0 else 0 logger.info(f"Analyzing {symbol} (invested {investment_time.strftime('%Y-%m-%d %H:%M')})...") # Get pre-investment news news_items = get_pre_investment_news(symbol, investment_time) # Analyze sentiment avg_sentiment, predicted_change, prediction_label, source_breakdown = analyze_pre_investment_sentiment(news_items) # Get actual performance first_hour_change, day_change, actual_open = get_actual_performance(symbol, investment_time, avg_buy_price) if first_hour_change is not None: # Calculate metrics error = abs(predicted_change - first_hour_change) total_error += error valid_results += 1 # Check direction predicted_direction = "UP" if predicted_change > 0 else "DOWN" if predicted_change < 0 else "FLAT" actual_direction = "UP" if first_hour_change > 0 else "DOWN" if first_hour_change < 0 else "FLAT" direction_correct = predicted_direction == actual_direction if direction_correct: correct_directions += 1 # Show top sources reddit_items = source_breakdown['Reddit'] news_items_found = source_breakdown['Google News'] top_reddit_title = "" if reddit_items: top_reddit = max(reddit_items, key=lambda x: abs(x['sentiment'])) top_reddit_title = top_reddit['title'] top_news_title = "" if news_items_found: top_news = max(news_items_found, key=lambda x: abs(x['sentiment'])) top_news_title = top_news['title'] result = { 'Symbol': symbol, 'Investment Date': investment_time.strftime('%Y-%m-%d'), 'Investment Price': f"${avg_buy_price:.2f}", 'Predicted Change': f"{predicted_change:+.1f}%", 'Actual 1H Change': f"{first_hour_change:+.1f}%", 'Error': f"{error:.1f}%", 'Direction': 'โœ… Correct' if direction_correct else 'โŒ Wrong', 'Sentiment': prediction_label.title(), 'News Sources': len(news_items), 'Reddit Posts': len(reddit_items), 'Top Reddit': top_reddit_title, 'Top News': top_news_title } else: result = { 'Symbol': symbol, 'Investment Date': investment_time.strftime('%Y-%m-%d'), 'Investment Price': f"${avg_buy_price:.2f}", 'Predicted Change': f"{predicted_change:+.1f}%", 'Actual 1H Change': 'N/A', 'Error': 'N/A', 'Direction': 'โ“ No Data', 'Sentiment': prediction_label.title(), 'News Sources': len(news_items), 'Reddit Posts': len(source_breakdown['Reddit']), 'Top Reddit': '', 'Top News': '' } results.append(result) # Calculate summary statistics if valid_results > 0: avg_error = total_error / valid_results direction_accuracy = (correct_directions / valid_results) * 100 summary_text += f"๐Ÿ“ˆ BACKTESTING RESULTS SUMMARY:\n" summary_text += f" Total Investments Tested: {len(results)}\n" summary_text += f" Valid Results: {valid_results}\n" summary_text += f" Average Error: {avg_error:.1f}%\n" summary_text += f" Direction Accuracy: {direction_accuracy:.1f}% ({correct_directions}/{valid_results})\n\n" if direction_accuracy >= 60: summary_text += f" โœ… Strong predictive value!\n" elif direction_accuracy >= 40: summary_text += f" โšก Some predictive value\n" else: summary_text += f" โŒ Needs improvement\n" else: summary_text += f"โŒ No valid results available for analysis\n" # Create DataFrame df = pd.DataFrame(results) return summary_text, df except Exception as e: error_msg = f"โŒ Error running backtesting: {str(e)}" logger.error(error_msg) return error_msg, pd.DataFrame() def clear_terminal(): """Clear terminal output""" return "๐Ÿ–ฅ๏ธ VM Terminal Ready\n$ " def run_quick_command(cmd): """Helper for quick command buttons""" def execute(current_output): return run_vm_command(cmd, current_output) return execute # Custom CSS for gorgeous design custom_css = """ .gradio-container { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; background: #fafafa !important; } .main-header { background: linear-gradient(135deg, #0070f3 0%, #0051a5 100%); color: white; padding: 2rem; border-radius: 16px; margin-bottom: 2rem; box-shadow: 0 10px 40px rgba(0, 112, 243, 0.3); } .metric-card { background: white; border: 1px solid #eaeaea; border-radius: 12px; padding: 1.5rem; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04); transition: all 0.3s ease; } .metric-card:hover { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); transform: translateY(-4px); } .gr-button { background: linear-gradient(135deg, #0070f3 0%, #0051a5 100%) !important; color: white !important; border: none !important; border-radius: 12px !important; font-weight: 600 !important; padding: 1rem 2rem !important; transition: all 0.3s ease !important; box-shadow: 0 4px 16px rgba(0, 112, 243, 0.3) !important; } .gr-button:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 32px rgba(0, 112, 243, 0.4) !important; } .gr-textbox, .gr-dataframe { border: 1px solid #eaeaea !important; border-radius: 12px !important; background: white !important; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04) !important; } .plotly-graph-div { border-radius: 16px !important; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important; background: white !important; } .status-invested { color: #00d647 !important; font-weight: 600 !important; } .status-eligible { color: #f5a623 !important; font-weight: 600 !important; } .status-wrong { color: #8b949e !important; } .status-unknown { color: #ff0080 !important; } /* Investment Performance Table Styling */ .investment-table { width: 100%; border-collapse: separate; border-spacing: 0; background: white; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); } .investment-table th { background: linear-gradient(135deg, #0070f3 0%, #0051a5 100%); color: white; padding: 1rem; font-weight: 600; text-align: left; border: none; } .investment-table th:first-child { border-top-left-radius: 16px; } .investment-table th:last-child { border-top-right-radius: 16px; } .investment-table td { padding: 1rem; border-bottom: 1px solid #f5f5f5; font-weight: 500; } .profit-row { background: rgba(0, 214, 71, 0.1) !important; border-left: 4px solid #00d647; } .loss-row { background: rgba(255, 0, 128, 0.1) !important; border-left: 4px solid #ff0080; } .neutral-row { background: rgba(139, 148, 158, 0.05) !important; border-left: 4px solid #8b949e; } .investment-table tr:last-child td:first-child { border-bottom-left-radius: 16px; } .investment-table tr:last-child td:last-child { border-bottom-right-radius: 16px; } .profit-positive { color: #00d647 !important; font-weight: 600 !important; } .profit-negative { color: #ff0080 !important; font-weight: 600 !important; } .profit-neutral { color: #8b949e !important; } .terminal-container { background: #000000 !important; border: 1px solid #333 !important; border-radius: 8px !important; padding: 0 !important; margin: 1rem 0 !important; height: 500px !important; overflow-y: auto !important; display: flex !important; flex-direction: column !important; /* Hide scrollbars but keep functionality */ scrollbar-width: none !important; /* Firefox */ -ms-overflow-style: none !important; /* IE/Edge */ } .terminal-container::-webkit-scrollbar { display: none !important; /* Chrome/Safari/Webkit */ } .terminal-display { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace !important; background: #000000 !important; color: #ffffff !important; padding: 1rem !important; font-size: 14px !important; line-height: 1.4 !important; white-space: pre-wrap !important; word-wrap: break-word !important; margin: 0 !important; flex-grow: 1 !important; overflow-anchor: none !important; /* Always stick to bottom */ display: flex !important; flex-direction: column !important; justify-content: flex-end !important; } .terminal-display::-webkit-scrollbar { display: none !important; /* Chrome/Safari/Webkit */ } .terminal-input input { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace !important; background: #1a1a1a !important; color: #ffffff !important; border: 1px solid #333 !important; border-radius: 4px !important; font-size: 14px !important; } .terminal-input input:focus { border-color: #00ff00 !important; box-shadow: 0 0 5px rgba(0, 255, 0, 0.3) !important; } /* Force Gradio HTML to stick to bottom */ .gr-html { height: 500px !important; overflow-y: auto !important; scrollbar-width: none !important; -ms-overflow-style: none !important; display: flex !important; flex-direction: column !important; } .gr-html::-webkit-scrollbar { display: none !important; } /* Force content to bottom with CSS anchor */ .gr-html > div { display: flex !important; flex-direction: column !important; justify-content: flex-end !important; min-height: 100% !important; } """ def create_dashboard(): logger.info("๐ŸŽจ Creating Gradio dashboard interface...") try: with gr.Blocks( title="๐Ÿš€ Premium Trading Dashboard", theme=gr.themes.Soft(primary_hue="blue"), css=custom_css ) as demo: logger.info("๐Ÿ–ผ๏ธ Dashboard blocks created successfully") # Header gr.HTML("""

๐Ÿš€ Premium Trading Dashboard

Real-time portfolio monitoring with IPO discovery analytics

""") with gr.Tabs(): # Portfolio Overview Tab with gr.Tab("๐Ÿ“Š Portfolio Overview"): gr.Markdown("## ๐Ÿ’ผ Account Summary") with gr.Row(): portfolio_value = gr.Textbox(label="๐Ÿ’ฐ Portfolio Value", interactive=False, elem_classes=["metric-card"]) buying_power = gr.Textbox(label="๐Ÿ’ณ Buying Power", interactive=False, elem_classes=["metric-card"]) cash = gr.Textbox(label="๐Ÿ’ต Cash", interactive=False, elem_classes=["metric-card"]) day_change = gr.Textbox(label="๐Ÿ“ˆ Day Change", interactive=False, elem_classes=["metric-card"]) equity = gr.Textbox(label="๐Ÿฆ Total Equity", interactive=False, elem_classes=["metric-card"]) gr.Markdown("## ๐Ÿ“ˆ Portfolio Performance") portfolio_chart = gr.Plot(label="Portfolio Value Over Time") refresh_overview_btn = gr.Button("๐Ÿ”„ Refresh Portfolio Data", variant="primary", size="lg") # IPO Discoveries Tab with gr.Tab("๐Ÿ” IPO Discoveries"): gr.Markdown("## ๐Ÿ“Š IPO Discovery Analytics") with gr.Row(): total_ipos = gr.Textbox(label="๐ŸŽฏ Total IPOs Detected", interactive=False, elem_classes=["metric-card"]) ipos_invested = gr.Textbox(label="๐Ÿ’ฐ IPOs Invested", interactive=False, elem_classes=["metric-card"]) cs_stocks = gr.Textbox(label="๐Ÿ“ˆ CS Stocks Found", interactive=False, elem_classes=["metric-card"]) investment_rate = gr.Textbox(label="๐ŸŽฒ Investment Rate", interactive=False, elem_classes=["metric-card"]) last_updated = gr.Textbox(label="๐Ÿ•’ Last Updated", interactive=False, elem_classes=["metric-card"]) with gr.Row(): with gr.Column(scale=1): ipo_chart = gr.Plot(label="Investment Decision Breakdown") with gr.Column(scale=2): gr.Markdown("## ๐Ÿ†• Recent IPO Discoveries") ipo_table = gr.Dataframe( label="IPO Discoveries with Investment Decisions", elem_classes=["gr-dataframe"] ) refresh_ipo_btn = gr.Button("๐Ÿ”„ Refresh IPO Data", variant="primary", size="lg") # Investment Performance Tab with gr.Tab("๐Ÿ’ฐ Investment Performance"): gr.Markdown("## ๐ŸŽฏ IPO Investment Performance") gr.Markdown("### Track profit/loss on your IPO investments with **real-time sentiment analysis**") gr.Markdown("๐Ÿง  **NEW**: Each row automatically shows sentiment predictions from Reddit + Google News!") investment_performance_table = gr.HTML( label="IPO Investment P&L Analysis", value="
Click Refresh to load investment performance data
" ) refresh_investment_btn = gr.Button("๐Ÿ”„ Refresh Investment Performance", variant="primary", size="lg") gr.Markdown("### ๐Ÿงฎ Trading Statistics & Analysis") gr.Markdown("Calculate interesting metrics from your trading data") with gr.Row(): calc_sequential_btn = gr.Button("๐Ÿ“ˆ Sequential Reinvestment P&L%", variant="secondary", size="sm") calc_equal_weight_btn = gr.Button("โš–๏ธ Equal Weight Portfolio P&L%", variant="secondary", size="sm") calc_best_worst_btn = gr.Button("๐Ÿ† Best vs Worst Performers", variant="secondary", size="sm") with gr.Row(): calc_win_rate_btn = gr.Button("๐ŸŽฏ Win Rate & Avg Returns", variant="secondary", size="sm") calc_risk_metrics_btn = gr.Button("โš ๏ธ Risk Metrics & Volatility", variant="secondary", size="sm") calc_time_analysis_btn = gr.Button("โฐ Time-based Performance", variant="secondary", size="sm") stats_output = gr.Textbox( label="Statistical Analysis Results", lines=8, interactive=False, elem_classes=["gr-textbox"] ) gr.Markdown("### ๐Ÿ”ง Debug API Calls") debug_output = gr.Textbox( label="Debug Output", lines=10, interactive=False ) with gr.Row(): debug_orders_btn = gr.Button("๐Ÿ” Debug Order History", variant="secondary") debug_positions_btn = gr.Button("๐Ÿ“Š Debug Current Positions", variant="secondary") debug_ipos_btn = gr.Button("๐ŸŽฏ Debug IPO Data", variant="secondary") debug_account_btn = gr.Button("๐Ÿ’ผ Debug Account Info", variant="secondary") # VM Terminal Tab with gr.Tab("๐Ÿ’ป VM Terminal"): gr.Markdown("## ๐Ÿ–ฅ๏ธ Remote VM Terminal") gr.Markdown("### Execute commands directly on your trading VM") # Hidden state for command history command_history = gr.State("") with gr.Row(): with gr.Column(scale=4): command_input = gr.Textbox( label="Command (Press Enter to run)", placeholder="Enter command to run on VM...", interactive=True, elem_classes=["terminal-input"] ) with gr.Column(scale=1): run_command_btn = gr.Button("โ–ถ๏ธ Run", variant="primary", size="lg") clear_terminal_btn = gr.Button("๐Ÿ—‘๏ธ Clear", variant="secondary", size="lg") terminal_output = gr.HTML( label="Terminal Output", value='
๐Ÿ–ฅ๏ธ VM Terminal Ready
$
', elem_classes=["terminal-container"] ) gr.Markdown("**๐Ÿ“ File & System Commands:**") with gr.Row(): quick_ls = gr.Button("๐Ÿ“ ls -la", size="sm") quick_pwd = gr.Button("๐Ÿ“ pwd", size="sm") quick_ps = gr.Button("๐Ÿ”„ ps aux | grep python", size="sm") quick_vm_status = gr.Button("๐Ÿ–ฅ๏ธ uptime && df -h", size="sm") quick_who = gr.Button("๐Ÿ‘ค whoami", size="sm") gr.Markdown("**๐Ÿ“‹ Log Files:**") with gr.Row(): quick_script_log = gr.Button("๐Ÿ“œ tail -50 script.log", size="sm") quick_server_log = gr.Button("๐Ÿ–ฅ๏ธ tail -50 server.log", size="sm") quick_cron_log = gr.Button("โฐ tail -50 /var/log/cron", size="sm") quick_portfolio = gr.Button("๐Ÿ’ผ cat portfolio.txt", size="sm") quick_tickers = gr.Button("๐ŸŽฏ head -20 new_tickers_log.csv", size="sm") gr.Markdown("**๐Ÿ” Search & Analysis:**") with gr.Row(): quick_errors = gr.Button("๐Ÿšจ grep -i error script.log | tail -10", size="sm") quick_trades = gr.Button("๐Ÿ’ฐ grep -i 'buy\\|sell' script.log | tail -10", size="sm") quick_ipos = gr.Button("๐Ÿ†• grep -i 'new ticker' script.log | tail -10", size="sm") # System Logs Tab with gr.Tab("๐Ÿ“‹ System Logs"): gr.Markdown("## ๐Ÿ–ฅ๏ธ Trading Bot Activity") with gr.Row(): with gr.Column(): gr.Markdown("### ๐ŸŽฏ Parsed Logs (Color Coded)") system_logs = gr.Textbox( label="Recent System Activity", lines=20, max_lines=20, interactive=False, elem_classes=["gr-textbox"] ) with gr.Column(): gr.Markdown("### ๐Ÿ“„ Raw Cron Logs") raw_logs = gr.Textbox( label="Raw Log Output", lines=20, max_lines=20, interactive=False, elem_classes=["gr-textbox"] ) refresh_logs_btn = gr.Button("๐Ÿ”„ Refresh All Logs", variant="primary", size="lg") # Footer gr.HTML("""

๐Ÿค– Automated Trading Dashboard

Real-time data from Alpaca Markets + VM Analytics | Built with โค๏ธ

""") logger.info("๐Ÿ”— Setting up event handlers...") # Event Handlers # Portfolio tab refresh_overview_btn.click( fn=refresh_account_overview, outputs=[portfolio_value, buying_power, cash, day_change, equity] ) refresh_overview_btn.click( fn=create_portfolio_chart, outputs=[portfolio_chart] ) # IPO tab refresh_ipo_btn.click( fn=refresh_vm_stats, outputs=[total_ipos, ipos_invested, cs_stocks, investment_rate, last_updated] ) refresh_ipo_btn.click( fn=create_ipo_discovery_chart, outputs=[ipo_chart] ) refresh_ipo_btn.click( fn=refresh_ipo_discoveries_table, outputs=[ipo_table] ) # Investment Performance tab refresh_investment_btn.click( fn=refresh_investment_performance_html, outputs=[investment_performance_table] ) # Debug buttons debug_orders_btn.click( fn=debug_order_history, outputs=[debug_output] ) debug_positions_btn.click( fn=debug_current_positions, outputs=[debug_output] ) debug_ipos_btn.click( fn=debug_ipo_data, outputs=[debug_output] ) debug_account_btn.click( fn=debug_account_info, outputs=[debug_output] ) # Trading Statistics buttons calc_sequential_btn.click( fn=calculate_sequential_reinvestment, outputs=[stats_output] ) calc_equal_weight_btn.click( fn=calculate_equal_weight_portfolio, outputs=[stats_output] ) calc_best_worst_btn.click( fn=calculate_best_worst_performers, outputs=[stats_output] ) calc_win_rate_btn.click( fn=calculate_win_rate_metrics, outputs=[stats_output] ) calc_risk_metrics_btn.click( fn=calculate_risk_metrics, outputs=[stats_output] ) calc_time_analysis_btn.click( fn=calculate_time_analysis, outputs=[stats_output] ) # VM Terminal tab - RADICAL FIX: Use
 instead of 
and force scroll with element replacement def run_and_clear(cmd, output, history): new_output, _, new_history = run_vm_command(cmd, output, history) # DON'T escape HTML since we want color spans to render # The colorize_output function already creates safe HTML spans # Create a unique ID for this update to force refresh unique_id = f"terminal-{hash(new_output) % 100000}" html_output = f'''
{new_output}
''' return html_output, "", new_history def clear_and_reset(): return '
๐Ÿ–ฅ๏ธ VM Terminal Ready
$
', "" run_command_btn.click( fn=run_and_clear, inputs=[command_input, terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) command_input.submit( fn=run_and_clear, inputs=[command_input, terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) clear_terminal_btn.click( fn=clear_and_reset, outputs=[terminal_output, command_input] ) # File & System Commands quick_ls.click( fn=lambda output, hist: run_and_clear("ls -la", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_pwd.click( fn=lambda output, hist: run_and_clear("pwd", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_ps.click( fn=lambda output, hist: run_and_clear("ps aux | grep python", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_vm_status.click( fn=lambda output, hist: run_and_clear("uptime && df -h", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_who.click( fn=lambda output, hist: run_and_clear("whoami", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) # Log Files quick_script_log.click( fn=lambda output, hist: run_and_clear("tail -50 script.log", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_server_log.click( fn=lambda output, hist: run_and_clear("tail -50 server.log", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_cron_log.click( fn=lambda output, hist: run_and_clear("tail -50 /var/log/cron", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_portfolio.click( fn=lambda output, hist: run_and_clear("cat portfolio.txt", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_tickers.click( fn=lambda output, hist: run_and_clear("head -20 new_tickers_log.csv", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) # Search & Analysis quick_errors.click( fn=lambda output, hist: run_and_clear("grep -i error script.log | tail -10", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_trades.click( fn=lambda output, hist: run_and_clear("grep -i 'buy\\|sell' script.log | tail -10", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) quick_ipos.click( fn=lambda output, hist: run_and_clear("grep -i 'new ticker' script.log | tail -10", output, hist), inputs=[terminal_output, command_history], outputs=[terminal_output, command_input, command_history] ) # Logs tab refresh_logs_btn.click( fn=refresh_system_logs, outputs=[system_logs] ) refresh_logs_btn.click( fn=refresh_raw_logs, outputs=[raw_logs] ) # Initial data load demo.load( fn=refresh_account_overview, outputs=[portfolio_value, buying_power, cash, day_change, equity] ) demo.load(fn=create_portfolio_chart, outputs=[portfolio_chart]) demo.load( fn=refresh_vm_stats, outputs=[total_ipos, ipos_invested, cs_stocks, investment_rate, last_updated] ) demo.load(fn=create_ipo_discovery_chart, outputs=[ipo_chart]) demo.load(fn=refresh_ipo_discoveries_table, outputs=[ipo_table]) demo.load(fn=refresh_investment_performance_html, outputs=[investment_performance_table]) logger.info("โœ… All event handlers configured successfully") return demo except Exception as e: logger.error(f"โŒ Failed to create dashboard: {e}") raise # Create and launch logger.info("๐Ÿ—๏ธ Building dashboard...") try: demo = create_dashboard() logger.info("โœ… Dashboard created successfully!") except Exception as e: logger.error(f"โŒ Dashboard creation failed: {e}") raise if __name__ == "__main__": logger.info("๐Ÿš€ Launching dashboard server...") try: demo.launch() logger.info("โœ… Dashboard launched successfully!") except Exception as e: logger.error(f"โŒ Dashboard launch failed: {e}") raise