UNPKG

maher-tradingview-api

Version:

Tradingview instant stocks API, indicator alerts, trading bot, and more !

363 lines (318 loc) 13.3 kB
// Stock Details Chart class StockDetailsChart { constructor() { this.mainChart = null; this.currentData = null; this.setupEventListeners(); } async initialize(symbol) { try { this.showLoading(); const initialData = window.initialStockData; if (initialData?.data?.length > 0) { this.currentData = initialData; } else { const response = await fetch(`/api/market-data/${symbol}`); if (!response.ok) throw new Error('فشل تحميل البيانات'); this.currentData = await response.json(); } this.hideLoading(); if (this.currentData?.data?.length > 0) { this.renderMainChart(); this.renderTechnicalIndicators(); this.setupLiveUpdates(symbol); } else { throw new Error('لا توجد بيانات متاحة'); } } catch (error) { console.error('خطأ في تهيئة الرسم البياني:', error); this.showError(error.message); } } showLoading() { const containers = document.querySelectorAll('.chart-container'); containers.forEach(container => { const loader = document.createElement('div'); loader.className = 'chart-loader'; loader.innerHTML = '<div class="loader-spinner"></div><div class="loader-text">جاري تحميل البيانات...</div>'; container.appendChild(loader); }); } hideLoading() { document.querySelectorAll('.chart-loader').forEach(loader => loader.remove()); } renderMainChart() { if (!this.currentData?.data?.length) { console.warn('No data available for chart rendering'); return; } const chartData = this.currentData.data; const mainChartElement = document.getElementById('mainChart'); if (!mainChartElement) { console.error('Chart canvas element not found'); return; } const ctx = mainChartElement.getContext('2d'); if (this.mainChart) { this.mainChart.destroy(); } const gradient = ctx.createLinearGradient(0, 0, 0, mainChartElement.height); gradient.addColorStop(0, 'rgba(33, 150, 243, 0.3)'); gradient.addColorStop(1, 'rgba(33, 150, 243, 0.05)'); this.mainChart = new Chart(ctx, { type: 'line', data: { labels: chartData.map(item => new Date(item.time)), datasets: [{ label: this.currentData.symbol, data: chartData.map(item => ({ x: new Date(item.time), y: item.close, open: item.open, high: item.high, low: item.low, close: item.close, volume: item.volume })), borderColor: '#2196F3', backgroundColor: gradient, borderWidth: 2, fill: true, tension: 0.1, pointRadius: 0, pointHoverRadius: 5, pointHoverBackgroundColor: '#2196F3', pointHoverBorderColor: '#fff', pointHoverBorderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, animation: { duration: 0 }, scales: { x: { type: 'time', time: { unit: 'day', displayFormats: { millisecond: 'HH:mm:ss.SSS', second: 'HH:mm:ss', minute: 'HH:mm', hour: 'HH:mm', day: 'MM/dd', week: 'MM/dd', month: 'yyyy/MM', year: 'yyyy' } }, grid: { display: true, color: 'rgba(0, 0, 0, 0.05)' } }, y: { position: 'right', grid: { color: 'rgba(0, 0, 0, 0.05)' } } }, plugins: { legend: { display: true, position: 'top', align: 'end' }, tooltip: { enabled: true, mode: 'index', intersect: false, callbacks: { label(context) { const dataPoint = context.raw; if (!dataPoint) return null; return [ `الافتتاح: ${dataPoint.open?.toFixed(2) || 'N/A'}`, `الأعلى: ${dataPoint.high?.toFixed(2) || 'N/A'}`, `الأدنى: ${dataPoint.low?.toFixed(2) || 'N/A'}`, `الإغلاق: ${dataPoint.close?.toFixed(2) || 'N/A'}`, `الحجم: ${dataPoint.volume?.toLocaleString() || 'N/A'}` ]; } } } } } }); } renderTechnicalIndicators() { if (!this.currentData?.data) return; const data = this.currentData.data; if (document.getElementById('rsiChart')) { window.TechnicalIndicators.renderRSIChart('rsiChart', data, 14); } if (document.getElementById('macdChart')) { window.TechnicalIndicators.renderMACDChart('macdChart', data, 12, 26, 9); } if (document.getElementById('bbChart')) { window.TechnicalIndicators.renderBollingerBandsChart('bbChart', data, 20, 2); } if (document.getElementById('maChart')) { window.TechnicalIndicators.renderMAChart('maChart', data, [20, 50, 200]); } } setupEventListeners() { const chartTypeButtons = document.querySelectorAll('.chart-type-button'); chartTypeButtons.forEach(button => { button.addEventListener('click', () => { chartTypeButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); this.updateChartType(button.dataset.type); }); }); const timeframeButtons = document.querySelectorAll('.timeframe-button'); timeframeButtons.forEach(button => { button.addEventListener('click', async () => { timeframeButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); await this.updateTimeframe(button.dataset.interval); }); }); document.querySelectorAll('.indicator-tab').forEach(tab => { tab.addEventListener('click', () => { const tabContents = document.querySelectorAll('.indicator-content'); tabContents.forEach(content => content.style.display = 'none'); const targetId = tab.dataset.target; const targetContent = document.getElementById(targetId); if (targetContent) { targetContent.style.display = 'block'; } this.renderTechnicalIndicators(); }); }); } setupLiveUpdates(symbol) { const ws = new WebSocket(`ws://${window.location.host}/ws/stock/${symbol}`); ws.onmessage = (event) => { try { const update = JSON.parse(event.data); this.updateChartData(update); } catch (error) { console.error('Error processing live update:', error); } }; ws.onerror = (error) => { console.error('WebSocket error:', error); this.showError('فشل في الاتصال بالتحديثات المباشرة'); }; setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send('ping'); } }, 30000); } updateChartData(update) { if (!this.currentData?.data) return; this.currentData.data.push(update); if (this.currentData.data.length > 1000) { this.currentData.data.shift(); } this.renderMainChart(); this.renderTechnicalIndicators(); } showError(message) { const errorContainer = document.createElement('div'); errorContainer.className = 'chart-error'; errorContainer.innerHTML = ` <div class="error-icon">⚠️</div> <div class="error-message">${message}</div> `; const containers = document.querySelectorAll('.chart-container'); containers.forEach(container => { container.appendChild(errorContainer.cloneNode(true)); }); } async updateChartType(type) { if (!this.mainChart) return; const currentData = this.mainChart.data.datasets[0].data; const chartConfig = { line: { type: 'line', tension: 0.1, fill: true }, candlestick: { type: 'candlestick', tension: 0, fill: false }, area: { type: 'line', tension: 0.4, fill: true } }; const config = chartConfig[type] || chartConfig.line; this.mainChart.config.type = config.type; this.mainChart.data.datasets[0].tension = config.tension; this.mainChart.data.datasets[0].fill = config.fill; if (type === 'candlestick') { this.mainChart.data.datasets[0].data = currentData.map(d => ({ t: d.x, o: d.open, h: d.high, l: d.low, c: d.close })); } else { this.mainChart.data.datasets[0].data = currentData.map(d => ({ x: d.x, y: d.close, open: d.open, high: d.high, low: d.low, close: d.close })); } this.mainChart.update('none'); } async updateTimeframe(timeframe) { try { this.showLoading(); const symbol = this.currentData.symbol; const response = await fetch(`/api/market-data/${symbol}?timeframe=${timeframe}`); if (!response.ok) throw new Error('فشل تحميل البيانات'); const newData = await response.json(); if (newData?.data?.length > 0) { this.currentData = newData; this.renderMainChart(); this.renderTechnicalIndicators(); } } catch (error) { console.error('خطأ في تحديث الفترة الزمنية:', error); this.showError('فشل في تحديث البيانات'); } finally { this.hideLoading(); } } } // Initialize the chart when the DOM is loaded document.addEventListener('DOMContentLoaded', () => { const chartContainer = document.querySelector('.chart-container'); if (chartContainer) { const symbol = chartContainer.dataset.symbol; console.log('تهيئة الرسم البياني للرمز:', symbol); const stockChart = new StockDetailsChart(); stockChart.initialize(symbol).catch(error => { console.error('خطأ في تهيئة الرسم البياني:', error); stockChart.showError('حدث خطأ في تحميل الرسم البياني'); }); } });