@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
JavaScript
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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