Spaces:
Runtime error
Runtime error
| /** | |
| * ═══════════════════════════════════════════════════════════════════ | |
| * TRADINGVIEW STYLE CHARTS | |
| * Professional Trading Charts with Advanced Features | |
| * ═══════════════════════════════════════════════════════════════════ | |
| */ | |
| // Chart instances storage | |
| const tradingViewCharts = {}; | |
| /** | |
| * Create TradingView-style candlestick chart | |
| */ | |
| export function createCandlestickChart(canvasId, data, options = {}) { | |
| const ctx = document.getElementById(canvasId); | |
| if (!ctx) return null; | |
| // Destroy existing chart | |
| if (tradingViewCharts[canvasId]) { | |
| tradingViewCharts[canvasId].destroy(); | |
| } | |
| const { | |
| symbol = 'BTC', | |
| timeframe = '1D', | |
| showVolume = true, | |
| showIndicators = true | |
| } = options; | |
| // Process candlestick data | |
| const labels = data.map(d => new Date(d.time).toLocaleDateString()); | |
| const opens = data.map(d => d.open); | |
| const highs = data.map(d => d.high); | |
| const lows = data.map(d => d.low); | |
| const closes = data.map(d => d.close); | |
| const volumes = data.map(d => d.volume || 0); | |
| // Determine colors based on price movement | |
| const colors = data.map((d, i) => { | |
| if (i === 0) return closes[i] >= opens[i] ? '#10B981' : '#EF4444'; | |
| return closes[i] >= closes[i - 1] ? '#10B981' : '#EF4444'; | |
| }); | |
| const datasets = [ | |
| { | |
| label: 'Price', | |
| data: closes, | |
| borderColor: '#00D4FF', | |
| backgroundColor: 'rgba(0, 212, 255, 0.1)', | |
| borderWidth: 2, | |
| fill: true, | |
| tension: 0.1, | |
| pointRadius: 0, | |
| pointHoverRadius: 6, | |
| pointHoverBackgroundColor: '#00D4FF', | |
| pointHoverBorderColor: '#fff', | |
| pointHoverBorderWidth: 2, | |
| yAxisID: 'y' | |
| } | |
| ]; | |
| if (showVolume) { | |
| datasets.push({ | |
| label: 'Volume', | |
| data: volumes, | |
| type: 'bar', | |
| backgroundColor: colors.map(c => c + '40'), | |
| borderColor: colors, | |
| borderWidth: 1, | |
| yAxisID: 'y1', | |
| order: 2 | |
| }); | |
| } | |
| tradingViewCharts[canvasId] = new Chart(ctx, { | |
| type: 'line', | |
| data: { labels, datasets }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| interaction: { | |
| mode: 'index', | |
| intersect: false | |
| }, | |
| plugins: { | |
| legend: { | |
| display: true, | |
| position: 'top', | |
| align: 'end', | |
| labels: { | |
| usePointStyle: true, | |
| padding: 15, | |
| font: { | |
| size: 12, | |
| weight: 600, | |
| family: "'Manrope', sans-serif" | |
| }, | |
| color: '#E2E8F0' | |
| } | |
| }, | |
| tooltip: { | |
| enabled: true, | |
| backgroundColor: 'rgba(15, 23, 42, 0.98)', | |
| titleColor: '#00D4FF', | |
| bodyColor: '#E2E8F0', | |
| borderColor: 'rgba(0, 212, 255, 0.5)', | |
| borderWidth: 1, | |
| padding: 16, | |
| displayColors: true, | |
| boxPadding: 8, | |
| usePointStyle: true, | |
| callbacks: { | |
| title: function(context) { | |
| return context[0].label; | |
| }, | |
| label: function(context) { | |
| if (context.datasetIndex === 0) { | |
| return `Price: $${context.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; | |
| } else { | |
| return `Volume: ${context.parsed.y.toLocaleString()}`; | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| grid: { | |
| display: false, | |
| color: 'rgba(255, 255, 255, 0.05)' | |
| }, | |
| ticks: { | |
| color: '#94A3B8', | |
| font: { | |
| size: 11, | |
| family: "'Manrope', sans-serif" | |
| }, | |
| maxRotation: 0, | |
| autoSkip: true, | |
| maxTicksLimit: 12 | |
| }, | |
| border: { | |
| display: false | |
| } | |
| }, | |
| y: { | |
| type: 'linear', | |
| position: 'left', | |
| grid: { | |
| color: 'rgba(255, 255, 255, 0.05)', | |
| drawBorder: false | |
| }, | |
| ticks: { | |
| color: '#94A3B8', | |
| font: { | |
| size: 11, | |
| family: "'Manrope', sans-serif" | |
| }, | |
| callback: function(value) { | |
| return '$' + value.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 }); | |
| } | |
| } | |
| }, | |
| y1: showVolume ? { | |
| type: 'linear', | |
| position: 'right', | |
| grid: { | |
| display: false, | |
| drawBorder: false | |
| }, | |
| ticks: { | |
| display: false | |
| } | |
| } : undefined | |
| } | |
| } | |
| }); | |
| return tradingViewCharts[canvasId]; | |
| } | |
| /** | |
| * Create advanced line chart with indicators | |
| */ | |
| export function createAdvancedLineChart(canvasId, priceData, indicators = {}) { | |
| const ctx = document.getElementById(canvasId); | |
| if (!ctx) return null; | |
| if (tradingViewCharts[canvasId]) { | |
| tradingViewCharts[canvasId].destroy(); | |
| } | |
| const labels = priceData.map(d => new Date(d.time || d.timestamp).toLocaleDateString()); | |
| const prices = priceData.map(d => d.price || d.value); | |
| // Calculate indicators | |
| const ma20 = indicators.ma20 || calculateMA(prices, 20); | |
| const ma50 = indicators.ma50 || calculateMA(prices, 50); | |
| const rsi = indicators.rsi || calculateRSI(prices, 14); | |
| const datasets = [ | |
| { | |
| label: 'Price', | |
| data: prices, | |
| borderColor: '#00D4FF', | |
| backgroundColor: 'rgba(0, 212, 255, 0.1)', | |
| borderWidth: 2.5, | |
| fill: true, | |
| tension: 0.1, | |
| pointRadius: 0, | |
| pointHoverRadius: 6, | |
| yAxisID: 'y', | |
| order: 1 | |
| } | |
| ]; | |
| if (indicators.showMA20) { | |
| datasets.push({ | |
| label: 'MA 20', | |
| data: ma20, | |
| borderColor: '#8B5CF6', | |
| backgroundColor: 'transparent', | |
| borderWidth: 1.5, | |
| borderDash: [5, 5], | |
| fill: false, | |
| tension: 0.1, | |
| pointRadius: 0, | |
| yAxisID: 'y', | |
| order: 2 | |
| }); | |
| } | |
| if (indicators.showMA50) { | |
| datasets.push({ | |
| label: 'MA 50', | |
| data: ma50, | |
| borderColor: '#EC4899', | |
| backgroundColor: 'transparent', | |
| borderWidth: 1.5, | |
| borderDash: [5, 5], | |
| fill: false, | |
| tension: 0.1, | |
| pointRadius: 0, | |
| yAxisID: 'y', | |
| order: 3 | |
| }); | |
| } | |
| tradingViewCharts[canvasId] = new Chart(ctx, { | |
| type: 'line', | |
| data: { labels, datasets }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| interaction: { | |
| mode: 'index', | |
| intersect: false | |
| }, | |
| plugins: { | |
| legend: { | |
| display: true, | |
| position: 'top', | |
| align: 'end', | |
| labels: { | |
| usePointStyle: true, | |
| padding: 15, | |
| font: { | |
| size: 12, | |
| weight: 600, | |
| family: "'Manrope', sans-serif" | |
| }, | |
| color: '#E2E8F0' | |
| } | |
| }, | |
| tooltip: { | |
| enabled: true, | |
| backgroundColor: 'rgba(15, 23, 42, 0.98)', | |
| titleColor: '#00D4FF', | |
| bodyColor: '#E2E8F0', | |
| borderColor: 'rgba(0, 212, 255, 0.5)', | |
| borderWidth: 1, | |
| padding: 16, | |
| displayColors: true, | |
| boxPadding: 8 | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| grid: { | |
| display: false | |
| }, | |
| ticks: { | |
| color: '#94A3B8', | |
| font: { | |
| size: 11, | |
| family: "'Manrope', sans-serif" | |
| }, | |
| maxRotation: 0, | |
| autoSkip: true | |
| }, | |
| border: { | |
| display: false | |
| } | |
| }, | |
| y: { | |
| grid: { | |
| color: 'rgba(255, 255, 255, 0.05)', | |
| drawBorder: false | |
| }, | |
| ticks: { | |
| color: '#94A3B8', | |
| font: { | |
| size: 11, | |
| family: "'Manrope', sans-serif" | |
| }, | |
| callback: function(value) { | |
| return '$' + value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| return tradingViewCharts[canvasId]; | |
| } | |
| /** | |
| * Calculate Moving Average | |
| */ | |
| function calculateMA(data, period) { | |
| const result = []; | |
| for (let i = 0; i < data.length; i++) { | |
| if (i < period - 1) { | |
| result.push(null); | |
| } else { | |
| const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0); | |
| result.push(sum / period); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Calculate RSI (Relative Strength Index) | |
| */ | |
| function calculateRSI(data, period = 14) { | |
| const result = []; | |
| const gains = []; | |
| const losses = []; | |
| for (let i = 1; i < data.length; i++) { | |
| const change = data[i] - data[i - 1]; | |
| gains.push(change > 0 ? change : 0); | |
| losses.push(change < 0 ? Math.abs(change) : 0); | |
| } | |
| for (let i = 0; i < data.length; i++) { | |
| if (i < period) { | |
| result.push(null); | |
| } else { | |
| const avgGain = gains.slice(i - period, i).reduce((a, b) => a + b, 0) / period; | |
| const avgLoss = losses.slice(i - period, i).reduce((a, b) => a + b, 0) / period; | |
| const rs = avgLoss === 0 ? 100 : avgGain / avgLoss; | |
| const rsi = 100 - (100 / (1 + rs)); | |
| result.push(rsi); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Create volume chart | |
| */ | |
| export function createVolumeChart(canvasId, volumeData) { | |
| const ctx = document.getElementById(canvasId); | |
| if (!ctx) return null; | |
| if (tradingViewCharts[canvasId]) { | |
| tradingViewCharts[canvasId].destroy(); | |
| } | |
| const labels = volumeData.map(d => new Date(d.time).toLocaleDateString()); | |
| const volumes = volumeData.map(d => d.volume); | |
| const colors = volumeData.map((d, i) => { | |
| if (i === 0) return '#10B981'; | |
| return volumes[i] >= volumes[i - 1] ? '#10B981' : '#EF4444'; | |
| }); | |
| tradingViewCharts[canvasId] = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels, | |
| datasets: [{ | |
| label: 'Volume', | |
| data: volumes, | |
| backgroundColor: colors.map(c => c + '60'), | |
| borderColor: colors, | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| display: false | |
| }, | |
| tooltip: { | |
| backgroundColor: 'rgba(15, 23, 42, 0.98)', | |
| titleColor: '#00D4FF', | |
| bodyColor: '#E2E8F0', | |
| borderColor: 'rgba(0, 212, 255, 0.5)', | |
| borderWidth: 1, | |
| padding: 12 | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| grid: { | |
| display: false | |
| }, | |
| ticks: { | |
| color: '#94A3B8', | |
| font: { | |
| size: 10, | |
| family: "'Manrope', sans-serif" | |
| } | |
| }, | |
| border: { | |
| display: false | |
| } | |
| }, | |
| y: { | |
| grid: { | |
| color: 'rgba(255, 255, 255, 0.05)', | |
| drawBorder: false | |
| }, | |
| ticks: { | |
| color: '#94A3B8', | |
| font: { | |
| size: 10, | |
| family: "'Manrope', sans-serif" | |
| }, | |
| callback: function(value) { | |
| if (value >= 1e9) return (value / 1e9).toFixed(2) + 'B'; | |
| if (value >= 1e6) return (value / 1e6).toFixed(2) + 'M'; | |
| if (value >= 1e3) return (value / 1e3).toFixed(2) + 'K'; | |
| return value; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| return tradingViewCharts[canvasId]; | |
| } | |
| /** | |
| * Destroy chart | |
| */ | |
| export function destroyChart(canvasId) { | |
| if (tradingViewCharts[canvasId]) { | |
| tradingViewCharts[canvasId].destroy(); | |
| delete tradingViewCharts[canvasId]; | |
| } | |
| } | |
| /** | |
| * Update chart data | |
| */ | |
| export function updateChart(canvasId, newData) { | |
| if (tradingViewCharts[canvasId]) { | |
| tradingViewCharts[canvasId].data = newData; | |
| tradingViewCharts[canvasId].update(); | |
| } | |
| } | |