@fin.cx/opendata 3.5.0 โ 3.6.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/{npmextra.json โ .smartconfig.json} +12 -6
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.businessrecord.js +86 -43
- package/dist_ts/classes.handelsregister.d.ts +2 -0
- package/dist_ts/classes.handelsregister.js +126 -107
- package/dist_ts/classes.jsonldata.js +9 -6
- package/dist_ts/classes.main.opendata.d.ts +2 -1
- package/dist_ts/classes.main.opendata.js +59 -26
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/laws/classes.lawrecord.d.ts +24 -0
- package/dist_ts/laws/classes.lawrecord.js +181 -0
- package/dist_ts/laws/classes.lawservice.d.ts +52 -0
- package/dist_ts/laws/classes.lawservice.js +740 -0
- package/dist_ts/laws/index.d.ts +3 -0
- package/dist_ts/laws/index.js +4 -0
- package/dist_ts/laws/interfaces.law.d.ts +35 -0
- package/dist_ts/laws/interfaces.law.js +2 -0
- package/dist_ts/plugins.d.ts +8 -2
- package/dist_ts/plugins.js +14 -3
- package/dist_ts/stocks/classes.baseproviderservice.js +8 -8
- package/dist_ts/stocks/classes.fundamentalsservice.js +9 -9
- package/dist_ts/stocks/classes.stockdataservice.js +23 -23
- package/dist_ts/stocks/classes.stockservice.js +9 -9
- package/dist_ts/stocks/providers/provider.coingecko.d.ts +1 -1
- package/dist_ts/stocks/providers/provider.coingecko.js +45 -39
- package/dist_ts/stocks/providers/provider.marketstack.d.ts +3 -3
- package/dist_ts/stocks/providers/provider.marketstack.js +20 -16
- package/dist_ts/stocks/providers/provider.secedgar.d.ts +1 -1
- package/dist_ts/stocks/providers/provider.secedgar.js +22 -22
- package/dist_ts/stocks/providers/provider.yahoo.d.ts +2 -2
- package/dist_ts/stocks/providers/provider.yahoo.js +14 -13
- package/package.json +22 -19
- package/readme.md +280 -676
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.businessrecord.ts +1 -1
- package/ts/classes.handelsregister.ts +45 -20
- package/ts/classes.jsonldata.ts +7 -6
- package/ts/classes.main.opendata.ts +63 -30
- package/ts/index.ts +1 -0
- package/ts/laws/classes.lawrecord.ts +81 -0
- package/ts/laws/classes.lawservice.ts +1010 -0
- package/ts/laws/index.ts +3 -0
- package/ts/laws/interfaces.law.ts +48 -0
- package/ts/plugins.ts +19 -0
- package/ts/stocks/classes.fundamentalsservice.ts +1 -1
- package/ts/stocks/classes.stockdataservice.ts +17 -7
- package/ts/stocks/classes.stockservice.ts +2 -2
- package/ts/stocks/providers/provider.marketstack.ts +9 -7
- package/ts/stocks/providers/provider.secedgar.ts +2 -2
- package/ts/stocks/providers/provider.yahoo.ts +3 -3
- package/dist_ts/classes.germanbusinessdata.d.ts +0 -9
- package/dist_ts/classes.germanbusinessdata.js +0 -78
- package/readme.plan.md +0 -193
package/readme.md
CHANGED
|
@@ -1,801 +1,405 @@
|
|
|
1
1
|
# @fin.cx/opendata
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@fin.cx/opendata` is a TypeScript toolbox for serious data plumbing: market data, SEC fundamentals, German company registry workflows, and locally synced law corpora. It is built for programmers who want scriptable APIs, local persistence, and real workflows instead of a thin wrapper around one remote endpoint.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It currently gives you four practical lanes in one package:
|
|
6
|
+
|
|
7
|
+
- `StockPriceService` for stock and crypto prices with caching, retries, failover, historical ranges, and intraday support
|
|
8
|
+
- `FundamentalsService` and `StockDataService` for SEC fundamentals and combined price + fundamentals payloads
|
|
9
|
+
- `OpenData` for German company ingestion plus browser-driven Handelsregister lookup and document download
|
|
10
|
+
- `LawService` for syncing and searching German, EU, and US law texts in a local embedded database
|
|
11
|
+
|
|
12
|
+
## Issue Reporting and Security
|
|
13
|
+
|
|
14
|
+
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
6
15
|
|
|
7
16
|
## Installation
|
|
8
17
|
|
|
9
18
|
```bash
|
|
10
|
-
npm install @fin.cx/opendata
|
|
11
|
-
# or
|
|
12
19
|
pnpm add @fin.cx/opendata
|
|
13
20
|
```
|
|
14
21
|
|
|
15
|
-
##
|
|
22
|
+
## What You Get
|
|
16
23
|
|
|
17
|
-
###
|
|
24
|
+
### Market Data
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
- `StockPriceService`
|
|
27
|
+
Fetches current, batch, historical, and intraday price data.
|
|
28
|
+
- `FundamentalsService`
|
|
29
|
+
Fetches SEC EDGAR fundamentals for US-listed companies.
|
|
30
|
+
- `StockDataService`
|
|
31
|
+
Combines price + fundamentals and enriches fundamentals with metrics like `marketCap`, `priceToEarnings`, and `priceToBook`.
|
|
32
|
+
- `MarketstackProvider`
|
|
33
|
+
Authenticated stock market provider with current, batch, historical, and intraday support.
|
|
34
|
+
- `CoinGeckoProvider`
|
|
35
|
+
Crypto market provider behind the same stock-price interface.
|
|
36
|
+
- `SecEdgarProvider`
|
|
37
|
+
Fundamentals provider backed by SEC EDGAR company facts.
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
import { StockDataService, YahooFinanceProvider, SecEdgarProvider } from '@fin.cx/opendata';
|
|
39
|
+
### German Company Data
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
- `OpenData`
|
|
42
|
+
Starts a local embedded database, imports the public German company JSONL dump, and exposes a `handelsregister` helper for company lookup and file download.
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
stockData.registerPriceProvider(new YahooFinanceProvider());
|
|
29
|
-
stockData.registerFundamentalsProvider(new SecEdgarProvider({
|
|
30
|
-
userAgent: 'YourCompany youremail@example.com'
|
|
31
|
-
}));
|
|
44
|
+
### Laws
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
- `LawService`
|
|
47
|
+
Syncs laws into a local embedded database and lets you search them later.
|
|
48
|
+
- `LawRecord`
|
|
49
|
+
The stored law document model returned by sync and search operations.
|
|
35
50
|
|
|
36
|
-
|
|
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
|
|
44
|
-
});
|
|
51
|
+
## Before You Start
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
- No stock or fundamentals providers are pre-registered. You must register them before calling the services.
|
|
54
|
+
- `MarketstackProvider` requires an API key.
|
|
55
|
+
- `SecEdgarProvider` requires a valid `User-Agent` string in the format `Company Name email@example.com`.
|
|
56
|
+
- `CoinGeckoProvider` works without an API key, but a key gives you better rate limits.
|
|
57
|
+
- `OpenData` requires `nogitDir`, `downloadDir`, and `germanBusinessDataDir`.
|
|
58
|
+
- `OpenData.start()` is required before using `OpenData.handelsregister` or `buildInitialDb()`.
|
|
59
|
+
- `LawService` stores data locally. It auto-starts on demand, but explicit `start()` and `stop()` keep lifecycle handling cleaner.
|
|
60
|
+
- EU law syncing and Handelsregister access use a headless browser under the hood.
|
|
61
|
+
- `StockDataService` defaults to a 24 hour price cache. If you want fresher quote caching, pass `cache.priceTTL` explicitly or use `StockPriceService` directly.
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
console.log(`${stock.ticker}: $${stock.price.price.toFixed(2)}, ` +
|
|
51
|
-
`P/E ${stock.fundamentals?.priceToEarnings?.toFixed(2)}`);
|
|
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
|
|
57
|
-
```
|
|
58
|
-
|
|
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
|
|
63
|
+
## Quick Start
|
|
65
64
|
|
|
66
|
-
###
|
|
65
|
+
### Combined Stock Data
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
Use `StockDataService` when you want one call that returns both the latest price and SEC fundamentals.
|
|
69
68
|
|
|
70
|
-
```
|
|
71
|
-
import {
|
|
69
|
+
```ts
|
|
70
|
+
import {
|
|
71
|
+
MarketstackProvider,
|
|
72
|
+
SecEdgarProvider,
|
|
73
|
+
StockDataService,
|
|
74
|
+
} from '@fin.cx/opendata';
|
|
72
75
|
|
|
73
|
-
const
|
|
74
|
-
|
|
76
|
+
const stocks = new StockDataService({
|
|
77
|
+
cache: {
|
|
78
|
+
priceTTL: 60_000,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
stocks.registerPriceProvider(
|
|
83
|
+
new MarketstackProvider(process.env.MARKETSTACK_API_KEY!)
|
|
84
|
+
);
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
stocks.registerFundamentalsProvider(
|
|
87
|
+
new SecEdgarProvider({
|
|
88
|
+
userAgent: 'Acme Inc. dev@acme.dev',
|
|
89
|
+
})
|
|
90
|
+
);
|
|
82
91
|
|
|
83
|
-
const
|
|
84
|
-
stockService.register(new YahooFinanceProvider());
|
|
92
|
+
const apple = await stocks.getStockData('AAPL');
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
console.log({
|
|
95
|
+
ticker: apple.ticker,
|
|
96
|
+
price: apple.price.price,
|
|
97
|
+
provider: apple.price.provider,
|
|
98
|
+
companyName: apple.fundamentals?.companyName,
|
|
99
|
+
marketCap: apple.fundamentals?.marketCap,
|
|
100
|
+
priceToEarnings: apple.fundamentals?.priceToEarnings,
|
|
101
|
+
fetchedAt: apple.fetchedAt,
|
|
102
|
+
});
|
|
88
103
|
```
|
|
89
104
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Get real-time crypto prices with the CoinGecko provider:
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
import { StockPriceService, CoinGeckoProvider } from '@fin.cx/opendata';
|
|
105
|
+
You can also batch this:
|
|
96
106
|
|
|
97
|
-
|
|
107
|
+
```ts
|
|
108
|
+
const batch = await stocks.getBatchStockData(['AAPL', 'MSFT', 'NVDA']);
|
|
109
|
+
```
|
|
98
110
|
|
|
99
|
-
|
|
100
|
-
// const provider = new CoinGeckoProvider('your-api-key');
|
|
101
|
-
const provider = new CoinGeckoProvider();
|
|
111
|
+
### Price-Only Stocks And Intraday Data
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
Use `StockPriceService` if you want smarter per-request caching and direct access to current, historical, and intraday request types.
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
console.log(`${btc.ticker}: $${btc.price.toLocaleString()}`);
|
|
108
|
-
console.log(`24h Change: ${btc.changePercent.toFixed(2)}%`);
|
|
109
|
-
console.log(`24h Volume: $${btc.volume?.toLocaleString()}`);
|
|
115
|
+
```ts
|
|
116
|
+
import { MarketstackProvider, StockPriceService } from '@fin.cx/opendata';
|
|
110
117
|
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
const prices = new StockPriceService();
|
|
119
|
+
prices.register(new MarketstackProvider(process.env.MARKETSTACK_API_KEY!));
|
|
113
120
|
|
|
114
|
-
|
|
115
|
-
const cryptos = await stockService.getPrices({
|
|
116
|
-
tickers: ['BTC', 'ETH', 'USDT', 'BNB', 'SOL']
|
|
117
|
-
});
|
|
121
|
+
const current = await prices.getPrice({ ticker: 'AAPL' });
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
console.log(`${crypto.ticker}: $${crypto.price.toFixed(2)} (${crypto.changePercent >= 0 ? '+' : ''}${crypto.changePercent.toFixed(2)}%)`);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Historical crypto prices
|
|
124
|
-
const history = await stockService.getData({
|
|
123
|
+
const historical = await prices.getData({
|
|
125
124
|
type: 'historical',
|
|
126
|
-
ticker: '
|
|
125
|
+
ticker: 'AAPL',
|
|
127
126
|
from: new Date('2025-01-01'),
|
|
128
|
-
to: new Date('2025-01-31')
|
|
127
|
+
to: new Date('2025-01-31'),
|
|
128
|
+
sort: 'DESC',
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
const intraday = await stockService.getData({
|
|
131
|
+
const intraday = await prices.getData({
|
|
133
132
|
type: 'intraday',
|
|
134
|
-
ticker: '
|
|
133
|
+
ticker: 'AAPL',
|
|
135
134
|
interval: '1hour',
|
|
136
|
-
limit: 24
|
|
135
|
+
limit: 24,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log({
|
|
139
|
+
live: current.price,
|
|
140
|
+
candles: intraday.length,
|
|
141
|
+
historicalRows: historical.length,
|
|
137
142
|
});
|
|
138
143
|
```
|
|
139
144
|
|
|
140
|
-
|
|
145
|
+
Supported stock request shapes:
|
|
141
146
|
|
|
142
|
-
|
|
147
|
+
- `{ type: 'current', ticker, exchange? }`
|
|
148
|
+
- `{ type: 'batch', tickers, exchange? }`
|
|
149
|
+
- `{ type: 'historical', ticker, from, to, exchange?, sort?, limit?, offset? }`
|
|
150
|
+
- `{ type: 'intraday', ticker, interval, exchange?, limit?, date? }`
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
import { StockDataService, SecEdgarProvider } from '@fin.cx/opendata';
|
|
152
|
+
`StockPriceService` also exposes operational helpers like `checkProvidersHealth()`, `getProviderStats()`, `clearCache()`, and `getCacheStats()`.
|
|
146
153
|
|
|
147
|
-
|
|
148
|
-
stockData.registerFundamentalsProvider(new SecEdgarProvider({
|
|
149
|
-
userAgent: 'YourCompany youremail@example.com'
|
|
150
|
-
}));
|
|
154
|
+
### Crypto Prices With The Same Interface
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
const fundamentals = await stockData.getFundamentals('AAPL');
|
|
156
|
+
`CoinGeckoProvider` plugs into `StockPriceService`, so crypto lookups use the same request model as stock lookups.
|
|
154
157
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
eps: fundamentals.earningsPerShareDiluted,
|
|
158
|
-
revenue: fundamentals.revenue,
|
|
159
|
-
sharesOutstanding: fundamentals.sharesOutstanding
|
|
160
|
-
});
|
|
158
|
+
```ts
|
|
159
|
+
import { CoinGeckoProvider, StockPriceService } from '@fin.cx/opendata';
|
|
161
160
|
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
const prices = new StockPriceService({ ttl: 30_000 });
|
|
162
|
+
prices.register(new CoinGeckoProvider(process.env.COINGECKO_API_KEY));
|
|
164
163
|
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
userAgent: 'YourCompany youremail@example.com'
|
|
168
|
-
}));
|
|
164
|
+
const btc = await prices.getPrice({ ticker: 'BTC' });
|
|
165
|
+
const top = await prices.getPrices({ tickers: ['BTC', 'ETH', 'SOL'] });
|
|
169
166
|
|
|
170
|
-
|
|
167
|
+
console.log(btc.price, top.map((item) => item.ticker));
|
|
171
168
|
```
|
|
172
169
|
|
|
173
|
-
|
|
170
|
+
You can also pass CoinGecko ids like `bitcoin` or `ethereum` instead of ticker symbols.
|
|
174
171
|
|
|
175
|
-
|
|
172
|
+
### Fundamentals Only
|
|
176
173
|
|
|
177
|
-
|
|
178
|
-
import { OpenData } from '@fin.cx/opendata';
|
|
179
|
-
import * as path from 'path';
|
|
174
|
+
Use `FundamentalsService` when you only care about SEC filing data.
|
|
180
175
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
nogitDir: path.join(process.cwd(), '.nogit'),
|
|
184
|
-
downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
|
|
185
|
-
germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
|
|
186
|
-
});
|
|
187
|
-
await openData.start();
|
|
176
|
+
```ts
|
|
177
|
+
import { FundamentalsService, SecEdgarProvider } from '@fin.cx/opendata';
|
|
188
178
|
|
|
189
|
-
|
|
190
|
-
|
|
179
|
+
const fundamentals = new FundamentalsService();
|
|
180
|
+
fundamentals.register(
|
|
181
|
+
new SecEdgarProvider({
|
|
182
|
+
userAgent: 'Acme Inc. dev@acme.dev',
|
|
183
|
+
})
|
|
184
|
+
);
|
|
191
185
|
|
|
192
|
-
|
|
193
|
-
const details = await openData.handelsregister.getSpecificCompany({
|
|
194
|
-
court: "Munich",
|
|
195
|
-
type: "HRB",
|
|
196
|
-
number: "6684"
|
|
197
|
-
});
|
|
198
|
-
```
|
|
186
|
+
const apple = await fundamentals.getFundamentals('AAPL');
|
|
199
187
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
- **Company Names** - Automatic company name extraction (e.g., "Apple Inc (NASDAQ:AAPL)")
|
|
207
|
-
- **Historical Data** - Up to 15 years of daily EOD prices with pagination
|
|
208
|
-
- **OHLCV Data** - Open, High, Low, Close, Volume for technical analysis
|
|
209
|
-
- **Exchange Filtering** - Query specific exchanges via MIC codes (XNAS, XLON, XNYS)
|
|
210
|
-
- **Smart Caching** - Data-type aware TTL (historical cached forever, EOD 24h, live 30s)
|
|
211
|
-
- **Batch Operations** - Fetch 100+ symbols in one request
|
|
212
|
-
- **Type-Safe API** - Full TypeScript support with discriminated unions
|
|
213
|
-
- **Multi-Provider** - Automatic fallback between providers
|
|
214
|
-
|
|
215
|
-
### ๐ฐ Fundamental Data Module
|
|
216
|
-
|
|
217
|
-
- **SEC EDGAR Integration** - FREE fundamental data directly from SEC filings
|
|
218
|
-
- **Comprehensive Metrics** - EPS, Revenue, Assets, Liabilities, Cash Flow, and more
|
|
219
|
-
- **All US Public Companies** - Complete coverage of SEC-registered companies
|
|
220
|
-
- **Historical Filings** - Data back to ~2009
|
|
221
|
-
- **CIK Lookup** - Automatic ticker-to-CIK mapping with smart caching
|
|
222
|
-
- **Calculated Ratios** - Market Cap, P/E, P/B ratios when combined with prices
|
|
223
|
-
- **No API Key Required** - Direct access to SEC's public API
|
|
224
|
-
- **Rate Limit Management** - Built-in 10 req/sec rate limiting
|
|
225
|
-
|
|
226
|
-
### ๐ฉ๐ช German Business Intelligence
|
|
227
|
-
|
|
228
|
-
- **MongoDB Integration** - Scalable data storage for millions of records
|
|
229
|
-
- **Bulk JSONL Import** - Process multi-GB datasets efficiently
|
|
230
|
-
- **Handelsregister Automation** - Automated document retrieval
|
|
231
|
-
- **CRUD Operations** - Full database management with validation
|
|
232
|
-
- **Streaming Processing** - Handle large datasets without memory issues
|
|
233
|
-
|
|
234
|
-
## Advanced Examples
|
|
235
|
-
|
|
236
|
-
### Combined Market Analysis (Unified API)
|
|
237
|
-
|
|
238
|
-
Analyze multiple companies with automatic enrichment:
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
import { StockDataService, YahooFinanceProvider, SecEdgarProvider } from '@fin.cx/opendata';
|
|
242
|
-
|
|
243
|
-
// Setup unified service
|
|
244
|
-
const stockData = new StockDataService();
|
|
245
|
-
stockData.registerPriceProvider(new YahooFinanceProvider());
|
|
246
|
-
stockData.registerFundamentalsProvider(new SecEdgarProvider({
|
|
247
|
-
userAgent: 'YourCompany youremail@example.com'
|
|
248
|
-
}));
|
|
249
|
-
|
|
250
|
-
// Analyze multiple companies with ONE call
|
|
251
|
-
const tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN'];
|
|
252
|
-
const stocks = await stockData.getBatchStockData(tickers);
|
|
253
|
-
|
|
254
|
-
// All metrics are automatically calculated!
|
|
255
|
-
stocks.forEach(stock => {
|
|
256
|
-
if (stock.fundamentals) {
|
|
257
|
-
console.log(`\n${stock.fundamentals.companyName} (${stock.ticker})`);
|
|
258
|
-
console.log(` Price: $${stock.price.price.toFixed(2)}`);
|
|
259
|
-
console.log(` Market Cap: $${(stock.fundamentals.marketCap! / 1e9).toFixed(2)}B`);
|
|
260
|
-
console.log(` P/E Ratio: ${stock.fundamentals.priceToEarnings!.toFixed(2)}`);
|
|
261
|
-
console.log(` Revenue: $${(stock.fundamentals.revenue! / 1e9).toFixed(2)}B`);
|
|
262
|
-
console.log(` EPS: $${stock.fundamentals.earningsPerShareDiluted!.toFixed(2)}`);
|
|
263
|
-
}
|
|
188
|
+
console.log({
|
|
189
|
+
companyName: apple.companyName,
|
|
190
|
+
revenue: apple.revenue,
|
|
191
|
+
netIncome: apple.netIncome,
|
|
192
|
+
earningsPerShareDiluted: apple.earningsPerShareDiluted,
|
|
193
|
+
filingDate: apple.filingDate,
|
|
264
194
|
});
|
|
265
|
-
|
|
266
|
-
// Or analyze one-by-one with automatic enrichment
|
|
267
|
-
for (const ticker of tickers) {
|
|
268
|
-
const stock = await stockData.getStockData(ticker);
|
|
269
|
-
|
|
270
|
-
// Everything is already enriched - no manual calculations needed!
|
|
271
|
-
console.log(`${ticker}: P/E ${stock.fundamentals?.priceToEarnings?.toFixed(2)}`);
|
|
272
|
-
}
|
|
273
195
|
```
|
|
274
196
|
|
|
275
|
-
###
|
|
197
|
+
### German Company Data And Handelsregister Automation
|
|
276
198
|
|
|
277
|
-
|
|
199
|
+
`OpenData` is the local workflow layer for German company data. It creates its directories, boots an embedded SmartDB-backed database, and starts a browser automation session for Handelsregister access.
|
|
278
200
|
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const stocks = await stockData.getBatchStockData(tickers);
|
|
201
|
+
```ts
|
|
202
|
+
import { OpenData } from '@fin.cx/opendata';
|
|
203
|
+
import * as path from 'node:path';
|
|
283
204
|
|
|
284
|
-
|
|
285
|
-
const valueStocks = stocks.filter(stock => {
|
|
286
|
-
const f = stock.fundamentals;
|
|
287
|
-
return f &&
|
|
288
|
-
f.priceToEarnings! < 30 && // P/E under 30
|
|
289
|
-
f.priceToBook! < 10 && // P/B under 10
|
|
290
|
-
f.revenue! > 100_000_000_000; // Revenue > $100B
|
|
291
|
-
});
|
|
205
|
+
const nogitDir = path.join(process.cwd(), '.nogit');
|
|
292
206
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
207
|
+
const openData = new OpenData({
|
|
208
|
+
nogitDir,
|
|
209
|
+
downloadDir: path.join(nogitDir, 'downloads'),
|
|
210
|
+
germanBusinessDataDir: path.join(nogitDir, 'germanbusinessdata'),
|
|
297
211
|
});
|
|
298
|
-
```
|
|
299
212
|
|
|
300
|
-
|
|
213
|
+
await openData.start();
|
|
301
214
|
|
|
302
|
-
|
|
215
|
+
const matches = await openData.handelsregister.searchCompany('Siemens AG', 20);
|
|
303
216
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
ticker: 'AAPL',
|
|
309
|
-
from: new Date('2024-01-01'),
|
|
310
|
-
to: new Date('2024-12-31'),
|
|
311
|
-
sort: 'DESC' // Newest first
|
|
217
|
+
const company = await openData.handelsregister.getSpecificCompany({
|
|
218
|
+
court: 'Munich',
|
|
219
|
+
type: 'HRB',
|
|
220
|
+
number: '6684',
|
|
312
221
|
});
|
|
313
222
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const low52Week = Math.min(...prices);
|
|
318
|
-
const avgPrice = prices.reduce((a, b) => a + b) / prices.length;
|
|
319
|
-
|
|
320
|
-
console.log(`52-Week Analysis for AAPL:`);
|
|
321
|
-
console.log(` High: $${high52Week.toFixed(2)}`);
|
|
322
|
-
console.log(` Low: $${low52Week.toFixed(2)}`);
|
|
323
|
-
console.log(` Average: $${avgPrice.toFixed(2)}`);
|
|
324
|
-
console.log(` Days: ${history.length}`);
|
|
325
|
-
|
|
326
|
-
// Calculate Simple Moving Average
|
|
327
|
-
const calculateSMA = (data: IStockPrice[], period: number) => {
|
|
328
|
-
const sma: number[] = [];
|
|
329
|
-
for (let i = period - 1; i < data.length; i++) {
|
|
330
|
-
const sum = data.slice(i - period + 1, i + 1)
|
|
331
|
-
.reduce((acc, p) => acc + p.price, 0);
|
|
332
|
-
sma.push(sum / period);
|
|
333
|
-
}
|
|
334
|
-
return sma;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const sma20 = calculateSMA(history, 20);
|
|
338
|
-
const sma50 = calculateSMA(history, 50);
|
|
339
|
-
|
|
340
|
-
console.log(`\nMoving Averages:`);
|
|
341
|
-
console.log(` 20-day SMA: $${sma20[0].toFixed(2)}`);
|
|
342
|
-
console.log(` 50-day SMA: $${sma50[0].toFixed(2)}`);
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### OHLCV Technical Analysis
|
|
346
|
-
|
|
347
|
-
Use OHLCV data for technical indicators:
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
const history = await stockService.getData({
|
|
351
|
-
type: 'historical',
|
|
352
|
-
ticker: 'TSLA',
|
|
353
|
-
from: new Date('2024-11-01'),
|
|
354
|
-
to: new Date('2024-11-30')
|
|
223
|
+
console.log({
|
|
224
|
+
searchHits: matches.length,
|
|
225
|
+
downloadedFiles: company.files.map((file) => file.path),
|
|
355
226
|
});
|
|
356
227
|
|
|
357
|
-
|
|
358
|
-
for (const day of history) {
|
|
359
|
-
const range = day.high! - day.low!;
|
|
360
|
-
const rangePercent = (range / day.low!) * 100;
|
|
361
|
-
|
|
362
|
-
console.log(`${day.timestamp.toISOString().split('T')[0]}:`);
|
|
363
|
-
console.log(` Open: $${day.open}`);
|
|
364
|
-
console.log(` High: $${day.high}`);
|
|
365
|
-
console.log(` Low: $${day.low}`);
|
|
366
|
-
console.log(` Close: $${day.price}`);
|
|
367
|
-
console.log(` Volume: ${day.volume?.toLocaleString()}`);
|
|
368
|
-
console.log(` Range: $${range.toFixed(2)} (${rangePercent.toFixed(2)}%)`);
|
|
369
|
-
}
|
|
228
|
+
await openData.stop();
|
|
370
229
|
```
|
|
371
230
|
|
|
372
|
-
|
|
231
|
+
Useful `OpenData` workflows today:
|
|
373
232
|
|
|
374
|
-
|
|
233
|
+
- `await openData.start()`
|
|
234
|
+
- `await openData.buildInitialDb()` to ingest the public German company JSONL dataset
|
|
235
|
+
- `await openData.handelsregister.searchCompany(name, limit)`
|
|
236
|
+
- `await openData.handelsregister.getSpecificCompany({ court, type, number })`
|
|
237
|
+
- `await openData.handelsregister.getSpecificCompanyByName(name)`
|
|
375
238
|
|
|
376
|
-
|
|
377
|
-
const tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA'];
|
|
239
|
+
The download workflow currently fetches the Handelsregister files into a temporary folder and returns the files to you as `SmartFile` objects.
|
|
378
240
|
|
|
379
|
-
|
|
380
|
-
const allFundamentals = await fundamentalsService.getBatchFundamentals(tickers);
|
|
241
|
+
### Law Syncing And Search
|
|
381
242
|
|
|
382
|
-
|
|
383
|
-
const prices = await stockService.getData({
|
|
384
|
-
type: 'batch',
|
|
385
|
-
tickers: tickers
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
// Create price map
|
|
389
|
-
const priceMap = new Map(prices.map(p => [p.ticker, p.price]));
|
|
243
|
+
`LawService` stores synced laws in a local embedded database and supports three jurisdictions:
|
|
390
244
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
priceMap
|
|
395
|
-
);
|
|
245
|
+
- Germany via `gesetze-im-internet`
|
|
246
|
+
- EU via `EUR-Lex`
|
|
247
|
+
- US via GovInfo and Cornell LII
|
|
396
248
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
f.priceToEarnings && f.priceToEarnings < 25 &&
|
|
400
|
-
f.priceToBook && f.priceToBook < 5
|
|
401
|
-
);
|
|
249
|
+
```ts
|
|
250
|
+
import { LawService } from '@fin.cx/opendata';
|
|
402
251
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
console.log(` P/B Ratio: ${stock.priceToBook!.toFixed(2)}`);
|
|
408
|
-
console.log(` Market Cap: $${(stock.marketCap! / 1e9).toFixed(2)}B`);
|
|
252
|
+
const laws = new LawService({
|
|
253
|
+
dbFolderPath: '.nogit/law-smartdb',
|
|
254
|
+
dbName: 'laws',
|
|
255
|
+
govInfoApiKey: process.env.GOVINFO_API_KEY,
|
|
409
256
|
});
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
### Market Dashboard
|
|
413
|
-
|
|
414
|
-
Create a comprehensive market overview:
|
|
415
257
|
|
|
416
|
-
|
|
417
|
-
const indicators = [
|
|
418
|
-
{ ticker: 'AAPL', name: 'Apple' },
|
|
419
|
-
{ ticker: 'MSFT', name: 'Microsoft' },
|
|
420
|
-
{ ticker: 'GOOGL', name: 'Alphabet' },
|
|
421
|
-
{ ticker: 'AMZN', name: 'Amazon' },
|
|
422
|
-
{ ticker: 'TSLA', name: 'Tesla' }
|
|
423
|
-
];
|
|
258
|
+
await laws.start();
|
|
424
259
|
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
260
|
+
const germanLaw = await laws.syncLaw({
|
|
261
|
+
jurisdiction: 'de',
|
|
262
|
+
identifier: 'aeg',
|
|
428
263
|
});
|
|
429
264
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const color = price.change >= 0 ? '\x1b[32m' : '\x1b[31m';
|
|
435
|
-
|
|
436
|
-
console.log(
|
|
437
|
-
`${price.companyName!.padEnd(25)} $${price.price.toFixed(2).padStart(8)} ` +
|
|
438
|
-
`${color}${arrow} ${price.changePercent.toFixed(2)}%\x1b[0m`
|
|
439
|
-
);
|
|
440
|
-
});
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### Exchange-Specific Trading
|
|
444
|
-
|
|
445
|
-
Compare prices across different exchanges:
|
|
446
|
-
|
|
447
|
-
```typescript
|
|
448
|
-
// Vodafone trades on both London and NYSE
|
|
449
|
-
const exchanges = [
|
|
450
|
-
{ mic: 'XLON', name: 'London Stock Exchange' },
|
|
451
|
-
{ mic: 'XNYS', name: 'New York Stock Exchange' }
|
|
452
|
-
];
|
|
453
|
-
|
|
454
|
-
for (const exchange of exchanges) {
|
|
455
|
-
try {
|
|
456
|
-
const price = await stockService.getData({
|
|
457
|
-
type: 'current',
|
|
458
|
-
ticker: 'VOD',
|
|
459
|
-
exchange: exchange.mic
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
console.log(`${exchange.name}:`);
|
|
463
|
-
console.log(` Price: ${price.price} ${price.currency}`);
|
|
464
|
-
console.log(` Volume: ${price.volume?.toLocaleString()}`);
|
|
465
|
-
console.log(` Exchange: ${price.exchangeName}`);
|
|
466
|
-
} catch (error) {
|
|
467
|
-
console.log(`${exchange.name}: Not available`);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
## Configuration
|
|
473
|
-
|
|
474
|
-
### Stock Service Options
|
|
475
|
-
|
|
476
|
-
```typescript
|
|
477
|
-
const stockService = new StockPriceService({
|
|
478
|
-
ttl: 60000, // Default cache TTL in ms
|
|
479
|
-
maxEntries: 10000 // Max cached entries
|
|
265
|
+
const euLaw = await laws.syncLaw({
|
|
266
|
+
jurisdiction: 'eu',
|
|
267
|
+
identifier: '32024R1689',
|
|
268
|
+
language: 'EN',
|
|
480
269
|
});
|
|
481
270
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
priority: 100,
|
|
486
|
-
timeout: 10000,
|
|
487
|
-
retryAttempts: 3,
|
|
488
|
-
retryDelay: 1000
|
|
271
|
+
const usCode = await laws.syncLaw({
|
|
272
|
+
jurisdiction: 'us',
|
|
273
|
+
identifier: '8 U.S.C. ยง 1226',
|
|
489
274
|
});
|
|
490
275
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
276
|
+
const results = await laws.searchLaws({
|
|
277
|
+
jurisdiction: 'eu',
|
|
278
|
+
query: 'Artificial Intelligence Act',
|
|
279
|
+
limit: 5,
|
|
495
280
|
});
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
### Fundamentals Service Options
|
|
499
281
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
282
|
+
console.log({
|
|
283
|
+
germany: germanLaw.title,
|
|
284
|
+
eu: euLaw.title,
|
|
285
|
+
us: usCode.title,
|
|
286
|
+
hits: results.length,
|
|
504
287
|
});
|
|
505
288
|
|
|
506
|
-
|
|
507
|
-
fundamentalsService.register(new SecEdgarProvider({
|
|
508
|
-
userAgent: 'YourCompany youremail@example.com',
|
|
509
|
-
cikCacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
510
|
-
fundamentalsCacheTTL: 90 * 24 * 60 * 60 * 1000, // 90 days
|
|
511
|
-
timeout: 30000
|
|
512
|
-
}));
|
|
289
|
+
await laws.stop();
|
|
513
290
|
```
|
|
514
291
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
All directory paths are mandatory when using German business data features:
|
|
292
|
+
`LawService` also supports bulk sync:
|
|
518
293
|
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
nogitDir: path.join(process.cwd(), '.nogit'),
|
|
526
|
-
downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
|
|
527
|
-
germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
// Production
|
|
531
|
-
const openDataProd = new OpenData({
|
|
532
|
-
nogitDir: '/var/lib/myapp/data',
|
|
533
|
-
downloadDir: '/var/lib/myapp/data/downloads',
|
|
534
|
-
germanBusinessDataDir: '/var/lib/myapp/data/germanbusinessdata'
|
|
294
|
+
```ts
|
|
295
|
+
await laws.syncLaws({
|
|
296
|
+
jurisdiction: 'us',
|
|
297
|
+
usCollection: 'PLAW',
|
|
298
|
+
limit: 25,
|
|
299
|
+
since: new Date('2025-01-01'),
|
|
535
300
|
});
|
|
536
301
|
```
|
|
537
302
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
cash?: number;
|
|
613
|
-
propertyPlantEquipment?: number;
|
|
614
|
-
|
|
615
|
-
// Calculated metrics (requires price)
|
|
616
|
-
marketCap?: number; // price ร sharesOutstanding
|
|
617
|
-
priceToEarnings?: number; // price / EPS
|
|
618
|
-
priceToBook?: number; // marketCap / stockholdersEquity
|
|
619
|
-
|
|
620
|
-
// Metadata
|
|
621
|
-
fiscalYear?: string;
|
|
622
|
-
fiscalQuarter?: string;
|
|
623
|
-
filingDate?: Date;
|
|
624
|
-
form?: '10-K' | '10-Q' | string;
|
|
625
|
-
}
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
### Key Methods
|
|
629
|
-
|
|
630
|
-
**StockPriceService**
|
|
631
|
-
- `getData(request)` - Unified method for all stock data (current, historical, batch)
|
|
632
|
-
- `getPrice(request)` - Convenience method for single current price
|
|
633
|
-
- `getPrices(request)` - Convenience method for batch current prices
|
|
634
|
-
- `register(provider, config)` - Add data provider with priority and retry config
|
|
635
|
-
- `checkProvidersHealth()` - Test all providers and return health status
|
|
636
|
-
- `getProviderStats()` - Get success/error statistics for each provider
|
|
637
|
-
- `clearCache()` - Clear price cache
|
|
638
|
-
- `setCacheTTL(ttl)` - Update cache TTL dynamically
|
|
639
|
-
|
|
640
|
-
**FundamentalsService**
|
|
641
|
-
- `getFundamentals(ticker)` - Get fundamental data for single ticker
|
|
642
|
-
- `getBatchFundamentals(tickers)` - Get fundamentals for multiple tickers
|
|
643
|
-
- `enrichWithPrice(fundamentals, price)` - Calculate market cap, P/E, P/B ratios
|
|
644
|
-
- `enrichBatchWithPrices(fundamentals, priceMap)` - Batch enrich with prices
|
|
645
|
-
- `register(provider, config)` - Add fundamentals provider
|
|
646
|
-
- `checkProvidersHealth()` - Test all providers
|
|
647
|
-
- `getProviderStats()` - Get success/error statistics
|
|
648
|
-
- `clearCache()` - Clear fundamentals cache
|
|
649
|
-
|
|
650
|
-
**SecEdgarProvider**
|
|
651
|
-
- โ
FREE - No API key required
|
|
652
|
-
- โ
All US public companies
|
|
653
|
-
- โ
Comprehensive US GAAP financial metrics
|
|
654
|
-
- โ
Historical data back to ~2009
|
|
655
|
-
- โ
Direct access to SEC filings (10-K, 10-Q)
|
|
656
|
-
- โ
Smart caching (CIK: 30 days, Fundamentals: 90 days)
|
|
657
|
-
- โ
Rate limiting (10 requests/second)
|
|
658
|
-
- โน๏ธ Requires User-Agent header in format: "Company Name Email"
|
|
659
|
-
|
|
660
|
-
**MarketstackProvider**
|
|
661
|
-
- โ
End-of-Day (EOD) stock prices
|
|
662
|
-
- โ
500,000+ tickers across 72+ exchanges worldwide
|
|
663
|
-
- โ
Historical data with pagination
|
|
664
|
-
- โ
Batch fetching support
|
|
665
|
-
- โ
OHLCV data (Open, High, Low, Close, Volume)
|
|
666
|
-
- โ
Company names included automatically
|
|
667
|
-
- โ ๏ธ Requires API key (free tier: 100 requests/month)
|
|
668
|
-
|
|
669
|
-
**YahooFinanceProvider**
|
|
670
|
-
- โ
Real-time stock prices
|
|
671
|
-
- โ
No API key required
|
|
672
|
-
- โ
Global coverage
|
|
673
|
-
- โ
Company names included
|
|
674
|
-
- โ ๏ธ Rate limits may apply
|
|
675
|
-
|
|
676
|
-
**CoinGeckoProvider**
|
|
677
|
-
- โ
Cryptocurrency prices (Bitcoin, Ethereum, 13M+ tokens)
|
|
678
|
-
- โ
Current, historical, and intraday data
|
|
679
|
-
- โ
24/7 market data (crypto never closes)
|
|
680
|
-
- โ
OHLCV data with market cap and volume
|
|
681
|
-
- โ
Supports both ticker symbols (BTC, ETH) and CoinGecko IDs (bitcoin, ethereum)
|
|
682
|
-
- โ
240+ networks, 1600+ exchanges
|
|
683
|
-
- โน๏ธ Optional API key (free tier: 30 requests/min, 10K/month)
|
|
684
|
-
|
|
685
|
-
**OpenData**
|
|
686
|
-
- `start()` - Initialize MongoDB connection
|
|
687
|
-
- `buildInitialDb()` - Import bulk data
|
|
688
|
-
- `CBusinessRecord` - Business record class
|
|
689
|
-
- `handelsregister` - German registry automation
|
|
690
|
-
|
|
691
|
-
## Provider Architecture
|
|
692
|
-
|
|
693
|
-
Add custom data providers easily:
|
|
694
|
-
|
|
695
|
-
```typescript
|
|
696
|
-
class MyCustomProvider implements IStockProvider {
|
|
697
|
-
name = 'My Provider';
|
|
698
|
-
priority = 50;
|
|
699
|
-
requiresAuth = true;
|
|
700
|
-
rateLimit = { requestsPerMinute: 60 };
|
|
701
|
-
|
|
702
|
-
async fetchData(request: IStockDataRequest): Promise<IStockPrice | IStockPrice[]> {
|
|
703
|
-
// Implement unified data fetching
|
|
704
|
-
switch (request.type) {
|
|
705
|
-
case 'current':
|
|
706
|
-
return this.fetchCurrentPrice(request);
|
|
707
|
-
case 'batch':
|
|
708
|
-
return this.fetchBatchPrices(request);
|
|
709
|
-
case 'historical':
|
|
710
|
-
return this.fetchHistoricalPrices(request);
|
|
711
|
-
default:
|
|
712
|
-
throw new Error(`Unsupported request type`);
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
async isAvailable(): Promise<boolean> {
|
|
717
|
-
// Health check
|
|
718
|
-
return true;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
supportsMarket(market: string): boolean {
|
|
722
|
-
return ['US', 'UK', 'DE'].includes(market);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
supportsTicker(ticker: string): boolean {
|
|
726
|
-
return /^[A-Z]{1,5}$/.test(ticker);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
stockService.register(new MyCustomProvider());
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
## Performance
|
|
734
|
-
|
|
735
|
-
- **Batch Fetching**: Get 100+ prices in one API request
|
|
736
|
-
- **Smart Caching**: Data-type aware TTL (historical cached forever, EOD 24h, live 30s)
|
|
737
|
-
- **Rate Limit Management**: Automatic retry logic for API limits
|
|
738
|
-
- **Concurrent Processing**: Handle 1000+ records/second
|
|
739
|
-
- **Streaming**: Process GB-sized datasets without memory issues
|
|
740
|
-
- **Provider Fallback**: Automatic failover between data sources
|
|
303
|
+
Useful request patterns:
|
|
304
|
+
|
|
305
|
+
- Germany: `identifier: 'aeg'`
|
|
306
|
+
- EU: `identifier: '32024R1689'`, `language: 'EN'`
|
|
307
|
+
- US public law package: `identifier: 'PLAW-119publ1'`, `usCollection: 'PLAW'`
|
|
308
|
+
- US Code citation: `identifier: '8 U.S.C. ยง 1226'`
|
|
309
|
+
|
|
310
|
+
## API At A Glance
|
|
311
|
+
|
|
312
|
+
### `StockPriceService`
|
|
313
|
+
|
|
314
|
+
- `register(provider, config?)`
|
|
315
|
+
- `unregister(providerName)`
|
|
316
|
+
- `getPrice({ ticker })`
|
|
317
|
+
- `getPrices({ tickers })`
|
|
318
|
+
- `getData(request)`
|
|
319
|
+
- `checkProvidersHealth()`
|
|
320
|
+
- `getProviderStats()`
|
|
321
|
+
- `clearCache()`
|
|
322
|
+
- `getCacheStats()`
|
|
323
|
+
|
|
324
|
+
### `FundamentalsService`
|
|
325
|
+
|
|
326
|
+
- `register(provider, config?)`
|
|
327
|
+
- `getFundamentals(ticker)`
|
|
328
|
+
- `getBatchFundamentals(tickers)`
|
|
329
|
+
- `getData(request)`
|
|
330
|
+
- `enrichWithPrice(fundamentals, price)`
|
|
331
|
+
- `checkProvidersHealth()`
|
|
332
|
+
- `getProviderStats()`
|
|
333
|
+
- `clearCache()`
|
|
334
|
+
|
|
335
|
+
### `StockDataService`
|
|
336
|
+
|
|
337
|
+
- `registerPriceProvider(provider, config?)`
|
|
338
|
+
- `registerFundamentalsProvider(provider, config?)`
|
|
339
|
+
- `getPrice(ticker)`
|
|
340
|
+
- `getPrices(tickers)`
|
|
341
|
+
- `getFundamentals(ticker)`
|
|
342
|
+
- `getBatchFundamentals(tickers)`
|
|
343
|
+
- `getStockData(ticker | request)`
|
|
344
|
+
- `getBatchStockData(tickers | request)`
|
|
345
|
+
- `checkProvidersHealth()`
|
|
346
|
+
- `getProviderStats()`
|
|
347
|
+
- `clearCache()`
|
|
348
|
+
|
|
349
|
+
### `OpenData`
|
|
350
|
+
|
|
351
|
+
- `start()`
|
|
352
|
+
- `stop()`
|
|
353
|
+
- `buildInitialDb()`
|
|
354
|
+
- `handelsregister.searchCompany(name, limit?)`
|
|
355
|
+
- `handelsregister.getSpecificCompany({ court, type, number })`
|
|
356
|
+
- `handelsregister.getSpecificCompanyByName(name)`
|
|
357
|
+
|
|
358
|
+
### `LawService`
|
|
359
|
+
|
|
360
|
+
- `start()`
|
|
361
|
+
- `stop()`
|
|
362
|
+
- `getLaw(request)`
|
|
363
|
+
- `syncLaw(request)`
|
|
364
|
+
- `syncLaws(request)`
|
|
365
|
+
- `searchLaws({ query, jurisdiction?, limit? })`
|
|
366
|
+
|
|
367
|
+
## Behavior And Caveats
|
|
368
|
+
|
|
369
|
+
- Historical market data is cached aggressively because it is treated as immutable.
|
|
370
|
+
- Intraday data uses interval-aware caching and tries incremental refreshes for recent data.
|
|
371
|
+
- `StockDataService` is convenience-first. `StockPriceService` is the better low-level choice if you care about request-shape-specific cache behavior.
|
|
372
|
+
- `OpenData` is a real workflow layer, not just types. It writes local data, launches a browser, and manages downloads.
|
|
373
|
+
- Handelsregister and EUR-Lex integrations depend on current site structure. They are practical automation workflows, not guaranteed stable official APIs.
|
|
374
|
+
- The most useful public `OpenData` features today are startup, JSONL import, and Handelsregister operations. Some extra validation/search helper methods exist in source but are still stubs.
|
|
375
|
+
- `LawService` persists synced laws locally, so repeated searches can stay offline once the data has been synced.
|
|
376
|
+
- There is no hosted backend in this package. Everything is meant to run from your own TypeScript/Node process.
|
|
741
377
|
|
|
742
378
|
## Testing
|
|
743
379
|
|
|
744
|
-
Run the comprehensive test suite:
|
|
745
|
-
|
|
746
380
|
```bash
|
|
747
381
|
pnpm test
|
|
748
382
|
```
|
|
749
383
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
```bash
|
|
753
|
-
# Stock price providers
|
|
754
|
-
pnpm tstest test/test.marketstack.node.ts --verbose
|
|
755
|
-
pnpm tstest test/test.stocks.ts --verbose
|
|
756
|
-
|
|
757
|
-
# Fundamental data
|
|
758
|
-
pnpm tstest test/test.secedgar.provider.node.ts --verbose
|
|
759
|
-
pnpm tstest test/test.fundamentals.service.node.ts --verbose
|
|
760
|
-
|
|
761
|
-
# German business data
|
|
762
|
-
pnpm tstest test/test.handelsregister.ts --verbose
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
## Getting API Keys
|
|
766
|
-
|
|
767
|
-
### Marketstack (EOD Stock Data)
|
|
768
|
-
|
|
769
|
-
1. Visit [marketstack.com](https://marketstack.com)
|
|
770
|
-
2. Sign up for a free account (100 requests/month)
|
|
771
|
-
3. Get your API key from the dashboard
|
|
772
|
-
4. Set environment variable: `MARKETSTACK_COM_TOKEN=your_key_here`
|
|
773
|
-
|
|
774
|
-
### SEC EDGAR (Fundamental Data)
|
|
775
|
-
|
|
776
|
-
**No API key required!** SEC EDGAR is completely free and public. Just provide your company name and email in the User-Agent:
|
|
777
|
-
|
|
778
|
-
```typescript
|
|
779
|
-
new SecEdgarProvider({
|
|
780
|
-
userAgent: 'YourCompany youremail@example.com'
|
|
781
|
-
});
|
|
782
|
-
```
|
|
384
|
+
Some tests hit live remote services, so they may depend on network access, optional API credentials, or browser automation support.
|
|
783
385
|
|
|
784
386
|
## License and Legal Information
|
|
785
387
|
|
|
786
|
-
This repository contains open-source code
|
|
388
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./license) file.
|
|
787
389
|
|
|
788
390
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
789
391
|
|
|
790
392
|
### Trademarks
|
|
791
393
|
|
|
792
|
-
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein.
|
|
394
|
+
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
|
395
|
+
|
|
396
|
+
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
|
793
397
|
|
|
794
398
|
### Company Information
|
|
795
399
|
|
|
796
|
-
Task Venture Capital GmbH
|
|
797
|
-
Registered at District
|
|
400
|
+
Task Venture Capital GmbH
|
|
401
|
+
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
798
402
|
|
|
799
|
-
For any legal inquiries or
|
|
403
|
+
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
|
800
404
|
|
|
801
405
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|