UNPKG

@fin.cx/opendata

Version:

A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stoc

627 lines 53.5 kB
import * as plugins from '../../plugins.js'; /** * Custom error for rate limit exceeded responses */ class RateLimitError extends Error { waitTime; retryAfter; constructor(message, waitTime, retryAfter) { super(message); this.waitTime = waitTime; this.retryAfter = retryAfter; this.name = 'RateLimitError'; } } /** * Rate limiter for CoinGecko API * Free tier (Demo): 30 requests per minute * Without registration: 5-15 requests per minute */ class RateLimiter { requestTimes = []; maxRequestsPerMinute; consecutiveRateLimitErrors = 0; constructor(maxRequestsPerMinute = 30) { this.maxRequestsPerMinute = maxRequestsPerMinute; } async waitForSlot() { const now = Date.now(); const oneMinuteAgo = now - 60000; // Remove requests older than 1 minute this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo); // If we've hit the limit, wait if (this.requestTimes.length >= this.maxRequestsPerMinute) { const oldestRequest = this.requestTimes[0]; const waitTime = 60000 - (now - oldestRequest) + 100; // +100ms buffer await plugins.smartdelay.delayFor(waitTime); return this.waitForSlot(); // Recursively check again } // Record this request this.requestTimes.push(now); } /** * Get time in milliseconds until next request slot is available */ getTimeUntilNextSlot() { const now = Date.now(); const oneMinuteAgo = now - 60000; // Clean old requests const recentRequests = this.requestTimes.filter(time => time > oneMinuteAgo); if (recentRequests.length < this.maxRequestsPerMinute) { return 0; // Slot available now } // Calculate wait time until oldest request expires const oldestRequest = recentRequests[0]; return Math.max(0, 60000 - (now - oldestRequest) + 100); } /** * Handle rate limit error with exponential backoff * Returns wait time in milliseconds */ handleRateLimitError() { this.consecutiveRateLimitErrors++; // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 60s (max) const baseWait = 1000; // 1 second const exponent = this.consecutiveRateLimitErrors - 1; const backoff = Math.min(baseWait * Math.pow(2, exponent), 60000 // max 60 seconds ); // After 3 consecutive 429s, reduce rate limit to 80% as safety measure if (this.consecutiveRateLimitErrors >= 3) { const newLimit = Math.floor(this.maxRequestsPerMinute * 0.8); if (newLimit < this.maxRequestsPerMinute) { console.warn(`Adjusting rate limit from ${this.maxRequestsPerMinute} to ${newLimit} requests/min due to repeated 429 errors`); this.maxRequestsPerMinute = newLimit; } } return backoff; } /** * Reset consecutive error count on successful request */ resetErrors() { if (this.consecutiveRateLimitErrors > 0) { this.consecutiveRateLimitErrors = 0; } } } /** * CoinGecko Crypto Price Provider * * Documentation: https://docs.coingecko.com/v3.0.1/reference/endpoint-overview * * Features: * - Current crypto prices (single and batch) * - Historical price data with OHLCV * - 13M+ tokens, 240+ networks, 1600+ exchanges * - Accepts both ticker symbols (BTC, ETH) and CoinGecko IDs (bitcoin, ethereum) * - 24/7 market data (crypto never closes) * * Rate Limits: * - Free tier (no key): 5-15 requests/minute * - Demo plan (free with registration): ~30 requests/minute, 10,000/month * - Paid plans: Higher limits * * API Authentication: * - Optional API key for Demo/paid plans * - Header: x-cg-demo-api-key (Demo) or x-cg-pro-api-key (paid) */ export class CoinGeckoProvider { config; name = 'CoinGecko'; priority = 90; // High priority for crypto, between Yahoo (100) and Marketstack (80) requiresAuth = false; // API key is optional rateLimit = { requestsPerMinute: 30, // Demo plan default requestsPerDay: 10000 // Demo plan monthly quota / 30 }; logger = console; baseUrl = 'https://api.coingecko.com/api/v3'; apiKey; rateLimiter; // Coin mapping cache coinMapCache = new Map(); // ticker/id -> coingecko id coinListLoadedAt = null; coinListCacheTTL = 24 * 60 * 60 * 1000; // 24 hours // Priority map for common crypto tickers (to avoid conflicts) priorityTickerMap = new Map([ ['btc', 'bitcoin'], ['eth', 'ethereum'], ['usdt', 'tether'], ['bnb', 'binancecoin'], ['sol', 'solana'], ['usdc', 'usd-coin'], ['xrp', 'ripple'], ['ada', 'cardano'], ['doge', 'dogecoin'], ['trx', 'tron'], ['dot', 'polkadot'], ['matic', 'matic-network'], ['ltc', 'litecoin'], ['shib', 'shiba-inu'], ['avax', 'avalanche-2'], ['link', 'chainlink'], ['atom', 'cosmos'], ['uni', 'uniswap'], ['etc', 'ethereum-classic'], ['xlm', 'stellar'] ]); constructor(apiKey, config) { this.config = config; this.apiKey = apiKey; this.rateLimiter = new RateLimiter(this.rateLimit.requestsPerMinute); } /** * Unified data fetching method supporting all request types */ async fetchData(request) { switch (request.type) { case 'current': return this.fetchCurrentPrice(request); case 'batch': return this.fetchBatchCurrentPrices(request); case 'historical': return this.fetchHistoricalPrices(request); case 'intraday': return this.fetchIntradayPrices(request); default: throw new Error(`Unsupported request type: ${request.type}`); } } /** * Fetch current price for a single crypto */ async fetchCurrentPrice(request) { return this.fetchWithRateLimitRetry(async () => { // Resolve ticker to CoinGecko ID const coinId = await this.resolveCoinId(request.ticker); // Build URL const params = new URLSearchParams({ ids: coinId, vs_currencies: 'usd', include_market_cap: 'true', include_24hr_vol: 'true', include_24hr_change: 'true', include_last_updated_at: 'true' }); const url = `${this.baseUrl}/simple/price?${params}`; // Wait for rate limit slot await this.rateLimiter.waitForSlot(); // Make request const response = await plugins.smartrequest.SmartRequest.create() .url(url) .headers(this.buildHeaders()) .timeout(this.config?.timeout || 10000) .get(); const responseData = await response.json(); // Check for rate limit error if (this.isRateLimitError(responseData)) { const waitTime = this.rateLimiter.handleRateLimitError(); throw new RateLimitError(`Rate limit exceeded for ${request.ticker}`, waitTime); } if (!responseData[coinId]) { throw new Error(`No data found for ${request.ticker} (${coinId})`); } return this.mapToStockPrice(request.ticker, coinId, responseData[coinId], 'live'); }, `current price for ${request.ticker}`); } /** * Fetch batch current prices for multiple cryptos */ async fetchBatchCurrentPrices(request) { return this.fetchWithRateLimitRetry(async () => { // Resolve all tickers to CoinGecko IDs const coinIds = await Promise.all(request.tickers.map(ticker => this.resolveCoinId(ticker))); // Build URL with comma-separated IDs const params = new URLSearchParams({ ids: coinIds.join(','), vs_currencies: 'usd', include_market_cap: 'true', include_24hr_vol: 'true', include_24hr_change: 'true', include_last_updated_at: 'true' }); const url = `${this.baseUrl}/simple/price?${params}`; // Wait for rate limit slot await this.rateLimiter.waitForSlot(); // Make request const response = await plugins.smartrequest.SmartRequest.create() .url(url) .headers(this.buildHeaders()) .timeout(this.config?.timeout || 15000) .get(); const responseData = await response.json(); // Check for rate limit error if (this.isRateLimitError(responseData)) { const waitTime = this.rateLimiter.handleRateLimitError(); throw new RateLimitError(`Rate limit exceeded for batch request`, waitTime); } const prices = []; // Map responses back to original tickers for (let i = 0; i < request.tickers.length; i++) { const ticker = request.tickers[i]; const coinId = coinIds[i]; if (responseData[coinId]) { try { prices.push(this.mapToStockPrice(ticker, coinId, responseData[coinId], 'live')); } catch (error) { this.logger.warn(`Failed to parse data for ${ticker}:`, error); } } else { this.logger.warn(`No data returned for ${ticker} (${coinId})`); } } if (prices.length === 0) { throw new Error('No valid price data received from batch request'); } return prices; }, `batch prices for ${request.tickers.length} tickers`); } /** * Fetch historical prices with OHLCV data */ async fetchHistoricalPrices(request) { return this.fetchWithRateLimitRetry(async () => { const coinId = await this.resolveCoinId(request.ticker); // Calculate days between dates const days = Math.ceil((request.to.getTime() - request.from.getTime()) / (1000 * 60 * 60 * 24)); // Build URL const params = new URLSearchParams({ vs_currency: 'usd', days: days.toString(), interval: 'daily' // Explicit daily granularity for historical data }); const url = `${this.baseUrl}/coins/${coinId}/market_chart?${params}`; // Wait for rate limit slot await this.rateLimiter.waitForSlot(); // Make request const response = await plugins.smartrequest.SmartRequest.create() .url(url) .headers(this.buildHeaders()) .timeout(this.config?.timeout || 20000) .get(); const responseData = await response.json(); // Check for rate limit error if (this.isRateLimitError(responseData)) { const waitTime = this.rateLimiter.handleRateLimitError(); throw new RateLimitError(`Rate limit exceeded for historical ${request.ticker}`, waitTime); } if (!responseData.prices || !Array.isArray(responseData.prices)) { this.logger.error(`Invalid API response for ${request.ticker}:`, JSON.stringify(responseData).substring(0, 500)); throw new Error(`Invalid response format for ${request.ticker}: ${JSON.stringify(responseData).substring(0, 200)}`); } const prices = []; const priceData = responseData.prices; const marketCapData = responseData.market_caps || []; const volumeData = responseData.total_volumes || []; // Warn if processing large amount of historical data const maxRecords = this.config?.maxRecords || 10000; if (priceData.length > maxRecords) { this.logger.warn(`Historical request for ${request.ticker} returned ${priceData.length} records, ` + `which exceeds maxRecords limit of ${maxRecords}. Processing first ${maxRecords} only.`); } // Process each data point (up to maxRecords) const recordsToProcess = Math.min(priceData.length, maxRecords); for (let i = 0; i < recordsToProcess; i++) { const [timestamp, price] = priceData[i]; const date = new Date(timestamp); // Filter by date range if (date < request.from || date > request.to) continue; const marketCap = marketCapData[i]?.[1]; const volume = volumeData[i]?.[1]; // Calculate previous close for change calculation const previousClose = i > 0 ? priceData[i - 1][1] : price; const change = price - previousClose; const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0; prices.push({ ticker: request.ticker.toUpperCase(), price: price, currency: 'USD', change: change, changePercent: changePercent, previousClose: previousClose, timestamp: date, provider: this.name, marketState: 'REGULAR', // Crypto markets are always open // OHLCV data (note: market_chart doesn't provide OHLC, only close prices) volume: volume, dataType: 'eod', fetchedAt: new Date(), companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1) }); } return prices; }, `historical prices for ${request.ticker}`); } /** * Fetch intraday prices with hourly intervals */ async fetchIntradayPrices(request) { return this.fetchWithRateLimitRetry(async () => { const coinId = await this.resolveCoinId(request.ticker); // Map interval to days parameter (CoinGecko auto-granularity) // For hourly data, request 1-7 days let days = 1; switch (request.interval) { case '1min': case '5min': case '10min': case '15min': case '30min': throw new Error('CoinGecko only supports hourly intervals in market_chart. Use interval: "1hour"'); case '1hour': days = 1; // Last 24 hours with hourly granularity break; } // Build URL (omit interval param for automatic granularity based on days) const params = new URLSearchParams({ vs_currency: 'usd', days: days.toString() }); const url = `${this.baseUrl}/coins/${coinId}/market_chart?${params}`; // Wait for rate limit slot await this.rateLimiter.waitForSlot(); // Make request const response = await plugins.smartrequest.SmartRequest.create() .url(url) .headers(this.buildHeaders()) .timeout(this.config?.timeout || 15000) .get(); const responseData = await response.json(); // Check for rate limit error if (this.isRateLimitError(responseData)) { const waitTime = this.rateLimiter.handleRateLimitError(); throw new RateLimitError(`Rate limit exceeded for intraday ${request.ticker}`, waitTime); } if (!responseData.prices || !Array.isArray(responseData.prices)) { this.logger.error(`Invalid API response for ${request.ticker}:`, JSON.stringify(responseData).substring(0, 500)); throw new Error(`Invalid response format for ${request.ticker}: ${JSON.stringify(responseData).substring(0, 200)}`); } const prices = []; const priceData = responseData.prices; const marketCapData = responseData.market_caps || []; const volumeData = responseData.total_volumes || []; // Apply default limit if user didn't specify one (performance optimization) const effectiveLimit = request.limit || this.config?.defaultIntradayLimit || 1000; // Warn if fetching large amount of data without explicit limit if (!request.limit && priceData.length > effectiveLimit) { this.logger.warn(`Intraday request for ${request.ticker} returned ${priceData.length} records but no limit specified. ` + `Applying default limit of ${effectiveLimit}. Consider adding a limit to the request for better performance.`); } // Apply limit (take most recent data) const limit = Math.min(effectiveLimit, priceData.length); const dataToProcess = priceData.slice(-limit); for (let i = 0; i < dataToProcess.length; i++) { const actualIndex = priceData.length - limit + i; const [timestamp, price] = dataToProcess[i]; const date = new Date(timestamp); const marketCap = marketCapData[actualIndex]?.[1]; const volume = volumeData[actualIndex]?.[1]; const previousClose = i > 0 ? dataToProcess[i - 1][1] : price; const change = price - previousClose; const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0; prices.push({ ticker: request.ticker.toUpperCase(), price: price, currency: 'USD', change: change, changePercent: changePercent, previousClose: previousClose, timestamp: date, provider: this.name, marketState: 'REGULAR', volume: volume, dataType: 'intraday', fetchedAt: new Date(), companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1) }); } return prices; }, `intraday prices for ${request.ticker}`); } /** * Check if CoinGecko API is available */ async isAvailable() { try { const url = `${this.baseUrl}/simple/price?ids=bitcoin&vs_currencies=usd`; await this.rateLimiter.waitForSlot(); const response = await plugins.smartrequest.SmartRequest.create() .url(url) .headers(this.buildHeaders()) .timeout(5000) .get(); const responseData = await response.json(); return responseData.bitcoin?.usd !== undefined; } catch (error) { this.logger.warn('CoinGecko provider is not available:', error); return false; } } /** * Check if a market/network is supported * CoinGecko supports 240+ networks */ supportsMarket(market) { // CoinGecko has extensive crypto network coverage const supportedNetworks = [ 'CRYPTO', 'BTC', 'ETH', 'BSC', 'POLYGON', 'AVALANCHE', 'SOLANA', 'ARBITRUM', 'OPTIMISM', 'BASE' ]; return supportedNetworks.includes(market.toUpperCase()); } /** * Check if a ticker format is supported * Supports both ticker symbols (BTC) and CoinGecko IDs (bitcoin) */ supportsTicker(ticker) { // Accept alphanumeric with hyphens (for coin IDs like 'wrapped-bitcoin') return /^[A-Za-z0-9\-]{1,50}$/.test(ticker); } /** * Resolve ticker symbol or CoinGecko ID to canonical CoinGecko ID * Supports both formats: "BTC" -> "bitcoin", "bitcoin" -> "bitcoin" */ async resolveCoinId(tickerOrId) { const normalized = tickerOrId.toLowerCase(); // Check priority map first (for common cryptos) if (this.priorityTickerMap.has(normalized)) { const coinId = this.priorityTickerMap.get(normalized); this.coinMapCache.set(normalized, coinId); return coinId; } // Check cache if (this.coinMapCache.has(normalized)) { return this.coinMapCache.get(normalized); } // Check if it's already a valid CoinGecko ID (contains hyphens or is all lowercase with original case) if (normalized.includes('-') || normalized === tickerOrId) { // Assume it's a CoinGecko ID, cache it this.coinMapCache.set(normalized, normalized); return normalized; } // Load coin list if needed if (!this.coinListLoadedAt || Date.now() - this.coinListLoadedAt.getTime() > this.coinListCacheTTL) { await this.loadCoinList(); } // Try to find in cache after loading if (this.coinMapCache.has(normalized)) { return this.coinMapCache.get(normalized); } // Not found - return as-is and let API handle the error this.logger.warn(`Could not resolve ticker ${tickerOrId} to CoinGecko ID, using as-is`); return normalized; } /** * Load complete coin list from CoinGecko API */ async loadCoinList() { try { const url = `${this.baseUrl}/coins/list`; await this.rateLimiter.waitForSlot(); const response = await plugins.smartrequest.SmartRequest.create() .url(url) .headers(this.buildHeaders()) .timeout(10000) .get(); const coinList = await response.json(); // Clear cache before rebuilding to prevent memory leak // Keep only entries that are in priorityTickerMap const priorityEntries = new Map(); for (const [key, value] of this.priorityTickerMap) { priorityEntries.set(key, value); } this.coinMapCache.clear(); // Restore priority mappings for (const [key, value] of priorityEntries) { this.coinMapCache.set(key, value); } // Build mapping: symbol -> id for (const coin of coinList) { const symbol = coin.symbol.toLowerCase(); const id = coin.id.toLowerCase(); // Don't overwrite priority mappings if (!this.priorityTickerMap.has(symbol)) { this.coinMapCache.set(symbol, id); } // Always cache the ID mapping (id -> id for when users pass CoinGecko IDs directly) this.coinMapCache.set(id, id); } this.coinListLoadedAt = new Date(); this.logger.info(`Loaded ${coinList.length} coins from CoinGecko (cache: ${this.coinMapCache.size} entries)`); } catch (error) { this.logger.error('Failed to load coin list from CoinGecko:', error); // Don't throw - we can still work with direct IDs } } /** * Map CoinGecko simple/price response to IStockPrice */ mapToStockPrice(ticker, coinId, data, dataType) { const price = data.usd; const change24h = data.usd_24h_change || 0; // Calculate previous close from 24h change const changePercent = change24h; const change = (price * changePercent) / 100; const previousClose = price - change; // Parse last updated timestamp const timestamp = data.last_updated_at ? new Date(data.last_updated_at * 1000) : new Date(); return { ticker: ticker.toUpperCase(), price: price, currency: 'USD', change: change, changePercent: changePercent, previousClose: previousClose, timestamp: timestamp, provider: this.name, marketState: 'REGULAR', // Crypto markets are 24/7 // Volume and market cap volume: data.usd_24h_vol, dataType: dataType, fetchedAt: new Date(), // Company identification (use coin name) companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1), companyFullName: `${coinId.charAt(0).toUpperCase() + coinId.slice(1)} (${ticker.toUpperCase()})` }; } /** * Build HTTP headers with optional API key */ buildHeaders() { const headers = { 'Accept': 'application/json' }; if (this.apiKey) { // Use Demo or Pro API key header // CoinGecko accepts both x-cg-demo-api-key and x-cg-pro-api-key headers['x-cg-demo-api-key'] = this.apiKey; } return headers; } /** * Check if response indicates a rate limit error (429) */ isRateLimitError(responseData) { return responseData?.status?.error_code === 429; } /** * Wrapper for fetch operations with automatic rate limit retry and exponential backoff */ async fetchWithRateLimitRetry(fetchFn, operationName, maxRetries = 3) { let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { try { const result = await fetchFn(); this.rateLimiter.resetErrors(); return result; } catch (error) { lastError = error; if (error instanceof RateLimitError) { const attemptInfo = `${attempt + 1}/${maxRetries}`; this.logger.warn(`Rate limit hit for ${operationName}, waiting ${error.waitTime}ms before retry ${attemptInfo}`); if (attempt < maxRetries - 1) { await plugins.smartdelay.delayFor(error.waitTime); continue; } else { this.logger.error(`Max retries (${maxRetries}) exceeded for ${operationName} due to rate limiting`); throw error; } } // Non-rate-limit errors: throw immediately throw error; } } throw lastError; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXIuY29pbmdlY2tvLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvc3RvY2tzL3Byb3ZpZGVycy9wcm92aWRlci5jb2luZ2Vja28udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQVc1Qzs7R0FFRztBQUNILE1BQU0sY0FBZSxTQUFRLEtBQUs7SUFHdkI7SUFDQTtJQUhULFlBQ0UsT0FBZSxFQUNSLFFBQWdCLEVBQ2hCLFVBQW1CO1FBRTFCLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUhSLGFBQVEsR0FBUixRQUFRLENBQVE7UUFDaEIsZUFBVSxHQUFWLFVBQVUsQ0FBUztRQUcxQixJQUFJLENBQUMsSUFBSSxHQUFHLGdCQUFnQixDQUFDO0lBQy9CLENBQUM7Q0FDRjtBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFdBQVc7SUFDUCxZQUFZLEdBQWEsRUFBRSxDQUFDO0lBQzVCLG9CQUFvQixDQUFTO0lBQzdCLDBCQUEwQixHQUFXLENBQUMsQ0FBQztJQUUvQyxZQUFZLHVCQUErQixFQUFFO1FBQzNDLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxvQkFBb0IsQ0FBQztJQUNuRCxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVc7UUFDdEIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sWUFBWSxHQUFHLEdBQUcsR0FBRyxLQUFLLENBQUM7UUFFakMsc0NBQXNDO1FBQ3RDLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLEdBQUcsWUFBWSxDQUFDLENBQUM7UUFFMUUsK0JBQStCO1FBQy9CLElBQUksSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7WUFDMUQsTUFBTSxhQUFhLEdB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