import apiClient from './apiClient.js'; import errorHelper from './errorHelper.js'; import { createAdvancedLineChart, createCandlestickChart, createVolumeChart } from './tradingview-charts.js'; // Cryptocurrency symbols list const CRYPTO_SYMBOLS = [ { symbol: 'BTC', name: 'Bitcoin' }, { symbol: 'ETH', name: 'Ethereum' }, { symbol: 'BNB', name: 'Binance Coin' }, { symbol: 'SOL', name: 'Solana' }, { symbol: 'XRP', name: 'Ripple' }, { symbol: 'ADA', name: 'Cardano' }, { symbol: 'DOGE', name: 'Dogecoin' }, { symbol: 'DOT', name: 'Polkadot' }, { symbol: 'MATIC', name: 'Polygon' }, { symbol: 'AVAX', name: 'Avalanche' }, { symbol: 'LINK', name: 'Chainlink' }, { symbol: 'UNI', name: 'Uniswap' }, { symbol: 'LTC', name: 'Litecoin' }, { symbol: 'ATOM', name: 'Cosmos' }, { symbol: 'ALGO', name: 'Algorand' }, { symbol: 'TRX', name: 'Tron' }, { symbol: 'XLM', name: 'Stellar' }, { symbol: 'VET', name: 'VeChain' }, { symbol: 'FIL', name: 'Filecoin' }, { symbol: 'ETC', name: 'Ethereum Classic' }, { symbol: 'AAVE', name: 'Aave' }, { symbol: 'MKR', name: 'Maker' }, { symbol: 'COMP', name: 'Compound' }, { symbol: 'SUSHI', name: 'SushiSwap' }, { symbol: 'YFI', name: 'Yearn Finance' }, ]; class ChartLabView { constructor(section) { this.section = section; this.symbolInput = section.querySelector('[data-chart-symbol-input]'); this.symbolDropdown = section.querySelector('[data-chart-symbol-dropdown]'); this.symbolOptions = section.querySelector('[data-chart-symbol-options]'); this.timeframeButtons = section.querySelectorAll('[data-timeframe]'); this.indicatorButtons = section.querySelectorAll('[data-indicator]'); this.loadButton = section.querySelector('[data-load-chart]'); this.runAnalysisButton = section.querySelector('[data-run-analysis]'); this.canvas = section.querySelector('#price-chart'); this.analysisOutput = section.querySelector('[data-analysis-output]'); this.chartTitle = section.querySelector('[data-chart-title]'); this.chartLegend = section.querySelector('[data-chart-legend]'); this.chart = null; this.symbol = 'BTC'; this.timeframe = '7d'; this.filteredSymbols = [...CRYPTO_SYMBOLS]; } async init() { this.setupCombobox(); this.bindEvents(); await this.loadChart(); } setupCombobox() { if (!this.symbolInput || !this.symbolOptions) return; // Populate options this.renderOptions(); // Set initial value this.symbolInput.value = 'BTC - Bitcoin'; // Input event for filtering this.symbolInput.addEventListener('input', (e) => { const query = e.target.value.trim().toUpperCase(); this.filterSymbols(query); }); // Focus event to show dropdown this.symbolInput.addEventListener('focus', () => { this.symbolDropdown.style.display = 'block'; this.filterSymbols(this.symbolInput.value.trim().toUpperCase()); }); // Click outside to close document.addEventListener('click', (e) => { if (!this.symbolInput.contains(e.target) && !this.symbolDropdown.contains(e.target)) { this.symbolDropdown.style.display = 'none'; } }); } filterSymbols(query) { if (!query) { this.filteredSymbols = [...CRYPTO_SYMBOLS]; } else { this.filteredSymbols = CRYPTO_SYMBOLS.filter(item => item.symbol.includes(query) || item.name.toUpperCase().includes(query) ); } this.renderOptions(); } renderOptions() { if (!this.symbolOptions) return; if (this.filteredSymbols.length === 0) { this.symbolOptions.innerHTML = '
Loading ${symbol} chart data...
`; } // Update title if (this.chartTitle) { this.chartTitle.textContent = `${symbol} Price Chart (${this.timeframe})`; } try { const result = await apiClient.getPriceChart(symbol, this.timeframe); // Remove loading if (container) { const loadingNode = container.querySelector('.chart-loading'); if (loadingNode) loadingNode.remove(); } if (!result.ok) { const errorAnalysis = errorHelper.analyzeError(new Error(result.error), { symbol, timeframe: this.timeframe }); if (container) { let errorNode = container.querySelector('.chart-error'); if (!errorNode) { errorNode = document.createElement('div'); errorNode.className = 'inline-message inline-error chart-error'; container.appendChild(errorNode); } errorNode.innerHTML = ` Error loading chart:${result.error || 'Failed to load chart data'}
Symbol: ${symbol} | Timeframe: ${this.timeframe}
`; } return; } if (container) { const errorNode = container.querySelector('.chart-error'); if (errorNode) errorNode.remove(); } // Parse chart data const chartData = result.data || {}; const points = chartData.data || chartData || []; if (!points || points.length === 0) { if (container) { const errorNode = document.createElement('div'); errorNode.className = 'inline-message inline-warn'; errorNode.innerHTML = 'No data availableNo price data found for this symbol and timeframe.
'; container.appendChild(errorNode); } return; } // Format labels and data const labels = points.map((point) => { const ts = point.time || point.timestamp || point.date; if (!ts) return ''; const date = new Date(ts); if (this.timeframe === '1d') { return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); } return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); }); const prices = points.map((point) => { const price = point.price || point.close || point.value || 0; return parseFloat(price) || 0; }); // Destroy existing chart if (this.chart) { this.chart.destroy(); } // Calculate min/max for better scaling const minPrice = Math.min(...prices); const maxPrice = Math.max(...prices); const priceRange = maxPrice - minPrice; const firstPrice = prices[0]; const lastPrice = prices[prices.length - 1]; const priceChange = lastPrice - firstPrice; const priceChangePercent = ((priceChange / firstPrice) * 100).toFixed(2); const isPriceUp = priceChange >= 0; // Get indicator states const showMA20 = this.section.querySelector('[data-indicator="MA20"]')?.checked || false; const showMA50 = this.section.querySelector('[data-indicator="MA50"]')?.checked || false; const showRSI = this.section.querySelector('[data-indicator="RSI"]')?.checked || false; const showVolume = this.section.querySelector('[data-indicator="Volume"]')?.checked || false; // Prepare price data for TradingView chart const priceData = points.map((point, index) => ({ time: point.time || point.timestamp || point.date || new Date().getTime() + (index * 60000), price: parseFloat(point.price || point.close || point.value || 0), volume: parseFloat(point.volume || 0) })); // Create TradingView-style chart with indicators this.chart = createAdvancedLineChart('chart-lab-canvas', priceData, { showMA20, showMA50, showRSI, showVolume }); // If volume is enabled, create separate volume chart if (showVolume && priceData.some(p => p.volume > 0)) { const volumeContainer = this.section.querySelector('[data-volume-chart]'); if (volumeContainer) { createVolumeChart('volume-chart-canvas', priceData); } } // Update legend with TradingView-style info if (this.chartLegend && prices.length > 0) { const currentPrice = prices[prices.length - 1]; const firstPrice = prices[0]; const change = currentPrice - firstPrice; const changePercent = ((change / firstPrice) * 100).toFixed(2); const isUp = change >= 0; this.chartLegend.innerHTML = `${error.message || 'Failed to load chart'}
`; container.appendChild(errorNode); } } } async runAnalysis() { if (!this.analysisOutput) return; const enabledIndicators = Array.from(this.indicatorButtons) .filter((btn) => btn.classList.contains('active')) .map((btn) => btn.dataset.indicator); this.analysisOutput.innerHTML = `Running AI analysis with ${enabledIndicators.length > 0 ? enabledIndicators.join(', ') : 'default'} indicators...
${summary}