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

298 lines 20.9 kB
import * as plugins from '../plugins.js'; /** * Service for managing fundamental data providers and caching * Parallel to StockPriceService but for fundamental data instead of prices */ export class FundamentalsService { providers = new Map(); cache = new Map(); logger = console; cacheConfig = { ttl: 90 * 24 * 60 * 60 * 1000, // 90 days default (fundamentals change quarterly) maxEntries: 10000 }; constructor(cacheConfig) { if (cacheConfig) { this.cacheConfig = { ...this.cacheConfig, ...cacheConfig }; } } /** * Register a fundamentals provider */ register(provider, config) { const defaultConfig = { enabled: true, priority: provider.priority, timeout: 30000, // Longer timeout for fundamental data retryAttempts: 2, retryDelay: 1000, cacheTTL: this.cacheConfig.ttl }; const mergedConfig = { ...defaultConfig, ...config }; this.providers.set(provider.name, { provider, config: mergedConfig, successCount: 0, errorCount: 0 }); console.log(`Registered fundamentals provider: ${provider.name}`); } /** * Unregister a provider */ unregister(providerName) { this.providers.delete(providerName); console.log(`Unregistered fundamentals provider: ${providerName}`); } /** * Get a specific provider by name */ getProvider(name) { return this.providers.get(name)?.provider; } /** * Get all registered providers */ getAllProviders() { return Array.from(this.providers.values()).map(entry => entry.provider); } /** * Get enabled providers sorted by priority */ getEnabledProviders() { return Array.from(this.providers.values()) .filter(entry => entry.config.enabled) .sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0)) .map(entry => entry.provider); } /** * Get fundamental data for a single ticker */ async getFundamentals(ticker) { const result = await this.getData({ type: 'fundamentals-current', ticker }); return result; } /** * Get fundamental data for multiple tickers */ async getBatchFundamentals(tickers) { const result = await this.getData({ type: 'fundamentals-batch', tickers }); return result; } /** * Unified data fetching method */ async getData(request) { const cacheKey = this.getCacheKey(request); const cached = this.getFromCache(cacheKey); if (cached) { console.log(`Cache hit for ${this.getRequestDescription(request)}`); return cached; } const providers = this.getEnabledProviders(); if (providers.length === 0) { throw new Error('No fundamentals providers available'); } let lastError; for (const provider of providers) { const entry = this.providers.get(provider.name); try { const result = await this.fetchWithRetry(() => provider.fetchData(request), entry.config); entry.successCount++; // Use provider-specific cache TTL or default const ttl = entry.config.cacheTTL || this.cacheConfig.ttl; this.addToCache(cacheKey, result, ttl); console.log(`Successfully fetched ${this.getRequestDescription(request)} from ${provider.name}`); return result; } catch (error) { entry.errorCount++; entry.lastError = error; entry.lastErrorTime = new Date(); lastError = error; console.warn(`Provider ${provider.name} failed for ${this.getRequestDescription(request)}: ${plugins.getErrorMessage(error)}`); } } throw new Error(`Failed to fetch ${this.getRequestDescription(request)} from all providers. Last error: ${lastError?.message}`); } /** * Enrich fundamentals with calculated metrics using current price */ async enrichWithPrice(fundamentals, price) { const enriched = { ...fundamentals }; // Calculate market cap: price × shares outstanding if (fundamentals.sharesOutstanding) { enriched.marketCap = price * fundamentals.sharesOutstanding; } // Calculate P/E ratio: price / EPS if (fundamentals.earningsPerShareDiluted && fundamentals.earningsPerShareDiluted > 0) { enriched.priceToEarnings = price / fundamentals.earningsPerShareDiluted; } // Calculate price-to-book: market cap / stockholders equity if (enriched.marketCap && fundamentals.stockholdersEquity && fundamentals.stockholdersEquity > 0) { enriched.priceToBook = enriched.marketCap / fundamentals.stockholdersEquity; } return enriched; } /** * Enrich batch fundamentals with prices */ async enrichBatchWithPrices(fundamentalsList, priceMap) { return Promise.all(fundamentalsList.map(fundamentals => { const price = priceMap.get(fundamentals.ticker); if (price) { return this.enrichWithPrice(fundamentals, price); } return Promise.resolve(fundamentals); })); } /** * Check health of all providers */ async checkProvidersHealth() { const health = new Map(); for (const [name, entry] of this.providers) { if (!entry.config.enabled) { health.set(name, false); continue; } try { const isAvailable = await entry.provider.isAvailable(); health.set(name, isAvailable); } catch (error) { health.set(name, false); console.error(`Health check failed for ${name}:`, error); } } return health; } /** * Get provider statistics */ getProviderStats() { const stats = new Map(); for (const [name, entry] of this.providers) { stats.set(name, { successCount: entry.successCount, errorCount: entry.errorCount, lastError: entry.lastError?.message, lastErrorTime: entry.lastErrorTime }); } return stats; } /** * Clear all cached data */ clearCache() { this.cache.clear(); console.log('Fundamentals cache cleared'); } /** * Set cache TTL */ setCacheTTL(ttl) { this.cacheConfig.ttl = ttl; console.log(`Fundamentals cache TTL set to ${ttl}ms`); } /** * Get cache statistics */ getCacheStats() { return { size: this.cache.size, maxEntries: this.cacheConfig.maxEntries, ttl: this.cacheConfig.ttl }; } /** * Fetch with retry logic */ async fetchWithRetry(fetchFn, config) { const maxAttempts = config.retryAttempts || 1; let lastError; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fetchFn(); } catch (error) { lastError = error; if (attempt < maxAttempts) { const delay = (config.retryDelay || 1000) * attempt; console.log(`Retry attempt ${attempt} after ${delay}ms`); await plugins.smartdelay.delayFor(delay); } } } throw lastError || new Error('Unknown error during fetch'); } /** * Generate cache key for request */ getCacheKey(request) { switch (request.type) { case 'fundamentals-current': return `fundamentals:${request.ticker}`; case 'fundamentals-batch': const tickers = request.tickers.sort().join(','); return `fundamentals-batch:${tickers}`; default: return `unknown:${JSON.stringify(request)}`; } } /** * Get from cache if not expired */ getFromCache(key) { const entry = this.cache.get(key); if (!entry) { return null; } // Check if cache entry has expired const age = Date.now() - entry.timestamp.getTime(); if (entry.ttl !== Infinity && age > entry.ttl) { this.cache.delete(key); return null; } return entry.fundamentals; } /** * Add to cache with TTL */ addToCache(key, fundamentals, ttl) { // Enforce max entries limit if (this.cache.size >= this.cacheConfig.maxEntries) { // Remove oldest entry const oldestKey = this.cache.keys().next().value; if (oldestKey) { this.cache.delete(oldestKey); } } this.cache.set(key, { fundamentals, timestamp: new Date(), ttl: ttl || this.cacheConfig.ttl }); } /** * Get human-readable request description */ getRequestDescription(request) { switch (request.type) { case 'fundamentals-current': return `fundamentals for ${request.ticker}`; case 'fundamentals-batch': return `fundamentals for ${request.tickers.length} tickers`; default: return 'fundamentals data'; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5mdW5kYW1lbnRhbHNzZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvc3RvY2tzL2NsYXNzZXMuZnVuZGFtZW50YWxzc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQXdCekM7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLG1CQUFtQjtJQUN0QixTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQTBCLENBQUM7SUFDOUMsS0FBSyxHQUFHLElBQUksR0FBRyxFQUF1QixDQUFDO0lBQ3ZDLE1BQU0sR0FBRyxPQUFPLENBQUM7SUFFakIsV0FBVyxHQUFHO1FBQ3BCLEdBQUcsRUFBRSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLGtEQUFrRDtRQUNqRixVQUFVLEVBQUUsS0FBSztLQUNsQixDQUFDO0lBRUYsWUFBWSxXQUFtRDtRQUM3RCxJQUFJLFdBQVcsRUFBRSxDQUFDO1lBQ2hCLElBQUksQ0FBQyxXQUFXLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxXQUFXLEVBQUUsR0FBRyxXQUFXLEVBQUUsQ0FBQztRQUM3RCxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ksUUFBUSxDQUFDLFFBQStCLEVBQUUsTUFBb0M7UUFDbkYsTUFBTSxhQUFhLEdBQWdDO1lBQ2pELE9BQU8sRUFBRSxJQUFJO1lBQ2IsUUFBUSxFQUFFLFFBQVEsQ0FBQyxRQUFRO1lBQzNCLE9BQU8sRUFBRSxLQUFLLEVBQUUsc0NBQXNDO1lBQ3RELGFBQWEsRUFBRSxDQUFDO1lBQ2hCLFVBQVUsRUFBRSxJQUFJO1lBQ2hCLFFBQVEsRUFBRSxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUc7U0FDL0IsQ0FBQztRQUVGLE1BQU0sWUFBWSxHQUFHLEVBQUUsR0FBRyxhQUFhLEVBQUUsR0FBRyxNQUFNLEVBQUUsQ0FBQztRQUVyRCxJQUFJLENB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