maher-tradingview-api
Version:
Tradingview instant stocks API, indicator alerts, trading bot, and more !
827 lines (715 loc) • 28.6 kB
JavaScript
/* eslint-disable linebreak-style */
/* eslint-disable no-plusplus */
/* eslint-disable no-unused-vars */
/* eslint-disable prefer-const */
/* eslint-disable no-undef */
/**
* data-sources-manager.js
* مدير مصادر البيانات المتعددة للرسوم البيانية
* يتيح التبديل بين مصادر مختلفة في حالة فشل المصدر الرئيسي
*/
// تعريف مصادر البيانات المتاحة
const DATA_SOURCES = {
PRIMARY: 'tradingview', // المصدر الرئيسي - TradingView API
SECONDARY: 'alphavantage', // المصدر الثانوي - Alpha Vantage API
FALLBACK: 'yahoo', // المصدر الاحتياطي - Yahoo Finance
HISTORICAL: 'iex', // مصدر بيانات تاريخية - IEX Cloud
REALTIME: 'finnhub' // بيانات فورية - Finnhub
};
// مفاتيح API (يتم تحميلها من متغيرات البيئة)
const API_KEYS = {
ALPHAVANTAGE: 'HQRYOMVV0758HND5',
FINNHUB: 'd0ka9a9r01qn937jjnn0d0ka9a9r01qn937jjnng',
IEX: '' // TODO: Add IEX API key
};
// المصدر الحالي النشط
let currentSource = DATA_SOURCES.PRIMARY;
// سجل محاولات الفشل لكل مصدر
const failureAttempts = {
[DATA_SOURCES.PRIMARY]: 0,
[DATA_SOURCES.SECONDARY]: 0,
[DATA_SOURCES.FALLBACK]: 0,
[DATA_SOURCES.HISTORICAL]: 0,
[DATA_SOURCES.REALTIME]: 0
};
// الحد الأقصى لمحاولات الفشل قبل التبديل إلى مصدر آخر
const MAX_FAILURE_ATTEMPTS = 3;
/**
* حالة مصادر البيانات ومؤشراتها
*/
const sourcesStatus = {
[DATA_SOURCES.PRIMARY]: { available: true, lastCheck: Date.now(), errorCount: 0 },
[DATA_SOURCES.SECONDARY]: { available: true, lastCheck: null, errorCount: 0 },
[DATA_SOURCES.FALLBACK]: { available: true, lastCheck: null, errorCount: 0 },
[DATA_SOURCES.HISTORICAL]: { available: true, lastCheck: null, errorCount: 0 },
[DATA_SOURCES.REALTIME]: { available: true, lastCheck: null, errorCount: 0 }
};
/**
* الحصول على المصدر الحالي النشط
* @return {string} رمز المصدر الحالي
*/
function getCurrentSource() {
return currentSource;
}
/**
* تعيين مصدر البيانات النشط
* @param {string} source - رمز المصدر من كائن DATA_SOURCES
*/
function setDataSource(source) {
if (Object.values(DATA_SOURCES).includes(source)) {
currentSource = source;
console.log(`تم تغيير مصدر البيانات إلى: ${source}`);
// إرسال حدث تغيير مصدر البيانات
const event = new CustomEvent('dataSourceChanged', {
detail: { source: source }
});
document.dispatchEvent(event);
return true;
} else {
console.error(`مصدر البيانات غير صالح: ${source}`);
return false;
}
}
/**
* تسجيل فشل في مصدر البيانات الحالي
* والانتقال إلى المصدر التالي إذا تجاوزنا الحد الأقصى للمحاولات
*/
function reportFailure() {
failureAttempts[currentSource]++;
console.warn(`تم تسجيل فشل في مصدر البيانات ${currentSource} (عدد المحاولات: ${failureAttempts[currentSource]})`);
if (failureAttempts[currentSource] >= MAX_FAILURE_ATTEMPTS) {
// انتقل إلى المصدر التالي
switchToNextSource();
}
}
/**
* إعادة تعيين عداد فشل المصدر المحدد
* @param {string} source - رمز المصدر (اختياري، إذا لم يتم تحديده يتم استخدام المصدر الحالي)
*/
function resetFailureCount(source = currentSource) {
if (failureAttempts[source] !== undefined) {
failureAttempts[source] = 0;
console.log(`تم إعادة تعيين عداد الفشل لمصدر البيانات: ${source}`);
}
}
/**
* الانتقال إلى المصدر التالي حسب الأولوية
* @return {string} رمز المصدر الجديد
*/
function switchToNextSource() {
const sources = Object.values(DATA_SOURCES);
const currentIndex = sources.indexOf(currentSource);
// البحث عن المصدر التالي الذي لديه أقل من الحد الأقصى للمحاولات
for (let i = 1; i <= sources.length; i++) {
const nextIndex = (currentIndex + i) % sources.length;
const nextSource = sources[nextIndex];
if (failureAttempts[nextSource] < MAX_FAILURE_ATTEMPTS) {
// تعيين المصدر الجديد
setDataSource(nextSource);
console.log(`تم الانتقال إلى مصدر البيانات التالي: ${nextSource}`);
// تخزين المصدر الحالي في التخزين المحلي للمتصفح
try {
localStorage.setItem('preferredDataSource', nextSource);
} catch (e) {
console.warn('لا يمكن تخزين مصدر البيانات المفضل في التخزين المحلي:', e);
}
return nextSource;
}
}
// إذا وصلنا إلى هنا، فجميع المصادر فشلت
console.error('جميع مصادر البيانات استنفذت الحد الأقصى للمحاولات');
// إعادة ضبط عدادات الفشل وبدء من المصدر الرئيسي
Object.keys(failureAttempts).forEach(source => {
failureAttempts[source] = 0;
});
setDataSource(DATA_SOURCES.PRIMARY);
return DATA_SOURCES.PRIMARY;
}
/**
* التحقق من توافر مصدر بيانات محدد
* @param {string} source - مصدر البيانات للتحقق منه
* @returns {Promise<boolean>} وعد يُحل لقيمة boolean تُظهر ما إذا كان المصدر متاحًا
*/
async function checkSourceAvailability(source) {
try {
let endpoint;
switch (source) {
case DATA_SOURCES.PRIMARY:
// لا يمكن التحقق مباشرة، نفترض أنه متاح
return true;
case DATA_SOURCES.SECONDARY:
endpoint = '/api/alphavantage?symbol=AAPL';
break;
case DATA_SOURCES.FALLBACK:
endpoint = '/api/yahoo?symbol=AAPL';
break;
case DATA_SOURCES.HISTORICAL:
endpoint = '/api/iex?symbol=AAPL';
break;
case DATA_SOURCES.REALTIME:
endpoint = '/api/finnhub?symbol=AAPL';
break;
default:
return false;
}
// تعيين وقت آخر فحص
sourcesStatus[source].lastCheck = Date.now();
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`فشل الاتصال بـ ${source}: ${response.status}`);
}
const data = await response.json();
// تحديث حالة المصدر
sourcesStatus[source].available = true;
sourcesStatus[source].errorCount = 0;
return true;
} catch (error) {
console.error(`خطأ أثناء التحقق من توافر ${source}:`, error);
// تحديث حالة المصدر
sourcesStatus[source].available = false;
sourcesStatus[source].errorCount++;
return false;
}
}
/**
* التحقق من توافر جميع مصادر البيانات المتاحة
* @returns {Promise<Object>} وعد يُظهر حالة كل مصدر
*/
async function checkAllSourcesAvailability() {
const results = {};
for (const source of Object.values(DATA_SOURCES)) {
results[source] = await checkSourceAvailability(source);
}
return results;
}
/**
* تمكين مصدر بديل بناءً على معلومات الحالة والأولوية
* @returns {string|null} المصدر الجديد إذا تم التبديل، أو null إذا لم يتم
*/
function enableFallbackSource() {
// بناء قائمة بالمصادر المتاحة حسب الأولوية
const availableSources = Object.values(DATA_SOURCES).filter(
source => sourcesStatus[source].available && source !== currentSource
);
if (availableSources.length === 0) {
console.warn('لا توجد مصادر بيانات بديلة متاحة حاليًا');
return null;
}
// اختيار المصدر الأعلى أولوية
const newSource = availableSources[0];
setDataSource(newSource);
// إرسال حدث بتغيير المصدر
const event = new CustomEvent('dataSourceChanged', {
detail: {
source: newSource,
reason: 'fallback_enabled',
previousSource: currentSource
}
});
document.dispatchEvent(event);
return newSource;
}
/**
* محاولة التبديل إلى مصدر بديل في حالة فشل مصدر محدد
* @param {string} failedSource - المصدر الذي فشل
* @param {Error} error - كائن الخطأ (اختياري)
* @returns {string|null} المصدر الجديد إذا تم التبديل، أو null إذا لم يتم
*/
function handleSourceFailure(failedSource, error = null) {
if (error) {
console.error(`فشل مصدر البيانات ${failedSource}:`, error);
} else {
console.warn(`تم الإبلاغ عن فشل مصدر البيانات ${failedSource}`);
}
// تحديث حالة المصدر
sourcesStatus[failedSource].available = false;
sourcesStatus[failedSource].errorCount++;
// محاولة التبديل إلى مصدر بديل
return switchToNextSource();
}
/**
* تنفيذ فحص دوري لتوافر مصادر البيانات
* @param {number} interval - الفاصل الزمني بالمللي ثانية بين عمليات الفحص
*/
function startPeriodicHealthCheck(interval = 300000) { // افتراضيًا 5 دقائق
setInterval(async () => {
console.log('جارٍ إجراء فحص دوري لتوافر مصادر البيانات...');
const results = await checkAllSourcesAvailability();
// إذا كان المصدر الحالي غير متاح، قم بالتبديل
if (!results[currentSource]) {
console.warn(`المصدر الحالي ${currentSource} غير متاح، محاولة التبديل...`);
switchToNextSource();
}
// تحديث واجهة المستخدم بحالة المصادر
updateDataSourcesStatus();
}, interval);
}
/**
* تحديث حالة مصادر البيانات في واجهة المستخدم
*/
function updateDataSourcesStatus() {
const container = document.querySelector('.data-sources-status');
if (!container) return;
// إنشاء أو تحديث عناصر حالة المصدر
for (const [source, status] of Object.entries(sourcesStatus)) {
const sourceElement = document.getElementById(`source-status-${source}`);
if (sourceElement) {
// تحديث العنصر الموجود
const statusClass = status.available ? 'available' : 'unavailable';
sourceElement.className = `data-source-status ${statusClass}`;
sourceElement.textContent = status.available ? 'متاح' : 'غير متاح';
} else {
// إنشاء عنصر جديد
const sourceItem = document.createElement('div');
sourceItem.className = 'data-source-status-item';
sourceItem.innerHTML = `
<span class="data-source-name">${getSourceDisplayName(source)}</span>
<span id="source-status-${source}" class="data-source-status ${status.available ? 'available' : 'unavailable'}">
${status.available ? 'متاح' : 'غير متاح'}
</span>
`;
container.appendChild(sourceItem);
}
}
}
/**
* الحصول على اسم العرض لمصدر البيانات
* @param {string} source - رمز المصدر
* @returns {string} الاسم المعروض
*/
function getSourceDisplayName(source) {
const names = {
[DATA_SOURCES.PRIMARY]: 'TradingView API',
[DATA_SOURCES.SECONDARY]: 'Alpha Vantage',
[DATA_SOURCES.FALLBACK]: 'Yahoo Finance',
[DATA_SOURCES.HISTORICAL]: 'IEX Cloud',
[DATA_SOURCES.REALTIME]: 'Finnhub'
};
return names[source] || source;
}
/**
* التبديل إلى مصدر بيانات محدد يدويًا
* @param {string} source - المصدر المطلوب الانتقال إليه
* @returns {boolean} ما إذا كان التبديل ناجحًا
*/
function switchToSource(source) {
if (!Object.values(DATA_SOURCES).includes(source)) {
console.error(`مصدر بيانات غير صالح: ${source}`);
return false;
}
// التحقق من توافر المصدر
if (!sourcesStatus[source].available && sourcesStatus[source].errorCount > 0) {
console.warn(`المصدر ${source} مُبلغ عنه بأنه غير متاح. محاولة الاتصال...`);
}
// محاولة التبديل
const previousSource = currentSource;
const success = setDataSource(source);
if (success) {
// إرسال حدث بتغيير المصدر
const event = new CustomEvent('dataSourceChanged', {
detail: {
source,
reason: 'manual_switch',
previousSource
}
});
document.dispatchEvent(event);
// تحديث واجهة المستخدم
if (typeof updateDataSourceIndicators === 'function') {
updateDataSourceIndicators(source);
}
return true;
}
return false;
}
/**
* الحصول على بيانات من Alpha Vantage API
* @param {string} symbol - رمز السهم
* @param {string} timeframe - الإطار الزمني ('D' لليومي، 'W' للأسبوعي، إلخ)
* @return {Promise} وعد بالبيانات
*/
async function fetchAlphaVantageData(symbol, timeframe = 'D') {
const apiKey = API_KEYS.ALPHAVANTAGE;
let interval = 'daily'; // افتراضي
// تحويل الإطار الزمني إلى تنسيق Alpha Vantage
switch (timeframe) {
case 'D':
interval = 'daily';
break;
case 'W':
interval = 'weekly';
break;
case 'M':
interval = 'monthly';
break;
default:
interval = 'daily';
}
try {
const response = await fetch(`https://www.alphavantage.co/query?function=TIME_SERIES_${interval.toUpperCase()}&symbol=${symbol}&apikey=${apiKey}`);
if (!response.ok) {
throw new Error(`Alpha Vantage API error: ${response.status}`);
}
const data = await response.json();
const timeSeriesKey = `Time Series (${interval.charAt(0).toUpperCase() + interval.slice(1)})`;
if (!data || !data[timeSeriesKey]) {
throw new Error('بيانات غير صالحة من Alpha Vantage');
}
// تحويل البيانات إلى تنسيق متوافق مع التطبيق
const timeSeriesData = data[timeSeriesKey];
const formattedData = Object.entries(timeSeriesData).map(([date, values]) => ({
time: date,
open: parseFloat(values['1. open']),
high: parseFloat(values['2. high']),
low: parseFloat(values['3. low']),
close: parseFloat(values['4. close']),
volume: parseFloat(values['5. volume'])
}));
// ترتيب البيانات تصاعدياً حسب التاريخ
return formattedData.sort((a, b) => new Date(a.time) - new Date(b.time));
} catch (error) {
console.error('خطأ في جلب بيانات Alpha Vantage:', error);
reportFailure();
return null;
}
}
/**
* الحصول على بيانات من Yahoo Finance
* @param {string} symbol - رمز السهم
* @param {string} timeframe - الإطار الزمني ('D' لليومي، 'W' للأسبوعي، إلخ)
* @return {Promise} وعد بالبيانات
*/
async function fetchYahooData(symbol, timeframe = 'D') {
// تحويل الإطار الزمني إلى تنسيق Yahoo Finance
let interval = '1d'; // افتراضي
let range = '6mo'; // افتراضي
switch (timeframe) {
case 'D':
interval = '1d';
range = '6mo';
break;
case 'W':
interval = '1wk';
range = '2y';
break;
case 'M':
interval = '1mo';
range = '5y';
break;
case '60':
interval = '60m';
range = '1mo';
break;
case '30':
interval = '30m';
range = '1mo';
break;
case '15':
interval = '15m';
range = '7d';
break;
case '5':
interval = '5m';
range = '5d';
break;
case '1':
interval = '1m';
range = '1d';
break;
}
try {
const response = await fetch(`/api/yahoo?symbol=${symbol}&interval=${interval}&range=${range}`);
if (!response.ok) {
throw new Error(`Yahoo Finance API error: ${response.status}`);
}
const data = await response.json();
if (!data || !data.chart || !data.chart.result || data.chart.result.length === 0) {
throw new Error('بيانات غير صالحة من Yahoo Finance');
}
// تحويل البيانات إلى تنسيق متوافق مع التطبيق
const result = data.chart.result[0];
const timestamps = result.timestamp;
const quotes = result.indicators.quote[0];
const formattedData = timestamps.map((time, i) => ({
time: new Date(time * 1000).toISOString().split('T')[0],
open: quotes.open[i],
high: quotes.high[i],
low: quotes.low[i],
close: quotes.close[i],
volume: quotes.volume[i] || 0
}));
// ترتيب البيانات تصاعدياً حسب التاريخ
return formattedData.sort((a, b) => new Date(a.time) - new Date(b.time));
} catch (error) {
console.error('خطأ في جلب بيانات Yahoo Finance:', error);
reportFailure();
return null;
}
}
/**
* الحصول على بيانات من IEX Cloud
* @param {string} symbol - رمز السهم
* @param {string} timeframe - الإطار الزمني ('D' لليومي، 'W' للأسبوعي، إلخ)
* @return {Promise} وعد بالبيانات
*/
async function fetchIEXData(symbol, timeframe = 'D') {
// تحويل الإطار الزمني إلى تنسيق IEX Cloud
let range = '6m'; // افتراضي
switch (timeframe) {
case 'D':
range = '6m';
break;
case 'W':
range = '1y';
break;
case 'M':
range = '5y';
break;
case '60':
range = '1m';
break;
case '30':
case '15':
case '5':
range = '5d';
break;
case '1':
range = '1d';
break;
}
try {
const response = await fetch(`/api/iex?symbol=${symbol}&range=${range}`);
if (!response.ok) {
throw new Error(`IEX Cloud API error: ${response.status}`);
}
const data = await response.json();
if (!Array.isArray(data) || data.length === 0) {
throw new Error('بيانات غير صالحة من IEX Cloud');
}
// تحويل البيانات إلى تنسيق متوافق مع التطبيق
const formattedData = data.map(candle => ({
time: candle.date,
open: candle.open,
high: candle.high,
low: candle.low,
close: candle.close,
volume: candle.volume || 0
}));
// ترتيب البيانات تصاعدياً حسب التاريخ
return formattedData.sort((a, b) => new Date(a.time) - new Date(b.time));
} catch (error) {
console.error('خطأ في جلب بيانات IEX Cloud:', error);
reportFailure();
return null;
}
}
/**
* الحصول على بيانات من Finnhub
* @param {string} symbol - رمز السهم
* @param {string} timeframe - الإطار الزمني ('D' لليومي، 'W' للأسبوعي، إلخ)
* @return {Promise} وعد بالبيانات
*/
async function fetchFinnhubData(symbol, timeframe = 'D') {
// تحويل الإطار الزمني إلى تنسيق Finnhub
let resolution = 'D'; // افتراضي
let from = Math.floor(Date.now() / 1000 - 180 * 24 * 60 * 60); // 180 يوم للخلف (افتراضي)
switch (timeframe) {
case 'D':
resolution = 'D';
from = Math.floor(Date.now() / 1000 - 365 * 24 * 60 * 60); // سنة واحدة
break;
case 'W':
resolution = 'W';
from = Math.floor(Date.now() / 1000 - 3 * 365 * 24 * 60 * 60); // 3 سنوات
break;
case 'M':
resolution = 'M';
from = Math.floor(Date.now() / 1000 - 5 * 365 * 24 * 60 * 60); // 5 سنوات
break;
case '60':
resolution = '60';
from = Math.floor(Date.now() / 1000 - 30 * 24 * 60 * 60); // 30 يوم
break;
case '30':
resolution = '30';
from = Math.floor(Date.now() / 1000 - 15 * 24 * 60 * 60); // 15 يوم
break;
case '15':
resolution = '15';
from = Math.floor(Date.now() / 1000 - 10 * 24 * 60 * 60); // 10 أيام
break;
case '5':
resolution = '5';
from = Math.floor(Date.now() / 1000 - 5 * 24 * 60 * 60); // 5 أيام
break;
case '1':
resolution = '1';
from = Math.floor(Date.now() / 1000 - 24 * 60 * 60); // يوم واحد
break;
}
const to = Math.floor(Date.now() / 1000); // الوقت الحالي
try {
const response = await fetch(`/api/finnhub?symbol=${symbol}&resolution=${resolution}&from=${from}&to=${to}`);
if (!response.ok) {
throw new Error(`Finnhub API error: ${response.status}`);
}
const data = await response.json();
if (!data || data.s !== 'ok' || !data.t || data.t.length === 0) {
throw new Error('بيانات غير صالحة من Finnhub');
}
// تحويل البيانات إلى تنسيق متوافق مع التطبيق
const formattedData = data.t.map((timestamp, i) => ({
time: new Date(timestamp * 1000).toISOString().split('T')[0],
open: data.o[i],
high: data.h[i],
low: data.l[i],
close: data.c[i],
volume: data.v[i] || 0
}));
// ترتيب البيانات تصاعدياً حسب التاريخ
return formattedData.sort((a, b) => new Date(a.time) - new Date(b.time));
} catch (error) {
console.error('خطأ في جلب بيانات Finnhub:', error);
reportFailure();
return null;
}
}
/**
* جلب بيانات الرسم البياني من المصدر الحالي
* @param {string} symbol - رمز السهم
* @param {string} timeframe - الإطار الزمني
* @return {Promise} وعد بالبيانات
*/
async function fetchChartData(symbol, timeframe = 'D') {
console.log(`جلب بيانات الرسم البياني لـ ${symbol} (${timeframe}) من المصدر: ${currentSource}`);
try {
let data = null;
// استخدام المصدر الحالي لجلب البيانات
switch (currentSource) {
case DATA_SOURCES.PRIMARY:
// استخدام TradingView API الأصلي (من خلال بوابة الخادم)
data = await fetchTradingViewData(symbol, timeframe);
break;
case DATA_SOURCES.SECONDARY:
data = await fetchAlphaVantageData(symbol, timeframe);
break;
case DATA_SOURCES.FALLBACK:
data = await fetchYahooData(symbol, timeframe);
break;
case DATA_SOURCES.HISTORICAL:
data = await fetchIEXData(symbol, timeframe);
break;
case DATA_SOURCES.REALTIME:
data = await fetchFinnhubData(symbol, timeframe);
break;
default:
throw new Error(`مصدر بيانات غير معروف: ${currentSource}`);
}
// التحقق من نجاح جلب البيانات
if (!data || data.length === 0) {
throw new Error(`فشل في جلب البيانات من المصدر ${currentSource}`);
}
// تم الحصول على البيانات بنجاح، إعادة تعيين عداد الفشل
resetFailureCount();
// تخزين البيانات في ذاكرة التخزين المؤقت
if (typeof cacheChartData === 'function') {
cacheChartData(symbol, data);
}
// إرسال حدث تحميل البيانات
if (typeof window.dispatchChartDataLoaded === 'function') {
window.dispatchChartDataLoaded(symbol, data);
}
return data;
} catch (error) {
console.error(`فشل في جلب بيانات الرسم البياني من المصدر ${currentSource}:`, error);
// تسجيل الفشل وتجربة المصدر التالي
reportFailure();
// محاولة استخدام المصدر التالي
if (failureAttempts[currentSource] >= MAX_FAILURE_ATTEMPTS) {
const nextSource = switchToNextSource();
return fetchChartData(symbol, timeframe); // تكرار الطلب بالمصدر الجديد
}
// إذا كانت هناك بيانات مخزنة مؤقتًا، استخدمها كحل احتياطي
if (typeof getCachedChartData === 'function') {
const cachedData = getCachedChartData(symbol);
if (cachedData) {
console.log(`استخدام البيانات المخزنة مؤقتًا لـ ${symbol} كبديل`);
return cachedData;
}
}
return null;
}
}
/**
* استدعاء TradingView API الأصلي من خلال خادم الوسيط
* ملاحظة: هذه الوظيفة تفترض وجود طرف خادم يتعامل مع TradingView API
* @param {string} symbol - رمز السهم
* @param {string} timeframe - الإطار الزمني
* @return {Promise} وعد بالبيانات
*/
async function fetchTradingViewData(symbol, timeframe = 'D') {
try {
// هذا مجرد مثال، في النظام الحقيقي سيكون لديك نقطة نهاية API خاصة بك
const response = await fetch(`/api/chart-data?symbol=${symbol}&timeframe=${timeframe}`);
if (!response.ok) {
throw new Error(`TradingView API error: ${response.status}`);
}
const data = await response.json();
if (!data || !data.data || data.data.length === 0) {
throw new Error('بيانات غير صالحة من TradingView API');
}
return data.data;
} catch (error) {
console.error('خطأ في جلب بيانات TradingView:', error);
reportFailure();
return null;
}
}
// إنشاء كائن مدير مصادر البيانات العام
window.DataSourcesManager = {
// الثوابت
DATA_SOURCES,
API_KEYS,
// خصائص الحالة
currentSource,
failureAttempts,
sourcesStatus,
// الدوال الأساسية
getCurrentSource,
setDataSource,
reportFailure,
resetFailureCount,
switchToNextSource,
switchToSource,
// دوال فحص الصحة
checkSourceAvailability,
checkAllSourcesAvailability,
startPeriodicHealthCheck,
// دوال المصادر البديلة
enableFallbackSource,
handleSourceFailure,
// دوال جلب البيانات
fetchAlphaVantageData,
fetchYahooData,
fetchIEXData,
fetchFinnhubData,
// دوال واجهة المستخدم
updateDataSourcesStatus,
getSourceDisplayName
};
// بدء الفحص الدوري للصحة
document.addEventListener('DOMContentLoaded', function() {
console.log('تهيئة مدير مصادر البيانات...');
// استعادة المصدر المفضل من التخزين المحلي
try {
const savedSource = localStorage.getItem('preferredDataSource');
if (savedSource && Object.values(DATA_SOURCES).includes(savedSource)) {
console.log(`استخدام مصدر البيانات المحفوظ: ${savedSource}`);
setDataSource(savedSource);
}
} catch (e) {
console.warn('لا يمكن استعادة مصدر البيانات المفضل:', e);
}
// بدء الفحص الدوري كل 5 دقائق
startPeriodicHealthCheck(300000);
});