@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.
package/readme.md CHANGED
@@ -1,154 +1,134 @@
1
1
  # @fin.cx/opendata
2
2
 
3
- 🚀 **Real-time financial data and German business intelligence in one powerful TypeScript library**
3
+ 🚀 **Complete financial intelligence toolkit for TypeScript**
4
4
 
5
- Access live stock prices, cryptocurrencies, forex, commodities AND comprehensive German company data - all through a single, unified API.
5
+ Access real-time stock prices, fundamental financial data, and comprehensive German company information - all through a single, unified API.
6
6
 
7
- ## âš ī¸ Breaking Changes
8
-
9
- ### v2.1 - Enhanced Stock Market API (Current)
7
+ ## Installation
10
8
 
11
- **The stock market API has been significantly enhanced with a new unified request system.**
9
+ ```bash
10
+ npm install @fin.cx/opendata
11
+ # or
12
+ pnpm add @fin.cx/opendata
13
+ ```
12
14
 
13
- **What Changed:**
14
- - New discriminated union request types (`IStockDataRequest`)
15
- - Enhanced `IStockPrice` interface with OHLCV data and metadata
16
- - New `getData()` method replaces legacy `getPrice()` and `getPrices()`
17
- - Historical data support with date ranges
18
- - Exchange filtering via MIC codes
19
- - Smart caching with data-type aware TTL
15
+ ## Quick Start
20
16
 
21
- **Migration:**
22
- ```typescript
23
- // OLD (v2.0 and earlier)
24
- const price = await service.getPrice({ ticker: 'AAPL' });
25
- const prices = await service.getPrices({ tickers: ['AAPL', 'MSFT'] });
17
+ ### ✨ Unified Stock Data API (Recommended)
26
18
 
27
- // NEW (v2.1+) - Unified API
28
- const price = await service.getData({ type: 'current', ticker: 'AAPL' });
29
- const prices = await service.getData({ type: 'batch', tickers: ['AAPL', 'MSFT'] });
19
+ Get complete stock data with automatic enrichment - the elegant way:
30
20
 
31
- // NEW - Historical data
32
- const history = await service.getData({
33
- type: 'historical',
34
- ticker: 'AAPL',
35
- from: new Date('2024-01-01'),
36
- to: new Date('2024-12-31')
21
+ ```typescript
22
+ import { StockDataService, YahooFinanceProvider, SecEdgarProvider } from '@fin.cx/opendata';
23
+
24
+ // Initialize unified service
25
+ const stockData = new StockDataService();
26
+
27
+ // Register providers
28
+ stockData.registerPriceProvider(new YahooFinanceProvider());
29
+ stockData.registerFundamentalsProvider(new SecEdgarProvider({
30
+ userAgent: 'YourCompany youremail@example.com'
31
+ }));
32
+
33
+ // ✨ Get complete stock data with ONE method call
34
+ const apple = await stockData.getStockData('AAPL');
35
+
36
+ console.log({
37
+ company: apple.fundamentals.companyName, // "Apple Inc."
38
+ price: apple.price.price, // $270.37
39
+ marketCap: apple.fundamentals.marketCap, // $4.13T (auto-calculated!)
40
+ peRatio: apple.fundamentals.priceToEarnings, // 28.42 (auto-calculated!)
41
+ pbRatio: apple.fundamentals.priceToBook, // 45.12 (auto-calculated!)
42
+ eps: apple.fundamentals.earningsPerShareDiluted, // $6.13
43
+ revenue: apple.fundamentals.revenue // $385.6B
37
44
  });
38
45
 
39
- // NEW - Exchange filtering
40
- const price = await service.getData({
41
- type: 'current',
42
- ticker: 'VOD',
43
- exchange: 'XLON' // London Stock Exchange
46
+ // Batch fetch with automatic enrichment
47
+ const stocks = await stockData.getBatchStockData(['AAPL', 'MSFT', 'GOOGL']);
48
+
49
+ stocks.forEach(stock => {
50
+ console.log(`${stock.ticker}: $${stock.price.price.toFixed(2)}, ` +
51
+ `P/E ${stock.fundamentals?.priceToEarnings?.toFixed(2)}`);
44
52
  });
53
+ // Output:
54
+ // AAPL: $270.37, P/E 28.42
55
+ // MSFT: $425.50, P/E 34.21
56
+ // GOOGL: $142.15, P/E 25.63
45
57
  ```
46
58
 
47
- **Note:** Legacy `getPrice()` and `getPrices()` methods still work but are deprecated.
59
+ **Why use the unified API?**
60
+ - ✅ **Single service** for both prices and fundamentals
61
+ - ✅ **Automatic enrichment** - Market cap, P/E, P/B calculated automatically
62
+ - ✅ **One method call** - No manual `enrichWithPrice()` calls
63
+ - ✅ **Simplified code** - Less boilerplate, more readable
64
+ - ✅ **Type-safe** - Full TypeScript support
48
65
 
49
- ### v2.0 - Directory Configuration
66
+ ### 📈 Price-Only Data (Alternative)
50
67
 
51
- **Directory paths are now MANDATORY when using German business data features.** The package no longer creates `.nogit/` directories automatically. You must explicitly configure all directory paths when instantiating `OpenData`:
68
+ If you only need prices without fundamentals:
52
69
 
53
70
  ```typescript
54
- const openData = new OpenData({
55
- nogitDir: '/path/to/your/data',
56
- downloadDir: '/path/to/your/data/downloads',
57
- germanBusinessDataDir: '/path/to/your/data/germanbusinessdata'
58
- });
59
- ```
71
+ import { StockDataService, YahooFinanceProvider } from '@fin.cx/opendata';
60
72
 
61
- This change enables the package to work in read-only filesystems (like Deno compiled binaries) and gives you full control over where data is stored.
73
+ const stockData = new StockDataService();
74
+ stockData.registerPriceProvider(new YahooFinanceProvider());
62
75
 
63
- ## Installation
76
+ // Get just the price
77
+ const price = await stockData.getPrice('AAPL');
78
+ console.log(`${price.ticker}: $${price.price}`);
64
79
 
65
- ```bash
66
- npm install @fin.cx/opendata
67
- # or
68
- pnpm add @fin.cx/opendata
69
- ```
80
+ // Or use StockPriceService directly for more control
81
+ import { StockPriceService } from '@fin.cx/opendata';
70
82
 
71
- ## Quick Start
72
-
73
- ### 📈 Stock Market Data (v2.1+ Enhanced)
83
+ const stockService = new StockPriceService({ ttl: 60000 });
84
+ stockService.register(new YahooFinanceProvider());
74
85
 
75
- Get comprehensive market data with EOD, historical, and OHLCV data:
76
-
77
- ```typescript
78
- import { StockPriceService, MarketstackProvider } from '@fin.cx/opendata';
86
+ const apple = await stockService.getData({ type: 'current', ticker: 'AAPL' });
87
+ console.log(`${apple.companyFullName}: $${apple.price}`);
88
+ ```
79
89
 
80
- // Initialize the service with smart caching
81
- const stockService = new StockPriceService({
82
- ttl: 60000, // Default cache TTL (historical data cached forever)
83
- maxEntries: 10000 // Increased for historical data
84
- });
90
+ ### 💰 Fundamentals-Only Data (Alternative)
85
91
 
86
- // Register Marketstack provider with API key
87
- stockService.register(new MarketstackProvider('YOUR_API_KEY'), {
88
- priority: 100,
89
- retryAttempts: 3
90
- });
92
+ If you only need fundamentals without prices:
91
93
 
92
- // Get current price (new unified API)
93
- const apple = await stockService.getData({ type: 'current', ticker: 'AAPL' });
94
- console.log(`Apple: $${apple.price} (${apple.changePercent.toFixed(2)}%)`);
95
- console.log(`OHLCV: O=${apple.open} H=${apple.high} L=${apple.low} V=${apple.volume}`);
96
- console.log(`Company: ${apple.companyName}`); // "Apple Inc"
97
- console.log(`Full: ${apple.companyFullName}`); // "Apple Inc (NASDAQ:AAPL)"
94
+ ```typescript
95
+ import { StockDataService, SecEdgarProvider } from '@fin.cx/opendata';
98
96
 
99
- // Get historical data (1 year of daily prices)
100
- const history = await stockService.getData({
101
- type: 'historical',
102
- ticker: 'AAPL',
103
- from: new Date('2024-01-01'),
104
- to: new Date('2024-12-31'),
105
- sort: 'DESC' // Newest first
106
- });
107
- console.log(`Fetched ${history.length} trading days`);
97
+ const stockData = new StockDataService();
98
+ stockData.registerFundamentalsProvider(new SecEdgarProvider({
99
+ userAgent: 'YourCompany youremail@example.com'
100
+ }));
108
101
 
109
- // Exchange-specific data (London vs NYSE)
110
- const vodLondon = await stockService.getData({
111
- type: 'current',
112
- ticker: 'VOD',
113
- exchange: 'XLON' // London Stock Exchange
114
- });
102
+ // Get just fundamentals
103
+ const fundamentals = await stockData.getFundamentals('AAPL');
115
104
 
116
- const vodNYSE = await stockService.getData({
117
- type: 'current',
118
- ticker: 'VOD',
119
- exchange: 'XNYS' // New York Stock Exchange
105
+ console.log({
106
+ company: fundamentals.companyName,
107
+ eps: fundamentals.earningsPerShareDiluted,
108
+ revenue: fundamentals.revenue,
109
+ sharesOutstanding: fundamentals.sharesOutstanding
120
110
  });
121
111
 
122
- // Batch current prices with company names
123
- const prices = await stockService.getData({
124
- type: 'batch',
125
- tickers: ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
126
- });
112
+ // Or use FundamentalsService directly
113
+ import { FundamentalsService } from '@fin.cx/opendata';
127
114
 
128
- // Display with company names (automatically included - zero extra API calls!)
129
- for (const stock of prices) {
130
- console.log(`${stock.companyName}: $${stock.price}`);
131
- // Output:
132
- // Apple Inc: $271.40
133
- // Microsoft Corporation: $525.76
134
- // Alphabet Inc - Class A: $281.48
135
- // Amazon.com Inc: $222.86
136
- // Tesla Inc: $440.10
137
- }
115
+ const fundamentalsService = new FundamentalsService();
116
+ fundamentalsService.register(new SecEdgarProvider({
117
+ userAgent: 'YourCompany youremail@example.com'
118
+ }));
138
119
 
139
- // Use companyFullName for richer context
140
- console.log(prices[0].companyFullName); // "Apple Inc (NASDAQ:AAPL)"
120
+ const data = await fundamentalsService.getFundamentals('AAPL');
141
121
  ```
142
122
 
143
- ### đŸĸ German Business Data
123
+ ### đŸĸ German Business Intelligence
144
124
 
145
- Access comprehensive data on German companies:
125
+ Access comprehensive German company data:
146
126
 
147
127
  ```typescript
148
128
  import { OpenData } from '@fin.cx/opendata';
149
129
  import * as path from 'path';
150
130
 
151
- // REQUIRED: Configure directory paths
131
+ // Configure directory paths
152
132
  const openData = new OpenData({
153
133
  nogitDir: path.join(process.cwd(), '.nogit'),
154
134
  downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
@@ -156,55 +136,119 @@ const openData = new OpenData({
156
136
  });
157
137
  await openData.start();
158
138
 
159
- // Create a business record
160
- const company = new openData.CBusinessRecord();
161
- company.data = {
162
- name: "TechStart GmbH",
163
- city: "Berlin",
164
- registrationId: "HRB 123456",
165
- // ... more fields
166
- };
167
- await company.save();
168
-
169
- // Search companies by city
170
- const berlinCompanies = await openData.db
171
- .collection('businessrecords')
172
- .find({ city: "Berlin" })
173
- .toArray();
139
+ // Search for companies
140
+ const results = await openData.handelsregister.searchCompany("Siemens AG");
174
141
 
175
- // Import bulk data from official sources
176
- await openData.buildInitialDb();
142
+ // Get detailed information with documents
143
+ const details = await openData.handelsregister.getSpecificCompany({
144
+ court: "Munich",
145
+ type: "HRB",
146
+ number: "6684"
147
+ });
177
148
  ```
178
149
 
179
150
  ## Features
180
151
 
181
- ### đŸŽ¯ Stock Market Module (v2.1 Enhanced)
152
+ ### 📊 Stock Market Module
182
153
 
183
- - **Company Names** - Automatic company name extraction with zero extra API calls (e.g., "Apple Inc (NASDAQ:AAPL)")
184
- - **Historical Data** - Up to 15 years of daily EOD prices with automatic pagination
185
- - **Exchange Filtering** - Query specific exchanges via MIC codes (XNAS, XLON, XNYS, etc.)
186
- - **OHLCV Data** - Open, High, Low, Close, Volume for comprehensive analysis
154
+ - **Real-Time Prices** - Live and EOD stock prices from Yahoo Finance and Marketstack
155
+ - **Company Names** - Automatic company name extraction (e.g., "Apple Inc (NASDAQ:AAPL)")
156
+ - **Historical Data** - Up to 15 years of daily EOD prices with pagination
157
+ - **OHLCV Data** - Open, High, Low, Close, Volume for technical analysis
158
+ - **Exchange Filtering** - Query specific exchanges via MIC codes (XNAS, XLON, XNYS)
187
159
  - **Smart Caching** - Data-type aware TTL (historical cached forever, EOD 24h, live 30s)
188
- - **Marketstack API** - 500,000+ tickers across 72+ exchanges worldwide
189
160
  - **Batch Operations** - Fetch 100+ symbols in one request
190
- - **Unified API** - Discriminated union types for type-safe requests
191
- - **Extensible Providers** - Easy to add new data sources
192
- - **Retry Logic** - Configurable attempts and delays
193
- - **Type-Safe** - Full TypeScript support with detailed interfaces
161
+ - **Type-Safe API** - Full TypeScript support with discriminated unions
162
+ - **Multi-Provider** - Automatic fallback between providers
163
+
164
+ ### 💰 Fundamental Data Module
165
+
166
+ - **SEC EDGAR Integration** - FREE fundamental data directly from SEC filings
167
+ - **Comprehensive Metrics** - EPS, Revenue, Assets, Liabilities, Cash Flow, and more
168
+ - **All US Public Companies** - Complete coverage of SEC-registered companies
169
+ - **Historical Filings** - Data back to ~2009
170
+ - **CIK Lookup** - Automatic ticker-to-CIK mapping with smart caching
171
+ - **Calculated Ratios** - Market Cap, P/E, P/B ratios when combined with prices
172
+ - **No API Key Required** - Direct access to SEC's public API
173
+ - **Rate Limit Management** - Built-in 10 req/sec rate limiting
194
174
 
195
175
  ### 🇩đŸ‡Ē German Business Intelligence
196
176
 
197
- - **MongoDB integration** for scalable data storage
198
- - **Bulk JSONL import** from official German data sources
199
- - **Handelsregister automation** - automated document retrieval
200
- - **CRUD operations** with validation
201
- - **Streaming processing** for multi-GB datasets
177
+ - **MongoDB Integration** - Scalable data storage for millions of records
178
+ - **Bulk JSONL Import** - Process multi-GB datasets efficiently
179
+ - **Handelsregister Automation** - Automated document retrieval
180
+ - **CRUD Operations** - Full database management with validation
181
+ - **Streaming Processing** - Handle large datasets without memory issues
202
182
 
203
183
  ## Advanced Examples
204
184
 
205
- ### Phase 1: Historical Data Analysis
185
+ ### Combined Market Analysis (Unified API)
206
186
 
207
- Fetch and analyze historical price data:
187
+ Analyze multiple companies with automatic enrichment:
188
+
189
+ ```typescript
190
+ import { StockDataService, YahooFinanceProvider, SecEdgarProvider } from '@fin.cx/opendata';
191
+
192
+ // Setup unified service
193
+ const stockData = new StockDataService();
194
+ stockData.registerPriceProvider(new YahooFinanceProvider());
195
+ stockData.registerFundamentalsProvider(new SecEdgarProvider({
196
+ userAgent: 'YourCompany youremail@example.com'
197
+ }));
198
+
199
+ // Analyze multiple companies with ONE call
200
+ const tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN'];
201
+ const stocks = await stockData.getBatchStockData(tickers);
202
+
203
+ // All metrics are automatically calculated!
204
+ stocks.forEach(stock => {
205
+ if (stock.fundamentals) {
206
+ console.log(`\n${stock.fundamentals.companyName} (${stock.ticker})`);
207
+ console.log(` Price: $${stock.price.price.toFixed(2)}`);
208
+ console.log(` Market Cap: $${(stock.fundamentals.marketCap! / 1e9).toFixed(2)}B`);
209
+ console.log(` P/E Ratio: ${stock.fundamentals.priceToEarnings!.toFixed(2)}`);
210
+ console.log(` Revenue: $${(stock.fundamentals.revenue! / 1e9).toFixed(2)}B`);
211
+ console.log(` EPS: $${stock.fundamentals.earningsPerShareDiluted!.toFixed(2)}`);
212
+ }
213
+ });
214
+
215
+ // Or analyze one-by-one with automatic enrichment
216
+ for (const ticker of tickers) {
217
+ const stock = await stockData.getStockData(ticker);
218
+
219
+ // Everything is already enriched - no manual calculations needed!
220
+ console.log(`${ticker}: P/E ${stock.fundamentals?.priceToEarnings?.toFixed(2)}`);
221
+ }
222
+ ```
223
+
224
+ ### Fundamental Data Screening
225
+
226
+ Screen stocks by financial metrics:
227
+
228
+ ```typescript
229
+ // Fetch data for multiple tickers
230
+ const tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'NVDA', 'TSLA'];
231
+ const stocks = await stockData.getBatchStockData(tickers);
232
+
233
+ // Filter by criteria (all metrics auto-calculated!)
234
+ const valueStocks = stocks.filter(stock => {
235
+ const f = stock.fundamentals;
236
+ return f &&
237
+ f.priceToEarnings! < 30 && // P/E under 30
238
+ f.priceToBook! < 10 && // P/B under 10
239
+ f.revenue! > 100_000_000_000; // Revenue > $100B
240
+ });
241
+
242
+ console.log('\n💎 Value Stocks:');
243
+ valueStocks.forEach(stock => {
244
+ console.log(`${stock.ticker}: P/E ${stock.fundamentals!.priceToEarnings!.toFixed(2)}, ` +
245
+ `P/B ${stock.fundamentals!.priceToBook!.toFixed(2)}`);
246
+ });
247
+ ```
248
+
249
+ ### Historical Data Analysis
250
+
251
+ Fetch and analyze historical price trends:
208
252
 
209
253
  ```typescript
210
254
  // Get 1 year of historical data
@@ -213,7 +257,7 @@ const history = await stockService.getData({
213
257
  ticker: 'AAPL',
214
258
  from: new Date('2024-01-01'),
215
259
  to: new Date('2024-12-31'),
216
- sort: 'DESC' // Newest first (default)
260
+ sort: 'DESC' // Newest first
217
261
  });
218
262
 
219
263
  // Calculate statistics
@@ -223,50 +267,31 @@ const low52Week = Math.min(...prices);
223
267
  const avgPrice = prices.reduce((a, b) => a + b) / prices.length;
224
268
 
225
269
  console.log(`52-Week Analysis for AAPL:`);
226
- console.log(`High: $${high52Week.toFixed(2)}`);
227
- console.log(`Low: $${low52Week.toFixed(2)}`);
228
- console.log(`Average: $${avgPrice.toFixed(2)}`);
229
- console.log(`Days: ${history.length}`);
230
-
231
- // Calculate daily returns
232
- for (let i = 0; i < history.length - 1; i++) {
233
- const todayPrice = history[i].price;
234
- const yesterdayPrice = history[i + 1].price;
235
- const dailyReturn = ((todayPrice - yesterdayPrice) / yesterdayPrice) * 100;
236
- console.log(`${history[i].timestamp.toISOString().split('T')[0]}: ${dailyReturn.toFixed(2)}%`);
237
- }
238
- ```
239
-
240
- ### Phase 1: Exchange-Specific Trading
241
-
242
- Compare prices across different exchanges:
270
+ console.log(` High: $${high52Week.toFixed(2)}`);
271
+ console.log(` Low: $${low52Week.toFixed(2)}`);
272
+ console.log(` Average: $${avgPrice.toFixed(2)}`);
273
+ console.log(` Days: ${history.length}`);
243
274
 
244
- ```typescript
245
- // Vodafone trades on both London and NYSE
246
- const exchanges = [
247
- { mic: 'XLON', name: 'London Stock Exchange' },
248
- { mic: 'XNYS', name: 'New York Stock Exchange' }
249
- ];
275
+ // Calculate Simple Moving Average
276
+ const calculateSMA = (data: IStockPrice[], period: number) => {
277
+ const sma: number[] = [];
278
+ for (let i = period - 1; i < data.length; i++) {
279
+ const sum = data.slice(i - period + 1, i + 1)
280
+ .reduce((acc, p) => acc + p.price, 0);
281
+ sma.push(sum / period);
282
+ }
283
+ return sma;
284
+ };
250
285
 
251
- for (const exchange of exchanges) {
252
- try {
253
- const price = await stockService.getData({
254
- type: 'current',
255
- ticker: 'VOD',
256
- exchange: exchange.mic
257
- });
286
+ const sma20 = calculateSMA(history, 20);
287
+ const sma50 = calculateSMA(history, 50);
258
288
 
259
- console.log(`${exchange.name}:`);
260
- console.log(` Price: ${price.price} ${price.currency}`);
261
- console.log(` Volume: ${price.volume?.toLocaleString()}`);
262
- console.log(` Exchange: ${price.exchangeName}`);
263
- } catch (error) {
264
- console.log(`${exchange.name}: Not available`);
265
- }
266
- }
289
+ console.log(`\nMoving Averages:`);
290
+ console.log(` 20-day SMA: $${sma20[0].toFixed(2)}`);
291
+ console.log(` 50-day SMA: $${sma50[0].toFixed(2)}`);
267
292
  ```
268
293
 
269
- ### Phase 1: OHLCV Technical Analysis
294
+ ### OHLCV Technical Analysis
270
295
 
271
296
  Use OHLCV data for technical indicators:
272
297
 
@@ -280,8 +305,8 @@ const history = await stockService.getData({
280
305
 
281
306
  // Calculate daily trading range
282
307
  for (const day of history) {
283
- const range = day.high - day.low;
284
- const rangePercent = (range / day.low) * 100;
308
+ const range = day.high! - day.low!;
309
+ const rangePercent = (range / day.low!) * 100;
285
310
 
286
311
  console.log(`${day.timestamp.toISOString().split('T')[0]}:`);
287
312
  console.log(` Open: $${day.open}`);
@@ -291,73 +316,54 @@ for (const day of history) {
291
316
  console.log(` Volume: ${day.volume?.toLocaleString()}`);
292
317
  console.log(` Range: $${range.toFixed(2)} (${rangePercent.toFixed(2)}%)`);
293
318
  }
294
-
295
- // Calculate Simple Moving Average (SMA)
296
- const calculateSMA = (data: IStockPrice[], period: number) => {
297
- const sma: number[] = [];
298
- for (let i = period - 1; i < data.length; i++) {
299
- const sum = data.slice(i - period + 1, i + 1)
300
- .reduce((acc, p) => acc + p.price, 0);
301
- sma.push(sum / period);
302
- }
303
- return sma;
304
- };
305
-
306
- const sma20 = calculateSMA(history, 20);
307
- const sma50 = calculateSMA(history, 50);
308
-
309
- console.log(`20-day SMA: $${sma20[0].toFixed(2)}`);
310
- console.log(`50-day SMA: $${sma50[0].toFixed(2)}`);
311
319
  ```
312
320
 
313
- ### Phase 1: Smart Caching Performance
321
+ ### Fundamental Data Screening
314
322
 
315
- Leverage smart caching for efficiency:
323
+ Screen stocks based on fundamental metrics:
316
324
 
317
325
  ```typescript
318
- // Historical data is cached FOREVER (never changes)
319
- console.time('First historical fetch');
320
- const history1 = await stockService.getData({
321
- type: 'historical',
322
- ticker: 'AAPL',
323
- from: new Date('2024-01-01'),
324
- to: new Date('2024-12-31')
325
- });
326
- console.timeEnd('First historical fetch');
327
- // Output: First historical fetch: 2341ms
326
+ const tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA'];
328
327
 
329
- console.time('Second historical fetch (cached)');
330
- const history2 = await stockService.getData({
331
- type: 'historical',
332
- ticker: 'AAPL',
333
- from: new Date('2024-01-01'),
334
- to: new Date('2024-12-31')
335
- });
336
- console.timeEnd('Second historical fetch (cached)');
337
- // Output: Second historical fetch (cached): 2ms (1000x faster!)
328
+ // Fetch fundamentals for all tickers
329
+ const allFundamentals = await fundamentalsService.getBatchFundamentals(tickers);
338
330
 
339
- // EOD data cached for 24 hours
340
- const currentPrice = await stockService.getData({
341
- type: 'current',
342
- ticker: 'MSFT'
331
+ // Get current prices
332
+ const prices = await stockService.getData({
333
+ type: 'batch',
334
+ tickers: tickers
343
335
  });
344
- // Subsequent calls within 24h served from cache
345
336
 
346
- // Cache statistics
347
- console.log(`Cache size: ${stockService['cache'].size} entries`);
337
+ // Create price map
338
+ const priceMap = new Map(prices.map(p => [p.ticker, p.price]));
339
+
340
+ // Enrich with prices
341
+ const enriched = await fundamentalsService.enrichBatchWithPrices(
342
+ allFundamentals,
343
+ priceMap
344
+ );
345
+
346
+ // Screen for value stocks (P/E < 25, P/B < 5)
347
+ const valueStocks = enriched.filter(f =>
348
+ f.priceToEarnings && f.priceToEarnings < 25 &&
349
+ f.priceToBook && f.priceToBook < 5
350
+ );
351
+
352
+ console.log('Value Stocks:');
353
+ valueStocks.forEach(stock => {
354
+ console.log(`\n${stock.companyName} (${stock.ticker})`);
355
+ console.log(` P/E Ratio: ${stock.priceToEarnings!.toFixed(2)}`);
356
+ console.log(` P/B Ratio: ${stock.priceToBook!.toFixed(2)}`);
357
+ console.log(` Market Cap: $${(stock.marketCap! / 1e9).toFixed(2)}B`);
358
+ });
348
359
  ```
349
360
 
350
361
  ### Market Dashboard
351
362
 
352
- Create an EOD market overview:
363
+ Create a comprehensive market overview:
353
364
 
354
365
  ```typescript
355
366
  const indicators = [
356
- // Indices
357
- { ticker: '^GSPC', name: 'S&P 500' },
358
- { ticker: '^DJI', name: 'DOW Jones' },
359
-
360
- // Tech Giants
361
367
  { ticker: 'AAPL', name: 'Apple' },
362
368
  { ticker: 'MSFT', name: 'Microsoft' },
363
369
  { ticker: 'GOOGL', name: 'Alphabet' },
@@ -365,7 +371,8 @@ const indicators = [
365
371
  { ticker: 'TSLA', name: 'Tesla' }
366
372
  ];
367
373
 
368
- const prices = await stockService.getPrices({
374
+ const prices = await stockService.getData({
375
+ type: 'batch',
369
376
  tickers: indicators.map(i => i.ticker)
370
377
  });
371
378
 
@@ -376,197 +383,125 @@ prices.forEach(price => {
376
383
  const color = price.change >= 0 ? '\x1b[32m' : '\x1b[31m';
377
384
 
378
385
  console.log(
379
- `${indicator.name.padEnd(15)} ${price.price.toFixed(2).padStart(10)} ` +
386
+ `${price.companyName!.padEnd(25)} $${price.price.toFixed(2).padStart(8)} ` +
380
387
  `${color}${arrow} ${price.changePercent.toFixed(2)}%\x1b[0m`
381
388
  );
382
389
  });
383
390
  ```
384
391
 
385
- ### Provider Health and Statistics
392
+ ### Exchange-Specific Trading
386
393
 
387
- Monitor your provider health and track usage:
394
+ Compare prices across different exchanges:
388
395
 
389
396
  ```typescript
390
- // Check provider health
391
- const health = await stockService.checkProvidersHealth();
392
- console.log(`Marketstack: ${health.get('Marketstack') ? '✅' : '❌'}`);
393
-
394
- // Get provider statistics
395
- const stats = stockService.getProviderStats();
396
- const marketstackStats = stats.get('Marketstack');
397
- console.log('Marketstack Stats:', {
398
- successCount: marketstackStats.successCount,
399
- errorCount: marketstackStats.errorCount,
400
- lastError: marketstackStats.lastError
401
- });
397
+ // Vodafone trades on both London and NYSE
398
+ const exchanges = [
399
+ { mic: 'XLON', name: 'London Stock Exchange' },
400
+ { mic: 'XNYS', name: 'New York Stock Exchange' }
401
+ ];
402
+
403
+ for (const exchange of exchanges) {
404
+ try {
405
+ const price = await stockService.getData({
406
+ type: 'current',
407
+ ticker: 'VOD',
408
+ exchange: exchange.mic
409
+ });
410
+
411
+ console.log(`${exchange.name}:`);
412
+ console.log(` Price: ${price.price} ${price.currency}`);
413
+ console.log(` Volume: ${price.volume?.toLocaleString()}`);
414
+ console.log(` Exchange: ${price.exchangeName}`);
415
+ } catch (error) {
416
+ console.log(`${exchange.name}: Not available`);
417
+ }
418
+ }
402
419
  ```
403
420
 
404
- ### Handelsregister Integration
421
+ ## Configuration
405
422
 
406
- Automate German company data retrieval:
423
+ ### Stock Service Options
407
424
 
408
425
  ```typescript
409
- import { OpenData } from '@fin.cx/opendata';
410
- import * as path from 'path';
411
-
412
- // Configure paths first
413
- const openData = new OpenData({
414
- nogitDir: path.join(process.cwd(), '.nogit'),
415
- downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
416
- germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
426
+ const stockService = new StockPriceService({
427
+ ttl: 60000, // Default cache TTL in ms
428
+ maxEntries: 10000 // Max cached entries
417
429
  });
418
- await openData.start();
419
-
420
- // Search for a company
421
- const results = await openData.handelsregister.searchCompany("Siemens AG");
422
430
 
423
- // Get detailed information and documents
424
- const details = await openData.handelsregister.getSpecificCompany({
425
- court: "Munich",
426
- type: "HRB",
427
- number: "6684"
431
+ // Marketstack - EOD data (requires API key)
432
+ stockService.register(new MarketstackProvider('YOUR_API_KEY'), {
433
+ enabled: true,
434
+ priority: 100,
435
+ timeout: 10000,
436
+ retryAttempts: 3,
437
+ retryDelay: 1000
428
438
  });
429
439
 
430
- // Downloaded files include:
431
- // - XML data (SI files)
432
- // - PDF documents (AD files)
433
- for (const file of details.files) {
434
- await file.writeToDir('./downloads');
435
- }
440
+ // Yahoo Finance - Real-time data (no API key)
441
+ stockService.register(new YahooFinanceProvider(), {
442
+ enabled: true,
443
+ priority: 50
444
+ });
436
445
  ```
437
446
 
438
- ### Combined Data Analysis
439
-
440
- Merge financial and business data:
447
+ ### Fundamentals Service Options
441
448
 
442
449
  ```typescript
443
- import { OpenData, StockPriceService, MarketstackProvider } from '@fin.cx/opendata';
444
- import * as path from 'path';
445
-
446
- // Configure OpenData with paths
447
- const openData = new OpenData({
448
- nogitDir: path.join(process.cwd(), '.nogit'),
449
- downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
450
- germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
450
+ const fundamentalsService = new FundamentalsService({
451
+ ttl: 90 * 24 * 60 * 60 * 1000, // 90 days (quarterly refresh)
452
+ maxEntries: 10000
451
453
  });
452
- await openData.start();
453
-
454
- // Setup stock service
455
- const stockService = new StockPriceService({ ttl: 60000, maxEntries: 1000 });
456
- stockService.register(new MarketstackProvider('YOUR_API_KEY'));
457
-
458
- // Find all public German companies (AG)
459
- const publicCompanies = await openData.db
460
- .collection('businessrecords')
461
- .find({ legalForm: 'AG' })
462
- .toArray();
463
-
464
- // Enrich with stock data
465
- for (const company of publicCompanies) {
466
- try {
467
- // Map company to ticker (custom logic needed)
468
- const ticker = mapCompanyToTicker(company.data.name);
469
-
470
- if (ticker) {
471
- const stock = await stockService.getPrice({ ticker });
472
454
 
473
- // Add financial metrics
474
- company.data.stockPrice = stock.price;
475
- company.data.marketCap = stock.price * getSharesOutstanding(ticker);
476
- company.data.priceChange = stock.changePercent;
477
-
478
- await company.save();
479
- }
480
- } catch (error) {
481
- // Handle missing tickers gracefully
482
- }
483
- }
455
+ // SEC EDGAR provider (FREE - no API key!)
456
+ fundamentalsService.register(new SecEdgarProvider({
457
+ userAgent: 'YourCompany youremail@example.com',
458
+ cikCacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days
459
+ fundamentalsCacheTTL: 90 * 24 * 60 * 60 * 1000, // 90 days
460
+ timeout: 30000
461
+ }));
484
462
  ```
485
463
 
486
- ## Configuration
487
-
488
- ### Directory Configuration (Required for German Business Data)
464
+ ### Directory Configuration (German Business Data)
489
465
 
490
- **All directory paths are mandatory when using `OpenData`.** Here are examples for different environments:
466
+ All directory paths are mandatory when using German business data features:
491
467
 
492
- #### Development Environment
493
468
  ```typescript
494
469
  import { OpenData } from '@fin.cx/opendata';
495
470
  import * as path from 'path';
496
471
 
472
+ // Development
497
473
  const openData = new OpenData({
498
474
  nogitDir: path.join(process.cwd(), '.nogit'),
499
475
  downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
500
476
  germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
501
477
  });
502
- ```
503
478
 
504
- #### Production Environment
505
- ```typescript
506
- import { OpenData } from '@fin.cx/opendata';
507
- import * as path from 'path';
508
-
509
- const openData = new OpenData({
479
+ // Production
480
+ const openDataProd = new OpenData({
510
481
  nogitDir: '/var/lib/myapp/data',
511
482
  downloadDir: '/var/lib/myapp/data/downloads',
512
483
  germanBusinessDataDir: '/var/lib/myapp/data/germanbusinessdata'
513
484
  });
514
485
  ```
515
486
 
516
- #### Deno Compiled Binaries (or other read-only filesystems)
517
- ```typescript
518
- import { OpenData } from '@fin.cx/opendata';
519
-
520
- // Use OS temp directory or user data directory
521
- const dataDir = Deno.env.get('HOME') + '/.myapp/data';
522
-
523
- const openData = new OpenData({
524
- nogitDir: dataDir,
525
- downloadDir: dataDir + '/downloads',
526
- germanBusinessDataDir: dataDir + '/germanbusinessdata'
527
- });
528
- ```
529
-
530
- ### Stock Service Options
531
-
532
- ```typescript
533
- const stockService = new StockPriceService({
534
- ttl: 60000, // Cache for 1 minute
535
- maxEntries: 1000 // Max cached symbols
536
- });
537
-
538
- // Marketstack - EOD data, requires API key
539
- stockService.register(new MarketstackProvider('YOUR_API_KEY'), {
540
- enabled: true,
541
- priority: 100,
542
- timeout: 10000,
543
- retryAttempts: 3,
544
- retryDelay: 1000
545
- });
546
- ```
547
-
548
- ### MongoDB Setup
487
+ ### Environment Variables
549
488
 
550
- Set environment variables for German business data:
489
+ Set environment variables for API keys and database:
551
490
 
552
491
  ```env
492
+ # Marketstack API (for EOD stock data)
493
+ MARKETSTACK_COM_TOKEN=your_api_key_here
494
+
495
+ # MongoDB (for German business data)
553
496
  MONGODB_URL=mongodb://localhost:27017
554
497
  MONGODB_NAME=opendata
555
498
  MONGODB_USER=myuser
556
499
  MONGODB_PASS=mypass
557
500
  ```
558
501
 
559
- ### Marketstack API Key
560
-
561
- Get your free API key at [marketstack.com](https://marketstack.com) and set it in your environment:
562
-
563
- ```env
564
- MARKETSTACK_COM_TOKEN=your_api_key_here
565
- ```
566
-
567
502
  ## API Reference
568
503
 
569
- ### Stock Types
504
+ ### Stock Price Interfaces
570
505
 
571
506
  ```typescript
572
507
  interface IStockPrice {
@@ -581,62 +516,154 @@ interface IStockPrice {
581
516
  marketState: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED';
582
517
  exchange?: string;
583
518
  exchangeName?: string;
519
+
520
+ // OHLCV data
521
+ volume?: number;
522
+ open?: number;
523
+ high?: number;
524
+ low?: number;
525
+ adjusted?: boolean;
526
+ dataType: 'eod' | 'intraday' | 'live';
527
+ fetchedAt: Date;
528
+
529
+ // Company identification
530
+ companyName?: string; // "Apple Inc"
531
+ companyFullName?: string; // "Apple Inc (NASDAQ:AAPL)"
532
+ }
533
+ ```
534
+
535
+ ### Fundamental Data Interfaces
536
+
537
+ ```typescript
538
+ interface IStockFundamentals {
539
+ ticker: string;
540
+ cik: string;
541
+ companyName: string;
542
+ provider: string;
543
+ timestamp: Date;
544
+ fetchedAt: Date;
545
+
546
+ // Per-share metrics
547
+ earningsPerShareBasic?: number;
548
+ earningsPerShareDiluted?: number;
549
+ sharesOutstanding?: number;
550
+
551
+ // Income statement (annual USD)
552
+ revenue?: number;
553
+ netIncome?: number;
554
+ operatingIncome?: number;
555
+ grossProfit?: number;
556
+
557
+ // Balance sheet (annual USD)
558
+ assets?: number;
559
+ liabilities?: number;
560
+ stockholdersEquity?: number;
561
+ cash?: number;
562
+ propertyPlantEquipment?: number;
563
+
564
+ // Calculated metrics (requires price)
565
+ marketCap?: number; // price × sharesOutstanding
566
+ priceToEarnings?: number; // price / EPS
567
+ priceToBook?: number; // marketCap / stockholdersEquity
568
+
569
+ // Metadata
570
+ fiscalYear?: string;
571
+ fiscalQuarter?: string;
572
+ filingDate?: Date;
573
+ form?: '10-K' | '10-Q' | string;
584
574
  }
585
575
  ```
586
576
 
587
577
  ### Key Methods
588
578
 
589
579
  **StockPriceService**
590
- - `getPrice(request)` - Single stock price with automatic provider selection
591
- - `getPrices(request)` - Batch prices (100+ symbols in one request)
580
+ - `getData(request)` - Unified method for all stock data (current, historical, batch)
581
+ - `getPrice(request)` - Convenience method for single current price
582
+ - `getPrices(request)` - Convenience method for batch current prices
592
583
  - `register(provider, config)` - Add data provider with priority and retry config
593
584
  - `checkProvidersHealth()` - Test all providers and return health status
594
585
  - `getProviderStats()` - Get success/error statistics for each provider
595
586
  - `clearCache()` - Clear price cache
596
587
  - `setCacheTTL(ttl)` - Update cache TTL dynamically
597
588
 
589
+ **FundamentalsService**
590
+ - `getFundamentals(ticker)` - Get fundamental data for single ticker
591
+ - `getBatchFundamentals(tickers)` - Get fundamentals for multiple tickers
592
+ - `enrichWithPrice(fundamentals, price)` - Calculate market cap, P/E, P/B ratios
593
+ - `enrichBatchWithPrices(fundamentals, priceMap)` - Batch enrich with prices
594
+ - `register(provider, config)` - Add fundamentals provider
595
+ - `checkProvidersHealth()` - Test all providers
596
+ - `getProviderStats()` - Get success/error statistics
597
+ - `clearCache()` - Clear fundamentals cache
598
+
599
+ **SecEdgarProvider**
600
+ - ✅ FREE - No API key required
601
+ - ✅ All US public companies
602
+ - ✅ Comprehensive US GAAP financial metrics
603
+ - ✅ Historical data back to ~2009
604
+ - ✅ Direct access to SEC filings (10-K, 10-Q)
605
+ - ✅ Smart caching (CIK: 30 days, Fundamentals: 90 days)
606
+ - ✅ Rate limiting (10 requests/second)
607
+ - â„šī¸ Requires User-Agent header in format: "Company Name Email"
608
+
598
609
  **MarketstackProvider**
599
- - ✅ End-of-Day (EOD) data
600
- - ✅ 125,000+ tickers across 72+ exchanges worldwide
601
- - ✅ Batch fetching support (multiple symbols in one request)
602
- - ✅ Comprehensive data: open, high, low, close, volume, splits, dividends
610
+ - ✅ End-of-Day (EOD) stock prices
611
+ - ✅ 500,000+ tickers across 72+ exchanges worldwide
612
+ - ✅ Historical data with pagination
613
+ - ✅ Batch fetching support
614
+ - ✅ OHLCV data (Open, High, Low, Close, Volume)
615
+ - ✅ Company names included automatically
603
616
  - âš ī¸ Requires API key (free tier: 100 requests/month)
604
- - âš ī¸ EOD data only (not real-time)
617
+
618
+ **YahooFinanceProvider**
619
+ - ✅ Real-time stock prices
620
+ - ✅ No API key required
621
+ - ✅ Global coverage
622
+ - ✅ Company names included
623
+ - âš ī¸ Rate limits may apply
605
624
 
606
625
  **OpenData**
607
626
  - `start()` - Initialize MongoDB connection
608
627
  - `buildInitialDb()` - Import bulk data
609
628
  - `CBusinessRecord` - Business record class
610
- - `handelsregister` - Registry automation
629
+ - `handelsregister` - German registry automation
611
630
 
612
631
  ## Provider Architecture
613
632
 
614
- The library uses a flexible provider system that makes it easy to add new data sources:
633
+ Add custom data providers easily:
615
634
 
616
635
  ```typescript
617
636
  class MyCustomProvider implements IStockProvider {
618
637
  name = 'My Provider';
619
638
  priority = 50;
620
639
  requiresAuth = true;
621
-
622
- async fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice> {
623
- // Your implementation
624
- }
625
-
626
- async fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]> {
627
- // Batch implementation
640
+ rateLimit = { requestsPerMinute: 60 };
641
+
642
+ async fetchData(request: IStockDataRequest): Promise<IStockPrice | IStockPrice[]> {
643
+ // Implement unified data fetching
644
+ switch (request.type) {
645
+ case 'current':
646
+ return this.fetchCurrentPrice(request);
647
+ case 'batch':
648
+ return this.fetchBatchPrices(request);
649
+ case 'historical':
650
+ return this.fetchHistoricalPrices(request);
651
+ default:
652
+ throw new Error(`Unsupported request type`);
653
+ }
628
654
  }
629
655
 
630
656
  async isAvailable(): Promise<boolean> {
631
657
  // Health check
658
+ return true;
632
659
  }
633
660
 
634
661
  supportsMarket(market: string): boolean {
635
- // Market validation
662
+ return ['US', 'UK', 'DE'].includes(market);
636
663
  }
637
664
 
638
665
  supportsTicker(ticker: string): boolean {
639
- // Ticker validation
666
+ return /^[A-Z]{1,5}$/.test(ticker);
640
667
  }
641
668
  }
642
669
 
@@ -645,30 +672,53 @@ stockService.register(new MyCustomProvider());
645
672
 
646
673
  ## Performance
647
674
 
648
- - **Batch fetching**: Get 100+ EOD prices in one API request
649
- - **Smart caching**: Instant repeated queries with configurable TTL
650
- - **Rate limit aware**: Automatic retry logic for API limits
651
- - **Concurrent processing**: Handle 1000+ business records/second
675
+ - **Batch Fetching**: Get 100+ prices in one API request
676
+ - **Smart Caching**: Data-type aware TTL (historical cached forever, EOD 24h, live 30s)
677
+ - **Rate Limit Management**: Automatic retry logic for API limits
678
+ - **Concurrent Processing**: Handle 1000+ records/second
652
679
  - **Streaming**: Process GB-sized datasets without memory issues
680
+ - **Provider Fallback**: Automatic failover between data sources
653
681
 
654
682
  ## Testing
655
683
 
656
684
  Run the comprehensive test suite:
657
685
 
658
686
  ```bash
659
- npm test
687
+ pnpm test
660
688
  ```
661
689
 
662
- Test stock provider:
690
+ Test specific modules:
663
691
 
664
692
  ```bash
665
- npx tstest test/test.marketstack.node.ts --verbose
693
+ # Stock price providers
694
+ pnpm tstest test/test.marketstack.node.ts --verbose
695
+ pnpm tstest test/test.stocks.ts --verbose
696
+
697
+ # Fundamental data
698
+ pnpm tstest test/test.secedgar.provider.node.ts --verbose
699
+ pnpm tstest test/test.fundamentals.service.node.ts --verbose
700
+
701
+ # German business data
702
+ pnpm tstest test/test.handelsregister.ts --verbose
666
703
  ```
667
704
 
668
- Test German business data:
705
+ ## Getting API Keys
669
706
 
670
- ```bash
671
- npx tstest test/test.handelsregister.ts --verbose
707
+ ### Marketstack (EOD Stock Data)
708
+
709
+ 1. Visit [marketstack.com](https://marketstack.com)
710
+ 2. Sign up for a free account (100 requests/month)
711
+ 3. Get your API key from the dashboard
712
+ 4. Set environment variable: `MARKETSTACK_COM_TOKEN=your_key_here`
713
+
714
+ ### SEC EDGAR (Fundamental Data)
715
+
716
+ **No API key required!** SEC EDGAR is completely free and public. Just provide your company name and email in the User-Agent:
717
+
718
+ ```typescript
719
+ new SecEdgarProvider({
720
+ userAgent: 'YourCompany youremail@example.com'
721
+ });
672
722
  ```
673
723
 
674
724
  ## License and Legal Information