maher-tradingview-api
Version:
Tradingview instant stocks API, indicator alerts, trading bot, and more !
363 lines (318 loc) • 13.3 kB
JavaScript
// 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('حدث خطأ في تحميل الرسم البياني');
});
}
});