UNPKG

@neabyte/chart-to-image

Version:

Convert trading charts to images using Node.js canvas with advanced features: 6 chart types, VWAP/EMA/SMA indicators, custom colors, themes, hide elements, scaling, and PNG/JPEG export formats.

129 lines (128 loc) 4 kB
export function formatTimestamp(timestamp) { const date = new Date(timestamp); const now = new Date(); const isToday = date.toDateString() === now.toDateString(); if (isToday) { return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }); } else { return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }); } } export function calculatePercentageChange(current, previous) { if (previous === 0) return 0; return ((current - previous) / previous) * 100; } export function calculateMovingAverage(data, period) { if (data.length < period) return []; const result = []; for (let i = period - 1; i < data.length; i++) { const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0); result.push(sum / period); } return result; } export function calculateRSI(data, period = 14) { if (data.length < period + 1) return []; const rsiValues = []; for (let i = period; i < data.length; i++) { const rsi = calculateRSIForPeriod(data, i, period); rsiValues.push(rsi); } return rsiValues; } function calculateRSIForPeriod(data, index, period) { let gains = 0; let losses = 0; for (let i = index - period + 1; i <= index; i++) { const change = data[i] - data[i - 1]; if (change > 0) { gains += change; } else { losses -= change; } } const avgGain = gains / period; const avgLoss = losses / period; if (avgLoss === 0) return 100; const rs = avgGain / avgLoss; return 100 - 100 / (1 + rs); } export function validateChartData(data) { if (!Array.isArray(data) || data.length === 0) return false; return data.every(item => { return (typeof item.timestamp === 'number' && typeof item.open === 'number' && typeof item.high === 'number' && typeof item.low === 'number' && typeof item.close === 'number' && item.high >= Math.max(item.open, item.close) && item.low <= Math.min(item.open, item.close) && item.timestamp > 0); }); } export function sortChartData(data) { return [...data].sort((a, b) => a.timestamp - b.timestamp); } export function filterChartDataByDate(data, startDate, endDate) { const startTimestamp = startDate.getTime(); const endTimestamp = endDate.getTime(); return data.filter(item => { return item.timestamp >= startTimestamp && item.timestamp <= endTimestamp; }); } export function timeframeToMs(timeframe) { const value = parseInt(timeframe.slice(0, -1)); const unit = timeframe.slice(-1); switch (unit) { case 'm': return value * 60 * 1000; case 'h': return value * 60 * 60 * 1000; case 'd': return value * 24 * 60 * 60 * 1000; case 'w': return value * 7 * 24 * 60 * 60 * 1000; default: throw new Error(`Unsupported timeframe unit: ${unit}`); } } export function getPriceChangeColor(current, previous) { if (current > previous) return '#26a69a'; if (current < previous) return '#ef5350'; return '#424242'; } export function formatPrice(price, decimals = 2) { return price.toFixed(decimals); } export function calculateVWAP(data) { if (data.length === 0) return 0; let totalVolume = 0; let totalVolumePrice = 0; data.forEach(item => { const volume = item.volume || 0; const typicalPrice = (item.high + item.low + item.close) / 3; totalVolume += volume; totalVolumePrice += volume * typicalPrice; }); return totalVolume > 0 ? totalVolumePrice / totalVolume : 0; }