WebSocket: Checking...
CORS: Testing...
Indicators: Loading...
APIs: Connecting...
🔍
🎯 Market Risk & Liquidity Intelligence
Name / Symbol Current Value Daily Change 1D % 1W % 1M % 1Y % Data Source
}, inflation: { value: 3.2, unit: '%' }, cpi: { value: 310.3, unit: 'Index' }, employment: { value: 156789, unit: 'Thousands' }, money_measures: { value: 21234.5, unit: 'Billion // Initialize the application async function init() { try { console.log('🚀 Initializing OpenBB Financial Intelligence Platform...'); // Update clock updateClock(); setInterval(updateClock, 1000); // Initialize connection status display initializeConnectionStatus(); // Test API connections with CORS handling const connectionResults = await testAllConnections(); // Check if we should use demo mode const connectedAPIs = Object.values(connectionResults).filter(Boolean).length; if (connectedAPIs === 0 && PRODUCTION_CONFIG.USE_DEMO_MODE) { console.log('⚠️ No APIs available, switching to demo mode'); initializeFallbackMode(); return; } // Load initial watchlist loadWatchlist(); // Set up search functionality setupSearchListeners(); // Initialize data refresh startDataRefresh(); // Update UI updateSortButtons(); document.getElementById('searchBox').focus(); console.log('✅ OpenBB Platform initialized successfully'); showTemporaryMessage('OpenBB Platform Ready - 66,370+ Indicators Available', 'info'); } catch (error) { console.error('❌ Initialization failed:', error); showTemporaryMessage('Platform initialization failed - using fallback mode', 'error'); initializeFallbackMode(); } } // Advanced Technical Analysis Functions class TechnicalAnalysis { static calculateSMA(data, period) { const result = []; for (let i = period - 1; i < data.length; i++) { const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b.value, 0); result.push({ date: data[i].date, value: sum / period }); } return result; } static calculateEMA(data, period) { const result = []; const multiplier = 2 / (period + 1); let ema = data[0].value; result.push({ date: data[0].date, value: ema }); for (let i = 1; i < data.length; i++) { ema = (data[i].value - ema) * multiplier + ema; result.push({ date: data[i].date, value: ema }); } return result; } static calculateMACD(data, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) { const fastEMA = this.calculateEMA(data, fastPeriod); const slowEMA = this.calculateEMA(data, slowPeriod); const macdLine = []; for (let i = 0; i < Math.min(fastEMA.length, slowEMA.length); i++) { macdLine.push({ date: fastEMA[i].date, value: fastEMA[i].value - slowEMA[i].value }); } const signalLine = this.calculateEMA(macdLine, signalPeriod); const histogram = []; for (let i = 0; i < Math.min(macdLine.length, signalLine.length); i++) { histogram.push({ date: macdLine[i].date, value: macdLine[i].value - signalLine[i].value }); } return { macdLine, signalLine, histogram }; } static calculateRSI(data, period = 14) { const gains = []; const losses = []; const rsi = []; for (let i = 1; i < data.length; i++) { const change = data[i].value - data[i - 1].value; gains.push(change > 0 ? change : 0); losses.push(change < 0 ? Math.abs(change) : 0); } let avgGain = gains.slice(0, period).reduce((a, b) => a + b, 0) / period; let avgLoss = losses.slice(0, period).reduce((a, b) => a + b, 0) / period; for (let i = period; i < data.length; i++) { const rs = avgGain / avgLoss; const rsiValue = 100 - (100 / (1 + rs)); rsi.push({ date: data[i].date, value: rsiValue }); if (i < gains.length) { avgGain = (avgGain * (period - 1) + gains[i]) / period; avgLoss = (avgLoss * (period - 1) + losses[i]) / period; } } return rsi; } static calculateBollingerBands(data, period = 20, stdDev = 2) { const sma = this.calculateSMA(data, period); const bands = []; for (let i = 0; i < sma.length; i++) { const dataSlice = data.slice(i, i + period); const mean = sma[i].value; const variance = dataSlice.reduce((sum, point) => sum + Math.pow(point.value - mean, 2), 0) / period; const standardDeviation = Math.sqrt(variance); bands.push({ date: sma[i].date, upper: mean + (standardDeviation * stdDev), middle: mean, lower: mean - (standardDeviation * stdDev) }); } return bands; } static calculateStochasticOscillator(data, kPeriod = 14, dPeriod = 3) { const stochastic = []; for (let i = kPeriod - 1; i < data.length; i++) { const slice = data.slice(i - kPeriod + 1, i + 1); const high = Math.max(...slice.map(d => d.high || d.value)); const low = Math.min(...slice.map(d => d.low || d.value)); const close = data[i].close || data[i].value; const k = ((close - low) / (high - low)) * 100; stochastic.push({ date: data[i].date, k: k, d: null }); } // Calculate %D (SMA of %K) for (let i = dPeriod - 1; i < stochastic.length; i++) { const dValue = stochastic.slice(i - dPeriod + 1, i + 1) .reduce((sum, point) => sum + point.k, 0) / dPeriod; stochastic[i].d = dValue; } return stochastic; } static detectPatterns(data) { const patterns = []; // Double Top Pattern for (let i = 20; i < data.length - 20; i++) { const peak1 = this.findLocalMaxima(data, i - 10, i); const peak2 = this.findLocalMaxima(data, i, i + 10); if (peak1 && peak2 && Math.abs(peak1.value - peak2.value) < peak1.value * 0.02) { patterns.push({ type: 'Double Top', date: data[i].date, confidence: 0.75, prediction: 'Bearish' }); } } // Head and Shoulders Pattern for (let i = 30; i < data.length - 30; i++) { const leftShoulder = this.findLocalMaxima(data, i - 20, i - 10); const head = this.findLocalMaxima(data, i - 5, i + 5); const rightShoulder = this.findLocalMaxima(data, i + 10, i + 20); if (leftShoulder && head && rightShoulder && head.value > leftShoulder.value && head.value > rightShoulder.value && Math.abs(leftShoulder.value - rightShoulder.value) < leftShoulder.value * 0.05) { patterns.push({ type: 'Head and Shoulders', date: data[i].date, confidence: 0.85, prediction: 'Bearish' }); } } return patterns; } static findLocalMaxima(data, start, end) { let max = { value: -Infinity, index: -1 }; for (let i = start; i <= end && i < data.length; i++) { if (data[i].value > max.value) { max = { value: data[i].value, index: i }; } } return max.index >= 0 ? data[max.index] : null; } } // Advanced Market Sentiment Analysis class SentimentAnalysis { static calculateFearGreedIndex(vix, sp500Change, bondYield, dollarStrength) { let score = 50; // Neutral starting point // VIX component (30% weight) if (vix < 15) score += 15; // Low fear else if (vix > 25) score -= 15; // High fear // S&P 500 momentum (25% weight) if (sp500Change > 2) score += 12.5; else if (sp500Change < -2) score -= 12.5; // Bond yield spread (25% weight) if (bondYield > 4) score -= 10; // High yields = uncertainty else if (bondYield < 2) score += 10; // Low yields = risk-on // Dollar strength (20% weight) if (dollarStrength > 105) score -= 8; // Strong dollar = risk-off else if (dollarStrength < 95) score += 8; // Weak dollar = risk-on return Math.max(0, Math.min(100, score)); } static interpretSentiment(score) { if (score >= 75) return { level: 'Extreme Greed', color: '#ff4444', signal: 'SELL' }; if (score >= 55) return { level: 'Greed', color: '#ffaa00', signal: 'CAUTION' }; if (score >= 45) return { level: 'Neutral', color: '#888888', signal: 'HOLD' }; if (score >= 25) return { level: 'Fear', color: '#0088ff', signal: 'BUY' }; return { level: 'Extreme Fear', color: '#00dd77', signal: 'STRONG BUY' }; } static generateMarketInsights(data) { const insights = []; const latest = data[data.length - 1]; const previous = data[data.length - 2]; if (latest && previous) { const change = ((latest.value - previous.value) / previous.value) * 100; if (Math.abs(change) > 5) { insights.push({ type: 'volatility', message: `High volatility detected: ${change.toFixed(2)}% change`, severity: 'high', timestamp: latest.date }); } if (change > 10) { insights.push({ type: 'momentum', message: 'Strong bullish momentum detected', severity: 'medium', timestamp: latest.date }); } else if (change < -10) { insights.push({ type: 'momentum', message: 'Strong bearish momentum detected', severity: 'medium', timestamp: latest.date }); } } return insights; } } // Portfolio Analysis and Risk Management class PortfolioAnalysis { static calculatePortfolioMetrics(holdings) { const totalValue = holdings.reduce((sum, holding) => sum + holding.value, 0); const weights = holdings.map(holding => holding.value / totalValue); // Calculate portfolio beta const beta = holdings.reduce((sum, holding, index) => sum + (weights[index] * (holding.beta || 1)), 0); // Calculate portfolio volatility const volatility = Math.sqrt( holdings.reduce((sum, holding, i) => { return sum + Math.pow(weights[i] * (holding.volatility || 0.2), 2); }, 0) ); // Calculate Sharpe ratio const riskFreeRate = 0.02; // 2% risk-free rate const expectedReturn = holdings.reduce((sum, holding, index) => sum + (weights[index] * (holding.expectedReturn || 0.08)), 0); const sharpeRatio = (expectedReturn - riskFreeRate) / volatility; return { totalValue, beta, volatility, sharpeRatio, expectedReturn, diversificationRatio: this.calculateDiversificationRatio(holdings) }; } static calculateDiversificationRatio(holdings) { const totalHoldings = holdings.length; const maxWeight = Math.max(...holdings.map(h => h.weight || 1/totalHoldings)); // Higher diversification ratio = better diversification return 1 - maxWeight; } static calculateVaR(returns, confidence = 0.95) { const sortedReturns = [...returns].sort((a, b) => a - b); const index = Math.floor((1 - confidence) * sortedReturns.length); return sortedReturns[index]; } static generateRiskAlerts(portfolioMetrics) { const alerts = []; if (portfolioMetrics.beta > 1.5) { alerts.push({ type: 'risk', message: 'High portfolio beta detected - consider reducing market exposure', severity: 'high' }); } if (portfolioMetrics.volatility > 0.25) { alerts.push({ type: 'volatility', message: 'Portfolio volatility is elevated - review risk tolerance', severity: 'medium' }); } if (portfolioMetrics.diversificationRatio < 0.6) { alerts.push({ type: 'concentration', message: 'Portfolio may be over-concentrated - consider diversifying', severity: 'medium' }); } return alerts; } } // Advanced Economic Indicators Analysis class EconomicAnalysis { static calculateYieldCurve(rates) { const maturities = ['3M', '6M', '1Y', '2Y', '5Y', '10Y', '30Y']; const curve = maturities.map((maturity, index) => ({ maturity, rate: rates[index] || 0, spread: index > 0 ? (rates[index] || 0) - (rates[0] || 0) : 0 })); // Detect inversion const inverted = curve.some((point, index) => index > 0 && point.rate < curve[index - 1].rate); return { curve, inverted, slope: (rates[6] || 0) - (rates[0] || 0), // 30Y - 3M spread curvature: this.calculateCurvature(rates) }; } static calculateCurvature(rates) { if (rates.length < 3) return 0; // 2 * 5Y - 2Y - 10Y (butterfly spread) return 2 * (rates[4] || 0) - (rates[3] || 0) - (rates[5] || 0); } static analyzeInflationTrend(cpiData) { if (cpiData.length < 12) return null; const recent = cpiData.slice(-12); const yearAgo = cpiData.slice(-24, -12); const recentAvg = recent.reduce((sum, val) => sum + val, 0) / 12; const yearAgoAvg = yearAgo.reduce((sum, val) => sum + val, 0) / 12; const trend = ((recentAvg - yearAgoAvg) / yearAgoAvg) * 100; return { currentLevel: recent[recent.length - 1], trend, momentum: trend > 0 ? 'Rising' : 'Falling', severity: Math.abs(trend) > 2 ? 'High' : Math.abs(trend) > 0.5 ? 'Medium' : 'Low' }; } static calculateEconomicSurpriseIndex(forecasts, actuals) { if (forecasts.length !== actuals.length) return 0; const surprises = forecasts.map((forecast, index) => { const actual = actuals[index]; return ((actual - forecast) / Math.abs(forecast)) * 100; }); // Calculate weighted average with more recent data having higher weight const weights = surprises.map((_, index) => Math.pow(0.9, surprises.length - index - 1)); const totalWeight = weights.reduce((sum, weight) => sum + weight, 0); return surprises.reduce((sum, surprise, index) => sum + (surprise * weights[index]), 0) / totalWeight; } } // Real-time Data Streaming Simulator class DataStreamManager { constructor() { this.streams = new Map(); this.subscribers = new Map(); this.isStreaming = false; } subscribe(symbol, callback) { if (!this.subscribers.has(symbol)) { this.subscribers.set(symbol, []); } this.subscribers.get(symbol).push(callback); if (!this.streams.has(symbol)) { this.startStream(symbol); } } unsubscribe(symbol, callback) { const callbacks = this.subscribers.get(symbol); if (callbacks) { const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } if (callbacks.length === 0) { this.stopStream(symbol); } } } startStream(symbol) { const interval = setInterval(() => { const data = this.generateRealtimeData(symbol); this.notifySubscribers(symbol, data); }, 1000 + Math.random() * 2000); // Random interval 1-3 seconds this.streams.set(symbol, interval); } stopStream(symbol) { const interval = this.streams.get(symbol); if (interval) { clearInterval(interval); this.streams.delete(symbol); } } generateRealtimeData(symbol) { const baseValue = this.getBaseValue(symbol); const volatility = this.getVolatility(symbol); const change = (Math.random() - 0.5) * volatility * 2; return { symbol, value: baseValue * (1 + change), change: change * 100, volume: Math.floor(Math.random() * 1000000), timestamp: new Date().toISOString(), bid: baseValue * (1 + change - 0.001), ask: baseValue * (1 + change + 0.001) }; } getBaseValue(symbol) { const baseValues = { 'SPY': 450, 'QQQ': 380, 'AAPL': 180, 'MSFT': 350, 'GOOGL': 140, 'TSLA': 250, 'BTC': 45000, 'ETH': 3200, 'DXY': 102.5, 'VIX': 18.5 }; return baseValues[symbol] || 100; } getVolatility(symbol) { const volatilities = { 'SPY': 0.01, 'QQQ': 0.015, 'AAPL': 0.02, 'MSFT': 0.018, 'GOOGL': 0.025, 'TSLA': 0.04, 'BTC': 0.05, 'ETH': 0.06, 'DXY': 0.005, 'VIX': 0.1 }; return volatilities[symbol] || 0.02; } notifySubscribers(symbol, data) { const callbacks = this.subscribers.get(symbol); if (callbacks) { callbacks.forEach(callback => { try { callback(data); } catch (error) { console.error('Error in stream callback:', error); } }); } } startAllStreams() { this.isStreaming = true; console.log('🔴 Real-time data streaming started'); } stopAllStreams() { this.streams.forEach((interval, symbol) => { clearInterval(interval); }); this.streams.clear(); this.isStreaming = false; console.log('⏹️ Real-time data streaming stopped'); } } // Advanced Alert System class AlertSystem { constructor() { this.alerts = []; this.rules = new Map(); this.subscribers = []; } addRule(id, condition, action, priority = 'medium') { this.rules.set(id, { condition, action, priority, active: true, triggered: 0 }); } removeRule(id) { this.rules.delete(id); } checkRules(data) { this.rules.forEach((rule, id) => { if (rule.active && rule.condition(data)) { const alert = { id: Date.now() + Math.random(), ruleId: id, message: rule.action(data), priority: rule.priority, timestamp: new Date().toISOString(), data }; this.addAlert(alert); rule.triggered++; } }); } addAlert(alert) { this.alerts.unshift(alert); // Keep only last 100 alerts if (this.alerts.length > 100) { this.alerts = this.alerts.slice(0, 100); } this.notifySubscribers(alert); this.displayAlert(alert); } subscribe(callback) { this.subscribers.push(callback); } notifySubscribers(alert) { this.subscribers.forEach(callback => { try { callback(alert); } catch (error) { console.error('Error in alert callback:', error); } }); } displayAlert(alert) { const alertElement = document.createElement('div'); alertElement.className = `alert-notification alert-${alert.priority}`; alertElement.innerHTML = `
${this.getAlertIcon(alert.priority)} ${new Date(alert.timestamp).toLocaleTimeString()}
${alert.message}
`; document.body.appendChild(alertElement); // Auto-remove after 10 seconds setTimeout(() => { if (alertElement.parentElement) { alertElement.remove(); } }, 10000); } getAlertIcon(priority) { const icons = { 'low': 'ℹ️', 'medium': '⚠️', 'high': '🚨', 'critical': '🔥' }; return icons[priority] || 'ℹ️'; } // Predefined alert rules setupDefaultRules() { // Price spike alert this.addRule('price_spike', (data) => Math.abs(data.change) > 5, (data) => `${data.symbol} price spike: ${data.change.toFixed(2)}%`, 'high' ); // Volume spike alert this.addRule('volume_spike', (data) => data.volume > 1000000, (data) => `${data.symbol} volume spike: ${data.volume.toLocaleString()}`, 'medium' ); // Technical level breach this.addRule('support_resistance', (data) => this.checkTechnicalLevels(data), (data) => `${data.symbol} breached key technical level`, 'medium' ); } checkTechnicalLevels(data) { // Simplified technical level check const keyLevels = { 'SPY': [440, 450, 460], 'AAPL': [170, 180, 190], 'MSFT': [340, 350, 360] }; const levels = keyLevels[data.symbol]; if (!levels) return false; return levels.some(level => Math.abs(data.value - level) < level * 0.005 // Within 0.5% of level ); } } // Performance Monitoring and Optimization class PerformanceMonitor { constructor() { this.metrics = { apiCalls: 0, errors: 0, loadTimes: [], memoryUsage: [], cacheHits: 0, cacheMisses: 0 }; this.startTime = Date.now(); } recordAPICall(duration, success = true) { this.metrics.apiCalls++; this.metrics.loadTimes.push(duration); if (!success) { this.metrics.errors++; } // Keep only last 100 measurements if (this.metrics.loadTimes.length > 100) { this.metrics.loadTimes = this.metrics.loadTimes.slice(-100); } } recordCacheHit() { this.metrics.cacheHits++; } recordCacheMiss() { this.metrics.cacheMisses++; } getAverageLoadTime() { if (this.metrics.loadTimes.length === 0) return 0; return this.metrics.loadTimes.reduce((sum, time) => sum + time, 0) / this.metrics.loadTimes.length; } getErrorRate() { if (this.metrics.apiCalls === 0) return 0; return (this.metrics.errors / this.metrics.apiCalls) * 100; } getCacheHitRate() { const totalRequests = this.metrics.cacheHits + this.metrics.cacheMisses; if (totalRequests === 0) return 0; return (this.metrics.cacheHits / totalRequests) * 100; } getUptime() { return Date.now() - this.startTime; } generateReport() { return { uptime: this.getUptime(), apiCalls: this.metrics.apiCalls, averageLoadTime: this.getAverageLoadTime(), errorRate: this.getErrorRate(), cacheHitRate: this.getCacheHitRate(), memoryUsage: this.estimateMemoryUsage() }; } estimateMemoryUsage() { // Rough estimation based on data structures const cacheSize = AppState.cache.size * 1024; // Assume 1KB per cache entry const watchlistSize = Object.keys(watchlistData).length * 512; // 512B per symbol const chartSize = Object.keys(charts).length * 2048; // 2KB per chart return { cache: cacheSize, watchlist: watchlistSize, charts: chartSize, total: cacheSize + watchlistSize + chartSize }; } } // Initialize advanced systems const techAnalysis = new TechnicalAnalysis(); const sentimentAnalysis = new SentimentAnalysis(); const portfolioAnalysis = new PortfolioAnalysis(); const economicAnalysis = new EconomicAnalysis(); const dataStreamer = new DataStreamManager(); const alertSystem = new AlertSystem(); const performanceMonitor = new PerformanceMonitor(); // Enhanced Error Handling and Recovery class ErrorHandler { static handleAPIError(error, context) { console.error(`API Error in ${context}:`, error); const errorTypes = { 'NetworkError': 'Connection issue - retrying...', 'TypeError': 'Data format error - using fallback', 'SyntaxError': 'Response parsing error - using cache', 'TimeoutError': 'Request timeout - retrying with backup' }; const userMessage = errorTypes[error.name] || 'Unexpected error occurred'; showTemporaryMessage(userMessage, 'error'); // Log to performance monitor performanceMonitor.recordAPICall(0, false); return this.getErrorRecoveryStrategy(error); } static getErrorRecoveryStrategy(error) { if (error.name === 'NetworkError') { return 'retry_with_fallback'; } else if (error.name === 'TypeError') { return 'use_simulated_data'; } else if (error.message.includes('CORS')) { return 'try_proxy'; } else { return 'use_cache'; } } static async executeWithRetry(asyncFunction, maxRetries = 3, delay = 1000) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const startTime = Date.now(); const result = await asyncFunction(); const duration = Date.now() - startTime; performanceMonitor.recordAPICall(duration, true); return result; } catch (error) { console.warn(`Attempt ${attempt} failed:`, error.message); if (attempt === maxRetries) { throw error; } await new Promise(resolve => setTimeout(resolve, delay * attempt)); } } } } // Advanced Caching System class CacheManager { constructor() { this.cache = new Map(); this.ttl = new Map(); // Time to live this.defaultTTL = 300000; // 5 minutes this.maxSize = 1000; } set(key, value, customTTL = null) { const ttl = customTTL || this.defaultTTL; const expiry = Date.now() + ttl; this.cache.set(key, value); this.ttl.set(key, expiry); // Cleanup if cache is too large this.cleanup(); performanceMonitor.recordCacheMiss(); } get(key) { const expiry = this.ttl.get(key); if (!expiry || Date.now() > expiry) { this.delete(key); performanceMonitor.recordCacheMiss(); return null; } performanceMonitor.recordCacheHit(); return this.cache.get(key); } delete(key) { this.cache.delete(key); this.ttl.delete(key); } cleanup() { if (this.cache.size <= this.maxSize) return; // Remove expired entries first for (const [key, expiry] of this.ttl.entries()) { if (Date.now() > expiry) { this.delete(key); } } // If still too large, remove oldest entries if (this.cache.size > this.maxSize) { const entries = Array.from(this.cache.keys()); const toRemove = entries.slice(0, entries.length - this.maxSize); toRemove.forEach(key => this.delete(key)); } } clear() { this.cache.clear(); this.ttl.clear(); } getStats() { return { size: this.cache.size, hitRate: performanceMonitor.getCacheHitRate(), memoryUsage: this.estimateMemoryUsage() }; } estimateMemoryUsage() { let totalSize = 0; for (const value of this.cache.values()) { totalSize += JSON.stringify(value).length * 2; // Rough UTF-16 estimation } return totalSize; } } // Advanced Data Validation class DataValidator { static validateFinancialData(data, symbol) { const errors = []; if (!data) { errors.push('No data provided'); return { valid: false, errors }; } if (Array.isArray(data)) { data.forEach((item, index) => { if (!item.date) errors.push(`Missing date at index ${index}`); if (typeof item.value !== 'number' || isNaN(item.value)) { errors.push(`Invalid value at index ${index}`); } if (item.value < 0 && !this.allowsNegativeValues(symbol)) { errors.push(`Negative value not allowed for ${symbol} at index ${index}`); } }); } else { if (typeof data.value !== 'number' || isNaN(data.value)) { errors.push('Invalid value in data object'); } } return { valid: errors.length === 0, errors, warnings: this.generateWarnings(data, symbol) }; } static allowsNegativeValues(symbol) { const negativeAllowed = ['change', 'return', 'yield_spread', 'sentiment']; return negativeAllowed.some(type => symbol.toLowerCase().includes(type)); } static generateWarnings(data, symbol) { const warnings = []; if (Array.isArray(data) && data.length > 0) { const values = data.map(d => d.value).filter(v => typeof v === 'number'); const mean = values.reduce((sum, v) => sum + v, 0) / values.length; const stdDev = Math.sqrt(values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length); // Check for outliers values.forEach((value, index) => { if (Math.abs(value - mean) > 3 * stdDev) { warnings.push(`Potential outlier detected at index ${index}: ${value}`); } }); // Check for missing data patterns if (data.length < 10) { warnings.push('Limited data points available'); } } return warnings; } static sanitizeData(data) { if (Array.isArray(data)) { return data.filter(item => item && item.date && typeof item.value === 'number' && !isNaN(item.value) ); } return data; } } // Enhanced UI Components class UIComponents { static createLoadingSpinner(container, message = 'Loading...') { const spinner = document.createElement('div'); spinner.className = 'loading-spinner'; spinner.innerHTML = `
${message}
`; container.appendChild(spinner); return spinner; } static createProgressBar(container, progress = 0) { const progressBar = document.createElement('div'); progressBar.className = 'progress-bar-container'; progressBar.innerHTML = `
${progress}%
`; container.appendChild(progressBar); return progressBar; } static updateProgressBar(progressBar, progress) { const fill = progressBar.querySelector('.progress-fill'); const text = progressBar.querySelector('.progress-text'); fill.style.width = `${progress}%`; text.textContent = `${progress}%`; } static createTooltip(element, content) { element.setAttribute('data-tooltip', content); element.classList.add('tooltip'); } static createModal(title, content, options = {}) { const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.innerHTML = ` `; document.body.appendChild(modal); return modal; } static createNotification(message, type = 'info', duration = 5000) { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${this.getNotificationIcon(type)}
${message}
`; const container = document.getElementById('notification-container') || this.createNotificationContainer(); container.appendChild(notification); if (duration > 0) { setTimeout(() => { if (notification.parentElement) { notification.remove(); } }, duration); } return notification; } static createNotificationContainer() { const container = document.createElement('div'); container.id = 'notification-container'; container.className = 'notification-container'; document.body.appendChild(container); return container; } static getNotificationIcon(type) { const icons = { 'info': 'ℹ️', 'success': '✅', 'warning': '⚠️', 'error': '❌' }; return icons[type] || 'ℹ️'; } } // Data Export and Import Utilities class DataExporter { static exportToCSV(data, filename = 'openbb_data.csv') { if (!Array.isArray(data) || data.length === 0) { throw new Error('No data to export'); } const headers = Object.keys(data[0]); const csvContent = [ headers.join(','), ...data.map(row => headers.map(header => { const value = row[header]; return typeof value === 'string' ? `"${value}"` : value; }).join(',') ) ].join('\n'); this.downloadFile(csvContent, filename, 'text/csv'); } static exportToJSON(data, filename = 'openbb_data.json') { const jsonContent = JSON.stringify(data, null, 2); this.downloadFile(jsonContent, filename, 'application/json'); } static exportToExcel(data, filename = 'openbb_data.xlsx') { // Simplified Excel export (would require a library like SheetJS in production) console.warn('Excel export requires additional library - exporting as CSV instead'); this.exportToCSV(data, filename.replace('.xlsx', '.csv')); } static downloadFile(content, filename, mimeType) { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } static async importFromFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (event) => { try { const content = event.target.result; if (file.name.endsWith('.json')) { resolve(JSON.parse(content)); } else if (file.name.endsWith('.csv')) { resolve(this.parseCSV(content)); } else { reject(new Error('Unsupported file format')); } } catch (error) { reject(error); } }; reader.onerror = () => reject(new Error('File reading failed')); reader.readAsText(file); }); } static parseCSV(content) { const lines = content.split('\n').filter(line => line.trim()); if (lines.length < 2) throw new Error('Invalid CSV format'); const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, '')); const data = lines.slice(1).map(line => { const values = line.split(',').map(v => v.trim().replace(/"/g, '')); const row = {}; headers.forEach((header, index) => { row[header] = values[index] || ''; }); return row; }); return data; } } // Advanced Keyboard Shortcuts class KeyboardShortcuts { constructor() { this.shortcuts = new Map(); this.modifierKeys = { ctrl: false, shift: false, alt: false }; this.init(); } init() { document.addEventListener('keydown', (event) => this.handleKeyDown(event)); document.addEventListener('keyup', (event) => this.handleKeyUp(event)); // Register default shortcuts this.registerShortcut('ctrl+/', () => this.showHelp()); this.registerShortcut('ctrl+f', () => this.focusSearch()); this.registerShortcut('ctrl+r', () => this.refreshData()); this.registerShortcut('ctrl+n', () => addNewChart()); this.registerShortcut('ctrl+s', () => this.saveConfiguration()); this.registerShortcut('ctrl+e', () => this.exportData()); this.registerShortcut('escape', () => this.closeModals()); this.registerShortcut('ctrl+1', () => switchWatchlist('MAIN')); this.registerShortcut('ctrl+2', () => switchWatchlist('ECONOMIC')); this.registerShortcut('ctrl+3', () => switchWatchlist('MARKETS')); } registerShortcut(combination, action) { this.shortcuts.set(combination.toLowerCase(), action); } handleKeyDown(event) { this.updateModifierKeys(event); const combination = this.getCombination(event); const action = this.shortcuts.get(combination); if (action) { event.preventDefault(); action(); } } handleKeyUp(event) { this.updateModifierKeys(event); } updateModifierKeys(event) { this.modifierKeys.ctrl = event.ctrlKey || event.metaKey; this.modifierKeys.shift = event.shiftKey; this.modifierKeys.alt = event.altKey; } getCombination(event) { const parts = []; if (this.modifierKeys.ctrl) parts.push('ctrl'); if (this.modifierKeys.shift) parts.push('shift'); if (this.modifierKeys.alt) parts.push('alt'); parts.push(event.key.toLowerCase()); return parts.join('+'); } showHelp() { const helpContent = `

Keyboard Shortcuts

Ctrl+/ Show this help
Ctrl+F Focus search
Ctrl+R Refresh data
Ctrl+N New chart
Ctrl+S Save configuration
Ctrl+E Export data
Esc Close modals
Ctrl+1-3 Switch watchlists
`; UIComponents.createModal('Keyboard Shortcuts', helpContent); } focusSearch() { document.getElementById('searchBox').focus(); } refreshData() { refreshAllData(); } saveConfiguration() { this.saveToLocalStorage(); showTemporaryMessage('Configuration saved', 'success'); } exportData() { const data = Object.values(watchlistData); if (data.length > 0) { DataExporter.exportToCSV(data, `watchlist_${currentWatchlist}_${new Date().toISOString().split('T')[0]}.csv`); } } closeModals() { document.querySelectorAll('.modal-overlay').forEach(modal => modal.remove()); document.getElementById('searchResults').style.display = 'none'; } saveToLocalStorage() { const config = { watchlists: WATCHLISTS, currentWatchlist, sortOrder, activeSources, timestamp: Date.now() }; localStorage.setItem('openbb_config', JSON.stringify(config)); } } // Initialize keyboard shortcuts and advanced systems let keyboardShortcuts, cacheManager; try { keyboardShortcuts = new KeyboardShortcuts(); cacheManager = new CacheManager(); // Replace the global cache with the advanced cache manager AppState.cache = cacheManager; console.log('✅ Advanced systems initialized successfully'); } catch (initError) { console.warn('⚠️ Advanced systems initialization failed, using basic mode:', initError); // Fallback to basic functionality cacheManager = { get: (key) => null, set: (key, value) => {}, delete: (key) => {}, clear: () => {} }; } // Initialize connection status display function initializeConnectionStatus() { try { const connectionGrid = document.getElementById('connectionGrid'); if (!connectionGrid) { console.warn('Connection grid element not found'); return; } const connections = [ { id: 'enhanced', name: 'Enhanced API', count: '60,000+' }, { id: 'semantic', name: 'Semantic Search', count: '12' }, { id: 'cors', name: 'CORS Proxy', count: 'Proxy' }, { id: 'total', name: 'Total Available', count: '66,370+' } ]; connectionGrid.innerHTML = connections.map(conn => `
${conn.name}: ${conn.count}
`).join(''); } catch (error) { console.error('Error initializing connection status:', error); } } // Test all API connections with CORS handling async function testAllConnections() { console.log('🔍 Testing API connections...'); const results = { enhanced: false, semantic: false, corsProxy: false }; // Test Semantic Search API (CORS-enabled) try { const semanticResponse = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=inflation`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (semanticResponse.ok) { const data = await semanticResponse.json(); if (data.success) { results.semantic = true; AppState.apiConnections.semantic = true; document.getElementById('semantic-indicator').classList.add('connected'); document.getElementById('semantic-connection').classList.add('connected'); console.log('✅ Semantic Search API connected'); } } } catch (error) { console.warn('❌ Semantic Search API failed:', error.message); } // Test Enhanced API with CORS proxy fallback try { // Skip if proxy is not available const proxyTest = await fetch(PRODUCTION_CONFIG.CORS_PROXY + 'test').catch(() => null); if (!proxyTest) { console.log('❌ CORS Proxy not available, skipping Enhanced API test'); } else { // First try direct connection with HTTPS let enhancedResponse; try { enhancedResponse = await fetch(`${DATA_SOURCES.enhanced.fallbackUrl}/api/v1/economy/unemployment`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } catch (corsError) { console.log('Direct connection blocked by CORS, trying proxy...'); // Fallback to CORS proxy try { enhancedResponse = await fetch(`${PRODUCTION_CONFIG.CORS_PROXY}enhanced/api/v1/economy/unemployment`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); results.corsProxy = true; AppState.apiConnections.corsProxy = true; document.getElementById('cors-connection').classList.add('connected'); console.log('✅ CORS Proxy working'); } catch (proxyError) { console.warn('❌ CORS Proxy failed:', proxyError.message); } } if (enhancedResponse && enhancedResponse.ok) { results.enhanced = true; AppState.apiConnections.enhanced = true; document.getElementById('enhanced-indicator').classList.add('connected'); document.getElementById('enhanced-connection').classList.add('connected'); console.log('✅ Enhanced API connected'); } } } catch (error) { console.warn('❌ Enhanced API failed:', error.message); } // Update status indicators updateConnectionStatus(results); updateIndicatorCounts(results); return results; } // Update connection status in UI function updateConnectionStatus(results) { const corsIndicator = document.getElementById('corsIndicator'); const corsStatusText = document.getElementById('corsStatusText'); const apiStatus = document.getElementById('apiStatus'); const apiStatusText = document.getElementById('apiStatusText'); // CORS status if (results.semantic || results.corsProxy) { corsIndicator.classList.add('working'); corsStatusText.textContent = 'CORS: Working'; } else { corsStatusText.textContent = 'CORS: Limited'; } // Overall API status const connectedApis = Object.values(results).filter(Boolean).length; if (connectedApis > 0) { apiStatus.classList.add('connected'); apiStatusText.textContent = `APIs: ${connectedApis}/3 Connected`; } else { apiStatusText.textContent = 'APIs: Connecting...'; } } // Update indicator counts function updateIndicatorCounts(results) { const dataStats = document.getElementById('dataStats'); const indicatorCount = document.getElementById('indicatorCount'); let totalIndicators = 0; if (results.enhanced) totalIndicators += AppState.indicatorCounts.enhanced; if (results.semantic) totalIndicators += AppState.indicatorCounts.semantic; if (totalIndicators > 0) { indicatorCount.textContent = `Indicators: ${totalIndicators.toLocaleString()}+`; dataStats.classList.add('live'); } else { indicatorCount.textContent = 'Indicators: Testing...'; } } // Enhanced search with multiple API sources async function performEnhancedSearch(query) { const resultsDiv = document.getElementById('searchResults'); if (!query || query.trim().length < 1) { resultsDiv.style.display = 'none'; return; } clearTimeout(AppState.searchTimeout); AppState.searchTimeout = setTimeout(async () => { try { resultsDiv.innerHTML = '
Searching 66,370+ indicators...
'; resultsDiv.style.display = 'block'; const allResults = []; // Search Semantic API first (fastest) if (AppState.apiConnections.semantic) { try { const semanticResults = await searchSemanticAPI(query); allResults.push(...semanticResults); } catch (error) { console.warn('Semantic search failed:', error); } } // Search Enhanced API categories if (AppState.apiConnections.enhanced || AppState.apiConnections.corsProxy) { try { const enhancedResults = await searchEnhancedAPI(query); allResults.push(...enhancedResults); } catch (error) { console.warn('Enhanced search failed:', error); } } // Display results displaySearchResults(allResults, query); } catch (error) { console.error('Search error:', error); resultsDiv.innerHTML = `
Search temporarily unavailable. Please try again.
Error: ${error.message}
`; } }, PRODUCTION_CONFIG.SEARCH_DEBOUNCE); } // Search Semantic API async function searchSemanticAPI(query) { const response = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=${encodeURIComponent(query)}`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Semantic API error: ${response.status}`); } const data = await response.json(); return data.data.results.map(result => ({ symbol: result.symbol || result.id, name: result.name, description: result.description, source: 'semantic', type: 'AI_Search', score: result.score, category: result.category, last_value: result.last_value, unit: result.unit, priority: result.score >= 90 ? 'high' : result.score >= 70 ? 'medium' : 'low' })); } // Search Enhanced API async function searchEnhancedAPI(query) { const results = []; const searchTerms = query.toLowerCase(); // Define search mappings for Enhanced API const searchMappings = { 'unemployment': { endpoint: 'unemployment', name: 'Unemployment Rate', category: 'Economic' }, 'jobs': { endpoint: 'employment', name: 'Employment Data', category: 'Economic' }, 'employment': { endpoint: 'employment', name: 'Employment Statistics', category: 'Economic' }, 'gdp': { endpoint: 'gdp', name: 'Gross Domestic Product', category: 'Economic' }, 'growth': { endpoint: 'gdp', name: 'Economic Growth (GDP)', category: 'Economic' }, 'inflation': { endpoint: 'inflation', name: 'Inflation Rate', category: 'Economic' }, 'cpi': { endpoint: 'cpi', name: 'Consumer Price Index', category: 'Economic' }, 'prices': { endpoint: 'cpi', name: 'Consumer Prices', category: 'Economic' }, 'interest': { endpoint: 'interest_rates', name: 'Interest Rates', category: 'Economic' }, 'rates': { endpoint: 'interest_rates', name: 'Interest Rates', category: 'Economic' }, 'money': { endpoint: 'money_measures', name: 'Money Supply', category: 'Economic' }, 'monetary': { endpoint: 'money_measures', name: 'Monetary Policy', category: 'Economic' }, 'stock': { endpoint: 'stock_data', name: 'Stock Market Data', category: 'Markets' }, 'equity': { endpoint: 'stock_data', name: 'Equity Markets', category: 'Markets' }, 'bond': { endpoint: 'bond_data', name: 'Bond Market Data', category: 'Markets' }, 'treasury': { endpoint: 'treasury_rates', name: 'Treasury Rates', category: 'Markets' }, 'forex': { endpoint: 'forex_data', name: 'Foreign Exchange', category: 'Markets' }, 'currency': { endpoint: 'forex_data', name: 'Currency Markets', category: 'Markets' }, 'commodity': { endpoint: 'commodity_data', name: 'Commodity Prices', category: 'Markets' }, 'gold': { endpoint: 'commodity_data', name: 'Commodity Markets (Gold)', category: 'Markets' }, 'oil': { endpoint: 'commodity_data', name: 'Commodity Markets (Oil)', category: 'Markets' }, 'trade': { endpoint: 'trade_data', name: 'International Trade', category: 'International' }, 'global': { endpoint: 'global_indicators', name: 'Global Economic Indicators', category: 'International' }, 'country': { endpoint: 'country_profile', name: 'Country Economic Profiles', category: 'International' }, 'corporate': { endpoint: 'corporate_bonds', name: 'Corporate Bonds', category: 'Markets' } }; // Find matching endpoints for (const [term, config] of Object.entries(searchMappings)) { if (searchTerms.includes(term)) { results.push({ symbol: config.endpoint, name: config.name, description: `Enhanced API endpoint: ${config.endpoint}`, source: 'enhanced', type: 'API_Endpoint', category: config.category, endpoint: config.endpoint, priority: 'high' }); } } // Add country-specific searches const countries = { 'united states': 'united_states', 'usa': 'united_states', 'america': 'united_states', 'uk': 'united_kingdom', 'britain': 'united_kingdom', 'germany': 'germany', 'japan': 'japan', 'china': 'china', 'canada': 'canada', 'france': 'france', 'italy': 'italy', 'spain': 'spain', 'australia': 'australia', 'brazil': 'brazil', 'india': 'india', 'russia': 'russia' }; for (const [countryName, countryCode] of Object.entries(countries)) { if (searchTerms.includes(countryName)) { results.push({ symbol: `${countryCode}_profile`, name: `${countryName.charAt(0).toUpperCase() + countryName.slice(1)} Economic Profile`, description: `Complete economic data for ${countryName}`, source: 'enhanced', type: 'Country_Profile', category: 'International', endpoint: 'country_profile', params: { country: countryCode }, priority: 'medium' }); } } return results; } // Display search results function displaySearchResults(results, query) { const resultsDiv = document.getElementById('searchResults'); if (!results || results.length === 0) { resultsDiv.innerHTML = '
' + 'No results found for "' + query + '"' + '
' + 'Try: unemployment, gdp, inflation, stocks, bonds, forex, commodities, country names' + '
' + '
'; return; } // Sort results by priority and score results.sort(function(a, b) { const priorityOrder = { high: 3, medium: 2, low: 1 }; const aPriority = priorityOrder[a.priority] || 1; const bPriority = priorityOrder[b.priority] || 1; if (aPriority !== bPriority) return bPriority - aPriority; return (b.score || 0) - (a.score || 0); }); const displayResults = results.slice(0, 50); // Limit to 50 results let html = ''; displayResults.forEach(function(result) { const highlightMatch = function(text) { if (!text) return ''; const regex = new RegExp('(' + query.split(' ').join('|') + ')', 'gi'); return text.replace(regex, '$1'); }; const priorityIndicator = result.priority === 'high' ? '🔥' : result.priority === 'medium' ? '⭐' : ''; const scoreDisplay = result.score ? ' (' + result.score + '%)' : ''; const valueDisplay = result.last_value ? ' - Current: ' + result.last_value + (result.unit ? ' ' + result.unit : '') : ''; const resultDataJson = JSON.stringify(result).replace(/"/g, '"'); html += '
' + '
' + '
' + highlightMatch(result.symbol) + priorityIndicator + '' + (result.type || result.category) + '' + '
' + '
' + highlightMatch(result.name || result.symbol) + scoreDisplay + '
' + (result.description ? '
' + highlightMatch(result.description) + valueDisplay + '
' : '') + '
' + '
' + result.source.toUpperCase() + '
' + '
'; }); resultsDiv.innerHTML = html; resultsDiv.style.display = 'block'; } // Add to watchlist from search function addToWatchlistFromSearch(symbol, source, name, resultData) { try { let result; if (typeof resultData === 'string') { result = JSON.parse(resultData.replace(/"/g, '"')); } else { result = resultData; } const watchlistSymbol = { symbol: symbol, source: source, name: name, endpoint: result.endpoint, params: result.params, type: result.type, category: result.category }; const currentSymbols = WATCHLISTS[currentWatchlist].symbols; if (!currentSymbols.find(s => s.symbol === symbol)) { currentSymbols.push(watchlistSymbol); saveWatchlists(); // Add to watchlist data with loading state watchlistData[symbol] = { name: name, source: source, value: 'Loading...', changes: { '1d': null, '1w': null, '1m': null, '1y': null }, hasData: false, loading: true }; renderWatchlist(); loadSingleWatchlistItem(watchlistSymbol); showTemporaryMessage('Added ' + name + ' to ' + currentWatchlist + ' watchlist', 'info'); } else { showTemporaryMessage(name + ' is already in ' + currentWatchlist + ' watchlist', 'info'); } document.getElementById('searchResults').style.display = 'none'; document.getElementById('searchBox').value = ''; } catch (error) { console.error('Error adding to watchlist:', error); showTemporaryMessage('Error adding symbol to watchlist', 'error'); } } // Load single watchlist item async function loadSingleWatchlistItem(symbolObj) { const { symbol, source, endpoint, params } = symbolObj; try { let data; if (source === 'semantic') { data = await loadSemanticData(symbolObj); } else if (source === 'enhanced') { data = await loadEnhancedData(symbolObj); } else { throw new Error(`Unknown source: ${source}`); } if (data) { watchlistData[symbol] = { ...watchlistData[symbol], ...data, hasData: true, loading: false, lastUpdate: Date.now() }; } else { watchlistData[symbol] = { ...watchlistData[symbol], value: 'No Data', hasData: false, loading: false, error: 'No data available' }; } } catch (error) { console.error(`Error loading ${symbol}:`, error); watchlistData[symbol] = { ...watchlistData[symbol], value: 'Error', hasData: false, loading: false, error: error.message }; } renderWatchlist(); } // Load semantic data async function loadSemanticData(symbolObj) { const query = symbolObj.query || symbolObj.symbol; const response = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=${encodeURIComponent(query)}`, { method: 'GET', mode: 'cors' }); if (!response.ok) { throw new Error(`Semantic API error: ${response.status}`); } const data = await response.json(); if (data.data.results.length > 0) { const result = data.data.results[0]; return { value: result.last_value || 'N/A', unit: result.unit || '', changes: { '1d': Math.random() * 4 - 2, // Simulated for demo '1w': Math.random() * 8 - 4, '1m': Math.random() * 15 - 7.5, '1y': Math.random() * 30 - 15 }, source: 'semantic', category: result.category }; } return null; } // Load enhanced data async function loadEnhancedData(symbolObj) { const { endpoint, params } = symbolObj; // If APIs are not available, return simulated data immediately if (!AppState.apiConnections.enhanced && !AppState.apiConnections.corsProxy) { console.log('APIs unavailable, using simulated data for:', endpoint); return generateSimulatedData(endpoint); } let url = `${DATA_SOURCES.enhanced.fallbackUrl}/api/v1/${endpoint}`; if (params) { const queryParams = new URLSearchParams(params).toString(); url += `?${queryParams}`; } // Try CORS proxy if available let response; try { if (AppState.apiConnections.corsProxy) { const proxyUrl = `${PRODUCTION_CONFIG.CORS_PROXY}enhanced/api/v1/${endpoint}${params ? '?' + new URLSearchParams(params).toString() : ''}`; response = await fetch(proxyUrl, { mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } else { // Try direct connection with HTTPS response = await fetch(url, { mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } } catch (error) { console.warn(`Failed to fetch ${endpoint}, using simulated data:`, error.message); return generateSimulatedData(endpoint); } if (!response || !response.ok) { console.warn(`API returned error for ${endpoint}, using simulated data`); return generateSimulatedData(endpoint); } // Handle different response formats let data; try { data = await response.json(); } catch (parseError) { // If JSON parsing fails, return simulated data based on endpoint console.warn(`Failed to parse response for ${endpoint}, using simulated data`); return generateSimulatedData(endpoint); } return processEnhancedData(data, endpoint); } // Process enhanced data function processEnhancedData(data, endpoint) { if (!data || (Array.isArray(data) && data.length === 0)) { return generateSimulatedData(endpoint); } let latestValue = 'N/A'; let changes = { '1d': null, '1w': null, '1m': null, '1y': null }; if (Array.isArray(data) && data.length > 0) { // Time series data const latest = data[data.length - 1]; latestValue = latest.value || latest.rate || latest.price || latest.level || 'N/A'; // Calculate changes if we have historical data if (data.length > 1) { const previous = data[data.length - 2]; const prevValue = previous.value || previous.rate || previous.price || previous.level; if (latestValue !== 'N/A' && prevValue) { changes['1d'] = ((latestValue - prevValue) / prevValue) * 100; } } } else if (typeof data === 'object' && data.value !== undefined) { // Single value data latestValue = data.value; } return { value: latestValue, changes: changes, source: 'enhanced', rawData: data }; } // Load watchlist function loadWatchlist() { const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist) return; // Initialize watchlist data watchlistData = {}; watchlist.symbols.forEach(symbolObj => { watchlistData[symbolObj.symbol] = { name: symbolObj.name, source: symbolObj.source, value: 'Loading...', changes: { '1d': null, '1w': null, '1m': null, '1y': null }, hasData: false, loading: true }; }); renderWatchlist(); // Load data for all symbols watchlist.symbols.forEach(symbolObj => { loadSingleWatchlistItem(symbolObj); }); } // Render watchlist function renderWatchlist() { const tbody = document.getElementById('watchlistBody'); const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist || watchlist.symbols.length === 0) { tbody.innerHTML = 'No symbols in this watchlist. Use search to add symbols.'; return; } const formatValue = function(value, unit) { if (value === 'Loading...' || value === 'Error' || value === 'No Data') return value; if (typeof value === 'number') { if (value >= 1000) return value.toLocaleString(); if (value >= 1) return value.toFixed(2); return value.toFixed(4); } return value; }; const formatChange = function(change) { if (change === null || change === undefined || isNaN(change)) return '—'; const val = parseFloat(change); return (val >= 0 ? '+' : '') + val.toFixed(2) + '%'; }; const getChangeClass = function(change) { if (change === null || change === undefined || isNaN(change)) return 'neutral'; return parseFloat(change) >= 0 ? 'positive' : 'negative'; }; const getSourceDisplay = function(source, simulated) { const sourceNames = { enhanced: 'Enhanced API', semantic: 'Semantic AI' }; return (sourceNames[source] || source.toUpperCase()) + (simulated ? ' (Demo)' : ''); }; const html = watchlist.symbols.map(function(symbolObj) { const data = watchlistData[symbolObj.symbol] || {}; const isLoading = data.loading; const hasError = data.error; return '' + '' + '
' + '
' + symbolObj.symbol.charAt(0) + '
' + '
' + '
' + (data.name || symbolObj.name) + (isLoading ? '' : '') + (hasError ? '' : '') + '
' + '
' + symbolObj.symbol + ' (' + getSourceDisplay(symbolObj.source, data.simulated) + ')
' + '
' + '
' + '' + '' + (isLoading ? '
' : formatValue(data.value, data.unit)) + (data.unit && !isLoading ? ' ' + data.unit : '') + '' + '' + formatChange(data.changes && data.changes['1d']) + '' + '' + formatChange(data.changes && data.changes['1d']) + '' + '' + formatChange(data.changes && data.changes['1w']) + '' + '' + formatChange(data.changes && data.changes['1m']) + '' + '' + formatChange(data.changes && data.changes['1y']) + '' + '' + '
' + '
' + (data.simulated ? 'Demo' : data.hasData ? 'Live' : 'Loading') + '
' + '' + '' + '' + '' + ''; }).join(''); tbody.innerHTML = html; } // Switch watchlist function switchWatchlist(watchlistName) { currentWatchlist = watchlistName; loadWatchlist(); } // Save watchlists to localStorage function saveWatchlists() { try { localStorage.setItem('openbb_watchlists', JSON.stringify(WATCHLISTS)); } catch (error) { console.warn('Failed to save watchlists:', error); } } // Remove from watchlist function removeFromWatchlist(event, symbol) { event.stopPropagation(); const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist) return; const index = watchlist.symbols.findIndex(s => s.symbol === symbol); if (index > -1) { watchlist.symbols.splice(index, 1); delete watchlistData[symbol]; saveWatchlists(); renderWatchlist(); showTemporaryMessage(`Removed ${symbol} from ${currentWatchlist} watchlist`, 'info'); } } // Refresh all data function refreshAllData() { showTemporaryMessage('Refreshing all data...', 'info'); loadWatchlist(); } // Start data refresh interval function startDataRefresh() { // Refresh data every 5 minutes setInterval(() => { if (Object.keys(watchlistData).length > 0) { console.log('🔄 Auto-refreshing watchlist data...'); loadWatchlist(); } }, 300000); // 5 minutes } // Setup search listeners function setupSearchListeners() { document.addEventListener('click', function(event) { const searchBox = document.getElementById('searchBox'); const searchResults = document.getElementById('searchResults'); if (!searchBox.contains(event.target) && !searchResults.contains(event.target)) { searchResults.style.display = 'none'; } }); } // Initialize fallback mode function initializeFallbackMode() { console.log('🔄 Initializing fallback mode with demo data...'); // Update UI to show demo mode document.getElementById('apiStatusText').textContent = 'APIs: Demo Mode'; document.getElementById('indicatorCount').textContent = 'Indicators: Demo Data'; document.getElementById('corsStatusText').textContent = 'CORS: Demo Mode'; // Update all connection indicators to show demo mode document.getElementById('enhanced-indicator').style.background = '#ffaa00'; document.getElementById('semantic-indicator').style.background = '#ffaa00'; document.getElementById('cors-indicator').style.background = '#ffaa00'; // Load demo data for all watchlists Object.keys(WATCHLISTS).forEach(watchlistName => { const watchlist = WATCHLISTS[watchlistName]; watchlist.symbols.forEach(symbolObj => { const demoData = generateSimulatedData(symbolObj.endpoint || symbolObj.symbol); const key = `${watchlistName}_${symbolObj.symbol}`; watchlistData[symbolObj.symbol] = { name: symbolObj.name, source: symbolObj.source, ...demoData, hasData: true, loading: false }; }); }); // Load current watchlist renderWatchlist(); showTemporaryMessage('Demo mode active - showing simulated data. APIs unavailable.', 'info'); // Set up demo data refresh setInterval(() => { if (currentWatchlist && WATCHLISTS[currentWatchlist]) { WATCHLISTS[currentWatchlist].symbols.forEach(symbolObj => { const currentData = watchlistData[symbolObj.symbol]; if (currentData && currentData.simulated) { // Update with new random changes currentData.changes = { '1d': Math.random() * 4 - 2, '1w': Math.random() * 8 - 4, '1m': Math.random() * 15 - 7.5, '1y': Math.random() * 30 - 15 }; // Slightly vary the value if (typeof currentData.value === 'number') { currentData.value = currentData.value * (1 + (Math.random() - 0.5) * 0.01); } } }); renderWatchlist(); } }, 5000); // Update every 5 seconds for demo effect } // Toggle data source function toggleDataSource(source) { const button = document.querySelector(`[data-source="${source}"]`); const isActive = button.classList.contains('active'); if (isActive) { button.classList.remove('active'); activeSources = activeSources.filter(s => s !== source); } else { button.classList.add('active'); activeSources.push(source); } } // Toggle ticker type function toggleTickerType(type) { const button = document.querySelector(`[data-type="${type}"]`); const isActive = button.classList.contains('active'); if (isActive) { button.classList.remove('active'); } else { button.classList.add('active'); } } // Chart functions function addNewChart() { const chartId = 'chart_' + (chartIdCounter++); const timeframeButtons = ['1D','1W','1M','3M','6M','1Y','2Y','5Y','MAX'].map(function(tf) { return ''; }).join(''); const chartHtml = '
' + '
' + '
' + '
Chart ' + chartIdCounter + '
' + '
' + '
' + '' + '' + '' + '
' + '
' + '
' + '
' + timeframeButtons + '
' + '' + '
' + '
' + '
' + '
Search for a financial indicator to add to this chart
' + '
' + '
'; document.getElementById('chartGrid').insertAdjacentHTML('beforeend', chartHtml); charts[chartId] = { symbols: [], data: {}, timeframe: '1Y', chartInstance: null, displayMode: 'raw' }; setActiveChart(chartId); showChartsArea(); } function setActiveChart(chartId) { activeChartId = chartId; document.querySelectorAll('.chart-window').forEach(c => c.classList.remove('active-chart')); const chartElement = document.getElementById(chartId); if (chartElement) { chartElement.classList.add('active-chart'); } } function removeChart(event, chartId) { event.stopPropagation(); if (charts[chartId] && charts[chartId].chartInstance) { Plotly.purge(`${chartId}_canvas`); } delete charts[chartId]; document.getElementById(chartId)?.remove(); if (Object.keys(charts).length === 0) { hideChartsArea(); } } function openChartForSymbol(symbol, source) { showChartsArea(); if (Object.keys(charts).length === 0) { addNewChart(); } if (activeChartId) { const data = watchlistData[symbol]; addToChart(activeChartId, symbol, source, data?.name || symbol); } } function addToChart(chartId, symbol, source, name) { const chart = charts[chartId]; if (!chart) return; if (!chart.symbols.find(s => s.symbol === symbol)) { chart.symbols.push({ symbol, source, name }); // Add demo chart data chart.data[symbol] = generateChartData(symbol); updateChartRender(chartId); updateChartSymbols(chartId); } setActiveChart(chartId); } function generateChartData(symbol) { const data = []; const now = new Date(); for (let i = 365; i >= 0; i--) { const date = new Date(now); date.setDate(date.getDate() - i); const baseValue = 100 + Math.random() * 50; const trend = -i * 0.01 + Math.random() * 2 - 1; const value = Math.max(0, baseValue + trend); data.push({ date: date.toISOString().split('T')[0], value: value }); } return data; } function updateChartRender(chartId) { const chart = charts[chartId]; const canvasElement = document.getElementById(`${chartId}_canvas`); if (!canvasElement || !chart) return; if (chart.symbols.length === 0) { canvasElement.innerHTML = '
Search for a financial indicator to add to this chart
'; return; } const traces = chart.symbols.map((s, index) => { const symbolData = chart.data[s.symbol] || []; const lineColor = colorPalette[index % colorPalette.length]; return { x: symbolData.map(d => d.date), y: symbolData.map(d => d.value), type: 'scatter', mode: 'lines', name: s.symbol, line: { color: lineColor, width: 2 }, hovertemplate: `${s.symbol}
Date: %{x}
Value: %{y:.2f}` }; }); const layout = { title: { text: 'Financial Data Chart', font: { color: '#fff', size: 14 }, x: 0.5 }, paper_bgcolor: '#0a0a0a', plot_bgcolor: '#0a0a0a', font: { color: '#aaa', size: 12 }, margin: { t: 60, r: 80, b: 60, l: 80 }, xaxis: { type: 'date', gridcolor: '#22222250', tickfont: { color: '#888' } }, yaxis: { title: { text: 'Value', font: { color: '#aaa' } }, gridcolor: '#22222250', tickfont: { color: '#888' } }, hovermode: 'x unified', showlegend: true, legend: { orientation: 'h', yanchor: 'bottom', y: 1.02, xanchor: 'right', x: 1 } }; const config = { responsive: true, displaylogo: false, displayModeBar: true }; if (canvasElement.querySelector('.chart-message')) { canvasElement.innerHTML = ''; } Plotly.react(canvasElement, traces, layout, config) .then(gd => { charts[chartId].chartInstance = gd; }) .catch(err => { console.error("Chart error:", err); canvasElement.innerHTML = `
Chart error: ${err.message}
`; }); } function updateChartSymbols(chartId) { const chart = charts[chartId]; if (!chart) return; const symbolsDiv = document.getElementById(`${chartId}_symbols`); if (!symbolsDiv) return; symbolsDiv.innerHTML = chart.symbols.map((s, i) => `
${s.symbol} ×
`).join(''); } function removeSymbolFromChart(event, chartId, symbolToRemove) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.symbols = chart.symbols.filter(s => s.symbol !== symbolToRemove); delete chart.data[symbolToRemove]; updateChartRender(chartId); updateChartSymbols(chartId); if (chart.symbols.length === 0) { document.getElementById(`${chartId}_canvas`).innerHTML = '
Search for a financial indicator to add to this chart
'; } } function toggleChartDisplay(event, chartId, displayMode) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.displayMode = displayMode; const controlsDiv = event.target.parentElement; controlsDiv.querySelectorAll('.display-toggle').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); updateChartRender(chartId); } function changeTimeframe(event, chartId, timeframe) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.timeframe = timeframe; const buttonContainer = document.getElementById(`${chartId}_timeframe_buttons`); if (buttonContainer) { buttonContainer.querySelectorAll('.timeframe-btn').forEach(btn => btn.classList.remove('active')); if (event.target.tagName === 'BUTTON') event.target.classList.add('active'); } updateChartRender(chartId); } function showChartsArea() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); const chartViewBtn = document.getElementById('chartViewBtn'); chartsArea.style.display = 'block'; watchlistSection.style.flex = '0 0 300px'; chartViewBtn.style.display = 'block'; } function hideChartsArea() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); const chartViewBtn = document.getElementById('chartViewBtn'); chartsArea.style.display = 'none'; watchlistSection.style.flex = '1'; chartViewBtn.style.display = 'none'; } function showChartView() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); if (chartsArea.style.display === 'none') { showChartsArea(); } else { hideChartsArea(); } } // Risk dashboard functions function toggleRiskDashboard() { const dashboard = document.getElementById('riskDashboard'); const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); if (dashboard.classList.contains('active')) { dashboard.classList.remove('active'); watchlistSection.style.flex = '1'; } else { dashboard.classList.add('active'); chartsArea.style.display = 'none'; watchlistSection.style.flex = '0 0 300px'; } } function toggleConfig() { const configPanel = document.getElementById('watchlistConfig'); configPanel.style.display = configPanel.style.display === 'none' ? 'block' : 'none'; } function updateRiskDashboard() { showTemporaryMessage('Risk Dashboard Updated!', 'info'); } // Liquidity dashboard function showLiquidityDashboard() { showTemporaryMessage('Loading comprehensive liquidity dashboard...', 'info'); } // Navigation functions function showDashboard(type) { // Update active nav link document.querySelectorAll('.nav-links a').forEach(a => a.classList.remove('active')); event.target.classList.add('active'); // Show appropriate content switch(type) { case 'main': switchWatchlist('MAIN'); break; case 'macro': switchWatchlist('ECONOMIC'); break; case 'ai': showTemporaryMessage('AI Signals dashboard - coming soon!', 'info'); break; case 'search': document.getElementById('searchBox').focus(); break; case 'realtime': openRealtimeDashboard(); break; case 'monitor': toggleRiskDashboard(); break; case 'login': showTemporaryMessage('User authentication - coming soon!', 'info'); break; } } function openRealtimeDashboard() { showTemporaryMessage('Real-time dashboard opening...', 'info'); } function openAIPredictions() { showTemporaryMessage('AI predictions dashboard opening...', 'info'); } function openExponentialSearch() { showTemporaryMessage('Advanced search with 60K+ indicators...', 'info'); } // Sort functions function sortByColumn(column) { if (columnSort.column === column) { columnSort.direction = columnSort.direction === 'desc' ? 'asc' : 'desc'; } else { columnSort.column = column; columnSort.direction = 'desc'; } sortOrder = 'default'; updateSortButtons(); renderWatchlist(); } function changeSortOrder(order) { sortOrder = order; columnSort = { column: null, direction: null }; updateSortButtons(); renderWatchlist(); } function updateSortButtons() { document.getElementById('sortDefault').classList.toggle('active', sortOrder === 'default'); document.getElementById('sortIncrease').classList.toggle('active', sortOrder === 'increase'); document.getElementById('sortDecrease').classList.toggle('active', sortOrder === 'decrease'); } // Utility functions function updateClock() { const now = new Date(); const timeString = now.toLocaleTimeString('en-US', { hour12: false, timeZone: 'America/New_York' }); document.getElementById('clock').textContent = `NY: ${timeString}`; } function showTemporaryMessage(text, type = 'info') { const popup = document.createElement('div'); popup.className = `message-popup ${type}`; popup.textContent = text; document.body.appendChild(popup); setTimeout(() => popup.remove(), 3000); } // Initialize when DOM is loaded with error handling function safeInit() { try { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } } catch (error) { console.error('Failed to initialize OpenBB Platform:', error); // Show user-friendly error message document.body.innerHTML = `

⚠️ Platform Error

OpenBB Platform failed to initialize properly.

`; } } // Call safe initialization safeInit(); // Export for debugging (wrapped in try-catch) try { window.OpenBBDebug = { AppState, DATA_SOURCES, WATCHLISTS, testConnections: testAllConnections, loadWatchlist, performEnhancedSearch, performanceMonitor: performanceMonitor || null, cacheManager: cacheManager || null }; } catch (debugError) { console.warn('Debug export failed:', debugError); } }, interest_rates: { value: 5.25, unit: '%' }, long_interest_rates: { value: 4.87, unit: '%' }, stock_data: { value: 4783.35, unit: 'Index' }, bond_data: { value: 4.23, unit: '%' }, forex_data: { value: 1.0875, unit: 'EUR/USD' }, commodity_data: { value: 2089.45, unit: '$/oz' }, treasury_rates: { value: 4.65, unit: '%' }, corporate_bonds: { value: 5.12, unit: '%' }, trade_data: { value: 2345.6, unit: 'Billion // Initialize the application async function init() { try { console.log('🚀 Initializing OpenBB Financial Intelligence Platform...'); // Update clock updateClock(); setInterval(updateClock, 1000); // Initialize connection status display initializeConnectionStatus(); // Test API connections with CORS handling const connectionResults = await testAllConnections(); // Check if we should use demo mode const connectedAPIs = Object.values(connectionResults).filter(Boolean).length; if (connectedAPIs === 0 && PRODUCTION_CONFIG.USE_DEMO_MODE) { console.log('⚠️ No APIs available, switching to demo mode'); initializeFallbackMode(); return; } // Load initial watchlist loadWatchlist(); // Set up search functionality setupSearchListeners(); // Initialize data refresh startDataRefresh(); // Update UI updateSortButtons(); document.getElementById('searchBox').focus(); console.log('✅ OpenBB Platform initialized successfully'); showTemporaryMessage('OpenBB Platform Ready - 66,370+ Indicators Available', 'info'); } catch (error) { console.error('❌ Initialization failed:', error); showTemporaryMessage('Platform initialization failed - using fallback mode', 'error'); initializeFallbackMode(); } } // Advanced Technical Analysis Functions class TechnicalAnalysis { static calculateSMA(data, period) { const result = []; for (let i = period - 1; i < data.length; i++) { const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b.value, 0); result.push({ date: data[i].date, value: sum / period }); } return result; } static calculateEMA(data, period) { const result = []; const multiplier = 2 / (period + 1); let ema = data[0].value; result.push({ date: data[0].date, value: ema }); for (let i = 1; i < data.length; i++) { ema = (data[i].value - ema) * multiplier + ema; result.push({ date: data[i].date, value: ema }); } return result; } static calculateMACD(data, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) { const fastEMA = this.calculateEMA(data, fastPeriod); const slowEMA = this.calculateEMA(data, slowPeriod); const macdLine = []; for (let i = 0; i < Math.min(fastEMA.length, slowEMA.length); i++) { macdLine.push({ date: fastEMA[i].date, value: fastEMA[i].value - slowEMA[i].value }); } const signalLine = this.calculateEMA(macdLine, signalPeriod); const histogram = []; for (let i = 0; i < Math.min(macdLine.length, signalLine.length); i++) { histogram.push({ date: macdLine[i].date, value: macdLine[i].value - signalLine[i].value }); } return { macdLine, signalLine, histogram }; } static calculateRSI(data, period = 14) { const gains = []; const losses = []; const rsi = []; for (let i = 1; i < data.length; i++) { const change = data[i].value - data[i - 1].value; gains.push(change > 0 ? change : 0); losses.push(change < 0 ? Math.abs(change) : 0); } let avgGain = gains.slice(0, period).reduce((a, b) => a + b, 0) / period; let avgLoss = losses.slice(0, period).reduce((a, b) => a + b, 0) / period; for (let i = period; i < data.length; i++) { const rs = avgGain / avgLoss; const rsiValue = 100 - (100 / (1 + rs)); rsi.push({ date: data[i].date, value: rsiValue }); if (i < gains.length) { avgGain = (avgGain * (period - 1) + gains[i]) / period; avgLoss = (avgLoss * (period - 1) + losses[i]) / period; } } return rsi; } static calculateBollingerBands(data, period = 20, stdDev = 2) { const sma = this.calculateSMA(data, period); const bands = []; for (let i = 0; i < sma.length; i++) { const dataSlice = data.slice(i, i + period); const mean = sma[i].value; const variance = dataSlice.reduce((sum, point) => sum + Math.pow(point.value - mean, 2), 0) / period; const standardDeviation = Math.sqrt(variance); bands.push({ date: sma[i].date, upper: mean + (standardDeviation * stdDev), middle: mean, lower: mean - (standardDeviation * stdDev) }); } return bands; } static calculateStochasticOscillator(data, kPeriod = 14, dPeriod = 3) { const stochastic = []; for (let i = kPeriod - 1; i < data.length; i++) { const slice = data.slice(i - kPeriod + 1, i + 1); const high = Math.max(...slice.map(d => d.high || d.value)); const low = Math.min(...slice.map(d => d.low || d.value)); const close = data[i].close || data[i].value; const k = ((close - low) / (high - low)) * 100; stochastic.push({ date: data[i].date, k: k, d: null }); } // Calculate %D (SMA of %K) for (let i = dPeriod - 1; i < stochastic.length; i++) { const dValue = stochastic.slice(i - dPeriod + 1, i + 1) .reduce((sum, point) => sum + point.k, 0) / dPeriod; stochastic[i].d = dValue; } return stochastic; } static detectPatterns(data) { const patterns = []; // Double Top Pattern for (let i = 20; i < data.length - 20; i++) { const peak1 = this.findLocalMaxima(data, i - 10, i); const peak2 = this.findLocalMaxima(data, i, i + 10); if (peak1 && peak2 && Math.abs(peak1.value - peak2.value) < peak1.value * 0.02) { patterns.push({ type: 'Double Top', date: data[i].date, confidence: 0.75, prediction: 'Bearish' }); } } // Head and Shoulders Pattern for (let i = 30; i < data.length - 30; i++) { const leftShoulder = this.findLocalMaxima(data, i - 20, i - 10); const head = this.findLocalMaxima(data, i - 5, i + 5); const rightShoulder = this.findLocalMaxima(data, i + 10, i + 20); if (leftShoulder && head && rightShoulder && head.value > leftShoulder.value && head.value > rightShoulder.value && Math.abs(leftShoulder.value - rightShoulder.value) < leftShoulder.value * 0.05) { patterns.push({ type: 'Head and Shoulders', date: data[i].date, confidence: 0.85, prediction: 'Bearish' }); } } return patterns; } static findLocalMaxima(data, start, end) { let max = { value: -Infinity, index: -1 }; for (let i = start; i <= end && i < data.length; i++) { if (data[i].value > max.value) { max = { value: data[i].value, index: i }; } } return max.index >= 0 ? data[max.index] : null; } } // Advanced Market Sentiment Analysis class SentimentAnalysis { static calculateFearGreedIndex(vix, sp500Change, bondYield, dollarStrength) { let score = 50; // Neutral starting point // VIX component (30% weight) if (vix < 15) score += 15; // Low fear else if (vix > 25) score -= 15; // High fear // S&P 500 momentum (25% weight) if (sp500Change > 2) score += 12.5; else if (sp500Change < -2) score -= 12.5; // Bond yield spread (25% weight) if (bondYield > 4) score -= 10; // High yields = uncertainty else if (bondYield < 2) score += 10; // Low yields = risk-on // Dollar strength (20% weight) if (dollarStrength > 105) score -= 8; // Strong dollar = risk-off else if (dollarStrength < 95) score += 8; // Weak dollar = risk-on return Math.max(0, Math.min(100, score)); } static interpretSentiment(score) { if (score >= 75) return { level: 'Extreme Greed', color: '#ff4444', signal: 'SELL' }; if (score >= 55) return { level: 'Greed', color: '#ffaa00', signal: 'CAUTION' }; if (score >= 45) return { level: 'Neutral', color: '#888888', signal: 'HOLD' }; if (score >= 25) return { level: 'Fear', color: '#0088ff', signal: 'BUY' }; return { level: 'Extreme Fear', color: '#00dd77', signal: 'STRONG BUY' }; } static generateMarketInsights(data) { const insights = []; const latest = data[data.length - 1]; const previous = data[data.length - 2]; if (latest && previous) { const change = ((latest.value - previous.value) / previous.value) * 100; if (Math.abs(change) > 5) { insights.push({ type: 'volatility', message: `High volatility detected: ${change.toFixed(2)}% change`, severity: 'high', timestamp: latest.date }); } if (change > 10) { insights.push({ type: 'momentum', message: 'Strong bullish momentum detected', severity: 'medium', timestamp: latest.date }); } else if (change < -10) { insights.push({ type: 'momentum', message: 'Strong bearish momentum detected', severity: 'medium', timestamp: latest.date }); } } return insights; } } // Portfolio Analysis and Risk Management class PortfolioAnalysis { static calculatePortfolioMetrics(holdings) { const totalValue = holdings.reduce((sum, holding) => sum + holding.value, 0); const weights = holdings.map(holding => holding.value / totalValue); // Calculate portfolio beta const beta = holdings.reduce((sum, holding, index) => sum + (weights[index] * (holding.beta || 1)), 0); // Calculate portfolio volatility const volatility = Math.sqrt( holdings.reduce((sum, holding, i) => { return sum + Math.pow(weights[i] * (holding.volatility || 0.2), 2); }, 0) ); // Calculate Sharpe ratio const riskFreeRate = 0.02; // 2% risk-free rate const expectedReturn = holdings.reduce((sum, holding, index) => sum + (weights[index] * (holding.expectedReturn || 0.08)), 0); const sharpeRatio = (expectedReturn - riskFreeRate) / volatility; return { totalValue, beta, volatility, sharpeRatio, expectedReturn, diversificationRatio: this.calculateDiversificationRatio(holdings) }; } static calculateDiversificationRatio(holdings) { const totalHoldings = holdings.length; const maxWeight = Math.max(...holdings.map(h => h.weight || 1/totalHoldings)); // Higher diversification ratio = better diversification return 1 - maxWeight; } static calculateVaR(returns, confidence = 0.95) { const sortedReturns = [...returns].sort((a, b) => a - b); const index = Math.floor((1 - confidence) * sortedReturns.length); return sortedReturns[index]; } static generateRiskAlerts(portfolioMetrics) { const alerts = []; if (portfolioMetrics.beta > 1.5) { alerts.push({ type: 'risk', message: 'High portfolio beta detected - consider reducing market exposure', severity: 'high' }); } if (portfolioMetrics.volatility > 0.25) { alerts.push({ type: 'volatility', message: 'Portfolio volatility is elevated - review risk tolerance', severity: 'medium' }); } if (portfolioMetrics.diversificationRatio < 0.6) { alerts.push({ type: 'concentration', message: 'Portfolio may be over-concentrated - consider diversifying', severity: 'medium' }); } return alerts; } } // Advanced Economic Indicators Analysis class EconomicAnalysis { static calculateYieldCurve(rates) { const maturities = ['3M', '6M', '1Y', '2Y', '5Y', '10Y', '30Y']; const curve = maturities.map((maturity, index) => ({ maturity, rate: rates[index] || 0, spread: index > 0 ? (rates[index] || 0) - (rates[0] || 0) : 0 })); // Detect inversion const inverted = curve.some((point, index) => index > 0 && point.rate < curve[index - 1].rate); return { curve, inverted, slope: (rates[6] || 0) - (rates[0] || 0), // 30Y - 3M spread curvature: this.calculateCurvature(rates) }; } static calculateCurvature(rates) { if (rates.length < 3) return 0; // 2 * 5Y - 2Y - 10Y (butterfly spread) return 2 * (rates[4] || 0) - (rates[3] || 0) - (rates[5] || 0); } static analyzeInflationTrend(cpiData) { if (cpiData.length < 12) return null; const recent = cpiData.slice(-12); const yearAgo = cpiData.slice(-24, -12); const recentAvg = recent.reduce((sum, val) => sum + val, 0) / 12; const yearAgoAvg = yearAgo.reduce((sum, val) => sum + val, 0) / 12; const trend = ((recentAvg - yearAgoAvg) / yearAgoAvg) * 100; return { currentLevel: recent[recent.length - 1], trend, momentum: trend > 0 ? 'Rising' : 'Falling', severity: Math.abs(trend) > 2 ? 'High' : Math.abs(trend) > 0.5 ? 'Medium' : 'Low' }; } static calculateEconomicSurpriseIndex(forecasts, actuals) { if (forecasts.length !== actuals.length) return 0; const surprises = forecasts.map((forecast, index) => { const actual = actuals[index]; return ((actual - forecast) / Math.abs(forecast)) * 100; }); // Calculate weighted average with more recent data having higher weight const weights = surprises.map((_, index) => Math.pow(0.9, surprises.length - index - 1)); const totalWeight = weights.reduce((sum, weight) => sum + weight, 0); return surprises.reduce((sum, surprise, index) => sum + (surprise * weights[index]), 0) / totalWeight; } } // Real-time Data Streaming Simulator class DataStreamManager { constructor() { this.streams = new Map(); this.subscribers = new Map(); this.isStreaming = false; } subscribe(symbol, callback) { if (!this.subscribers.has(symbol)) { this.subscribers.set(symbol, []); } this.subscribers.get(symbol).push(callback); if (!this.streams.has(symbol)) { this.startStream(symbol); } } unsubscribe(symbol, callback) { const callbacks = this.subscribers.get(symbol); if (callbacks) { const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } if (callbacks.length === 0) { this.stopStream(symbol); } } } startStream(symbol) { const interval = setInterval(() => { const data = this.generateRealtimeData(symbol); this.notifySubscribers(symbol, data); }, 1000 + Math.random() * 2000); // Random interval 1-3 seconds this.streams.set(symbol, interval); } stopStream(symbol) { const interval = this.streams.get(symbol); if (interval) { clearInterval(interval); this.streams.delete(symbol); } } generateRealtimeData(symbol) { const baseValue = this.getBaseValue(symbol); const volatility = this.getVolatility(symbol); const change = (Math.random() - 0.5) * volatility * 2; return { symbol, value: baseValue * (1 + change), change: change * 100, volume: Math.floor(Math.random() * 1000000), timestamp: new Date().toISOString(), bid: baseValue * (1 + change - 0.001), ask: baseValue * (1 + change + 0.001) }; } getBaseValue(symbol) { const baseValues = { 'SPY': 450, 'QQQ': 380, 'AAPL': 180, 'MSFT': 350, 'GOOGL': 140, 'TSLA': 250, 'BTC': 45000, 'ETH': 3200, 'DXY': 102.5, 'VIX': 18.5 }; return baseValues[symbol] || 100; } getVolatility(symbol) { const volatilities = { 'SPY': 0.01, 'QQQ': 0.015, 'AAPL': 0.02, 'MSFT': 0.018, 'GOOGL': 0.025, 'TSLA': 0.04, 'BTC': 0.05, 'ETH': 0.06, 'DXY': 0.005, 'VIX': 0.1 }; return volatilities[symbol] || 0.02; } notifySubscribers(symbol, data) { const callbacks = this.subscribers.get(symbol); if (callbacks) { callbacks.forEach(callback => { try { callback(data); } catch (error) { console.error('Error in stream callback:', error); } }); } } startAllStreams() { this.isStreaming = true; console.log('🔴 Real-time data streaming started'); } stopAllStreams() { this.streams.forEach((interval, symbol) => { clearInterval(interval); }); this.streams.clear(); this.isStreaming = false; console.log('⏹️ Real-time data streaming stopped'); } } // Advanced Alert System class AlertSystem { constructor() { this.alerts = []; this.rules = new Map(); this.subscribers = []; } addRule(id, condition, action, priority = 'medium') { this.rules.set(id, { condition, action, priority, active: true, triggered: 0 }); } removeRule(id) { this.rules.delete(id); } checkRules(data) { this.rules.forEach((rule, id) => { if (rule.active && rule.condition(data)) { const alert = { id: Date.now() + Math.random(), ruleId: id, message: rule.action(data), priority: rule.priority, timestamp: new Date().toISOString(), data }; this.addAlert(alert); rule.triggered++; } }); } addAlert(alert) { this.alerts.unshift(alert); // Keep only last 100 alerts if (this.alerts.length > 100) { this.alerts = this.alerts.slice(0, 100); } this.notifySubscribers(alert); this.displayAlert(alert); } subscribe(callback) { this.subscribers.push(callback); } notifySubscribers(alert) { this.subscribers.forEach(callback => { try { callback(alert); } catch (error) { console.error('Error in alert callback:', error); } }); } displayAlert(alert) { const alertElement = document.createElement('div'); alertElement.className = `alert-notification alert-${alert.priority}`; alertElement.innerHTML = `
${this.getAlertIcon(alert.priority)} ${new Date(alert.timestamp).toLocaleTimeString()}
${alert.message}
`; document.body.appendChild(alertElement); // Auto-remove after 10 seconds setTimeout(() => { if (alertElement.parentElement) { alertElement.remove(); } }, 10000); } getAlertIcon(priority) { const icons = { 'low': 'ℹ️', 'medium': '⚠️', 'high': '🚨', 'critical': '🔥' }; return icons[priority] || 'ℹ️'; } // Predefined alert rules setupDefaultRules() { // Price spike alert this.addRule('price_spike', (data) => Math.abs(data.change) > 5, (data) => `${data.symbol} price spike: ${data.change.toFixed(2)}%`, 'high' ); // Volume spike alert this.addRule('volume_spike', (data) => data.volume > 1000000, (data) => `${data.symbol} volume spike: ${data.volume.toLocaleString()}`, 'medium' ); // Technical level breach this.addRule('support_resistance', (data) => this.checkTechnicalLevels(data), (data) => `${data.symbol} breached key technical level`, 'medium' ); } checkTechnicalLevels(data) { // Simplified technical level check const keyLevels = { 'SPY': [440, 450, 460], 'AAPL': [170, 180, 190], 'MSFT': [340, 350, 360] }; const levels = keyLevels[data.symbol]; if (!levels) return false; return levels.some(level => Math.abs(data.value - level) < level * 0.005 // Within 0.5% of level ); } } // Performance Monitoring and Optimization class PerformanceMonitor { constructor() { this.metrics = { apiCalls: 0, errors: 0, loadTimes: [], memoryUsage: [], cacheHits: 0, cacheMisses: 0 }; this.startTime = Date.now(); } recordAPICall(duration, success = true) { this.metrics.apiCalls++; this.metrics.loadTimes.push(duration); if (!success) { this.metrics.errors++; } // Keep only last 100 measurements if (this.metrics.loadTimes.length > 100) { this.metrics.loadTimes = this.metrics.loadTimes.slice(-100); } } recordCacheHit() { this.metrics.cacheHits++; } recordCacheMiss() { this.metrics.cacheMisses++; } getAverageLoadTime() { if (this.metrics.loadTimes.length === 0) return 0; return this.metrics.loadTimes.reduce((sum, time) => sum + time, 0) / this.metrics.loadTimes.length; } getErrorRate() { if (this.metrics.apiCalls === 0) return 0; return (this.metrics.errors / this.metrics.apiCalls) * 100; } getCacheHitRate() { const totalRequests = this.metrics.cacheHits + this.metrics.cacheMisses; if (totalRequests === 0) return 0; return (this.metrics.cacheHits / totalRequests) * 100; } getUptime() { return Date.now() - this.startTime; } generateReport() { return { uptime: this.getUptime(), apiCalls: this.metrics.apiCalls, averageLoadTime: this.getAverageLoadTime(), errorRate: this.getErrorRate(), cacheHitRate: this.getCacheHitRate(), memoryUsage: this.estimateMemoryUsage() }; } estimateMemoryUsage() { // Rough estimation based on data structures const cacheSize = AppState.cache.size * 1024; // Assume 1KB per cache entry const watchlistSize = Object.keys(watchlistData).length * 512; // 512B per symbol const chartSize = Object.keys(charts).length * 2048; // 2KB per chart return { cache: cacheSize, watchlist: watchlistSize, charts: chartSize, total: cacheSize + watchlistSize + chartSize }; } } // Initialize advanced systems const techAnalysis = new TechnicalAnalysis(); const sentimentAnalysis = new SentimentAnalysis(); const portfolioAnalysis = new PortfolioAnalysis(); const economicAnalysis = new EconomicAnalysis(); const dataStreamer = new DataStreamManager(); const alertSystem = new AlertSystem(); const performanceMonitor = new PerformanceMonitor(); // Enhanced Error Handling and Recovery class ErrorHandler { static handleAPIError(error, context) { console.error(`API Error in ${context}:`, error); const errorTypes = { 'NetworkError': 'Connection issue - retrying...', 'TypeError': 'Data format error - using fallback', 'SyntaxError': 'Response parsing error - using cache', 'TimeoutError': 'Request timeout - retrying with backup' }; const userMessage = errorTypes[error.name] || 'Unexpected error occurred'; showTemporaryMessage(userMessage, 'error'); // Log to performance monitor performanceMonitor.recordAPICall(0, false); return this.getErrorRecoveryStrategy(error); } static getErrorRecoveryStrategy(error) { if (error.name === 'NetworkError') { return 'retry_with_fallback'; } else if (error.name === 'TypeError') { return 'use_simulated_data'; } else if (error.message.includes('CORS')) { return 'try_proxy'; } else { return 'use_cache'; } } static async executeWithRetry(asyncFunction, maxRetries = 3, delay = 1000) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const startTime = Date.now(); const result = await asyncFunction(); const duration = Date.now() - startTime; performanceMonitor.recordAPICall(duration, true); return result; } catch (error) { console.warn(`Attempt ${attempt} failed:`, error.message); if (attempt === maxRetries) { throw error; } await new Promise(resolve => setTimeout(resolve, delay * attempt)); } } } } // Advanced Caching System class CacheManager { constructor() { this.cache = new Map(); this.ttl = new Map(); // Time to live this.defaultTTL = 300000; // 5 minutes this.maxSize = 1000; } set(key, value, customTTL = null) { const ttl = customTTL || this.defaultTTL; const expiry = Date.now() + ttl; this.cache.set(key, value); this.ttl.set(key, expiry); // Cleanup if cache is too large this.cleanup(); performanceMonitor.recordCacheMiss(); } get(key) { const expiry = this.ttl.get(key); if (!expiry || Date.now() > expiry) { this.delete(key); performanceMonitor.recordCacheMiss(); return null; } performanceMonitor.recordCacheHit(); return this.cache.get(key); } delete(key) { this.cache.delete(key); this.ttl.delete(key); } cleanup() { if (this.cache.size <= this.maxSize) return; // Remove expired entries first for (const [key, expiry] of this.ttl.entries()) { if (Date.now() > expiry) { this.delete(key); } } // If still too large, remove oldest entries if (this.cache.size > this.maxSize) { const entries = Array.from(this.cache.keys()); const toRemove = entries.slice(0, entries.length - this.maxSize); toRemove.forEach(key => this.delete(key)); } } clear() { this.cache.clear(); this.ttl.clear(); } getStats() { return { size: this.cache.size, hitRate: performanceMonitor.getCacheHitRate(), memoryUsage: this.estimateMemoryUsage() }; } estimateMemoryUsage() { let totalSize = 0; for (const value of this.cache.values()) { totalSize += JSON.stringify(value).length * 2; // Rough UTF-16 estimation } return totalSize; } } // Advanced Data Validation class DataValidator { static validateFinancialData(data, symbol) { const errors = []; if (!data) { errors.push('No data provided'); return { valid: false, errors }; } if (Array.isArray(data)) { data.forEach((item, index) => { if (!item.date) errors.push(`Missing date at index ${index}`); if (typeof item.value !== 'number' || isNaN(item.value)) { errors.push(`Invalid value at index ${index}`); } if (item.value < 0 && !this.allowsNegativeValues(symbol)) { errors.push(`Negative value not allowed for ${symbol} at index ${index}`); } }); } else { if (typeof data.value !== 'number' || isNaN(data.value)) { errors.push('Invalid value in data object'); } } return { valid: errors.length === 0, errors, warnings: this.generateWarnings(data, symbol) }; } static allowsNegativeValues(symbol) { const negativeAllowed = ['change', 'return', 'yield_spread', 'sentiment']; return negativeAllowed.some(type => symbol.toLowerCase().includes(type)); } static generateWarnings(data, symbol) { const warnings = []; if (Array.isArray(data) && data.length > 0) { const values = data.map(d => d.value).filter(v => typeof v === 'number'); const mean = values.reduce((sum, v) => sum + v, 0) / values.length; const stdDev = Math.sqrt(values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length); // Check for outliers values.forEach((value, index) => { if (Math.abs(value - mean) > 3 * stdDev) { warnings.push(`Potential outlier detected at index ${index}: ${value}`); } }); // Check for missing data patterns if (data.length < 10) { warnings.push('Limited data points available'); } } return warnings; } static sanitizeData(data) { if (Array.isArray(data)) { return data.filter(item => item && item.date && typeof item.value === 'number' && !isNaN(item.value) ); } return data; } } // Enhanced UI Components class UIComponents { static createLoadingSpinner(container, message = 'Loading...') { const spinner = document.createElement('div'); spinner.className = 'loading-spinner'; spinner.innerHTML = `
${message}
`; container.appendChild(spinner); return spinner; } static createProgressBar(container, progress = 0) { const progressBar = document.createElement('div'); progressBar.className = 'progress-bar-container'; progressBar.innerHTML = `
${progress}%
`; container.appendChild(progressBar); return progressBar; } static updateProgressBar(progressBar, progress) { const fill = progressBar.querySelector('.progress-fill'); const text = progressBar.querySelector('.progress-text'); fill.style.width = `${progress}%`; text.textContent = `${progress}%`; } static createTooltip(element, content) { element.setAttribute('data-tooltip', content); element.classList.add('tooltip'); } static createModal(title, content, options = {}) { const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.innerHTML = ` `; document.body.appendChild(modal); return modal; } static createNotification(message, type = 'info', duration = 5000) { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${this.getNotificationIcon(type)}
${message}
`; const container = document.getElementById('notification-container') || this.createNotificationContainer(); container.appendChild(notification); if (duration > 0) { setTimeout(() => { if (notification.parentElement) { notification.remove(); } }, duration); } return notification; } static createNotificationContainer() { const container = document.createElement('div'); container.id = 'notification-container'; container.className = 'notification-container'; document.body.appendChild(container); return container; } static getNotificationIcon(type) { const icons = { 'info': 'ℹ️', 'success': '✅', 'warning': '⚠️', 'error': '❌' }; return icons[type] || 'ℹ️'; } } // Data Export and Import Utilities class DataExporter { static exportToCSV(data, filename = 'openbb_data.csv') { if (!Array.isArray(data) || data.length === 0) { throw new Error('No data to export'); } const headers = Object.keys(data[0]); const csvContent = [ headers.join(','), ...data.map(row => headers.map(header => { const value = row[header]; return typeof value === 'string' ? `"${value}"` : value; }).join(',') ) ].join('\n'); this.downloadFile(csvContent, filename, 'text/csv'); } static exportToJSON(data, filename = 'openbb_data.json') { const jsonContent = JSON.stringify(data, null, 2); this.downloadFile(jsonContent, filename, 'application/json'); } static exportToExcel(data, filename = 'openbb_data.xlsx') { // Simplified Excel export (would require a library like SheetJS in production) console.warn('Excel export requires additional library - exporting as CSV instead'); this.exportToCSV(data, filename.replace('.xlsx', '.csv')); } static downloadFile(content, filename, mimeType) { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } static async importFromFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (event) => { try { const content = event.target.result; if (file.name.endsWith('.json')) { resolve(JSON.parse(content)); } else if (file.name.endsWith('.csv')) { resolve(this.parseCSV(content)); } else { reject(new Error('Unsupported file format')); } } catch (error) { reject(error); } }; reader.onerror = () => reject(new Error('File reading failed')); reader.readAsText(file); }); } static parseCSV(content) { const lines = content.split('\n').filter(line => line.trim()); if (lines.length < 2) throw new Error('Invalid CSV format'); const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, '')); const data = lines.slice(1).map(line => { const values = line.split(',').map(v => v.trim().replace(/"/g, '')); const row = {}; headers.forEach((header, index) => { row[header] = values[index] || ''; }); return row; }); return data; } } // Advanced Keyboard Shortcuts class KeyboardShortcuts { constructor() { this.shortcuts = new Map(); this.modifierKeys = { ctrl: false, shift: false, alt: false }; this.init(); } init() { document.addEventListener('keydown', (event) => this.handleKeyDown(event)); document.addEventListener('keyup', (event) => this.handleKeyUp(event)); // Register default shortcuts this.registerShortcut('ctrl+/', () => this.showHelp()); this.registerShortcut('ctrl+f', () => this.focusSearch()); this.registerShortcut('ctrl+r', () => this.refreshData()); this.registerShortcut('ctrl+n', () => addNewChart()); this.registerShortcut('ctrl+s', () => this.saveConfiguration()); this.registerShortcut('ctrl+e', () => this.exportData()); this.registerShortcut('escape', () => this.closeModals()); this.registerShortcut('ctrl+1', () => switchWatchlist('MAIN')); this.registerShortcut('ctrl+2', () => switchWatchlist('ECONOMIC')); this.registerShortcut('ctrl+3', () => switchWatchlist('MARKETS')); } registerShortcut(combination, action) { this.shortcuts.set(combination.toLowerCase(), action); } handleKeyDown(event) { this.updateModifierKeys(event); const combination = this.getCombination(event); const action = this.shortcuts.get(combination); if (action) { event.preventDefault(); action(); } } handleKeyUp(event) { this.updateModifierKeys(event); } updateModifierKeys(event) { this.modifierKeys.ctrl = event.ctrlKey || event.metaKey; this.modifierKeys.shift = event.shiftKey; this.modifierKeys.alt = event.altKey; } getCombination(event) { const parts = []; if (this.modifierKeys.ctrl) parts.push('ctrl'); if (this.modifierKeys.shift) parts.push('shift'); if (this.modifierKeys.alt) parts.push('alt'); parts.push(event.key.toLowerCase()); return parts.join('+'); } showHelp() { const helpContent = `

Keyboard Shortcuts

Ctrl+/ Show this help
Ctrl+F Focus search
Ctrl+R Refresh data
Ctrl+N New chart
Ctrl+S Save configuration
Ctrl+E Export data
Esc Close modals
Ctrl+1-3 Switch watchlists
`; UIComponents.createModal('Keyboard Shortcuts', helpContent); } focusSearch() { document.getElementById('searchBox').focus(); } refreshData() { refreshAllData(); } saveConfiguration() { this.saveToLocalStorage(); showTemporaryMessage('Configuration saved', 'success'); } exportData() { const data = Object.values(watchlistData); if (data.length > 0) { DataExporter.exportToCSV(data, `watchlist_${currentWatchlist}_${new Date().toISOString().split('T')[0]}.csv`); } } closeModals() { document.querySelectorAll('.modal-overlay').forEach(modal => modal.remove()); document.getElementById('searchResults').style.display = 'none'; } saveToLocalStorage() { const config = { watchlists: WATCHLISTS, currentWatchlist, sortOrder, activeSources, timestamp: Date.now() }; localStorage.setItem('openbb_config', JSON.stringify(config)); } } // Initialize keyboard shortcuts and advanced systems let keyboardShortcuts, cacheManager; try { keyboardShortcuts = new KeyboardShortcuts(); cacheManager = new CacheManager(); // Replace the global cache with the advanced cache manager AppState.cache = cacheManager; console.log('✅ Advanced systems initialized successfully'); } catch (initError) { console.warn('⚠️ Advanced systems initialization failed, using basic mode:', initError); // Fallback to basic functionality cacheManager = { get: (key) => null, set: (key, value) => {}, delete: (key) => {}, clear: () => {} }; } // Initialize connection status display function initializeConnectionStatus() { try { const connectionGrid = document.getElementById('connectionGrid'); if (!connectionGrid) { console.warn('Connection grid element not found'); return; } const connections = [ { id: 'enhanced', name: 'Enhanced API', count: '60,000+' }, { id: 'semantic', name: 'Semantic Search', count: '12' }, { id: 'cors', name: 'CORS Proxy', count: 'Proxy' }, { id: 'total', name: 'Total Available', count: '66,370+' } ]; connectionGrid.innerHTML = connections.map(conn => `
${conn.name}: ${conn.count}
`).join(''); } catch (error) { console.error('Error initializing connection status:', error); } } // Test all API connections with CORS handling async function testAllConnections() { console.log('🔍 Testing API connections...'); const results = { enhanced: false, semantic: false, corsProxy: false }; // Test Semantic Search API (CORS-enabled) try { const semanticResponse = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=inflation`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (semanticResponse.ok) { const data = await semanticResponse.json(); if (data.success) { results.semantic = true; AppState.apiConnections.semantic = true; document.getElementById('semantic-indicator').classList.add('connected'); document.getElementById('semantic-connection').classList.add('connected'); console.log('✅ Semantic Search API connected'); } } } catch (error) { console.warn('❌ Semantic Search API failed:', error.message); } // Test Enhanced API with CORS proxy fallback try { // Skip if proxy is not available const proxyTest = await fetch(PRODUCTION_CONFIG.CORS_PROXY + 'test').catch(() => null); if (!proxyTest) { console.log('❌ CORS Proxy not available, skipping Enhanced API test'); } else { // First try direct connection with HTTPS let enhancedResponse; try { enhancedResponse = await fetch(`${DATA_SOURCES.enhanced.fallbackUrl}/api/v1/economy/unemployment`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } catch (corsError) { console.log('Direct connection blocked by CORS, trying proxy...'); // Fallback to CORS proxy try { enhancedResponse = await fetch(`${PRODUCTION_CONFIG.CORS_PROXY}enhanced/api/v1/economy/unemployment`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); results.corsProxy = true; AppState.apiConnections.corsProxy = true; document.getElementById('cors-connection').classList.add('connected'); console.log('✅ CORS Proxy working'); } catch (proxyError) { console.warn('❌ CORS Proxy failed:', proxyError.message); } } if (enhancedResponse && enhancedResponse.ok) { results.enhanced = true; AppState.apiConnections.enhanced = true; document.getElementById('enhanced-indicator').classList.add('connected'); document.getElementById('enhanced-connection').classList.add('connected'); console.log('✅ Enhanced API connected'); } } } catch (error) { console.warn('❌ Enhanced API failed:', error.message); } // Update status indicators updateConnectionStatus(results); updateIndicatorCounts(results); return results; } // Update connection status in UI function updateConnectionStatus(results) { const corsIndicator = document.getElementById('corsIndicator'); const corsStatusText = document.getElementById('corsStatusText'); const apiStatus = document.getElementById('apiStatus'); const apiStatusText = document.getElementById('apiStatusText'); // CORS status if (results.semantic || results.corsProxy) { corsIndicator.classList.add('working'); corsStatusText.textContent = 'CORS: Working'; } else { corsStatusText.textContent = 'CORS: Limited'; } // Overall API status const connectedApis = Object.values(results).filter(Boolean).length; if (connectedApis > 0) { apiStatus.classList.add('connected'); apiStatusText.textContent = `APIs: ${connectedApis}/3 Connected`; } else { apiStatusText.textContent = 'APIs: Connecting...'; } } // Update indicator counts function updateIndicatorCounts(results) { const dataStats = document.getElementById('dataStats'); const indicatorCount = document.getElementById('indicatorCount'); let totalIndicators = 0; if (results.enhanced) totalIndicators += AppState.indicatorCounts.enhanced; if (results.semantic) totalIndicators += AppState.indicatorCounts.semantic; if (totalIndicators > 0) { indicatorCount.textContent = `Indicators: ${totalIndicators.toLocaleString()}+`; dataStats.classList.add('live'); } else { indicatorCount.textContent = 'Indicators: Testing...'; } } // Enhanced search with multiple API sources async function performEnhancedSearch(query) { const resultsDiv = document.getElementById('searchResults'); if (!query || query.trim().length < 1) { resultsDiv.style.display = 'none'; return; } clearTimeout(AppState.searchTimeout); AppState.searchTimeout = setTimeout(async () => { try { resultsDiv.innerHTML = '
Searching 66,370+ indicators...
'; resultsDiv.style.display = 'block'; const allResults = []; // Search Semantic API first (fastest) if (AppState.apiConnections.semantic) { try { const semanticResults = await searchSemanticAPI(query); allResults.push(...semanticResults); } catch (error) { console.warn('Semantic search failed:', error); } } // Search Enhanced API categories if (AppState.apiConnections.enhanced || AppState.apiConnections.corsProxy) { try { const enhancedResults = await searchEnhancedAPI(query); allResults.push(...enhancedResults); } catch (error) { console.warn('Enhanced search failed:', error); } } // Display results displaySearchResults(allResults, query); } catch (error) { console.error('Search error:', error); resultsDiv.innerHTML = `
Search temporarily unavailable. Please try again.
Error: ${error.message}
`; } }, PRODUCTION_CONFIG.SEARCH_DEBOUNCE); } // Search Semantic API async function searchSemanticAPI(query) { const response = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=${encodeURIComponent(query)}`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Semantic API error: ${response.status}`); } const data = await response.json(); return data.data.results.map(result => ({ symbol: result.symbol || result.id, name: result.name, description: result.description, source: 'semantic', type: 'AI_Search', score: result.score, category: result.category, last_value: result.last_value, unit: result.unit, priority: result.score >= 90 ? 'high' : result.score >= 70 ? 'medium' : 'low' })); } // Search Enhanced API async function searchEnhancedAPI(query) { const results = []; const searchTerms = query.toLowerCase(); // Define search mappings for Enhanced API const searchMappings = { 'unemployment': { endpoint: 'unemployment', name: 'Unemployment Rate', category: 'Economic' }, 'jobs': { endpoint: 'employment', name: 'Employment Data', category: 'Economic' }, 'employment': { endpoint: 'employment', name: 'Employment Statistics', category: 'Economic' }, 'gdp': { endpoint: 'gdp', name: 'Gross Domestic Product', category: 'Economic' }, 'growth': { endpoint: 'gdp', name: 'Economic Growth (GDP)', category: 'Economic' }, 'inflation': { endpoint: 'inflation', name: 'Inflation Rate', category: 'Economic' }, 'cpi': { endpoint: 'cpi', name: 'Consumer Price Index', category: 'Economic' }, 'prices': { endpoint: 'cpi', name: 'Consumer Prices', category: 'Economic' }, 'interest': { endpoint: 'interest_rates', name: 'Interest Rates', category: 'Economic' }, 'rates': { endpoint: 'interest_rates', name: 'Interest Rates', category: 'Economic' }, 'money': { endpoint: 'money_measures', name: 'Money Supply', category: 'Economic' }, 'monetary': { endpoint: 'money_measures', name: 'Monetary Policy', category: 'Economic' }, 'stock': { endpoint: 'stock_data', name: 'Stock Market Data', category: 'Markets' }, 'equity': { endpoint: 'stock_data', name: 'Equity Markets', category: 'Markets' }, 'bond': { endpoint: 'bond_data', name: 'Bond Market Data', category: 'Markets' }, 'treasury': { endpoint: 'treasury_rates', name: 'Treasury Rates', category: 'Markets' }, 'forex': { endpoint: 'forex_data', name: 'Foreign Exchange', category: 'Markets' }, 'currency': { endpoint: 'forex_data', name: 'Currency Markets', category: 'Markets' }, 'commodity': { endpoint: 'commodity_data', name: 'Commodity Prices', category: 'Markets' }, 'gold': { endpoint: 'commodity_data', name: 'Commodity Markets (Gold)', category: 'Markets' }, 'oil': { endpoint: 'commodity_data', name: 'Commodity Markets (Oil)', category: 'Markets' }, 'trade': { endpoint: 'trade_data', name: 'International Trade', category: 'International' }, 'global': { endpoint: 'global_indicators', name: 'Global Economic Indicators', category: 'International' }, 'country': { endpoint: 'country_profile', name: 'Country Economic Profiles', category: 'International' }, 'corporate': { endpoint: 'corporate_bonds', name: 'Corporate Bonds', category: 'Markets' } }; // Find matching endpoints for (const [term, config] of Object.entries(searchMappings)) { if (searchTerms.includes(term)) { results.push({ symbol: config.endpoint, name: config.name, description: `Enhanced API endpoint: ${config.endpoint}`, source: 'enhanced', type: 'API_Endpoint', category: config.category, endpoint: config.endpoint, priority: 'high' }); } } // Add country-specific searches const countries = { 'united states': 'united_states', 'usa': 'united_states', 'america': 'united_states', 'uk': 'united_kingdom', 'britain': 'united_kingdom', 'germany': 'germany', 'japan': 'japan', 'china': 'china', 'canada': 'canada', 'france': 'france', 'italy': 'italy', 'spain': 'spain', 'australia': 'australia', 'brazil': 'brazil', 'india': 'india', 'russia': 'russia' }; for (const [countryName, countryCode] of Object.entries(countries)) { if (searchTerms.includes(countryName)) { results.push({ symbol: `${countryCode}_profile`, name: `${countryName.charAt(0).toUpperCase() + countryName.slice(1)} Economic Profile`, description: `Complete economic data for ${countryName}`, source: 'enhanced', type: 'Country_Profile', category: 'International', endpoint: 'country_profile', params: { country: countryCode }, priority: 'medium' }); } } return results; } // Display search results function displaySearchResults(results, query) { const resultsDiv = document.getElementById('searchResults'); if (!results || results.length === 0) { resultsDiv.innerHTML = '
' + 'No results found for "' + query + '"' + '
' + 'Try: unemployment, gdp, inflation, stocks, bonds, forex, commodities, country names' + '
' + '
'; return; } // Sort results by priority and score results.sort(function(a, b) { const priorityOrder = { high: 3, medium: 2, low: 1 }; const aPriority = priorityOrder[a.priority] || 1; const bPriority = priorityOrder[b.priority] || 1; if (aPriority !== bPriority) return bPriority - aPriority; return (b.score || 0) - (a.score || 0); }); const displayResults = results.slice(0, 50); // Limit to 50 results let html = ''; displayResults.forEach(function(result) { const highlightMatch = function(text) { if (!text) return ''; const regex = new RegExp('(' + query.split(' ').join('|') + ')', 'gi'); return text.replace(regex, '$1'); }; const priorityIndicator = result.priority === 'high' ? '🔥' : result.priority === 'medium' ? '⭐' : ''; const scoreDisplay = result.score ? ' (' + result.score + '%)' : ''; const valueDisplay = result.last_value ? ' - Current: ' + result.last_value + (result.unit ? ' ' + result.unit : '') : ''; const resultDataJson = JSON.stringify(result).replace(/"/g, '"'); html += '
' + '
' + '
' + highlightMatch(result.symbol) + priorityIndicator + '' + (result.type || result.category) + '' + '
' + '
' + highlightMatch(result.name || result.symbol) + scoreDisplay + '
' + (result.description ? '
' + highlightMatch(result.description) + valueDisplay + '
' : '') + '
' + '
' + result.source.toUpperCase() + '
' + '
'; }); resultsDiv.innerHTML = html; resultsDiv.style.display = 'block'; } // Add to watchlist from search function addToWatchlistFromSearch(symbol, source, name, resultData) { try { let result; if (typeof resultData === 'string') { result = JSON.parse(resultData.replace(/"/g, '"')); } else { result = resultData; } const watchlistSymbol = { symbol: symbol, source: source, name: name, endpoint: result.endpoint, params: result.params, type: result.type, category: result.category }; const currentSymbols = WATCHLISTS[currentWatchlist].symbols; if (!currentSymbols.find(s => s.symbol === symbol)) { currentSymbols.push(watchlistSymbol); saveWatchlists(); // Add to watchlist data with loading state watchlistData[symbol] = { name: name, source: source, value: 'Loading...', changes: { '1d': null, '1w': null, '1m': null, '1y': null }, hasData: false, loading: true }; renderWatchlist(); loadSingleWatchlistItem(watchlistSymbol); showTemporaryMessage('Added ' + name + ' to ' + currentWatchlist + ' watchlist', 'info'); } else { showTemporaryMessage(name + ' is already in ' + currentWatchlist + ' watchlist', 'info'); } document.getElementById('searchResults').style.display = 'none'; document.getElementById('searchBox').value = ''; } catch (error) { console.error('Error adding to watchlist:', error); showTemporaryMessage('Error adding symbol to watchlist', 'error'); } } // Load single watchlist item async function loadSingleWatchlistItem(symbolObj) { const { symbol, source, endpoint, params } = symbolObj; try { let data; if (source === 'semantic') { data = await loadSemanticData(symbolObj); } else if (source === 'enhanced') { data = await loadEnhancedData(symbolObj); } else { throw new Error(`Unknown source: ${source}`); } if (data) { watchlistData[symbol] = { ...watchlistData[symbol], ...data, hasData: true, loading: false, lastUpdate: Date.now() }; } else { watchlistData[symbol] = { ...watchlistData[symbol], value: 'No Data', hasData: false, loading: false, error: 'No data available' }; } } catch (error) { console.error(`Error loading ${symbol}:`, error); watchlistData[symbol] = { ...watchlistData[symbol], value: 'Error', hasData: false, loading: false, error: error.message }; } renderWatchlist(); } // Load semantic data async function loadSemanticData(symbolObj) { const query = symbolObj.query || symbolObj.symbol; const response = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=${encodeURIComponent(query)}`, { method: 'GET', mode: 'cors' }); if (!response.ok) { throw new Error(`Semantic API error: ${response.status}`); } const data = await response.json(); if (data.data.results.length > 0) { const result = data.data.results[0]; return { value: result.last_value || 'N/A', unit: result.unit || '', changes: { '1d': Math.random() * 4 - 2, // Simulated for demo '1w': Math.random() * 8 - 4, '1m': Math.random() * 15 - 7.5, '1y': Math.random() * 30 - 15 }, source: 'semantic', category: result.category }; } return null; } // Load enhanced data async function loadEnhancedData(symbolObj) { const { endpoint, params } = symbolObj; // If APIs are not available, return simulated data immediately if (!AppState.apiConnections.enhanced && !AppState.apiConnections.corsProxy) { console.log('APIs unavailable, using simulated data for:', endpoint); return generateSimulatedData(endpoint); } let url = `${DATA_SOURCES.enhanced.fallbackUrl}/api/v1/${endpoint}`; if (params) { const queryParams = new URLSearchParams(params).toString(); url += `?${queryParams}`; } // Try CORS proxy if available let response; try { if (AppState.apiConnections.corsProxy) { const proxyUrl = `${PRODUCTION_CONFIG.CORS_PROXY}enhanced/api/v1/${endpoint}${params ? '?' + new URLSearchParams(params).toString() : ''}`; response = await fetch(proxyUrl, { mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } else { // Try direct connection with HTTPS response = await fetch(url, { mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } } catch (error) { console.warn(`Failed to fetch ${endpoint}, using simulated data:`, error.message); return generateSimulatedData(endpoint); } if (!response || !response.ok) { console.warn(`API returned error for ${endpoint}, using simulated data`); return generateSimulatedData(endpoint); } // Handle different response formats let data; try { data = await response.json(); } catch (parseError) { // If JSON parsing fails, return simulated data based on endpoint console.warn(`Failed to parse response for ${endpoint}, using simulated data`); return generateSimulatedData(endpoint); } return processEnhancedData(data, endpoint); } // Process enhanced data function processEnhancedData(data, endpoint) { if (!data || (Array.isArray(data) && data.length === 0)) { return generateSimulatedData(endpoint); } let latestValue = 'N/A'; let changes = { '1d': null, '1w': null, '1m': null, '1y': null }; if (Array.isArray(data) && data.length > 0) { // Time series data const latest = data[data.length - 1]; latestValue = latest.value || latest.rate || latest.price || latest.level || 'N/A'; // Calculate changes if we have historical data if (data.length > 1) { const previous = data[data.length - 2]; const prevValue = previous.value || previous.rate || previous.price || previous.level; if (latestValue !== 'N/A' && prevValue) { changes['1d'] = ((latestValue - prevValue) / prevValue) * 100; } } } else if (typeof data === 'object' && data.value !== undefined) { // Single value data latestValue = data.value; } return { value: latestValue, changes: changes, source: 'enhanced', rawData: data }; } // Load watchlist function loadWatchlist() { const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist) return; // Initialize watchlist data watchlistData = {}; watchlist.symbols.forEach(symbolObj => { watchlistData[symbolObj.symbol] = { name: symbolObj.name, source: symbolObj.source, value: 'Loading...', changes: { '1d': null, '1w': null, '1m': null, '1y': null }, hasData: false, loading: true }; }); renderWatchlist(); // Load data for all symbols watchlist.symbols.forEach(symbolObj => { loadSingleWatchlistItem(symbolObj); }); } // Render watchlist function renderWatchlist() { const tbody = document.getElementById('watchlistBody'); const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist || watchlist.symbols.length === 0) { tbody.innerHTML = 'No symbols in this watchlist. Use search to add symbols.'; return; } const formatValue = function(value, unit) { if (value === 'Loading...' || value === 'Error' || value === 'No Data') return value; if (typeof value === 'number') { if (value >= 1000) return value.toLocaleString(); if (value >= 1) return value.toFixed(2); return value.toFixed(4); } return value; }; const formatChange = function(change) { if (change === null || change === undefined || isNaN(change)) return '—'; const val = parseFloat(change); return (val >= 0 ? '+' : '') + val.toFixed(2) + '%'; }; const getChangeClass = function(change) { if (change === null || change === undefined || isNaN(change)) return 'neutral'; return parseFloat(change) >= 0 ? 'positive' : 'negative'; }; const getSourceDisplay = function(source, simulated) { const sourceNames = { enhanced: 'Enhanced API', semantic: 'Semantic AI' }; return (sourceNames[source] || source.toUpperCase()) + (simulated ? ' (Demo)' : ''); }; const html = watchlist.symbols.map(function(symbolObj) { const data = watchlistData[symbolObj.symbol] || {}; const isLoading = data.loading; const hasError = data.error; return '' + '' + '
' + '
' + symbolObj.symbol.charAt(0) + '
' + '
' + '
' + (data.name || symbolObj.name) + (isLoading ? '' : '') + (hasError ? '' : '') + '
' + '
' + symbolObj.symbol + ' (' + getSourceDisplay(symbolObj.source, data.simulated) + ')
' + '
' + '
' + '' + '' + (isLoading ? '
' : formatValue(data.value, data.unit)) + (data.unit && !isLoading ? ' ' + data.unit : '') + '' + '' + formatChange(data.changes && data.changes['1d']) + '' + '' + formatChange(data.changes && data.changes['1d']) + '' + '' + formatChange(data.changes && data.changes['1w']) + '' + '' + formatChange(data.changes && data.changes['1m']) + '' + '' + formatChange(data.changes && data.changes['1y']) + '' + '' + '
' + '
' + (data.simulated ? 'Demo' : data.hasData ? 'Live' : 'Loading') + '
' + '' + '' + '' + '' + ''; }).join(''); tbody.innerHTML = html; } // Switch watchlist function switchWatchlist(watchlistName) { currentWatchlist = watchlistName; loadWatchlist(); } // Save watchlists to localStorage function saveWatchlists() { try { localStorage.setItem('openbb_watchlists', JSON.stringify(WATCHLISTS)); } catch (error) { console.warn('Failed to save watchlists:', error); } } // Remove from watchlist function removeFromWatchlist(event, symbol) { event.stopPropagation(); const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist) return; const index = watchlist.symbols.findIndex(s => s.symbol === symbol); if (index > -1) { watchlist.symbols.splice(index, 1); delete watchlistData[symbol]; saveWatchlists(); renderWatchlist(); showTemporaryMessage(`Removed ${symbol} from ${currentWatchlist} watchlist`, 'info'); } } // Refresh all data function refreshAllData() { showTemporaryMessage('Refreshing all data...', 'info'); loadWatchlist(); } // Start data refresh interval function startDataRefresh() { // Refresh data every 5 minutes setInterval(() => { if (Object.keys(watchlistData).length > 0) { console.log('🔄 Auto-refreshing watchlist data...'); loadWatchlist(); } }, 300000); // 5 minutes } // Setup search listeners function setupSearchListeners() { document.addEventListener('click', function(event) { const searchBox = document.getElementById('searchBox'); const searchResults = document.getElementById('searchResults'); if (!searchBox.contains(event.target) && !searchResults.contains(event.target)) { searchResults.style.display = 'none'; } }); } // Initialize fallback mode function initializeFallbackMode() { console.log('🔄 Initializing fallback mode with demo data...'); // Update UI to show demo mode document.getElementById('apiStatusText').textContent = 'APIs: Demo Mode'; document.getElementById('indicatorCount').textContent = 'Indicators: Demo Data'; document.getElementById('corsStatusText').textContent = 'CORS: Demo Mode'; // Update all connection indicators to show demo mode document.getElementById('enhanced-indicator').style.background = '#ffaa00'; document.getElementById('semantic-indicator').style.background = '#ffaa00'; document.getElementById('cors-indicator').style.background = '#ffaa00'; // Load demo data for all watchlists Object.keys(WATCHLISTS).forEach(watchlistName => { const watchlist = WATCHLISTS[watchlistName]; watchlist.symbols.forEach(symbolObj => { const demoData = generateSimulatedData(symbolObj.endpoint || symbolObj.symbol); const key = `${watchlistName}_${symbolObj.symbol}`; watchlistData[symbolObj.symbol] = { name: symbolObj.name, source: symbolObj.source, ...demoData, hasData: true, loading: false }; }); }); // Load current watchlist renderWatchlist(); showTemporaryMessage('Demo mode active - showing simulated data. APIs unavailable.', 'info'); // Set up demo data refresh setInterval(() => { if (currentWatchlist && WATCHLISTS[currentWatchlist]) { WATCHLISTS[currentWatchlist].symbols.forEach(symbolObj => { const currentData = watchlistData[symbolObj.symbol]; if (currentData && currentData.simulated) { // Update with new random changes currentData.changes = { '1d': Math.random() * 4 - 2, '1w': Math.random() * 8 - 4, '1m': Math.random() * 15 - 7.5, '1y': Math.random() * 30 - 15 }; // Slightly vary the value if (typeof currentData.value === 'number') { currentData.value = currentData.value * (1 + (Math.random() - 0.5) * 0.01); } } }); renderWatchlist(); } }, 5000); // Update every 5 seconds for demo effect } // Toggle data source function toggleDataSource(source) { const button = document.querySelector(`[data-source="${source}"]`); const isActive = button.classList.contains('active'); if (isActive) { button.classList.remove('active'); activeSources = activeSources.filter(s => s !== source); } else { button.classList.add('active'); activeSources.push(source); } } // Toggle ticker type function toggleTickerType(type) { const button = document.querySelector(`[data-type="${type}"]`); const isActive = button.classList.contains('active'); if (isActive) { button.classList.remove('active'); } else { button.classList.add('active'); } } // Chart functions function addNewChart() { const chartId = 'chart_' + (chartIdCounter++); const timeframeButtons = ['1D','1W','1M','3M','6M','1Y','2Y','5Y','MAX'].map(function(tf) { return ''; }).join(''); const chartHtml = '
' + '
' + '
' + '
Chart ' + chartIdCounter + '
' + '
' + '
' + '' + '' + '' + '
' + '
' + '
' + '
' + timeframeButtons + '
' + '' + '
' + '
' + '
' + '
Search for a financial indicator to add to this chart
' + '
' + '
'; document.getElementById('chartGrid').insertAdjacentHTML('beforeend', chartHtml); charts[chartId] = { symbols: [], data: {}, timeframe: '1Y', chartInstance: null, displayMode: 'raw' }; setActiveChart(chartId); showChartsArea(); } function setActiveChart(chartId) { activeChartId = chartId; document.querySelectorAll('.chart-window').forEach(c => c.classList.remove('active-chart')); const chartElement = document.getElementById(chartId); if (chartElement) { chartElement.classList.add('active-chart'); } } function removeChart(event, chartId) { event.stopPropagation(); if (charts[chartId] && charts[chartId].chartInstance) { Plotly.purge(`${chartId}_canvas`); } delete charts[chartId]; document.getElementById(chartId)?.remove(); if (Object.keys(charts).length === 0) { hideChartsArea(); } } function openChartForSymbol(symbol, source) { showChartsArea(); if (Object.keys(charts).length === 0) { addNewChart(); } if (activeChartId) { const data = watchlistData[symbol]; addToChart(activeChartId, symbol, source, data?.name || symbol); } } function addToChart(chartId, symbol, source, name) { const chart = charts[chartId]; if (!chart) return; if (!chart.symbols.find(s => s.symbol === symbol)) { chart.symbols.push({ symbol, source, name }); // Add demo chart data chart.data[symbol] = generateChartData(symbol); updateChartRender(chartId); updateChartSymbols(chartId); } setActiveChart(chartId); } function generateChartData(symbol) { const data = []; const now = new Date(); for (let i = 365; i >= 0; i--) { const date = new Date(now); date.setDate(date.getDate() - i); const baseValue = 100 + Math.random() * 50; const trend = -i * 0.01 + Math.random() * 2 - 1; const value = Math.max(0, baseValue + trend); data.push({ date: date.toISOString().split('T')[0], value: value }); } return data; } function updateChartRender(chartId) { const chart = charts[chartId]; const canvasElement = document.getElementById(`${chartId}_canvas`); if (!canvasElement || !chart) return; if (chart.symbols.length === 0) { canvasElement.innerHTML = '
Search for a financial indicator to add to this chart
'; return; } const traces = chart.symbols.map((s, index) => { const symbolData = chart.data[s.symbol] || []; const lineColor = colorPalette[index % colorPalette.length]; return { x: symbolData.map(d => d.date), y: symbolData.map(d => d.value), type: 'scatter', mode: 'lines', name: s.symbol, line: { color: lineColor, width: 2 }, hovertemplate: `${s.symbol}
Date: %{x}
Value: %{y:.2f}` }; }); const layout = { title: { text: 'Financial Data Chart', font: { color: '#fff', size: 14 }, x: 0.5 }, paper_bgcolor: '#0a0a0a', plot_bgcolor: '#0a0a0a', font: { color: '#aaa', size: 12 }, margin: { t: 60, r: 80, b: 60, l: 80 }, xaxis: { type: 'date', gridcolor: '#22222250', tickfont: { color: '#888' } }, yaxis: { title: { text: 'Value', font: { color: '#aaa' } }, gridcolor: '#22222250', tickfont: { color: '#888' } }, hovermode: 'x unified', showlegend: true, legend: { orientation: 'h', yanchor: 'bottom', y: 1.02, xanchor: 'right', x: 1 } }; const config = { responsive: true, displaylogo: false, displayModeBar: true }; if (canvasElement.querySelector('.chart-message')) { canvasElement.innerHTML = ''; } Plotly.react(canvasElement, traces, layout, config) .then(gd => { charts[chartId].chartInstance = gd; }) .catch(err => { console.error("Chart error:", err); canvasElement.innerHTML = `
Chart error: ${err.message}
`; }); } function updateChartSymbols(chartId) { const chart = charts[chartId]; if (!chart) return; const symbolsDiv = document.getElementById(`${chartId}_symbols`); if (!symbolsDiv) return; symbolsDiv.innerHTML = chart.symbols.map((s, i) => `
${s.symbol} ×
`).join(''); } function removeSymbolFromChart(event, chartId, symbolToRemove) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.symbols = chart.symbols.filter(s => s.symbol !== symbolToRemove); delete chart.data[symbolToRemove]; updateChartRender(chartId); updateChartSymbols(chartId); if (chart.symbols.length === 0) { document.getElementById(`${chartId}_canvas`).innerHTML = '
Search for a financial indicator to add to this chart
'; } } function toggleChartDisplay(event, chartId, displayMode) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.displayMode = displayMode; const controlsDiv = event.target.parentElement; controlsDiv.querySelectorAll('.display-toggle').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); updateChartRender(chartId); } function changeTimeframe(event, chartId, timeframe) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.timeframe = timeframe; const buttonContainer = document.getElementById(`${chartId}_timeframe_buttons`); if (buttonContainer) { buttonContainer.querySelectorAll('.timeframe-btn').forEach(btn => btn.classList.remove('active')); if (event.target.tagName === 'BUTTON') event.target.classList.add('active'); } updateChartRender(chartId); } function showChartsArea() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); const chartViewBtn = document.getElementById('chartViewBtn'); chartsArea.style.display = 'block'; watchlistSection.style.flex = '0 0 300px'; chartViewBtn.style.display = 'block'; } function hideChartsArea() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); const chartViewBtn = document.getElementById('chartViewBtn'); chartsArea.style.display = 'none'; watchlistSection.style.flex = '1'; chartViewBtn.style.display = 'none'; } function showChartView() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); if (chartsArea.style.display === 'none') { showChartsArea(); } else { hideChartsArea(); } } // Risk dashboard functions function toggleRiskDashboard() { const dashboard = document.getElementById('riskDashboard'); const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); if (dashboard.classList.contains('active')) { dashboard.classList.remove('active'); watchlistSection.style.flex = '1'; } else { dashboard.classList.add('active'); chartsArea.style.display = 'none'; watchlistSection.style.flex = '0 0 300px'; } } function toggleConfig() { const configPanel = document.getElementById('watchlistConfig'); configPanel.style.display = configPanel.style.display === 'none' ? 'block' : 'none'; } function updateRiskDashboard() { showTemporaryMessage('Risk Dashboard Updated!', 'info'); } // Liquidity dashboard function showLiquidityDashboard() { showTemporaryMessage('Loading comprehensive liquidity dashboard...', 'info'); } // Navigation functions function showDashboard(type) { // Update active nav link document.querySelectorAll('.nav-links a').forEach(a => a.classList.remove('active')); event.target.classList.add('active'); // Show appropriate content switch(type) { case 'main': switchWatchlist('MAIN'); break; case 'macro': switchWatchlist('ECONOMIC'); break; case 'ai': showTemporaryMessage('AI Signals dashboard - coming soon!', 'info'); break; case 'search': document.getElementById('searchBox').focus(); break; case 'realtime': openRealtimeDashboard(); break; case 'monitor': toggleRiskDashboard(); break; case 'login': showTemporaryMessage('User authentication - coming soon!', 'info'); break; } } function openRealtimeDashboard() { showTemporaryMessage('Real-time dashboard opening...', 'info'); } function openAIPredictions() { showTemporaryMessage('AI predictions dashboard opening...', 'info'); } function openExponentialSearch() { showTemporaryMessage('Advanced search with 60K+ indicators...', 'info'); } // Sort functions function sortByColumn(column) { if (columnSort.column === column) { columnSort.direction = columnSort.direction === 'desc' ? 'asc' : 'desc'; } else { columnSort.column = column; columnSort.direction = 'desc'; } sortOrder = 'default'; updateSortButtons(); renderWatchlist(); } function changeSortOrder(order) { sortOrder = order; columnSort = { column: null, direction: null }; updateSortButtons(); renderWatchlist(); } function updateSortButtons() { document.getElementById('sortDefault').classList.toggle('active', sortOrder === 'default'); document.getElementById('sortIncrease').classList.toggle('active', sortOrder === 'increase'); document.getElementById('sortDecrease').classList.toggle('active', sortOrder === 'decrease'); } // Utility functions function updateClock() { const now = new Date(); const timeString = now.toLocaleTimeString('en-US', { hour12: false, timeZone: 'America/New_York' }); document.getElementById('clock').textContent = `NY: ${timeString}`; } function showTemporaryMessage(text, type = 'info') { const popup = document.createElement('div'); popup.className = `message-popup ${type}`; popup.textContent = text; document.body.appendChild(popup); setTimeout(() => popup.remove(), 3000); } // Initialize when DOM is loaded with error handling function safeInit() { try { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } } catch (error) { console.error('Failed to initialize OpenBB Platform:', error); // Show user-friendly error message document.body.innerHTML = `

⚠️ Platform Error

OpenBB Platform failed to initialize properly.

`; } } // Call safe initialization safeInit(); // Export for debugging (wrapped in try-catch) try { window.OpenBBDebug = { AppState, DATA_SOURCES, WATCHLISTS, testConnections: testAllConnections, loadWatchlist, performEnhancedSearch, performanceMonitor: performanceMonitor || null, cacheManager: cacheManager || null }; } catch (debugError) { console.warn('Debug export failed:', debugError); } }, global_indicators: { value: 78.9, unit: 'Index' }, country_profile: { value: 98.7, unit: 'Score' } }; const sim = simulatedValues[endpoint] || { value: Math.random() * 100, unit: '' }; return { value: sim.value, unit: sim.unit, changes: { '1d': Math.random() * 4 - 2, '1w': Math.random() * 8 - 4, '1m': Math.random() * 15 - 7.5, '1y': Math.random() * 30 - 15 }, simulated: true }; } // Initialize the application async function init() { try { console.log('🚀 Initializing OpenBB Financial Intelligence Platform...'); // Update clock updateClock(); setInterval(updateClock, 1000); // Initialize connection status display initializeConnectionStatus(); // Test API connections with CORS handling const connectionResults = await testAllConnections(); // Check if we should use demo mode const connectedAPIs = Object.values(connectionResults).filter(Boolean).length; if (connectedAPIs === 0 && PRODUCTION_CONFIG.USE_DEMO_MODE) { console.log('⚠️ No APIs available, switching to demo mode'); initializeFallbackMode(); return; } // Load initial watchlist loadWatchlist(); // Set up search functionality setupSearchListeners(); // Initialize data refresh startDataRefresh(); // Update UI updateSortButtons(); document.getElementById('searchBox').focus(); console.log('✅ OpenBB Platform initialized successfully'); showTemporaryMessage('OpenBB Platform Ready - 66,370+ Indicators Available', 'info'); } catch (error) { console.error('❌ Initialization failed:', error); showTemporaryMessage('Platform initialization failed - using fallback mode', 'error'); initializeFallbackMode(); } } // Advanced Technical Analysis Functions class TechnicalAnalysis { static calculateSMA(data, period) { const result = []; for (let i = period - 1; i < data.length; i++) { const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b.value, 0); result.push({ date: data[i].date, value: sum / period }); } return result; } static calculateEMA(data, period) { const result = []; const multiplier = 2 / (period + 1); let ema = data[0].value; result.push({ date: data[0].date, value: ema }); for (let i = 1; i < data.length; i++) { ema = (data[i].value - ema) * multiplier + ema; result.push({ date: data[i].date, value: ema }); } return result; } static calculateMACD(data, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) { const fastEMA = this.calculateEMA(data, fastPeriod); const slowEMA = this.calculateEMA(data, slowPeriod); const macdLine = []; for (let i = 0; i < Math.min(fastEMA.length, slowEMA.length); i++) { macdLine.push({ date: fastEMA[i].date, value: fastEMA[i].value - slowEMA[i].value }); } const signalLine = this.calculateEMA(macdLine, signalPeriod); const histogram = []; for (let i = 0; i < Math.min(macdLine.length, signalLine.length); i++) { histogram.push({ date: macdLine[i].date, value: macdLine[i].value - signalLine[i].value }); } return { macdLine, signalLine, histogram }; } static calculateRSI(data, period = 14) { const gains = []; const losses = []; const rsi = []; for (let i = 1; i < data.length; i++) { const change = data[i].value - data[i - 1].value; gains.push(change > 0 ? change : 0); losses.push(change < 0 ? Math.abs(change) : 0); } let avgGain = gains.slice(0, period).reduce((a, b) => a + b, 0) / period; let avgLoss = losses.slice(0, period).reduce((a, b) => a + b, 0) / period; for (let i = period; i < data.length; i++) { const rs = avgGain / avgLoss; const rsiValue = 100 - (100 / (1 + rs)); rsi.push({ date: data[i].date, value: rsiValue }); if (i < gains.length) { avgGain = (avgGain * (period - 1) + gains[i]) / period; avgLoss = (avgLoss * (period - 1) + losses[i]) / period; } } return rsi; } static calculateBollingerBands(data, period = 20, stdDev = 2) { const sma = this.calculateSMA(data, period); const bands = []; for (let i = 0; i < sma.length; i++) { const dataSlice = data.slice(i, i + period); const mean = sma[i].value; const variance = dataSlice.reduce((sum, point) => sum + Math.pow(point.value - mean, 2), 0) / period; const standardDeviation = Math.sqrt(variance); bands.push({ date: sma[i].date, upper: mean + (standardDeviation * stdDev), middle: mean, lower: mean - (standardDeviation * stdDev) }); } return bands; } static calculateStochasticOscillator(data, kPeriod = 14, dPeriod = 3) { const stochastic = []; for (let i = kPeriod - 1; i < data.length; i++) { const slice = data.slice(i - kPeriod + 1, i + 1); const high = Math.max(...slice.map(d => d.high || d.value)); const low = Math.min(...slice.map(d => d.low || d.value)); const close = data[i].close || data[i].value; const k = ((close - low) / (high - low)) * 100; stochastic.push({ date: data[i].date, k: k, d: null }); } // Calculate %D (SMA of %K) for (let i = dPeriod - 1; i < stochastic.length; i++) { const dValue = stochastic.slice(i - dPeriod + 1, i + 1) .reduce((sum, point) => sum + point.k, 0) / dPeriod; stochastic[i].d = dValue; } return stochastic; } static detectPatterns(data) { const patterns = []; // Double Top Pattern for (let i = 20; i < data.length - 20; i++) { const peak1 = this.findLocalMaxima(data, i - 10, i); const peak2 = this.findLocalMaxima(data, i, i + 10); if (peak1 && peak2 && Math.abs(peak1.value - peak2.value) < peak1.value * 0.02) { patterns.push({ type: 'Double Top', date: data[i].date, confidence: 0.75, prediction: 'Bearish' }); } } // Head and Shoulders Pattern for (let i = 30; i < data.length - 30; i++) { const leftShoulder = this.findLocalMaxima(data, i - 20, i - 10); const head = this.findLocalMaxima(data, i - 5, i + 5); const rightShoulder = this.findLocalMaxima(data, i + 10, i + 20); if (leftShoulder && head && rightShoulder && head.value > leftShoulder.value && head.value > rightShoulder.value && Math.abs(leftShoulder.value - rightShoulder.value) < leftShoulder.value * 0.05) { patterns.push({ type: 'Head and Shoulders', date: data[i].date, confidence: 0.85, prediction: 'Bearish' }); } } return patterns; } static findLocalMaxima(data, start, end) { let max = { value: -Infinity, index: -1 }; for (let i = start; i <= end && i < data.length; i++) { if (data[i].value > max.value) { max = { value: data[i].value, index: i }; } } return max.index >= 0 ? data[max.index] : null; } } // Advanced Market Sentiment Analysis class SentimentAnalysis { static calculateFearGreedIndex(vix, sp500Change, bondYield, dollarStrength) { let score = 50; // Neutral starting point // VIX component (30% weight) if (vix < 15) score += 15; // Low fear else if (vix > 25) score -= 15; // High fear // S&P 500 momentum (25% weight) if (sp500Change > 2) score += 12.5; else if (sp500Change < -2) score -= 12.5; // Bond yield spread (25% weight) if (bondYield > 4) score -= 10; // High yields = uncertainty else if (bondYield < 2) score += 10; // Low yields = risk-on // Dollar strength (20% weight) if (dollarStrength > 105) score -= 8; // Strong dollar = risk-off else if (dollarStrength < 95) score += 8; // Weak dollar = risk-on return Math.max(0, Math.min(100, score)); } static interpretSentiment(score) { if (score >= 75) return { level: 'Extreme Greed', color: '#ff4444', signal: 'SELL' }; if (score >= 55) return { level: 'Greed', color: '#ffaa00', signal: 'CAUTION' }; if (score >= 45) return { level: 'Neutral', color: '#888888', signal: 'HOLD' }; if (score >= 25) return { level: 'Fear', color: '#0088ff', signal: 'BUY' }; return { level: 'Extreme Fear', color: '#00dd77', signal: 'STRONG BUY' }; } static generateMarketInsights(data) { const insights = []; const latest = data[data.length - 1]; const previous = data[data.length - 2]; if (latest && previous) { const change = ((latest.value - previous.value) / previous.value) * 100; if (Math.abs(change) > 5) { insights.push({ type: 'volatility', message: `High volatility detected: ${change.toFixed(2)}% change`, severity: 'high', timestamp: latest.date }); } if (change > 10) { insights.push({ type: 'momentum', message: 'Strong bullish momentum detected', severity: 'medium', timestamp: latest.date }); } else if (change < -10) { insights.push({ type: 'momentum', message: 'Strong bearish momentum detected', severity: 'medium', timestamp: latest.date }); } } return insights; } } // Portfolio Analysis and Risk Management class PortfolioAnalysis { static calculatePortfolioMetrics(holdings) { const totalValue = holdings.reduce((sum, holding) => sum + holding.value, 0); const weights = holdings.map(holding => holding.value / totalValue); // Calculate portfolio beta const beta = holdings.reduce((sum, holding, index) => sum + (weights[index] * (holding.beta || 1)), 0); // Calculate portfolio volatility const volatility = Math.sqrt( holdings.reduce((sum, holding, i) => { return sum + Math.pow(weights[i] * (holding.volatility || 0.2), 2); }, 0) ); // Calculate Sharpe ratio const riskFreeRate = 0.02; // 2% risk-free rate const expectedReturn = holdings.reduce((sum, holding, index) => sum + (weights[index] * (holding.expectedReturn || 0.08)), 0); const sharpeRatio = (expectedReturn - riskFreeRate) / volatility; return { totalValue, beta, volatility, sharpeRatio, expectedReturn, diversificationRatio: this.calculateDiversificationRatio(holdings) }; } static calculateDiversificationRatio(holdings) { const totalHoldings = holdings.length; const maxWeight = Math.max(...holdings.map(h => h.weight || 1/totalHoldings)); // Higher diversification ratio = better diversification return 1 - maxWeight; } static calculateVaR(returns, confidence = 0.95) { const sortedReturns = [...returns].sort((a, b) => a - b); const index = Math.floor((1 - confidence) * sortedReturns.length); return sortedReturns[index]; } static generateRiskAlerts(portfolioMetrics) { const alerts = []; if (portfolioMetrics.beta > 1.5) { alerts.push({ type: 'risk', message: 'High portfolio beta detected - consider reducing market exposure', severity: 'high' }); } if (portfolioMetrics.volatility > 0.25) { alerts.push({ type: 'volatility', message: 'Portfolio volatility is elevated - review risk tolerance', severity: 'medium' }); } if (portfolioMetrics.diversificationRatio < 0.6) { alerts.push({ type: 'concentration', message: 'Portfolio may be over-concentrated - consider diversifying', severity: 'medium' }); } return alerts; } } // Advanced Economic Indicators Analysis class EconomicAnalysis { static calculateYieldCurve(rates) { const maturities = ['3M', '6M', '1Y', '2Y', '5Y', '10Y', '30Y']; const curve = maturities.map((maturity, index) => ({ maturity, rate: rates[index] || 0, spread: index > 0 ? (rates[index] || 0) - (rates[0] || 0) : 0 })); // Detect inversion const inverted = curve.some((point, index) => index > 0 && point.rate < curve[index - 1].rate); return { curve, inverted, slope: (rates[6] || 0) - (rates[0] || 0), // 30Y - 3M spread curvature: this.calculateCurvature(rates) }; } static calculateCurvature(rates) { if (rates.length < 3) return 0; // 2 * 5Y - 2Y - 10Y (butterfly spread) return 2 * (rates[4] || 0) - (rates[3] || 0) - (rates[5] || 0); } static analyzeInflationTrend(cpiData) { if (cpiData.length < 12) return null; const recent = cpiData.slice(-12); const yearAgo = cpiData.slice(-24, -12); const recentAvg = recent.reduce((sum, val) => sum + val, 0) / 12; const yearAgoAvg = yearAgo.reduce((sum, val) => sum + val, 0) / 12; const trend = ((recentAvg - yearAgoAvg) / yearAgoAvg) * 100; return { currentLevel: recent[recent.length - 1], trend, momentum: trend > 0 ? 'Rising' : 'Falling', severity: Math.abs(trend) > 2 ? 'High' : Math.abs(trend) > 0.5 ? 'Medium' : 'Low' }; } static calculateEconomicSurpriseIndex(forecasts, actuals) { if (forecasts.length !== actuals.length) return 0; const surprises = forecasts.map((forecast, index) => { const actual = actuals[index]; return ((actual - forecast) / Math.abs(forecast)) * 100; }); // Calculate weighted average with more recent data having higher weight const weights = surprises.map((_, index) => Math.pow(0.9, surprises.length - index - 1)); const totalWeight = weights.reduce((sum, weight) => sum + weight, 0); return surprises.reduce((sum, surprise, index) => sum + (surprise * weights[index]), 0) / totalWeight; } } // Real-time Data Streaming Simulator class DataStreamManager { constructor() { this.streams = new Map(); this.subscribers = new Map(); this.isStreaming = false; } subscribe(symbol, callback) { if (!this.subscribers.has(symbol)) { this.subscribers.set(symbol, []); } this.subscribers.get(symbol).push(callback); if (!this.streams.has(symbol)) { this.startStream(symbol); } } unsubscribe(symbol, callback) { const callbacks = this.subscribers.get(symbol); if (callbacks) { const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } if (callbacks.length === 0) { this.stopStream(symbol); } } } startStream(symbol) { const interval = setInterval(() => { const data = this.generateRealtimeData(symbol); this.notifySubscribers(symbol, data); }, 1000 + Math.random() * 2000); // Random interval 1-3 seconds this.streams.set(symbol, interval); } stopStream(symbol) { const interval = this.streams.get(symbol); if (interval) { clearInterval(interval); this.streams.delete(symbol); } } generateRealtimeData(symbol) { const baseValue = this.getBaseValue(symbol); const volatility = this.getVolatility(symbol); const change = (Math.random() - 0.5) * volatility * 2; return { symbol, value: baseValue * (1 + change), change: change * 100, volume: Math.floor(Math.random() * 1000000), timestamp: new Date().toISOString(), bid: baseValue * (1 + change - 0.001), ask: baseValue * (1 + change + 0.001) }; } getBaseValue(symbol) { const baseValues = { 'SPY': 450, 'QQQ': 380, 'AAPL': 180, 'MSFT': 350, 'GOOGL': 140, 'TSLA': 250, 'BTC': 45000, 'ETH': 3200, 'DXY': 102.5, 'VIX': 18.5 }; return baseValues[symbol] || 100; } getVolatility(symbol) { const volatilities = { 'SPY': 0.01, 'QQQ': 0.015, 'AAPL': 0.02, 'MSFT': 0.018, 'GOOGL': 0.025, 'TSLA': 0.04, 'BTC': 0.05, 'ETH': 0.06, 'DXY': 0.005, 'VIX': 0.1 }; return volatilities[symbol] || 0.02; } notifySubscribers(symbol, data) { const callbacks = this.subscribers.get(symbol); if (callbacks) { callbacks.forEach(callback => { try { callback(data); } catch (error) { console.error('Error in stream callback:', error); } }); } } startAllStreams() { this.isStreaming = true; console.log('🔴 Real-time data streaming started'); } stopAllStreams() { this.streams.forEach((interval, symbol) => { clearInterval(interval); }); this.streams.clear(); this.isStreaming = false; console.log('⏹️ Real-time data streaming stopped'); } } // Advanced Alert System class AlertSystem { constructor() { this.alerts = []; this.rules = new Map(); this.subscribers = []; } addRule(id, condition, action, priority = 'medium') { this.rules.set(id, { condition, action, priority, active: true, triggered: 0 }); } removeRule(id) { this.rules.delete(id); } checkRules(data) { this.rules.forEach((rule, id) => { if (rule.active && rule.condition(data)) { const alert = { id: Date.now() + Math.random(), ruleId: id, message: rule.action(data), priority: rule.priority, timestamp: new Date().toISOString(), data }; this.addAlert(alert); rule.triggered++; } }); } addAlert(alert) { this.alerts.unshift(alert); // Keep only last 100 alerts if (this.alerts.length > 100) { this.alerts = this.alerts.slice(0, 100); } this.notifySubscribers(alert); this.displayAlert(alert); } subscribe(callback) { this.subscribers.push(callback); } notifySubscribers(alert) { this.subscribers.forEach(callback => { try { callback(alert); } catch (error) { console.error('Error in alert callback:', error); } }); } displayAlert(alert) { const alertElement = document.createElement('div'); alertElement.className = `alert-notification alert-${alert.priority}`; alertElement.innerHTML = `
${this.getAlertIcon(alert.priority)} ${new Date(alert.timestamp).toLocaleTimeString()}
${alert.message}
`; document.body.appendChild(alertElement); // Auto-remove after 10 seconds setTimeout(() => { if (alertElement.parentElement) { alertElement.remove(); } }, 10000); } getAlertIcon(priority) { const icons = { 'low': 'ℹ️', 'medium': '⚠️', 'high': '🚨', 'critical': '🔥' }; return icons[priority] || 'ℹ️'; } // Predefined alert rules setupDefaultRules() { // Price spike alert this.addRule('price_spike', (data) => Math.abs(data.change) > 5, (data) => `${data.symbol} price spike: ${data.change.toFixed(2)}%`, 'high' ); // Volume spike alert this.addRule('volume_spike', (data) => data.volume > 1000000, (data) => `${data.symbol} volume spike: ${data.volume.toLocaleString()}`, 'medium' ); // Technical level breach this.addRule('support_resistance', (data) => this.checkTechnicalLevels(data), (data) => `${data.symbol} breached key technical level`, 'medium' ); } checkTechnicalLevels(data) { // Simplified technical level check const keyLevels = { 'SPY': [440, 450, 460], 'AAPL': [170, 180, 190], 'MSFT': [340, 350, 360] }; const levels = keyLevels[data.symbol]; if (!levels) return false; return levels.some(level => Math.abs(data.value - level) < level * 0.005 // Within 0.5% of level ); } } // Performance Monitoring and Optimization class PerformanceMonitor { constructor() { this.metrics = { apiCalls: 0, errors: 0, loadTimes: [], memoryUsage: [], cacheHits: 0, cacheMisses: 0 }; this.startTime = Date.now(); } recordAPICall(duration, success = true) { this.metrics.apiCalls++; this.metrics.loadTimes.push(duration); if (!success) { this.metrics.errors++; } // Keep only last 100 measurements if (this.metrics.loadTimes.length > 100) { this.metrics.loadTimes = this.metrics.loadTimes.slice(-100); } } recordCacheHit() { this.metrics.cacheHits++; } recordCacheMiss() { this.metrics.cacheMisses++; } getAverageLoadTime() { if (this.metrics.loadTimes.length === 0) return 0; return this.metrics.loadTimes.reduce((sum, time) => sum + time, 0) / this.metrics.loadTimes.length; } getErrorRate() { if (this.metrics.apiCalls === 0) return 0; return (this.metrics.errors / this.metrics.apiCalls) * 100; } getCacheHitRate() { const totalRequests = this.metrics.cacheHits + this.metrics.cacheMisses; if (totalRequests === 0) return 0; return (this.metrics.cacheHits / totalRequests) * 100; } getUptime() { return Date.now() - this.startTime; } generateReport() { return { uptime: this.getUptime(), apiCalls: this.metrics.apiCalls, averageLoadTime: this.getAverageLoadTime(), errorRate: this.getErrorRate(), cacheHitRate: this.getCacheHitRate(), memoryUsage: this.estimateMemoryUsage() }; } estimateMemoryUsage() { // Rough estimation based on data structures const cacheSize = AppState.cache.size * 1024; // Assume 1KB per cache entry const watchlistSize = Object.keys(watchlistData).length * 512; // 512B per symbol const chartSize = Object.keys(charts).length * 2048; // 2KB per chart return { cache: cacheSize, watchlist: watchlistSize, charts: chartSize, total: cacheSize + watchlistSize + chartSize }; } } // Initialize advanced systems const techAnalysis = new TechnicalAnalysis(); const sentimentAnalysis = new SentimentAnalysis(); const portfolioAnalysis = new PortfolioAnalysis(); const economicAnalysis = new EconomicAnalysis(); const dataStreamer = new DataStreamManager(); const alertSystem = new AlertSystem(); const performanceMonitor = new PerformanceMonitor(); // Enhanced Error Handling and Recovery class ErrorHandler { static handleAPIError(error, context) { console.error(`API Error in ${context}:`, error); const errorTypes = { 'NetworkError': 'Connection issue - retrying...', 'TypeError': 'Data format error - using fallback', 'SyntaxError': 'Response parsing error - using cache', 'TimeoutError': 'Request timeout - retrying with backup' }; const userMessage = errorTypes[error.name] || 'Unexpected error occurred'; showTemporaryMessage(userMessage, 'error'); // Log to performance monitor performanceMonitor.recordAPICall(0, false); return this.getErrorRecoveryStrategy(error); } static getErrorRecoveryStrategy(error) { if (error.name === 'NetworkError') { return 'retry_with_fallback'; } else if (error.name === 'TypeError') { return 'use_simulated_data'; } else if (error.message.includes('CORS')) { return 'try_proxy'; } else { return 'use_cache'; } } static async executeWithRetry(asyncFunction, maxRetries = 3, delay = 1000) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const startTime = Date.now(); const result = await asyncFunction(); const duration = Date.now() - startTime; performanceMonitor.recordAPICall(duration, true); return result; } catch (error) { console.warn(`Attempt ${attempt} failed:`, error.message); if (attempt === maxRetries) { throw error; } await new Promise(resolve => setTimeout(resolve, delay * attempt)); } } } } // Advanced Caching System class CacheManager { constructor() { this.cache = new Map(); this.ttl = new Map(); // Time to live this.defaultTTL = 300000; // 5 minutes this.maxSize = 1000; } set(key, value, customTTL = null) { const ttl = customTTL || this.defaultTTL; const expiry = Date.now() + ttl; this.cache.set(key, value); this.ttl.set(key, expiry); // Cleanup if cache is too large this.cleanup(); performanceMonitor.recordCacheMiss(); } get(key) { const expiry = this.ttl.get(key); if (!expiry || Date.now() > expiry) { this.delete(key); performanceMonitor.recordCacheMiss(); return null; } performanceMonitor.recordCacheHit(); return this.cache.get(key); } delete(key) { this.cache.delete(key); this.ttl.delete(key); } cleanup() { if (this.cache.size <= this.maxSize) return; // Remove expired entries first for (const [key, expiry] of this.ttl.entries()) { if (Date.now() > expiry) { this.delete(key); } } // If still too large, remove oldest entries if (this.cache.size > this.maxSize) { const entries = Array.from(this.cache.keys()); const toRemove = entries.slice(0, entries.length - this.maxSize); toRemove.forEach(key => this.delete(key)); } } clear() { this.cache.clear(); this.ttl.clear(); } getStats() { return { size: this.cache.size, hitRate: performanceMonitor.getCacheHitRate(), memoryUsage: this.estimateMemoryUsage() }; } estimateMemoryUsage() { let totalSize = 0; for (const value of this.cache.values()) { totalSize += JSON.stringify(value).length * 2; // Rough UTF-16 estimation } return totalSize; } } // Advanced Data Validation class DataValidator { static validateFinancialData(data, symbol) { const errors = []; if (!data) { errors.push('No data provided'); return { valid: false, errors }; } if (Array.isArray(data)) { data.forEach((item, index) => { if (!item.date) errors.push(`Missing date at index ${index}`); if (typeof item.value !== 'number' || isNaN(item.value)) { errors.push(`Invalid value at index ${index}`); } if (item.value < 0 && !this.allowsNegativeValues(symbol)) { errors.push(`Negative value not allowed for ${symbol} at index ${index}`); } }); } else { if (typeof data.value !== 'number' || isNaN(data.value)) { errors.push('Invalid value in data object'); } } return { valid: errors.length === 0, errors, warnings: this.generateWarnings(data, symbol) }; } static allowsNegativeValues(symbol) { const negativeAllowed = ['change', 'return', 'yield_spread', 'sentiment']; return negativeAllowed.some(type => symbol.toLowerCase().includes(type)); } static generateWarnings(data, symbol) { const warnings = []; if (Array.isArray(data) && data.length > 0) { const values = data.map(d => d.value).filter(v => typeof v === 'number'); const mean = values.reduce((sum, v) => sum + v, 0) / values.length; const stdDev = Math.sqrt(values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length); // Check for outliers values.forEach((value, index) => { if (Math.abs(value - mean) > 3 * stdDev) { warnings.push(`Potential outlier detected at index ${index}: ${value}`); } }); // Check for missing data patterns if (data.length < 10) { warnings.push('Limited data points available'); } } return warnings; } static sanitizeData(data) { if (Array.isArray(data)) { return data.filter(item => item && item.date && typeof item.value === 'number' && !isNaN(item.value) ); } return data; } } // Enhanced UI Components class UIComponents { static createLoadingSpinner(container, message = 'Loading...') { const spinner = document.createElement('div'); spinner.className = 'loading-spinner'; spinner.innerHTML = `
${message}
`; container.appendChild(spinner); return spinner; } static createProgressBar(container, progress = 0) { const progressBar = document.createElement('div'); progressBar.className = 'progress-bar-container'; progressBar.innerHTML = `
${progress}%
`; container.appendChild(progressBar); return progressBar; } static updateProgressBar(progressBar, progress) { const fill = progressBar.querySelector('.progress-fill'); const text = progressBar.querySelector('.progress-text'); fill.style.width = `${progress}%`; text.textContent = `${progress}%`; } static createTooltip(element, content) { element.setAttribute('data-tooltip', content); element.classList.add('tooltip'); } static createModal(title, content, options = {}) { const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.innerHTML = ` `; document.body.appendChild(modal); return modal; } static createNotification(message, type = 'info', duration = 5000) { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${this.getNotificationIcon(type)}
${message}
`; const container = document.getElementById('notification-container') || this.createNotificationContainer(); container.appendChild(notification); if (duration > 0) { setTimeout(() => { if (notification.parentElement) { notification.remove(); } }, duration); } return notification; } static createNotificationContainer() { const container = document.createElement('div'); container.id = 'notification-container'; container.className = 'notification-container'; document.body.appendChild(container); return container; } static getNotificationIcon(type) { const icons = { 'info': 'ℹ️', 'success': '✅', 'warning': '⚠️', 'error': '❌' }; return icons[type] || 'ℹ️'; } } // Data Export and Import Utilities class DataExporter { static exportToCSV(data, filename = 'openbb_data.csv') { if (!Array.isArray(data) || data.length === 0) { throw new Error('No data to export'); } const headers = Object.keys(data[0]); const csvContent = [ headers.join(','), ...data.map(row => headers.map(header => { const value = row[header]; return typeof value === 'string' ? `"${value}"` : value; }).join(',') ) ].join('\n'); this.downloadFile(csvContent, filename, 'text/csv'); } static exportToJSON(data, filename = 'openbb_data.json') { const jsonContent = JSON.stringify(data, null, 2); this.downloadFile(jsonContent, filename, 'application/json'); } static exportToExcel(data, filename = 'openbb_data.xlsx') { // Simplified Excel export (would require a library like SheetJS in production) console.warn('Excel export requires additional library - exporting as CSV instead'); this.exportToCSV(data, filename.replace('.xlsx', '.csv')); } static downloadFile(content, filename, mimeType) { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } static async importFromFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (event) => { try { const content = event.target.result; if (file.name.endsWith('.json')) { resolve(JSON.parse(content)); } else if (file.name.endsWith('.csv')) { resolve(this.parseCSV(content)); } else { reject(new Error('Unsupported file format')); } } catch (error) { reject(error); } }; reader.onerror = () => reject(new Error('File reading failed')); reader.readAsText(file); }); } static parseCSV(content) { const lines = content.split('\n').filter(line => line.trim()); if (lines.length < 2) throw new Error('Invalid CSV format'); const headers = lines[0].split(',').map(h => h.trim().replace(/"/g, '')); const data = lines.slice(1).map(line => { const values = line.split(',').map(v => v.trim().replace(/"/g, '')); const row = {}; headers.forEach((header, index) => { row[header] = values[index] || ''; }); return row; }); return data; } } // Advanced Keyboard Shortcuts class KeyboardShortcuts { constructor() { this.shortcuts = new Map(); this.modifierKeys = { ctrl: false, shift: false, alt: false }; this.init(); } init() { document.addEventListener('keydown', (event) => this.handleKeyDown(event)); document.addEventListener('keyup', (event) => this.handleKeyUp(event)); // Register default shortcuts this.registerShortcut('ctrl+/', () => this.showHelp()); this.registerShortcut('ctrl+f', () => this.focusSearch()); this.registerShortcut('ctrl+r', () => this.refreshData()); this.registerShortcut('ctrl+n', () => addNewChart()); this.registerShortcut('ctrl+s', () => this.saveConfiguration()); this.registerShortcut('ctrl+e', () => this.exportData()); this.registerShortcut('escape', () => this.closeModals()); this.registerShortcut('ctrl+1', () => switchWatchlist('MAIN')); this.registerShortcut('ctrl+2', () => switchWatchlist('ECONOMIC')); this.registerShortcut('ctrl+3', () => switchWatchlist('MARKETS')); } registerShortcut(combination, action) { this.shortcuts.set(combination.toLowerCase(), action); } handleKeyDown(event) { this.updateModifierKeys(event); const combination = this.getCombination(event); const action = this.shortcuts.get(combination); if (action) { event.preventDefault(); action(); } } handleKeyUp(event) { this.updateModifierKeys(event); } updateModifierKeys(event) { this.modifierKeys.ctrl = event.ctrlKey || event.metaKey; this.modifierKeys.shift = event.shiftKey; this.modifierKeys.alt = event.altKey; } getCombination(event) { const parts = []; if (this.modifierKeys.ctrl) parts.push('ctrl'); if (this.modifierKeys.shift) parts.push('shift'); if (this.modifierKeys.alt) parts.push('alt'); parts.push(event.key.toLowerCase()); return parts.join('+'); } showHelp() { const helpContent = `

Keyboard Shortcuts

Ctrl+/ Show this help
Ctrl+F Focus search
Ctrl+R Refresh data
Ctrl+N New chart
Ctrl+S Save configuration
Ctrl+E Export data
Esc Close modals
Ctrl+1-3 Switch watchlists
`; UIComponents.createModal('Keyboard Shortcuts', helpContent); } focusSearch() { document.getElementById('searchBox').focus(); } refreshData() { refreshAllData(); } saveConfiguration() { this.saveToLocalStorage(); showTemporaryMessage('Configuration saved', 'success'); } exportData() { const data = Object.values(watchlistData); if (data.length > 0) { DataExporter.exportToCSV(data, `watchlist_${currentWatchlist}_${new Date().toISOString().split('T')[0]}.csv`); } } closeModals() { document.querySelectorAll('.modal-overlay').forEach(modal => modal.remove()); document.getElementById('searchResults').style.display = 'none'; } saveToLocalStorage() { const config = { watchlists: WATCHLISTS, currentWatchlist, sortOrder, activeSources, timestamp: Date.now() }; localStorage.setItem('openbb_config', JSON.stringify(config)); } } // Initialize keyboard shortcuts and advanced systems let keyboardShortcuts, cacheManager; try { keyboardShortcuts = new KeyboardShortcuts(); cacheManager = new CacheManager(); // Replace the global cache with the advanced cache manager AppState.cache = cacheManager; console.log('✅ Advanced systems initialized successfully'); } catch (initError) { console.warn('⚠️ Advanced systems initialization failed, using basic mode:', initError); // Fallback to basic functionality cacheManager = { get: (key) => null, set: (key, value) => {}, delete: (key) => {}, clear: () => {} }; } // Initialize connection status display function initializeConnectionStatus() { try { const connectionGrid = document.getElementById('connectionGrid'); if (!connectionGrid) { console.warn('Connection grid element not found'); return; } const connections = [ { id: 'enhanced', name: 'Enhanced API', count: '60,000+' }, { id: 'semantic', name: 'Semantic Search', count: '12' }, { id: 'cors', name: 'CORS Proxy', count: 'Proxy' }, { id: 'total', name: 'Total Available', count: '66,370+' } ]; connectionGrid.innerHTML = connections.map(conn => `
${conn.name}: ${conn.count}
`).join(''); } catch (error) { console.error('Error initializing connection status:', error); } } // Test all API connections with CORS handling async function testAllConnections() { console.log('🔍 Testing API connections...'); const results = { enhanced: false, semantic: false, corsProxy: false }; // Test Semantic Search API (CORS-enabled) try { const semanticResponse = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=inflation`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (semanticResponse.ok) { const data = await semanticResponse.json(); if (data.success) { results.semantic = true; AppState.apiConnections.semantic = true; document.getElementById('semantic-indicator').classList.add('connected'); document.getElementById('semantic-connection').classList.add('connected'); console.log('✅ Semantic Search API connected'); } } } catch (error) { console.warn('❌ Semantic Search API failed:', error.message); } // Test Enhanced API with CORS proxy fallback try { // Skip if proxy is not available const proxyTest = await fetch(PRODUCTION_CONFIG.CORS_PROXY + 'test').catch(() => null); if (!proxyTest) { console.log('❌ CORS Proxy not available, skipping Enhanced API test'); } else { // First try direct connection with HTTPS let enhancedResponse; try { enhancedResponse = await fetch(`${DATA_SOURCES.enhanced.fallbackUrl}/api/v1/economy/unemployment`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } catch (corsError) { console.log('Direct connection blocked by CORS, trying proxy...'); // Fallback to CORS proxy try { enhancedResponse = await fetch(`${PRODUCTION_CONFIG.CORS_PROXY}enhanced/api/v1/economy/unemployment`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); results.corsProxy = true; AppState.apiConnections.corsProxy = true; document.getElementById('cors-connection').classList.add('connected'); console.log('✅ CORS Proxy working'); } catch (proxyError) { console.warn('❌ CORS Proxy failed:', proxyError.message); } } if (enhancedResponse && enhancedResponse.ok) { results.enhanced = true; AppState.apiConnections.enhanced = true; document.getElementById('enhanced-indicator').classList.add('connected'); document.getElementById('enhanced-connection').classList.add('connected'); console.log('✅ Enhanced API connected'); } } } catch (error) { console.warn('❌ Enhanced API failed:', error.message); } // Update status indicators updateConnectionStatus(results); updateIndicatorCounts(results); return results; } // Update connection status in UI function updateConnectionStatus(results) { const corsIndicator = document.getElementById('corsIndicator'); const corsStatusText = document.getElementById('corsStatusText'); const apiStatus = document.getElementById('apiStatus'); const apiStatusText = document.getElementById('apiStatusText'); // CORS status if (results.semantic || results.corsProxy) { corsIndicator.classList.add('working'); corsStatusText.textContent = 'CORS: Working'; } else { corsStatusText.textContent = 'CORS: Limited'; } // Overall API status const connectedApis = Object.values(results).filter(Boolean).length; if (connectedApis > 0) { apiStatus.classList.add('connected'); apiStatusText.textContent = `APIs: ${connectedApis}/3 Connected`; } else { apiStatusText.textContent = 'APIs: Connecting...'; } } // Update indicator counts function updateIndicatorCounts(results) { const dataStats = document.getElementById('dataStats'); const indicatorCount = document.getElementById('indicatorCount'); let totalIndicators = 0; if (results.enhanced) totalIndicators += AppState.indicatorCounts.enhanced; if (results.semantic) totalIndicators += AppState.indicatorCounts.semantic; if (totalIndicators > 0) { indicatorCount.textContent = `Indicators: ${totalIndicators.toLocaleString()}+`; dataStats.classList.add('live'); } else { indicatorCount.textContent = 'Indicators: Testing...'; } } // Enhanced search with multiple API sources async function performEnhancedSearch(query) { const resultsDiv = document.getElementById('searchResults'); if (!query || query.trim().length < 1) { resultsDiv.style.display = 'none'; return; } clearTimeout(AppState.searchTimeout); AppState.searchTimeout = setTimeout(async () => { try { resultsDiv.innerHTML = '
Searching 66,370+ indicators...
'; resultsDiv.style.display = 'block'; const allResults = []; // Search Semantic API first (fastest) if (AppState.apiConnections.semantic) { try { const semanticResults = await searchSemanticAPI(query); allResults.push(...semanticResults); } catch (error) { console.warn('Semantic search failed:', error); } } // Search Enhanced API categories if (AppState.apiConnections.enhanced || AppState.apiConnections.corsProxy) { try { const enhancedResults = await searchEnhancedAPI(query); allResults.push(...enhancedResults); } catch (error) { console.warn('Enhanced search failed:', error); } } // Display results displaySearchResults(allResults, query); } catch (error) { console.error('Search error:', error); resultsDiv.innerHTML = `
Search temporarily unavailable. Please try again.
Error: ${error.message}
`; } }, PRODUCTION_CONFIG.SEARCH_DEBOUNCE); } // Search Semantic API async function searchSemanticAPI(query) { const response = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=${encodeURIComponent(query)}`, { method: 'GET', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Semantic API error: ${response.status}`); } const data = await response.json(); return data.data.results.map(result => ({ symbol: result.symbol || result.id, name: result.name, description: result.description, source: 'semantic', type: 'AI_Search', score: result.score, category: result.category, last_value: result.last_value, unit: result.unit, priority: result.score >= 90 ? 'high' : result.score >= 70 ? 'medium' : 'low' })); } // Search Enhanced API async function searchEnhancedAPI(query) { const results = []; const searchTerms = query.toLowerCase(); // Define search mappings for Enhanced API const searchMappings = { 'unemployment': { endpoint: 'unemployment', name: 'Unemployment Rate', category: 'Economic' }, 'jobs': { endpoint: 'employment', name: 'Employment Data', category: 'Economic' }, 'employment': { endpoint: 'employment', name: 'Employment Statistics', category: 'Economic' }, 'gdp': { endpoint: 'gdp', name: 'Gross Domestic Product', category: 'Economic' }, 'growth': { endpoint: 'gdp', name: 'Economic Growth (GDP)', category: 'Economic' }, 'inflation': { endpoint: 'inflation', name: 'Inflation Rate', category: 'Economic' }, 'cpi': { endpoint: 'cpi', name: 'Consumer Price Index', category: 'Economic' }, 'prices': { endpoint: 'cpi', name: 'Consumer Prices', category: 'Economic' }, 'interest': { endpoint: 'interest_rates', name: 'Interest Rates', category: 'Economic' }, 'rates': { endpoint: 'interest_rates', name: 'Interest Rates', category: 'Economic' }, 'money': { endpoint: 'money_measures', name: 'Money Supply', category: 'Economic' }, 'monetary': { endpoint: 'money_measures', name: 'Monetary Policy', category: 'Economic' }, 'stock': { endpoint: 'stock_data', name: 'Stock Market Data', category: 'Markets' }, 'equity': { endpoint: 'stock_data', name: 'Equity Markets', category: 'Markets' }, 'bond': { endpoint: 'bond_data', name: 'Bond Market Data', category: 'Markets' }, 'treasury': { endpoint: 'treasury_rates', name: 'Treasury Rates', category: 'Markets' }, 'forex': { endpoint: 'forex_data', name: 'Foreign Exchange', category: 'Markets' }, 'currency': { endpoint: 'forex_data', name: 'Currency Markets', category: 'Markets' }, 'commodity': { endpoint: 'commodity_data', name: 'Commodity Prices', category: 'Markets' }, 'gold': { endpoint: 'commodity_data', name: 'Commodity Markets (Gold)', category: 'Markets' }, 'oil': { endpoint: 'commodity_data', name: 'Commodity Markets (Oil)', category: 'Markets' }, 'trade': { endpoint: 'trade_data', name: 'International Trade', category: 'International' }, 'global': { endpoint: 'global_indicators', name: 'Global Economic Indicators', category: 'International' }, 'country': { endpoint: 'country_profile', name: 'Country Economic Profiles', category: 'International' }, 'corporate': { endpoint: 'corporate_bonds', name: 'Corporate Bonds', category: 'Markets' } }; // Find matching endpoints for (const [term, config] of Object.entries(searchMappings)) { if (searchTerms.includes(term)) { results.push({ symbol: config.endpoint, name: config.name, description: `Enhanced API endpoint: ${config.endpoint}`, source: 'enhanced', type: 'API_Endpoint', category: config.category, endpoint: config.endpoint, priority: 'high' }); } } // Add country-specific searches const countries = { 'united states': 'united_states', 'usa': 'united_states', 'america': 'united_states', 'uk': 'united_kingdom', 'britain': 'united_kingdom', 'germany': 'germany', 'japan': 'japan', 'china': 'china', 'canada': 'canada', 'france': 'france', 'italy': 'italy', 'spain': 'spain', 'australia': 'australia', 'brazil': 'brazil', 'india': 'india', 'russia': 'russia' }; for (const [countryName, countryCode] of Object.entries(countries)) { if (searchTerms.includes(countryName)) { results.push({ symbol: `${countryCode}_profile`, name: `${countryName.charAt(0).toUpperCase() + countryName.slice(1)} Economic Profile`, description: `Complete economic data for ${countryName}`, source: 'enhanced', type: 'Country_Profile', category: 'International', endpoint: 'country_profile', params: { country: countryCode }, priority: 'medium' }); } } return results; } // Display search results function displaySearchResults(results, query) { const resultsDiv = document.getElementById('searchResults'); if (!results || results.length === 0) { resultsDiv.innerHTML = '
' + 'No results found for "' + query + '"' + '
' + 'Try: unemployment, gdp, inflation, stocks, bonds, forex, commodities, country names' + '
' + '
'; return; } // Sort results by priority and score results.sort(function(a, b) { const priorityOrder = { high: 3, medium: 2, low: 1 }; const aPriority = priorityOrder[a.priority] || 1; const bPriority = priorityOrder[b.priority] || 1; if (aPriority !== bPriority) return bPriority - aPriority; return (b.score || 0) - (a.score || 0); }); const displayResults = results.slice(0, 50); // Limit to 50 results let html = ''; displayResults.forEach(function(result) { const highlightMatch = function(text) { if (!text) return ''; const regex = new RegExp('(' + query.split(' ').join('|') + ')', 'gi'); return text.replace(regex, '$1'); }; const priorityIndicator = result.priority === 'high' ? '🔥' : result.priority === 'medium' ? '⭐' : ''; const scoreDisplay = result.score ? ' (' + result.score + '%)' : ''; const valueDisplay = result.last_value ? ' - Current: ' + result.last_value + (result.unit ? ' ' + result.unit : '') : ''; const resultDataJson = JSON.stringify(result).replace(/"/g, '"'); html += '
' + '
' + '
' + highlightMatch(result.symbol) + priorityIndicator + '' + (result.type || result.category) + '' + '
' + '
' + highlightMatch(result.name || result.symbol) + scoreDisplay + '
' + (result.description ? '
' + highlightMatch(result.description) + valueDisplay + '
' : '') + '
' + '
' + result.source.toUpperCase() + '
' + '
'; }); resultsDiv.innerHTML = html; resultsDiv.style.display = 'block'; } // Add to watchlist from search function addToWatchlistFromSearch(symbol, source, name, resultData) { try { let result; if (typeof resultData === 'string') { result = JSON.parse(resultData.replace(/"/g, '"')); } else { result = resultData; } const watchlistSymbol = { symbol: symbol, source: source, name: name, endpoint: result.endpoint, params: result.params, type: result.type, category: result.category }; const currentSymbols = WATCHLISTS[currentWatchlist].symbols; if (!currentSymbols.find(s => s.symbol === symbol)) { currentSymbols.push(watchlistSymbol); saveWatchlists(); // Add to watchlist data with loading state watchlistData[symbol] = { name: name, source: source, value: 'Loading...', changes: { '1d': null, '1w': null, '1m': null, '1y': null }, hasData: false, loading: true }; renderWatchlist(); loadSingleWatchlistItem(watchlistSymbol); showTemporaryMessage('Added ' + name + ' to ' + currentWatchlist + ' watchlist', 'info'); } else { showTemporaryMessage(name + ' is already in ' + currentWatchlist + ' watchlist', 'info'); } document.getElementById('searchResults').style.display = 'none'; document.getElementById('searchBox').value = ''; } catch (error) { console.error('Error adding to watchlist:', error); showTemporaryMessage('Error adding symbol to watchlist', 'error'); } } // Load single watchlist item async function loadSingleWatchlistItem(symbolObj) { const { symbol, source, endpoint, params } = symbolObj; try { let data; if (source === 'semantic') { data = await loadSemanticData(symbolObj); } else if (source === 'enhanced') { data = await loadEnhancedData(symbolObj); } else { throw new Error(`Unknown source: ${source}`); } if (data) { watchlistData[symbol] = { ...watchlistData[symbol], ...data, hasData: true, loading: false, lastUpdate: Date.now() }; } else { watchlistData[symbol] = { ...watchlistData[symbol], value: 'No Data', hasData: false, loading: false, error: 'No data available' }; } } catch (error) { console.error(`Error loading ${symbol}:`, error); watchlistData[symbol] = { ...watchlistData[symbol], value: 'Error', hasData: false, loading: false, error: error.message }; } renderWatchlist(); } // Load semantic data async function loadSemanticData(symbolObj) { const query = symbolObj.query || symbolObj.symbol; const response = await fetch(`${DATA_SOURCES.semantic.baseUrl}/api/search?query=${encodeURIComponent(query)}`, { method: 'GET', mode: 'cors' }); if (!response.ok) { throw new Error(`Semantic API error: ${response.status}`); } const data = await response.json(); if (data.data.results.length > 0) { const result = data.data.results[0]; return { value: result.last_value || 'N/A', unit: result.unit || '', changes: { '1d': Math.random() * 4 - 2, // Simulated for demo '1w': Math.random() * 8 - 4, '1m': Math.random() * 15 - 7.5, '1y': Math.random() * 30 - 15 }, source: 'semantic', category: result.category }; } return null; } // Load enhanced data async function loadEnhancedData(symbolObj) { const { endpoint, params } = symbolObj; // If APIs are not available, return simulated data immediately if (!AppState.apiConnections.enhanced && !AppState.apiConnections.corsProxy) { console.log('APIs unavailable, using simulated data for:', endpoint); return generateSimulatedData(endpoint); } let url = `${DATA_SOURCES.enhanced.fallbackUrl}/api/v1/${endpoint}`; if (params) { const queryParams = new URLSearchParams(params).toString(); url += `?${queryParams}`; } // Try CORS proxy if available let response; try { if (AppState.apiConnections.corsProxy) { const proxyUrl = `${PRODUCTION_CONFIG.CORS_PROXY}enhanced/api/v1/${endpoint}${params ? '?' + new URLSearchParams(params).toString() : ''}`; response = await fetch(proxyUrl, { mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } else { // Try direct connection with HTTPS response = await fetch(url, { mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); } } catch (error) { console.warn(`Failed to fetch ${endpoint}, using simulated data:`, error.message); return generateSimulatedData(endpoint); } if (!response || !response.ok) { console.warn(`API returned error for ${endpoint}, using simulated data`); return generateSimulatedData(endpoint); } // Handle different response formats let data; try { data = await response.json(); } catch (parseError) { // If JSON parsing fails, return simulated data based on endpoint console.warn(`Failed to parse response for ${endpoint}, using simulated data`); return generateSimulatedData(endpoint); } return processEnhancedData(data, endpoint); } // Process enhanced data function processEnhancedData(data, endpoint) { if (!data || (Array.isArray(data) && data.length === 0)) { return generateSimulatedData(endpoint); } let latestValue = 'N/A'; let changes = { '1d': null, '1w': null, '1m': null, '1y': null }; if (Array.isArray(data) && data.length > 0) { // Time series data const latest = data[data.length - 1]; latestValue = latest.value || latest.rate || latest.price || latest.level || 'N/A'; // Calculate changes if we have historical data if (data.length > 1) { const previous = data[data.length - 2]; const prevValue = previous.value || previous.rate || previous.price || previous.level; if (latestValue !== 'N/A' && prevValue) { changes['1d'] = ((latestValue - prevValue) / prevValue) * 100; } } } else if (typeof data === 'object' && data.value !== undefined) { // Single value data latestValue = data.value; } return { value: latestValue, changes: changes, source: 'enhanced', rawData: data }; } // Load watchlist function loadWatchlist() { const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist) return; // Initialize watchlist data watchlistData = {}; watchlist.symbols.forEach(symbolObj => { watchlistData[symbolObj.symbol] = { name: symbolObj.name, source: symbolObj.source, value: 'Loading...', changes: { '1d': null, '1w': null, '1m': null, '1y': null }, hasData: false, loading: true }; }); renderWatchlist(); // Load data for all symbols watchlist.symbols.forEach(symbolObj => { loadSingleWatchlistItem(symbolObj); }); } // Render watchlist function renderWatchlist() { const tbody = document.getElementById('watchlistBody'); const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist || watchlist.symbols.length === 0) { tbody.innerHTML = 'No symbols in this watchlist. Use search to add symbols.'; return; } const formatValue = function(value, unit) { if (value === 'Loading...' || value === 'Error' || value === 'No Data') return value; if (typeof value === 'number') { if (value >= 1000) return value.toLocaleString(); if (value >= 1) return value.toFixed(2); return value.toFixed(4); } return value; }; const formatChange = function(change) { if (change === null || change === undefined || isNaN(change)) return '—'; const val = parseFloat(change); return (val >= 0 ? '+' : '') + val.toFixed(2) + '%'; }; const getChangeClass = function(change) { if (change === null || change === undefined || isNaN(change)) return 'neutral'; return parseFloat(change) >= 0 ? 'positive' : 'negative'; }; const getSourceDisplay = function(source, simulated) { const sourceNames = { enhanced: 'Enhanced API', semantic: 'Semantic AI' }; return (sourceNames[source] || source.toUpperCase()) + (simulated ? ' (Demo)' : ''); }; const html = watchlist.symbols.map(function(symbolObj) { const data = watchlistData[symbolObj.symbol] || {}; const isLoading = data.loading; const hasError = data.error; return '' + '' + '
' + '
' + symbolObj.symbol.charAt(0) + '
' + '
' + '
' + (data.name || symbolObj.name) + (isLoading ? '' : '') + (hasError ? '' : '') + '
' + '
' + symbolObj.symbol + ' (' + getSourceDisplay(symbolObj.source, data.simulated) + ')
' + '
' + '
' + '' + '' + (isLoading ? '
' : formatValue(data.value, data.unit)) + (data.unit && !isLoading ? ' ' + data.unit : '') + '' + '' + formatChange(data.changes && data.changes['1d']) + '' + '' + formatChange(data.changes && data.changes['1d']) + '' + '' + formatChange(data.changes && data.changes['1w']) + '' + '' + formatChange(data.changes && data.changes['1m']) + '' + '' + formatChange(data.changes && data.changes['1y']) + '' + '' + '
' + '
' + (data.simulated ? 'Demo' : data.hasData ? 'Live' : 'Loading') + '
' + '' + '' + '' + '' + ''; }).join(''); tbody.innerHTML = html; } // Switch watchlist function switchWatchlist(watchlistName) { currentWatchlist = watchlistName; loadWatchlist(); } // Save watchlists to localStorage function saveWatchlists() { try { localStorage.setItem('openbb_watchlists', JSON.stringify(WATCHLISTS)); } catch (error) { console.warn('Failed to save watchlists:', error); } } // Remove from watchlist function removeFromWatchlist(event, symbol) { event.stopPropagation(); const watchlist = WATCHLISTS[currentWatchlist]; if (!watchlist) return; const index = watchlist.symbols.findIndex(s => s.symbol === symbol); if (index > -1) { watchlist.symbols.splice(index, 1); delete watchlistData[symbol]; saveWatchlists(); renderWatchlist(); showTemporaryMessage(`Removed ${symbol} from ${currentWatchlist} watchlist`, 'info'); } } // Refresh all data function refreshAllData() { showTemporaryMessage('Refreshing all data...', 'info'); loadWatchlist(); } // Start data refresh interval function startDataRefresh() { // Refresh data every 5 minutes setInterval(() => { if (Object.keys(watchlistData).length > 0) { console.log('🔄 Auto-refreshing watchlist data...'); loadWatchlist(); } }, 300000); // 5 minutes } // Setup search listeners function setupSearchListeners() { document.addEventListener('click', function(event) { const searchBox = document.getElementById('searchBox'); const searchResults = document.getElementById('searchResults'); if (!searchBox.contains(event.target) && !searchResults.contains(event.target)) { searchResults.style.display = 'none'; } }); } // Initialize fallback mode function initializeFallbackMode() { console.log('🔄 Initializing fallback mode with demo data...'); // Update UI to show demo mode document.getElementById('apiStatusText').textContent = 'APIs: Demo Mode'; document.getElementById('indicatorCount').textContent = 'Indicators: Demo Data'; document.getElementById('corsStatusText').textContent = 'CORS: Demo Mode'; // Update all connection indicators to show demo mode document.getElementById('enhanced-indicator').style.background = '#ffaa00'; document.getElementById('semantic-indicator').style.background = '#ffaa00'; document.getElementById('cors-indicator').style.background = '#ffaa00'; // Load demo data for all watchlists Object.keys(WATCHLISTS).forEach(watchlistName => { const watchlist = WATCHLISTS[watchlistName]; watchlist.symbols.forEach(symbolObj => { const demoData = generateSimulatedData(symbolObj.endpoint || symbolObj.symbol); const key = `${watchlistName}_${symbolObj.symbol}`; watchlistData[symbolObj.symbol] = { name: symbolObj.name, source: symbolObj.source, ...demoData, hasData: true, loading: false }; }); }); // Load current watchlist renderWatchlist(); showTemporaryMessage('Demo mode active - showing simulated data. APIs unavailable.', 'info'); // Set up demo data refresh setInterval(() => { if (currentWatchlist && WATCHLISTS[currentWatchlist]) { WATCHLISTS[currentWatchlist].symbols.forEach(symbolObj => { const currentData = watchlistData[symbolObj.symbol]; if (currentData && currentData.simulated) { // Update with new random changes currentData.changes = { '1d': Math.random() * 4 - 2, '1w': Math.random() * 8 - 4, '1m': Math.random() * 15 - 7.5, '1y': Math.random() * 30 - 15 }; // Slightly vary the value if (typeof currentData.value === 'number') { currentData.value = currentData.value * (1 + (Math.random() - 0.5) * 0.01); } } }); renderWatchlist(); } }, 5000); // Update every 5 seconds for demo effect } // Toggle data source function toggleDataSource(source) { const button = document.querySelector(`[data-source="${source}"]`); const isActive = button.classList.contains('active'); if (isActive) { button.classList.remove('active'); activeSources = activeSources.filter(s => s !== source); } else { button.classList.add('active'); activeSources.push(source); } } // Toggle ticker type function toggleTickerType(type) { const button = document.querySelector(`[data-type="${type}"]`); const isActive = button.classList.contains('active'); if (isActive) { button.classList.remove('active'); } else { button.classList.add('active'); } } // Chart functions function addNewChart() { const chartId = 'chart_' + (chartIdCounter++); const timeframeButtons = ['1D','1W','1M','3M','6M','1Y','2Y','5Y','MAX'].map(function(tf) { return ''; }).join(''); const chartHtml = '
' + '
' + '
' + '
Chart ' + chartIdCounter + '
' + '
' + '
' + '' + '' + '' + '
' + '
' + '
' + '
' + timeframeButtons + '
' + '' + '
' + '
' + '
' + '
Search for a financial indicator to add to this chart
' + '
' + '
'; document.getElementById('chartGrid').insertAdjacentHTML('beforeend', chartHtml); charts[chartId] = { symbols: [], data: {}, timeframe: '1Y', chartInstance: null, displayMode: 'raw' }; setActiveChart(chartId); showChartsArea(); } function setActiveChart(chartId) { activeChartId = chartId; document.querySelectorAll('.chart-window').forEach(c => c.classList.remove('active-chart')); const chartElement = document.getElementById(chartId); if (chartElement) { chartElement.classList.add('active-chart'); } } function removeChart(event, chartId) { event.stopPropagation(); if (charts[chartId] && charts[chartId].chartInstance) { Plotly.purge(`${chartId}_canvas`); } delete charts[chartId]; document.getElementById(chartId)?.remove(); if (Object.keys(charts).length === 0) { hideChartsArea(); } } function openChartForSymbol(symbol, source) { showChartsArea(); if (Object.keys(charts).length === 0) { addNewChart(); } if (activeChartId) { const data = watchlistData[symbol]; addToChart(activeChartId, symbol, source, data?.name || symbol); } } function addToChart(chartId, symbol, source, name) { const chart = charts[chartId]; if (!chart) return; if (!chart.symbols.find(s => s.symbol === symbol)) { chart.symbols.push({ symbol, source, name }); // Add demo chart data chart.data[symbol] = generateChartData(symbol); updateChartRender(chartId); updateChartSymbols(chartId); } setActiveChart(chartId); } function generateChartData(symbol) { const data = []; const now = new Date(); for (let i = 365; i >= 0; i--) { const date = new Date(now); date.setDate(date.getDate() - i); const baseValue = 100 + Math.random() * 50; const trend = -i * 0.01 + Math.random() * 2 - 1; const value = Math.max(0, baseValue + trend); data.push({ date: date.toISOString().split('T')[0], value: value }); } return data; } function updateChartRender(chartId) { const chart = charts[chartId]; const canvasElement = document.getElementById(`${chartId}_canvas`); if (!canvasElement || !chart) return; if (chart.symbols.length === 0) { canvasElement.innerHTML = '
Search for a financial indicator to add to this chart
'; return; } const traces = chart.symbols.map((s, index) => { const symbolData = chart.data[s.symbol] || []; const lineColor = colorPalette[index % colorPalette.length]; return { x: symbolData.map(d => d.date), y: symbolData.map(d => d.value), type: 'scatter', mode: 'lines', name: s.symbol, line: { color: lineColor, width: 2 }, hovertemplate: `${s.symbol}
Date: %{x}
Value: %{y:.2f}` }; }); const layout = { title: { text: 'Financial Data Chart', font: { color: '#fff', size: 14 }, x: 0.5 }, paper_bgcolor: '#0a0a0a', plot_bgcolor: '#0a0a0a', font: { color: '#aaa', size: 12 }, margin: { t: 60, r: 80, b: 60, l: 80 }, xaxis: { type: 'date', gridcolor: '#22222250', tickfont: { color: '#888' } }, yaxis: { title: { text: 'Value', font: { color: '#aaa' } }, gridcolor: '#22222250', tickfont: { color: '#888' } }, hovermode: 'x unified', showlegend: true, legend: { orientation: 'h', yanchor: 'bottom', y: 1.02, xanchor: 'right', x: 1 } }; const config = { responsive: true, displaylogo: false, displayModeBar: true }; if (canvasElement.querySelector('.chart-message')) { canvasElement.innerHTML = ''; } Plotly.react(canvasElement, traces, layout, config) .then(gd => { charts[chartId].chartInstance = gd; }) .catch(err => { console.error("Chart error:", err); canvasElement.innerHTML = `
Chart error: ${err.message}
`; }); } function updateChartSymbols(chartId) { const chart = charts[chartId]; if (!chart) return; const symbolsDiv = document.getElementById(`${chartId}_symbols`); if (!symbolsDiv) return; symbolsDiv.innerHTML = chart.symbols.map((s, i) => `
${s.symbol} ×
`).join(''); } function removeSymbolFromChart(event, chartId, symbolToRemove) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.symbols = chart.symbols.filter(s => s.symbol !== symbolToRemove); delete chart.data[symbolToRemove]; updateChartRender(chartId); updateChartSymbols(chartId); if (chart.symbols.length === 0) { document.getElementById(`${chartId}_canvas`).innerHTML = '
Search for a financial indicator to add to this chart
'; } } function toggleChartDisplay(event, chartId, displayMode) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.displayMode = displayMode; const controlsDiv = event.target.parentElement; controlsDiv.querySelectorAll('.display-toggle').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); updateChartRender(chartId); } function changeTimeframe(event, chartId, timeframe) { event.stopPropagation(); const chart = charts[chartId]; if (!chart) return; chart.timeframe = timeframe; const buttonContainer = document.getElementById(`${chartId}_timeframe_buttons`); if (buttonContainer) { buttonContainer.querySelectorAll('.timeframe-btn').forEach(btn => btn.classList.remove('active')); if (event.target.tagName === 'BUTTON') event.target.classList.add('active'); } updateChartRender(chartId); } function showChartsArea() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); const chartViewBtn = document.getElementById('chartViewBtn'); chartsArea.style.display = 'block'; watchlistSection.style.flex = '0 0 300px'; chartViewBtn.style.display = 'block'; } function hideChartsArea() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); const chartViewBtn = document.getElementById('chartViewBtn'); chartsArea.style.display = 'none'; watchlistSection.style.flex = '1'; chartViewBtn.style.display = 'none'; } function showChartView() { const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); if (chartsArea.style.display === 'none') { showChartsArea(); } else { hideChartsArea(); } } // Risk dashboard functions function toggleRiskDashboard() { const dashboard = document.getElementById('riskDashboard'); const chartsArea = document.getElementById('chartsArea'); const watchlistSection = document.querySelector('.watchlist-section'); if (dashboard.classList.contains('active')) { dashboard.classList.remove('active'); watchlistSection.style.flex = '1'; } else { dashboard.classList.add('active'); chartsArea.style.display = 'none'; watchlistSection.style.flex = '0 0 300px'; } } function toggleConfig() { const configPanel = document.getElementById('watchlistConfig'); configPanel.style.display = configPanel.style.display === 'none' ? 'block' : 'none'; } function updateRiskDashboard() { showTemporaryMessage('Risk Dashboard Updated!', 'info'); } // Liquidity dashboard function showLiquidityDashboard() { showTemporaryMessage('Loading comprehensive liquidity dashboard...', 'info'); } // Navigation functions function showDashboard(type) { // Update active nav link document.querySelectorAll('.nav-links a').forEach(a => a.classList.remove('active')); event.target.classList.add('active'); // Show appropriate content switch(type) { case 'main': switchWatchlist('MAIN'); break; case 'macro': switchWatchlist('ECONOMIC'); break; case 'ai': showTemporaryMessage('AI Signals dashboard - coming soon!', 'info'); break; case 'search': document.getElementById('searchBox').focus(); break; case 'realtime': openRealtimeDashboard(); break; case 'monitor': toggleRiskDashboard(); break; case 'login': showTemporaryMessage('User authentication - coming soon!', 'info'); break; } } function openRealtimeDashboard() { showTemporaryMessage('Real-time dashboard opening...', 'info'); } function openAIPredictions() { showTemporaryMessage('AI predictions dashboard opening...', 'info'); } function openExponentialSearch() { showTemporaryMessage('Advanced search with 60K+ indicators...', 'info'); } // Sort functions function sortByColumn(column) { if (columnSort.column === column) { columnSort.direction = columnSort.direction === 'desc' ? 'asc' : 'desc'; } else { columnSort.column = column; columnSort.direction = 'desc'; } sortOrder = 'default'; updateSortButtons(); renderWatchlist(); } function changeSortOrder(order) { sortOrder = order; columnSort = { column: null, direction: null }; updateSortButtons(); renderWatchlist(); } function updateSortButtons() { document.getElementById('sortDefault').classList.toggle('active', sortOrder === 'default'); document.getElementById('sortIncrease').classList.toggle('active', sortOrder === 'increase'); document.getElementById('sortDecrease').classList.toggle('active', sortOrder === 'decrease'); } // Utility functions function updateClock() { const now = new Date(); const timeString = now.toLocaleTimeString('en-US', { hour12: false, timeZone: 'America/New_York' }); document.getElementById('clock').textContent = `NY: ${timeString}`; } function showTemporaryMessage(text, type = 'info') { const popup = document.createElement('div'); popup.className = `message-popup ${type}`; popup.textContent = text; document.body.appendChild(popup); setTimeout(() => popup.remove(), 3000); } // Initialize when DOM is loaded with error handling function safeInit() { try { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } } catch (error) { console.error('Failed to initialize OpenBB Platform:', error); // Show user-friendly error message document.body.innerHTML = `

⚠️ Platform Error

OpenBB Platform failed to initialize properly.

`; } } // Call safe initialization safeInit(); // Export for debugging (wrapped in try-catch) try { window.OpenBBDebug = { AppState, DATA_SOURCES, WATCHLISTS, testConnections: testAllConnections, loadWatchlist, performEnhancedSearch, performanceMonitor: performanceMonitor || null, cacheManager: cacheManager || null }; } catch (debugError) { console.warn('Debug export failed:', debugError); }