@fin.cx/opendata 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,298 @@
1
+ import * as plugins from '../plugins.js';
2
+ /**
3
+ * Service for managing fundamental data providers and caching
4
+ * Parallel to StockPriceService but for fundamental data instead of prices
5
+ */
6
+ export class FundamentalsService {
7
+ constructor(cacheConfig) {
8
+ this.providers = new Map();
9
+ this.cache = new Map();
10
+ this.logger = console;
11
+ this.cacheConfig = {
12
+ ttl: 90 * 24 * 60 * 60 * 1000, // 90 days default (fundamentals change quarterly)
13
+ maxEntries: 10000
14
+ };
15
+ if (cacheConfig) {
16
+ this.cacheConfig = { ...this.cacheConfig, ...cacheConfig };
17
+ }
18
+ }
19
+ /**
20
+ * Register a fundamentals provider
21
+ */
22
+ register(provider, config) {
23
+ const defaultConfig = {
24
+ enabled: true,
25
+ priority: provider.priority,
26
+ timeout: 30000, // Longer timeout for fundamental data
27
+ retryAttempts: 2,
28
+ retryDelay: 1000,
29
+ cacheTTL: this.cacheConfig.ttl
30
+ };
31
+ const mergedConfig = { ...defaultConfig, ...config };
32
+ this.providers.set(provider.name, {
33
+ provider,
34
+ config: mergedConfig,
35
+ successCount: 0,
36
+ errorCount: 0
37
+ });
38
+ console.log(`Registered fundamentals provider: ${provider.name}`);
39
+ }
40
+ /**
41
+ * Unregister a provider
42
+ */
43
+ unregister(providerName) {
44
+ this.providers.delete(providerName);
45
+ console.log(`Unregistered fundamentals provider: ${providerName}`);
46
+ }
47
+ /**
48
+ * Get a specific provider by name
49
+ */
50
+ getProvider(name) {
51
+ return this.providers.get(name)?.provider;
52
+ }
53
+ /**
54
+ * Get all registered providers
55
+ */
56
+ getAllProviders() {
57
+ return Array.from(this.providers.values()).map(entry => entry.provider);
58
+ }
59
+ /**
60
+ * Get enabled providers sorted by priority
61
+ */
62
+ getEnabledProviders() {
63
+ return Array.from(this.providers.values())
64
+ .filter(entry => entry.config.enabled)
65
+ .sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0))
66
+ .map(entry => entry.provider);
67
+ }
68
+ /**
69
+ * Get fundamental data for a single ticker
70
+ */
71
+ async getFundamentals(ticker) {
72
+ const result = await this.getData({
73
+ type: 'fundamentals-current',
74
+ ticker
75
+ });
76
+ return result;
77
+ }
78
+ /**
79
+ * Get fundamental data for multiple tickers
80
+ */
81
+ async getBatchFundamentals(tickers) {
82
+ const result = await this.getData({
83
+ type: 'fundamentals-batch',
84
+ tickers
85
+ });
86
+ return result;
87
+ }
88
+ /**
89
+ * Unified data fetching method
90
+ */
91
+ async getData(request) {
92
+ const cacheKey = this.getCacheKey(request);
93
+ const cached = this.getFromCache(cacheKey);
94
+ if (cached) {
95
+ console.log(`Cache hit for ${this.getRequestDescription(request)}`);
96
+ return cached;
97
+ }
98
+ const providers = this.getEnabledProviders();
99
+ if (providers.length === 0) {
100
+ throw new Error('No fundamentals providers available');
101
+ }
102
+ let lastError;
103
+ for (const provider of providers) {
104
+ const entry = this.providers.get(provider.name);
105
+ try {
106
+ const result = await this.fetchWithRetry(() => provider.fetchData(request), entry.config);
107
+ entry.successCount++;
108
+ // Use provider-specific cache TTL or default
109
+ const ttl = entry.config.cacheTTL || this.cacheConfig.ttl;
110
+ this.addToCache(cacheKey, result, ttl);
111
+ console.log(`Successfully fetched ${this.getRequestDescription(request)} from ${provider.name}`);
112
+ return result;
113
+ }
114
+ catch (error) {
115
+ entry.errorCount++;
116
+ entry.lastError = error;
117
+ entry.lastErrorTime = new Date();
118
+ lastError = error;
119
+ console.warn(`Provider ${provider.name} failed for ${this.getRequestDescription(request)}: ${error.message}`);
120
+ }
121
+ }
122
+ throw new Error(`Failed to fetch ${this.getRequestDescription(request)} from all providers. Last error: ${lastError?.message}`);
123
+ }
124
+ /**
125
+ * Enrich fundamentals with calculated metrics using current price
126
+ */
127
+ async enrichWithPrice(fundamentals, price) {
128
+ const enriched = { ...fundamentals };
129
+ // Calculate market cap: price × shares outstanding
130
+ if (fundamentals.sharesOutstanding) {
131
+ enriched.marketCap = price * fundamentals.sharesOutstanding;
132
+ }
133
+ // Calculate P/E ratio: price / EPS
134
+ if (fundamentals.earningsPerShareDiluted && fundamentals.earningsPerShareDiluted > 0) {
135
+ enriched.priceToEarnings = price / fundamentals.earningsPerShareDiluted;
136
+ }
137
+ // Calculate price-to-book: market cap / stockholders equity
138
+ if (enriched.marketCap && fundamentals.stockholdersEquity && fundamentals.stockholdersEquity > 0) {
139
+ enriched.priceToBook = enriched.marketCap / fundamentals.stockholdersEquity;
140
+ }
141
+ return enriched;
142
+ }
143
+ /**
144
+ * Enrich batch fundamentals with prices
145
+ */
146
+ async enrichBatchWithPrices(fundamentalsList, priceMap) {
147
+ return Promise.all(fundamentalsList.map(fundamentals => {
148
+ const price = priceMap.get(fundamentals.ticker);
149
+ if (price) {
150
+ return this.enrichWithPrice(fundamentals, price);
151
+ }
152
+ return Promise.resolve(fundamentals);
153
+ }));
154
+ }
155
+ /**
156
+ * Check health of all providers
157
+ */
158
+ async checkProvidersHealth() {
159
+ const health = new Map();
160
+ for (const [name, entry] of this.providers) {
161
+ if (!entry.config.enabled) {
162
+ health.set(name, false);
163
+ continue;
164
+ }
165
+ try {
166
+ const isAvailable = await entry.provider.isAvailable();
167
+ health.set(name, isAvailable);
168
+ }
169
+ catch (error) {
170
+ health.set(name, false);
171
+ console.error(`Health check failed for ${name}:`, error);
172
+ }
173
+ }
174
+ return health;
175
+ }
176
+ /**
177
+ * Get provider statistics
178
+ */
179
+ getProviderStats() {
180
+ const stats = new Map();
181
+ for (const [name, entry] of this.providers) {
182
+ stats.set(name, {
183
+ successCount: entry.successCount,
184
+ errorCount: entry.errorCount,
185
+ lastError: entry.lastError?.message,
186
+ lastErrorTime: entry.lastErrorTime
187
+ });
188
+ }
189
+ return stats;
190
+ }
191
+ /**
192
+ * Clear all cached data
193
+ */
194
+ clearCache() {
195
+ this.cache.clear();
196
+ console.log('Fundamentals cache cleared');
197
+ }
198
+ /**
199
+ * Set cache TTL
200
+ */
201
+ setCacheTTL(ttl) {
202
+ this.cacheConfig.ttl = ttl;
203
+ console.log(`Fundamentals cache TTL set to ${ttl}ms`);
204
+ }
205
+ /**
206
+ * Get cache statistics
207
+ */
208
+ getCacheStats() {
209
+ return {
210
+ size: this.cache.size,
211
+ maxEntries: this.cacheConfig.maxEntries,
212
+ ttl: this.cacheConfig.ttl
213
+ };
214
+ }
215
+ /**
216
+ * Fetch with retry logic
217
+ */
218
+ async fetchWithRetry(fetchFn, config) {
219
+ const maxAttempts = config.retryAttempts || 1;
220
+ let lastError;
221
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
222
+ try {
223
+ return await fetchFn();
224
+ }
225
+ catch (error) {
226
+ lastError = error;
227
+ if (attempt < maxAttempts) {
228
+ const delay = (config.retryDelay || 1000) * attempt;
229
+ console.log(`Retry attempt ${attempt} after ${delay}ms`);
230
+ await plugins.smartdelay.delayFor(delay);
231
+ }
232
+ }
233
+ }
234
+ throw lastError || new Error('Unknown error during fetch');
235
+ }
236
+ /**
237
+ * Generate cache key for request
238
+ */
239
+ getCacheKey(request) {
240
+ switch (request.type) {
241
+ case 'fundamentals-current':
242
+ return `fundamentals:${request.ticker}`;
243
+ case 'fundamentals-batch':
244
+ const tickers = request.tickers.sort().join(',');
245
+ return `fundamentals-batch:${tickers}`;
246
+ default:
247
+ return `unknown:${JSON.stringify(request)}`;
248
+ }
249
+ }
250
+ /**
251
+ * Get from cache if not expired
252
+ */
253
+ getFromCache(key) {
254
+ const entry = this.cache.get(key);
255
+ if (!entry) {
256
+ return null;
257
+ }
258
+ // Check if cache entry has expired
259
+ const age = Date.now() - entry.timestamp.getTime();
260
+ if (entry.ttl !== Infinity && age > entry.ttl) {
261
+ this.cache.delete(key);
262
+ return null;
263
+ }
264
+ return entry.fundamentals;
265
+ }
266
+ /**
267
+ * Add to cache with TTL
268
+ */
269
+ addToCache(key, fundamentals, ttl) {
270
+ // Enforce max entries limit
271
+ if (this.cache.size >= this.cacheConfig.maxEntries) {
272
+ // Remove oldest entry
273
+ const oldestKey = this.cache.keys().next().value;
274
+ if (oldestKey) {
275
+ this.cache.delete(oldestKey);
276
+ }
277
+ }
278
+ this.cache.set(key, {
279
+ fundamentals,
280
+ timestamp: new Date(),
281
+ ttl: ttl || this.cacheConfig.ttl
282
+ });
283
+ }
284
+ /**
285
+ * Get human-readable request description
286
+ */
287
+ getRequestDescription(request) {
288
+ switch (request.type) {
289
+ case 'fundamentals-current':
290
+ return `fundamentals for ${request.ticker}`;
291
+ case 'fundamentals-batch':
292
+ return `fundamentals for ${request.tickers.length} tickers`;
293
+ default:
294
+ return 'fundamentals data';
295
+ }
296
+ }
297
+ }
298
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5mdW5kYW1lbnRhbHNzZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvc3RvY2tzL2NsYXNzZXMuZnVuZGFtZW50YWxzc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQXdCekM7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLG1CQUFtQjtJQVU5QixZQUFZLFdBQW1EO1FBVHZELGNBQVMsR0FBRyxJQUFJLEdBQUcsRUFBMEIsQ0FBQztRQUM5QyxVQUFLLEdBQUcsSUFBSSxHQUFHLEVBQXVCLENBQUM7UUFDdkMsV0FBTSxHQUFHLE9BQU8sQ0FBQztRQUVqQixnQkFBVyxHQUFHO1lBQ3BCLEdBQUcsRUFBRSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLGtEQUFrRDtZQUNqRixVQUFVLEVBQUUsS0FBSztTQUNsQixDQUFDO1FBR0EsSUFBSSxXQUFXLEVBQUUsQ0FBQztZQUNoQixJQUFJLENBQUMsV0FBVyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLEdBQUcsV0FBVyxFQUFFLENBQUM7UUFDN0QsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNJLFFBQVEsQ0FBQyxRQUErQixFQUFFLE1BQW9DO1FBQ25GLE1BQU0sYUFBYSxHQUFnQztZQUNqRCxPQUFPLEVBQUUsSUFBSTtZQUNiLFFBQVEsRUFBRSxRQUFRLENBQUMsUUFBUTtZQUMzQixPQUFPLEVBQUUsS0FBSyxFQUFFLHNDQUFzQztZQUN0RCxhQUFhLEVBQUUsQ0FBQztZQUNoQixVQUFVLEVBQUUsSUFBSTtZQUNoQixRQUFRLEVBQUUsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHO1NBQy9CLENBQUM7UUFFRixNQUFNLFlBQVksR0FBRyxFQUFFLEdBQUcsYUFBYSxFQUFFLEdBQUcsTUFBTSxFQUFFLENBQUM7UUFFckQsSUFBSSxD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
@@ -0,0 +1,121 @@
1
+ import type { IStockProvider, IProviderConfig } from './interfaces/provider.js';
2
+ import type { IFundamentalsProvider, IFundamentalsProviderConfig, IStockFundamentals } from './interfaces/fundamentals.js';
3
+ import type { IStockPrice } from './interfaces/stockprice.js';
4
+ import type { IStockData, IStockDataServiceConfig, ICompleteStockDataRequest, ICompleteStockDataBatchRequest } from './interfaces/stockdata.js';
5
+ /**
6
+ * Unified service for managing both stock prices and fundamentals
7
+ * Provides automatic enrichment and convenient combined data access
8
+ */
9
+ export declare class StockDataService {
10
+ private priceProviders;
11
+ private fundamentalsProviders;
12
+ private priceCache;
13
+ private fundamentalsCache;
14
+ private logger;
15
+ private config;
16
+ constructor(config?: IStockDataServiceConfig);
17
+ /**
18
+ * Register a price provider
19
+ */
20
+ registerPriceProvider(provider: IStockProvider, config?: IProviderConfig): void;
21
+ /**
22
+ * Register a fundamentals provider
23
+ */
24
+ registerFundamentalsProvider(provider: IFundamentalsProvider, config?: IFundamentalsProviderConfig): void;
25
+ /**
26
+ * Unregister a price provider
27
+ */
28
+ unregisterPriceProvider(providerName: string): void;
29
+ /**
30
+ * Unregister a fundamentals provider
31
+ */
32
+ unregisterFundamentalsProvider(providerName: string): void;
33
+ /**
34
+ * Get all registered price providers
35
+ */
36
+ getPriceProviders(): IStockProvider[];
37
+ /**
38
+ * Get all registered fundamentals providers
39
+ */
40
+ getFundamentalsProviders(): IFundamentalsProvider[];
41
+ /**
42
+ * Get enabled price providers sorted by priority
43
+ */
44
+ private getEnabledPriceProviders;
45
+ /**
46
+ * Get enabled fundamentals providers sorted by priority
47
+ */
48
+ private getEnabledFundamentalsProviders;
49
+ /**
50
+ * Get current price for a single ticker
51
+ */
52
+ getPrice(ticker: string): Promise<IStockPrice>;
53
+ /**
54
+ * Get current prices for multiple tickers
55
+ */
56
+ getPrices(tickers: string[]): Promise<IStockPrice[]>;
57
+ /**
58
+ * Get fundamentals for a single ticker
59
+ */
60
+ getFundamentals(ticker: string): Promise<IStockFundamentals>;
61
+ /**
62
+ * Get fundamentals for multiple tickers
63
+ */
64
+ getBatchFundamentals(tickers: string[]): Promise<IStockFundamentals[]>;
65
+ /**
66
+ * ✨ Get complete stock data (price + fundamentals) with automatic enrichment
67
+ */
68
+ getStockData(request: string | ICompleteStockDataRequest): Promise<IStockData>;
69
+ /**
70
+ * ✨ Get complete stock data for multiple tickers with automatic enrichment
71
+ */
72
+ getBatchStockData(request: string[] | ICompleteStockDataBatchRequest): Promise<IStockData[]>;
73
+ /**
74
+ * Enrich fundamentals with calculated metrics using current price
75
+ */
76
+ private enrichWithPrice;
77
+ /**
78
+ * Fetch with retry logic
79
+ */
80
+ private fetchWithRetry;
81
+ /**
82
+ * Get from cache if not expired
83
+ */
84
+ private getFromCache;
85
+ /**
86
+ * Add to cache with TTL
87
+ */
88
+ private addToCache;
89
+ /**
90
+ * Check health of all providers (both price and fundamentals)
91
+ */
92
+ checkProvidersHealth(): Promise<Map<string, boolean>>;
93
+ /**
94
+ * Get statistics for all providers
95
+ */
96
+ getProviderStats(): Map<string, {
97
+ type: 'price' | 'fundamentals';
98
+ successCount: number;
99
+ errorCount: number;
100
+ lastError?: string;
101
+ lastErrorTime?: Date;
102
+ }>;
103
+ /**
104
+ * Clear all caches
105
+ */
106
+ clearCache(): void;
107
+ /**
108
+ * Get cache statistics
109
+ */
110
+ getCacheStats(): {
111
+ priceCache: {
112
+ size: number;
113
+ ttl: number;
114
+ };
115
+ fundamentalsCache: {
116
+ size: number;
117
+ ttl: number;
118
+ };
119
+ maxEntries: number;
120
+ };
121
+ }