@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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/stocks/classes.baseproviderservice.d.ts +129 -0
- package/dist_ts/stocks/classes.baseproviderservice.js +204 -0
- package/dist_ts/stocks/classes.fundamentalsservice.d.ts +104 -0
- package/dist_ts/stocks/classes.fundamentalsservice.js +298 -0
- package/dist_ts/stocks/classes.stockdataservice.d.ts +121 -0
- package/dist_ts/stocks/classes.stockdataservice.js +495 -0
- package/dist_ts/stocks/index.d.ts +6 -0
- package/dist_ts/stocks/index.js +9 -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/stockdata.d.ts +57 -0
- package/dist_ts/stocks/interfaces/stockdata.js +2 -0
- package/dist_ts/stocks/providers/provider.secedgar.d.ts +102 -0
- package/dist_ts/stocks/providers/provider.secedgar.js +343 -0
- package/package.json +1 -1
- package/readme.md +453 -403
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/stocks/classes.baseproviderservice.ts +296 -0
- package/ts/stocks/classes.fundamentalsservice.ts +404 -0
- package/ts/stocks/classes.stockdataservice.ts +647 -0
- package/ts/stocks/index.ts +10 -2
- package/ts/stocks/interfaces/fundamentals.ts +102 -0
- package/ts/stocks/interfaces/stockdata.ts +65 -0
- package/ts/stocks/providers/provider.secedgar.ts +429 -0
package/readme.md
CHANGED
|
@@ -1,154 +1,134 @@
|
|
|
1
1
|
# @fin.cx/opendata
|
|
2
2
|
|
|
3
|
-
đ **
|
|
3
|
+
đ **Complete financial intelligence toolkit for TypeScript**
|
|
4
4
|
|
|
5
|
-
Access
|
|
5
|
+
Access real-time stock prices, fundamental financial data, and comprehensive German company information - all through a single, unified API.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
### v2.1 - Enhanced Stock Market API (Current)
|
|
7
|
+
## Installation
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install @fin.cx/opendata
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @fin.cx/opendata
|
|
13
|
+
```
|
|
12
14
|
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
**
|
|
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
|
-
###
|
|
66
|
+
### đ Price-Only Data (Alternative)
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
If you only need prices without fundamentals:
|
|
52
69
|
|
|
53
70
|
```typescript
|
|
54
|
-
|
|
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
|
-
|
|
73
|
+
const stockData = new StockDataService();
|
|
74
|
+
stockData.registerPriceProvider(new YahooFinanceProvider());
|
|
62
75
|
|
|
63
|
-
|
|
76
|
+
// Get just the price
|
|
77
|
+
const price = await stockData.getPrice('AAPL');
|
|
78
|
+
console.log(`${price.ticker}: $${price.price}`);
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
### đ Stock Market Data (v2.1+ Enhanced)
|
|
83
|
+
const stockService = new StockPriceService({ ttl: 60000 });
|
|
84
|
+
stockService.register(new YahooFinanceProvider());
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
//
|
|
110
|
-
const
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
105
|
+
console.log({
|
|
106
|
+
company: fundamentals.companyName,
|
|
107
|
+
eps: fundamentals.earningsPerShareDiluted,
|
|
108
|
+
revenue: fundamentals.revenue,
|
|
109
|
+
sharesOutstanding: fundamentals.sharesOutstanding
|
|
120
110
|
});
|
|
121
111
|
|
|
122
|
-
//
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
140
|
-
console.log(prices[0].companyFullName); // "Apple Inc (NASDAQ:AAPL)"
|
|
120
|
+
const data = await fundamentalsService.getFundamentals('AAPL');
|
|
141
121
|
```
|
|
142
122
|
|
|
143
|
-
### đĸ German Business
|
|
123
|
+
### đĸ German Business Intelligence
|
|
144
124
|
|
|
145
|
-
Access comprehensive
|
|
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
|
-
//
|
|
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
|
-
//
|
|
160
|
-
const
|
|
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
|
-
//
|
|
176
|
-
await openData.
|
|
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
|
-
###
|
|
152
|
+
### đ Stock Market Module
|
|
182
153
|
|
|
183
|
-
- **
|
|
184
|
-
- **
|
|
185
|
-
- **
|
|
186
|
-
- **OHLCV Data** - Open, High, Low, Close, Volume for
|
|
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
|
-
- **
|
|
191
|
-
- **
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
198
|
-
- **Bulk JSONL
|
|
199
|
-
- **Handelsregister
|
|
200
|
-
- **CRUD
|
|
201
|
-
- **Streaming
|
|
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
|
-
###
|
|
185
|
+
### Combined Market Analysis (Unified API)
|
|
206
186
|
|
|
207
|
-
|
|
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
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
321
|
+
### Fundamental Data Screening
|
|
314
322
|
|
|
315
|
-
|
|
323
|
+
Screen stocks based on fundamental metrics:
|
|
316
324
|
|
|
317
325
|
```typescript
|
|
318
|
-
|
|
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
|
-
|
|
330
|
-
const
|
|
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
|
-
//
|
|
340
|
-
const
|
|
341
|
-
type: '
|
|
342
|
-
|
|
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
|
-
//
|
|
347
|
-
|
|
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
|
|
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.
|
|
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
|
-
`${
|
|
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
|
-
###
|
|
392
|
+
### Exchange-Specific Trading
|
|
386
393
|
|
|
387
|
-
|
|
394
|
+
Compare prices across different exchanges:
|
|
388
395
|
|
|
389
396
|
```typescript
|
|
390
|
-
//
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
421
|
+
## Configuration
|
|
405
422
|
|
|
406
|
-
|
|
423
|
+
### Stock Service Options
|
|
407
424
|
|
|
408
425
|
```typescript
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
###
|
|
439
|
-
|
|
440
|
-
Merge financial and business data:
|
|
447
|
+
### Fundamentals Service Options
|
|
441
448
|
|
|
442
449
|
```typescript
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
### Directory Configuration (Required for German Business Data)
|
|
464
|
+
### Directory Configuration (German Business Data)
|
|
489
465
|
|
|
490
|
-
|
|
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
|
-
|
|
505
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
- `
|
|
591
|
-
- `
|
|
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)
|
|
600
|
-
- â
|
|
601
|
-
- â
|
|
602
|
-
- â
|
|
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
|
-
|
|
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` -
|
|
629
|
+
- `handelsregister` - German registry automation
|
|
611
630
|
|
|
612
631
|
## Provider Architecture
|
|
613
632
|
|
|
614
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
662
|
+
return ['US', 'UK', 'DE'].includes(market);
|
|
636
663
|
}
|
|
637
664
|
|
|
638
665
|
supportsTicker(ticker: string): boolean {
|
|
639
|
-
|
|
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
|
|
649
|
-
- **Smart
|
|
650
|
-
- **Rate
|
|
651
|
-
- **Concurrent
|
|
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
|
-
|
|
687
|
+
pnpm test
|
|
660
688
|
```
|
|
661
689
|
|
|
662
|
-
Test
|
|
690
|
+
Test specific modules:
|
|
663
691
|
|
|
664
692
|
```bash
|
|
665
|
-
|
|
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
|
-
|
|
705
|
+
## Getting API Keys
|
|
669
706
|
|
|
670
|
-
|
|
671
|
-
|
|
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
|