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