|
|
|
|
|
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); |