| |
| const API_URL = 'https://api.coingecko.com/api/v3/coins/markets'; |
| const API_PARAMS = { |
| vs_currency: 'usd', |
| order: 'market_cap_desc', |
| per_page: 50, |
| page: 1, |
| sparkline: true, |
| price_change_percentage: '1h,24h,7d' |
| }; |
| |
| const cryptoGrid = document.getElementById('crypto-grid'); |
|
|
| |
| async function fetchCryptoData() { |
| try { |
| const response = await fetch(`${API_URL}?${new URLSearchParams(API_PARAMS)}`); |
| const data = await response.json(); |
| return data; |
| } catch (error) { |
| console.error('Error fetching crypto data:', error); |
| return []; |
| } |
| } |
| |
| function calculateRSI(priceHistory, period = 14) { |
| if (!priceHistory || priceHistory.length < period + 1) return 50; |
| |
| |
| const changes = []; |
| for (let i = 1; i < priceHistory.length; i++) { |
| changes.push(priceHistory[i] - priceHistory[i-1]); |
| } |
| |
| |
| const gains = changes.map(change => change > 0 ? change : 0); |
| const losses = changes.map(change => change < 0 ? Math.abs(change) : 0); |
| |
| |
| let avgGain = gains.slice(0, period).reduce((sum, gain) => sum + gain, 0) / period; |
| let avgLoss = losses.slice(0, period).reduce((sum, loss) => sum + loss, 0) / period; |
| |
| |
| for (let i = period; i < changes.length; i++) { |
| avgGain = (avgGain * (period - 1) + gains[i]) / period; |
| avgLoss = (avgLoss * (period - 1) + losses[i]) / period; |
| } |
| |
| |
| if (avgLoss === 0) return 100; |
| |
| |
| const relativeStrength = avgGain / avgLoss; |
| const rsi = 100 - (100 / (1 + relativeStrength)); |
| |
| return Math.round(rsi * 100) / 100; |
| } |
| |
| function createCryptoCard(crypto) { |
| const rsi = calculateRSI(crypto.sparkline_in_7d.price); |
| const isPumping = crypto.price_change_percentage_24h > 0; |
| |
| let rsiClass = 'rsi-medium'; |
| if (rsi < 30) rsiClass = 'rsi-low'; |
| if (rsi > 70) rsiClass = 'rsi-high'; |
| |
| const card = document.createElement('div'); |
| card.className = `bg-gray-800 rounded-xl overflow-hidden shadow-lg smooth-transition hover:shadow-xl hover:-translate-y-1 ${isPumping ? 'border-l-4 border-primary-500' : 'border-l-4 border-red-500'}`; |
| |
| card.innerHTML = ` |
| <div class="p-6"> |
| <div class="flex justify-between items-start"> |
| <div class="flex items-center space-x-3"> |
| <img src="${crypto.image}" alt="${crypto.name}" class="w-10 h-10 rounded-full"> |
| <div> |
| <h3 class="font-bold text-lg">${crypto.symbol.toUpperCase()}</h3> |
| <p class="text-gray-400 text-sm">${crypto.name}</p> |
| </div> |
| </div> |
| <span class="px-3 py-1 rounded-full text-xs font-semibold ${rsiClass}"> |
| RSI: ${rsi} |
| </span> |
| </div> |
| |
| <div class="mt-4 h-16"> |
| <canvas class="sparkline" data-prices="${crypto.sparkline_in_7d.price.join(',')}" data-color="${isPumping ? '#10b981' : '#ef4444'}"></canvas> |
| </div> |
| |
| <div class="mt-6"> |
| <div class="flex justify-between items-center mb-2"> |
| <span class="text-gray-400">Price</span> |
| <span class="font-bold">${crypto.current_price.toLocaleString()}</span> |
| </div> |
| |
| <div class="flex justify-between items-center mb-2"> |
| <span class="text-gray-400">Market Cap</span> |
| <span class="font-bold">${(crypto.market_cap / 1000000000).toFixed(2)}B</span> |
| </div> |
| |
| <div class="flex justify-between items-center"> |
| <span class="text-gray-400">24h Change</span> |
| <span class="font-bold ${isPumping ? 'text-secondary-500' : 'text-red-500'}"> |
| ${isPumping ? '+' : ''}${crypto.price_change_percentage_24h.toFixed(2)}% |
| <i data-feather="${isPumping ? 'trending-up' : 'trending-down'}" class="inline ml-1"></i> |
| </span> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| return card; |
| } |
| |
| async function initApp() { |
| const cryptos = await fetchCryptoData(); |
| cryptoGrid.innerHTML = ''; |
| |
| cryptos.forEach(crypto => { |
| const card = createCryptoCard(crypto); |
| cryptoGrid.appendChild(card); |
| }); |
| |
| |
| document.querySelectorAll('.sparkline').forEach(canvas => { |
| const prices = canvas.dataset.prices.split(',').map(Number); |
| const color = canvas.dataset.color; |
| const ctx = canvas.getContext('2d'); |
| |
| |
| canvas.width = canvas.offsetWidth; |
| canvas.height = canvas.offsetHeight; |
| |
| |
| const max = Math.max(...prices); |
| const min = Math.min(...prices); |
| const range = max - min || 1; |
| |
| |
| ctx.beginPath(); |
| ctx.strokeStyle = color; |
| ctx.lineWidth = 2; |
| |
| prices.forEach((price, i) => { |
| const x = (canvas.width / (prices.length - 1)) * i; |
| const y = canvas.height - ((price - min) / range) * canvas.height; |
| |
| if (i === 0) { |
| ctx.moveTo(x, y); |
| } else { |
| ctx.lineTo(x, y); |
| } |
| }); |
| |
| ctx.stroke(); |
| }); |
| |
| feather.replace(); |
| } |
| |
| setInterval(initApp, 60000); |
|
|
| |
| document.addEventListener('DOMContentLoaded', initApp); |