@fin.cx/opendata 1.6.1 → 1.7.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/opendata',
6
- version: '1.6.1',
6
+ version: '1.7.0',
7
7
  description: 'A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stock market data from multiple providers.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxrQkFBa0I7SUFDeEIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHVTQUF1UztDQUNyVCxDQUFBIn0=
@@ -2,3 +2,4 @@ export * from './interfaces/stockprice.js';
2
2
  export * from './interfaces/provider.js';
3
3
  export * from './classes.stockservice.js';
4
4
  export * from './providers/provider.yahoo.js';
5
+ export * from './providers/provider.marketstack.js';
@@ -5,4 +5,5 @@ export * from './interfaces/provider.js';
5
5
  export * from './classes.stockservice.js';
6
6
  // Export providers
7
7
  export * from './providers/provider.yahoo.js';
8
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zdG9ja3MvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsd0JBQXdCO0FBQ3hCLGNBQWMsNEJBQTRCLENBQUM7QUFDM0MsY0FBYywwQkFBMEIsQ0FBQztBQUV6QyxzQkFBc0I7QUFDdEIsY0FBYywyQkFBMkIsQ0FBQztBQUUxQyxtQkFBbUI7QUFDbkIsY0FBYywrQkFBK0IsQ0FBQyJ9
8
+ export * from './providers/provider.marketstack.js';
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zdG9ja3MvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsd0JBQXdCO0FBQ3hCLGNBQWMsNEJBQTRCLENBQUM7QUFDM0MsY0FBYywwQkFBMEIsQ0FBQztBQUV6QyxzQkFBc0I7QUFDdEIsY0FBYywyQkFBMkIsQ0FBQztBQUUxQyxtQkFBbUI7QUFDbkIsY0FBYywrQkFBK0IsQ0FBQztBQUM5QyxjQUFjLHFDQUFxQyxDQUFDIn0=
@@ -0,0 +1,57 @@
1
+ import type { IStockProvider, IProviderConfig } from '../interfaces/provider.js';
2
+ import type { IStockPrice, IStockQuoteRequest, IStockBatchQuoteRequest } from '../interfaces/stockprice.js';
3
+ /**
4
+ * Marketstack API v2 Provider
5
+ * Documentation: https://marketstack.com/documentation_v2
6
+ *
7
+ * Features:
8
+ * - End-of-Day (EOD) stock prices
9
+ * - Supports 125,000+ tickers across 72+ exchanges worldwide
10
+ * - Requires API key authentication
11
+ *
12
+ * Rate Limits:
13
+ * - Free Plan: 100 requests/month (EOD only)
14
+ * - Basic Plan: 10,000 requests/month
15
+ * - Professional Plan: 100,000 requests/month
16
+ *
17
+ * Note: This provider returns EOD data, not real-time prices
18
+ */
19
+ export declare class MarketstackProvider implements IStockProvider {
20
+ private config?;
21
+ name: string;
22
+ priority: number;
23
+ readonly requiresAuth = true;
24
+ readonly rateLimit: {
25
+ requestsPerMinute: any;
26
+ requestsPerDay: any;
27
+ };
28
+ private logger;
29
+ private baseUrl;
30
+ private apiKey;
31
+ constructor(apiKey: string, config?: IProviderConfig);
32
+ /**
33
+ * Fetch latest EOD price for a single ticker
34
+ */
35
+ fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice>;
36
+ /**
37
+ * Fetch latest EOD prices for multiple tickers
38
+ */
39
+ fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]>;
40
+ /**
41
+ * Check if the Marketstack API is available and accessible
42
+ */
43
+ isAvailable(): Promise<boolean>;
44
+ /**
45
+ * Check if a market is supported
46
+ * Marketstack supports 72+ exchanges worldwide
47
+ */
48
+ supportsMarket(market: string): boolean;
49
+ /**
50
+ * Check if a ticker format is supported
51
+ */
52
+ supportsTicker(ticker: string): boolean;
53
+ /**
54
+ * Map Marketstack API response to IStockPrice interface
55
+ */
56
+ private mapToStockPrice;
57
+ }
@@ -0,0 +1,173 @@
1
+ import * as plugins from '../../plugins.js';
2
+ /**
3
+ * Marketstack API v2 Provider
4
+ * Documentation: https://marketstack.com/documentation_v2
5
+ *
6
+ * Features:
7
+ * - End-of-Day (EOD) stock prices
8
+ * - Supports 125,000+ tickers across 72+ exchanges worldwide
9
+ * - Requires API key authentication
10
+ *
11
+ * Rate Limits:
12
+ * - Free Plan: 100 requests/month (EOD only)
13
+ * - Basic Plan: 10,000 requests/month
14
+ * - Professional Plan: 100,000 requests/month
15
+ *
16
+ * Note: This provider returns EOD data, not real-time prices
17
+ */
18
+ export class MarketstackProvider {
19
+ constructor(apiKey, config) {
20
+ this.config = config;
21
+ this.name = 'Marketstack';
22
+ this.priority = 80; // Lower than Yahoo (100) due to rate limits and EOD-only data
23
+ this.requiresAuth = true;
24
+ this.rateLimit = {
25
+ requestsPerMinute: undefined, // No per-minute limit specified
26
+ requestsPerDay: undefined // Varies by plan
27
+ };
28
+ this.logger = console;
29
+ this.baseUrl = 'https://api.marketstack.com/v2';
30
+ if (!apiKey) {
31
+ throw new Error('API key is required for Marketstack provider');
32
+ }
33
+ this.apiKey = apiKey;
34
+ }
35
+ /**
36
+ * Fetch latest EOD price for a single ticker
37
+ */
38
+ async fetchPrice(request) {
39
+ try {
40
+ const url = `${this.baseUrl}/tickers/${request.ticker}/eod/latest?access_key=${this.apiKey}`;
41
+ const response = await plugins.smartrequest.SmartRequest.create()
42
+ .url(url)
43
+ .timeout(this.config?.timeout || 10000)
44
+ .get();
45
+ const responseData = await response.json();
46
+ // Check for API errors
47
+ if (responseData.error) {
48
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
49
+ }
50
+ // For single ticker endpoint, response is direct object (not wrapped in data field)
51
+ if (!responseData || !responseData.close) {
52
+ throw new Error(`No data found for ticker ${request.ticker}`);
53
+ }
54
+ return this.mapToStockPrice(responseData);
55
+ }
56
+ catch (error) {
57
+ this.logger.error(`Failed to fetch price for ${request.ticker}:`, error);
58
+ throw new Error(`Marketstack: Failed to fetch price for ${request.ticker}: ${error.message}`);
59
+ }
60
+ }
61
+ /**
62
+ * Fetch latest EOD prices for multiple tickers
63
+ */
64
+ async fetchPrices(request) {
65
+ try {
66
+ const symbols = request.tickers.join(',');
67
+ const url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
68
+ const response = await plugins.smartrequest.SmartRequest.create()
69
+ .url(url)
70
+ .timeout(this.config?.timeout || 15000)
71
+ .get();
72
+ const responseData = await response.json();
73
+ // Check for API errors
74
+ if (responseData.error) {
75
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
76
+ }
77
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
78
+ throw new Error('Invalid response format from Marketstack API');
79
+ }
80
+ const prices = [];
81
+ for (const data of responseData.data) {
82
+ try {
83
+ prices.push(this.mapToStockPrice(data));
84
+ }
85
+ catch (error) {
86
+ this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
87
+ // Continue processing other tickers
88
+ }
89
+ }
90
+ if (prices.length === 0) {
91
+ throw new Error('No valid price data received from batch request');
92
+ }
93
+ return prices;
94
+ }
95
+ catch (error) {
96
+ this.logger.error(`Failed to fetch batch prices:`, error);
97
+ throw new Error(`Marketstack: Failed to fetch batch prices: ${error.message}`);
98
+ }
99
+ }
100
+ /**
101
+ * Check if the Marketstack API is available and accessible
102
+ */
103
+ async isAvailable() {
104
+ try {
105
+ // Test with a well-known ticker
106
+ const url = `${this.baseUrl}/tickers/AAPL/eod/latest?access_key=${this.apiKey}`;
107
+ const response = await plugins.smartrequest.SmartRequest.create()
108
+ .url(url)
109
+ .timeout(5000)
110
+ .get();
111
+ const responseData = await response.json();
112
+ // Check if we got valid data (not an error)
113
+ // Single ticker endpoint returns direct object, not wrapped in data field
114
+ return !responseData.error && responseData.close !== undefined;
115
+ }
116
+ catch (error) {
117
+ this.logger.warn('Marketstack provider is not available:', error);
118
+ return false;
119
+ }
120
+ }
121
+ /**
122
+ * Check if a market is supported
123
+ * Marketstack supports 72+ exchanges worldwide
124
+ */
125
+ supportsMarket(market) {
126
+ // Marketstack has broad international coverage including:
127
+ // US, UK, DE, FR, JP, CN, HK, AU, CA, IN, etc.
128
+ const supportedMarkets = [
129
+ 'US', 'UK', 'GB', 'DE', 'FR', 'JP', 'CN', 'HK', 'AU', 'CA',
130
+ 'IN', 'BR', 'MX', 'IT', 'ES', 'NL', 'SE', 'CH', 'NO', 'DK'
131
+ ];
132
+ return supportedMarkets.includes(market.toUpperCase());
133
+ }
134
+ /**
135
+ * Check if a ticker format is supported
136
+ */
137
+ supportsTicker(ticker) {
138
+ // Basic validation - Marketstack supports most standard ticker formats
139
+ return /^[A-Z0-9\.\-]{1,10}$/.test(ticker.toUpperCase());
140
+ }
141
+ /**
142
+ * Map Marketstack API response to IStockPrice interface
143
+ */
144
+ mapToStockPrice(data) {
145
+ if (!data.close) {
146
+ throw new Error('Missing required price data');
147
+ }
148
+ // Calculate change and change percent
149
+ // EOD data: previous close is typically open price of the same day
150
+ // For better accuracy, we'd need previous day's close, but that requires another API call
151
+ const currentPrice = data.close;
152
+ const previousClose = data.open;
153
+ const change = currentPrice - previousClose;
154
+ const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
155
+ // Parse timestamp
156
+ const timestamp = data.date ? new Date(data.date) : new Date();
157
+ const stockPrice = {
158
+ ticker: data.symbol.toUpperCase(),
159
+ price: currentPrice,
160
+ currency: data.price_currency || 'USD',
161
+ change: change,
162
+ changePercent: changePercent,
163
+ previousClose: previousClose,
164
+ timestamp: timestamp,
165
+ provider: this.name,
166
+ marketState: 'CLOSED', // EOD data is always for closed markets
167
+ exchange: data.exchange,
168
+ exchangeName: data.exchange_code || data.name
169
+ };
170
+ return stockPrice;
171
+ }
172
+ }
173
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXIubWFya2V0c3RhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9zdG9ja3MvcHJvdmlkZXJzL3Byb3ZpZGVyLm1hcmtldHN0YWNrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFJNUM7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBQ0gsTUFBTSxPQUFPLG1CQUFtQjtJQWE5QixZQUFZLE1BQWMsRUFBVSxNQUF3QjtRQUF4QixXQUFNLEdBQU4sTUFBTSxDQUFrQjtRQVpyRCxTQUFJLEdBQUcsYUFBYSxDQUFDO1FBQ3JCLGFBQVEsR0FBRyxFQUFFLENBQUMsQ0FBQyw4REFBOEQ7UUFDcEUsaUJBQVksR0FBRyxJQUFJLENBQUM7UUFDcEIsY0FBUyxHQUFHO1lBQzFCLGlCQUFpQixFQUFFLFNBQVMsRUFBRSxnQ0FBZ0M7WUFDOUQsY0FBYyxFQUFFLFNBQVMsQ0FBQyxpQkFBaUI7U0FDNUMsQ0FBQztRQUVNLFdBQU0sR0FBRyxPQUFPLENBQUM7UUFDakIsWUFBTyxHQUFHLGdDQUFnQyxDQUFDO1FBSWpELElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLE1BQU0sSUFBSSxLQUFLLENBQUMsOENBQThDLENBQUMsQ0FBQztRQUNsRSxDQUFDO1FBRUQsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7SUFDdkIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFVBQVUsQ0FBQyxPQUEyQjtRQUNqRCxJQUFJLENBQUM7WUFDSCxNQUFNLEdBQUcsR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLFlBQVksT0FBTyxDQUFDLE1BQU0sMEJBQTBCLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUU3RixNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLE1BQU0sRUFBRTtpQkFDOUQsR0FBRyxDQUFDLEdBQUcsQ0FB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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fin.cx/opendata",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "private": false,
5
5
  "description": "A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stock market data from multiple providers.",
6
6
  "main": "dist_ts/index.js",
@@ -12,7 +12,7 @@
12
12
  "@git.zone/tsbuild": "^2.6.8",
13
13
  "@git.zone/tsbundle": "^2.5.1",
14
14
  "@git.zone/tsrun": "^1.3.3",
15
- "@git.zone/tstest": "^2.3.8",
15
+ "@git.zone/tstest": "^2.4.2",
16
16
  "@types/node": "^22.14.0"
17
17
  },
18
18
  "dependencies": {
package/readme.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # @fin.cx/opendata
2
+
2
3
  🚀 **Real-time financial data and German business intelligence in one powerful TypeScript library**
3
4
 
4
5
  Access live stock prices, cryptocurrencies, forex, commodities AND comprehensive German company data - all through a single, unified API.
@@ -8,34 +9,42 @@ Access live stock prices, cryptocurrencies, forex, commodities AND comprehensive
8
9
  ```bash
9
10
  npm install @fin.cx/opendata
10
11
  # or
11
- yarn add @fin.cx/opendata
12
+ pnpm add @fin.cx/opendata
12
13
  ```
13
14
 
14
15
  ## Quick Start
15
16
 
16
- ### 📈 Real-Time Stock Data
17
+ ### 📈 Stock Market Data
17
18
 
18
- Get live market data in seconds:
19
+ Get market data with EOD (End-of-Day) pricing:
19
20
 
20
21
  ```typescript
21
- import { StockPriceService, YahooFinanceProvider } from '@fin.cx/opendata';
22
+ import { StockPriceService, MarketstackProvider } from '@fin.cx/opendata';
23
+
24
+ // Initialize the service with caching
25
+ const stockService = new StockPriceService({
26
+ ttl: 60000, // Cache for 1 minute
27
+ maxEntries: 1000 // Max cached symbols
28
+ });
22
29
 
23
- // Initialize the service
24
- const stockService = new StockPriceService();
25
- stockService.register(new YahooFinanceProvider());
30
+ // Register Marketstack provider with API key
31
+ stockService.register(new MarketstackProvider('YOUR_API_KEY'), {
32
+ priority: 100,
33
+ retryAttempts: 3
34
+ });
26
35
 
27
36
  // Get single stock price
28
37
  const apple = await stockService.getPrice({ ticker: 'AAPL' });
29
38
  console.log(`Apple: $${apple.price} (${apple.changePercent.toFixed(2)}%)`);
30
39
 
31
- // Get multiple prices at once
32
- const prices = await stockService.getPrices({
33
- tickers: ['AAPL', 'MSFT', 'GOOGL', 'BTC-USD', 'ETH-USD']
40
+ // Get multiple prices at once (batch fetching)
41
+ const prices = await stockService.getPrices({
42
+ tickers: ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
34
43
  });
35
44
 
36
- // Market indices, crypto, forex, commodities - all supported!
37
- const marketData = await stockService.getPrices({
38
- tickers: ['^GSPC', '^DJI', 'BTC-USD', 'EURUSD=X', 'GC=F']
45
+ // 125,000+ tickers across 72+ exchanges worldwide
46
+ const internationalStocks = await stockService.getPrices({
47
+ tickers: ['AAPL', 'VOD.LON', 'SAP.DEX', 'TM', 'BABA']
39
48
  });
40
49
  ```
41
50
 
@@ -73,11 +82,12 @@ await openData.buildInitialDb();
73
82
 
74
83
  ### 🎯 Stock Market Module
75
84
 
76
- - **Real-time prices** for stocks, ETFs, indices, crypto, forex, and commodities
85
+ - **Marketstack API** - End-of-Day (EOD) data for 125,000+ tickers across 72+ exchanges
86
+ - **Stock prices** for stocks, ETFs, indices, and more
77
87
  - **Batch operations** - fetch 100+ symbols in one request
78
88
  - **Smart caching** - configurable TTL, automatic invalidation
79
- - **Provider system** - easily extensible for new data sources
80
- - **Automatic retries** and fallback mechanisms
89
+ - **Extensible provider system** - easily add new data sources
90
+ - **Retry logic** - configurable retry attempts and delays
81
91
  - **Type-safe** - full TypeScript support with detailed interfaces
82
92
 
83
93
  ### 🇩🇪 German Business Intelligence
@@ -92,29 +102,24 @@ await openData.buildInitialDb();
92
102
 
93
103
  ### Market Dashboard
94
104
 
95
- Create a real-time market overview:
105
+ Create an EOD market overview:
96
106
 
97
107
  ```typescript
98
108
  const indicators = [
99
109
  // Indices
100
110
  { ticker: '^GSPC', name: 'S&P 500' },
101
- { ticker: '^IXIC', name: 'NASDAQ' },
102
-
111
+ { ticker: '^DJI', name: 'DOW Jones' },
112
+
103
113
  // Tech Giants
104
114
  { ticker: 'AAPL', name: 'Apple' },
105
115
  { ticker: 'MSFT', name: 'Microsoft' },
106
-
107
- // Crypto
108
- { ticker: 'BTC-USD', name: 'Bitcoin' },
109
- { ticker: 'ETH-USD', name: 'Ethereum' },
110
-
111
- // Commodities
112
- { ticker: 'GC=F', name: 'Gold' },
113
- { ticker: 'CL=F', name: 'Oil' }
116
+ { ticker: 'GOOGL', name: 'Alphabet' },
117
+ { ticker: 'AMZN', name: 'Amazon' },
118
+ { ticker: 'TSLA', name: 'Tesla' }
114
119
  ];
115
120
 
116
- const prices = await stockService.getPrices({
117
- tickers: indicators.map(i => i.ticker)
121
+ const prices = await stockService.getPrices({
122
+ tickers: indicators.map(i => i.ticker)
118
123
  });
119
124
 
120
125
  // Display with color-coded changes
@@ -122,7 +127,7 @@ prices.forEach(price => {
122
127
  const indicator = indicators.find(i => i.ticker === price.ticker);
123
128
  const arrow = price.change >= 0 ? '↑' : '↓';
124
129
  const color = price.change >= 0 ? '\x1b[32m' : '\x1b[31m';
125
-
130
+
126
131
  console.log(
127
132
  `${indicator.name.padEnd(15)} ${price.price.toFixed(2).padStart(10)} ` +
128
133
  `${color}${arrow} ${price.changePercent.toFixed(2)}%\x1b[0m`
@@ -130,6 +135,25 @@ prices.forEach(price => {
130
135
  });
131
136
  ```
132
137
 
138
+ ### Provider Health and Statistics
139
+
140
+ Monitor your provider health and track usage:
141
+
142
+ ```typescript
143
+ // Check provider health
144
+ const health = await stockService.checkProvidersHealth();
145
+ console.log(`Marketstack: ${health.get('Marketstack') ? '✅' : '❌'}`);
146
+
147
+ // Get provider statistics
148
+ const stats = stockService.getProviderStats();
149
+ const marketstackStats = stats.get('Marketstack');
150
+ console.log('Marketstack Stats:', {
151
+ successCount: marketstackStats.successCount,
152
+ errorCount: marketstackStats.errorCount,
153
+ lastError: marketstackStats.lastError
154
+ });
155
+ ```
156
+
133
157
  ### Handelsregister Integration
134
158
 
135
159
  Automate German company data retrieval:
@@ -169,15 +193,15 @@ for (const company of publicCompanies) {
169
193
  try {
170
194
  // Map company to ticker (custom logic needed)
171
195
  const ticker = mapCompanyToTicker(company.data.name);
172
-
196
+
173
197
  if (ticker) {
174
198
  const stock = await stockService.getPrice({ ticker });
175
-
199
+
176
200
  // Add financial metrics
177
201
  company.data.stockPrice = stock.price;
178
202
  company.data.marketCap = stock.price * getSharesOutstanding(ticker);
179
203
  company.data.priceChange = stock.changePercent;
180
-
204
+
181
205
  await company.save();
182
206
  }
183
207
  } catch (error) {
@@ -196,8 +220,8 @@ const stockService = new StockPriceService({
196
220
  maxEntries: 1000 // Max cached symbols
197
221
  });
198
222
 
199
- // Provider configuration
200
- stockService.register(new YahooFinanceProvider(), {
223
+ // Marketstack - EOD data, requires API key
224
+ stockService.register(new MarketstackProvider('YOUR_API_KEY'), {
201
225
  enabled: true,
202
226
  priority: 100,
203
227
  timeout: 10000,
@@ -208,7 +232,7 @@ stockService.register(new YahooFinanceProvider(), {
208
232
 
209
233
  ### MongoDB Setup
210
234
 
211
- Set environment variables:
235
+ Set environment variables for German business data:
212
236
 
213
237
  ```env
214
238
  MONGODB_URL=mongodb://localhost:27017
@@ -217,6 +241,14 @@ MONGODB_USER=myuser
217
241
  MONGODB_PASS=mypass
218
242
  ```
219
243
 
244
+ ### Marketstack API Key
245
+
246
+ Get your free API key at [marketstack.com](https://marketstack.com) and set it in your environment:
247
+
248
+ ```env
249
+ MARKETSTACK_COM_TOKEN=your_api_key_here
250
+ ```
251
+
220
252
  ## API Reference
221
253
 
222
254
  ### Stock Types
@@ -240,10 +272,21 @@ interface IStockPrice {
240
272
  ### Key Methods
241
273
 
242
274
  **StockPriceService**
243
- - `getPrice(request)` - Single stock price
244
- - `getPrices(request)` - Batch prices
245
- - `register(provider)` - Add data provider
275
+ - `getPrice(request)` - Single stock price with automatic provider selection
276
+ - `getPrices(request)` - Batch prices (100+ symbols in one request)
277
+ - `register(provider, config)` - Add data provider with priority and retry config
278
+ - `checkProvidersHealth()` - Test all providers and return health status
279
+ - `getProviderStats()` - Get success/error statistics for each provider
246
280
  - `clearCache()` - Clear price cache
281
+ - `setCacheTTL(ttl)` - Update cache TTL dynamically
282
+
283
+ **MarketstackProvider**
284
+ - ✅ End-of-Day (EOD) data
285
+ - ✅ 125,000+ tickers across 72+ exchanges worldwide
286
+ - ✅ Batch fetching support (multiple symbols in one request)
287
+ - ✅ Comprehensive data: open, high, low, close, volume, splits, dividends
288
+ - ⚠️ Requires API key (free tier: 100 requests/month)
289
+ - ⚠️ EOD data only (not real-time)
247
290
 
248
291
  **OpenData**
249
292
  - `start()` - Initialize MongoDB connection
@@ -251,31 +294,48 @@ interface IStockPrice {
251
294
  - `CBusinessRecord` - Business record class
252
295
  - `handelsregister` - Registry automation
253
296
 
254
- ## Performance
255
-
256
- - **Batch fetching**: Get 100+ prices in <500ms
257
- - **Caching**: Instant repeated queries
258
- - **Concurrent processing**: Handle 1000+ records/second
259
- - **Streaming**: Process GB-sized datasets without memory issues
260
-
261
- ## Extensibility
297
+ ## Provider Architecture
262
298
 
263
- The provider architecture makes it easy to add new data sources:
299
+ The library uses a flexible provider system that makes it easy to add new data sources:
264
300
 
265
301
  ```typescript
266
302
  class MyCustomProvider implements IStockProvider {
267
303
  name = 'My Provider';
268
-
304
+ priority = 50;
305
+ requiresAuth = true;
306
+
269
307
  async fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice> {
270
308
  // Your implementation
271
309
  }
272
-
273
- // ... other required methods
310
+
311
+ async fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]> {
312
+ // Batch implementation
313
+ }
314
+
315
+ async isAvailable(): Promise<boolean> {
316
+ // Health check
317
+ }
318
+
319
+ supportsMarket(market: string): boolean {
320
+ // Market validation
321
+ }
322
+
323
+ supportsTicker(ticker: string): boolean {
324
+ // Ticker validation
325
+ }
274
326
  }
275
327
 
276
328
  stockService.register(new MyCustomProvider());
277
329
  ```
278
330
 
331
+ ## Performance
332
+
333
+ - **Batch fetching**: Get 100+ EOD prices in one API request
334
+ - **Smart caching**: Instant repeated queries with configurable TTL
335
+ - **Rate limit aware**: Automatic retry logic for API limits
336
+ - **Concurrent processing**: Handle 1000+ business records/second
337
+ - **Streaming**: Process GB-sized datasets without memory issues
338
+
279
339
  ## Testing
280
340
 
281
341
  Run the comprehensive test suite:
@@ -284,19 +344,21 @@ Run the comprehensive test suite:
284
344
  npm test
285
345
  ```
286
346
 
287
- View live market data:
347
+ Test stock provider:
288
348
 
289
349
  ```bash
290
- npm test -- --grep "market indicators"
350
+ npx tstest test/test.marketstack.node.ts --verbose
291
351
  ```
292
352
 
293
- ## Contributing
353
+ Test German business data:
294
354
 
295
- We welcome contributions! Please see our contributing guidelines for details.
355
+ ```bash
356
+ npx tstest test/test.handelsregister.ts --verbose
357
+ ```
296
358
 
297
359
  ## License and Legal Information
298
360
 
299
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
361
+ This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
300
362
 
301
363
  **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.
302
364
 
@@ -306,9 +368,9 @@ This project is owned and maintained by Task Venture Capital GmbH. The names and
306
368
 
307
369
  ### Company Information
308
370
 
309
- Task Venture Capital GmbH
371
+ Task Venture Capital GmbH
310
372
  Registered at District court Bremen HRB 35230 HB, Germany
311
373
 
312
374
  For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
313
375
 
314
- 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.
376
+ 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.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/opendata',
6
- version: '1.6.1',
6
+ version: '1.7.0',
7
7
  description: 'A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stock market data from multiple providers.'
8
8
  }
@@ -6,4 +6,5 @@ export * from './interfaces/provider.js';
6
6
  export * from './classes.stockservice.js';
7
7
 
8
8
  // Export providers
9
- export * from './providers/provider.yahoo.js';
9
+ export * from './providers/provider.yahoo.js';
10
+ export * from './providers/provider.marketstack.js';
@@ -0,0 +1,200 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { IStockProvider, IProviderConfig } from '../interfaces/provider.js';
3
+ import type { IStockPrice, IStockQuoteRequest, IStockBatchQuoteRequest } from '../interfaces/stockprice.js';
4
+
5
+ /**
6
+ * Marketstack API v2 Provider
7
+ * Documentation: https://marketstack.com/documentation_v2
8
+ *
9
+ * Features:
10
+ * - End-of-Day (EOD) stock prices
11
+ * - Supports 125,000+ tickers across 72+ exchanges worldwide
12
+ * - Requires API key authentication
13
+ *
14
+ * Rate Limits:
15
+ * - Free Plan: 100 requests/month (EOD only)
16
+ * - Basic Plan: 10,000 requests/month
17
+ * - Professional Plan: 100,000 requests/month
18
+ *
19
+ * Note: This provider returns EOD data, not real-time prices
20
+ */
21
+ export class MarketstackProvider implements IStockProvider {
22
+ public name = 'Marketstack';
23
+ public priority = 80; // Lower than Yahoo (100) due to rate limits and EOD-only data
24
+ public readonly requiresAuth = true;
25
+ public readonly rateLimit = {
26
+ requestsPerMinute: undefined, // No per-minute limit specified
27
+ requestsPerDay: undefined // Varies by plan
28
+ };
29
+
30
+ private logger = console;
31
+ private baseUrl = 'https://api.marketstack.com/v2';
32
+ private apiKey: string;
33
+
34
+ constructor(apiKey: string, private config?: IProviderConfig) {
35
+ if (!apiKey) {
36
+ throw new Error('API key is required for Marketstack provider');
37
+ }
38
+
39
+ this.apiKey = apiKey;
40
+ }
41
+
42
+ /**
43
+ * Fetch latest EOD price for a single ticker
44
+ */
45
+ public async fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice> {
46
+ try {
47
+ const url = `${this.baseUrl}/tickers/${request.ticker}/eod/latest?access_key=${this.apiKey}`;
48
+
49
+ const response = await plugins.smartrequest.SmartRequest.create()
50
+ .url(url)
51
+ .timeout(this.config?.timeout || 10000)
52
+ .get();
53
+
54
+ const responseData = await response.json() as any;
55
+
56
+ // Check for API errors
57
+ if (responseData.error) {
58
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
59
+ }
60
+
61
+ // For single ticker endpoint, response is direct object (not wrapped in data field)
62
+ if (!responseData || !responseData.close) {
63
+ throw new Error(`No data found for ticker ${request.ticker}`);
64
+ }
65
+
66
+ return this.mapToStockPrice(responseData);
67
+ } catch (error) {
68
+ this.logger.error(`Failed to fetch price for ${request.ticker}:`, error);
69
+ throw new Error(`Marketstack: Failed to fetch price for ${request.ticker}: ${error.message}`);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Fetch latest EOD prices for multiple tickers
75
+ */
76
+ public async fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]> {
77
+ try {
78
+ const symbols = request.tickers.join(',');
79
+ const url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
80
+
81
+ const response = await plugins.smartrequest.SmartRequest.create()
82
+ .url(url)
83
+ .timeout(this.config?.timeout || 15000)
84
+ .get();
85
+
86
+ const responseData = await response.json() as any;
87
+
88
+ // Check for API errors
89
+ if (responseData.error) {
90
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
91
+ }
92
+
93
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
94
+ throw new Error('Invalid response format from Marketstack API');
95
+ }
96
+
97
+ const prices: IStockPrice[] = [];
98
+
99
+ for (const data of responseData.data) {
100
+ try {
101
+ prices.push(this.mapToStockPrice(data));
102
+ } catch (error) {
103
+ this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
104
+ // Continue processing other tickers
105
+ }
106
+ }
107
+
108
+ if (prices.length === 0) {
109
+ throw new Error('No valid price data received from batch request');
110
+ }
111
+
112
+ return prices;
113
+ } catch (error) {
114
+ this.logger.error(`Failed to fetch batch prices:`, error);
115
+ throw new Error(`Marketstack: Failed to fetch batch prices: ${error.message}`);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Check if the Marketstack API is available and accessible
121
+ */
122
+ public async isAvailable(): Promise<boolean> {
123
+ try {
124
+ // Test with a well-known ticker
125
+ const url = `${this.baseUrl}/tickers/AAPL/eod/latest?access_key=${this.apiKey}`;
126
+
127
+ const response = await plugins.smartrequest.SmartRequest.create()
128
+ .url(url)
129
+ .timeout(5000)
130
+ .get();
131
+
132
+ const responseData = await response.json() as any;
133
+
134
+ // Check if we got valid data (not an error)
135
+ // Single ticker endpoint returns direct object, not wrapped in data field
136
+ return !responseData.error && responseData.close !== undefined;
137
+ } catch (error) {
138
+ this.logger.warn('Marketstack provider is not available:', error);
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Check if a market is supported
145
+ * Marketstack supports 72+ exchanges worldwide
146
+ */
147
+ public supportsMarket(market: string): boolean {
148
+ // Marketstack has broad international coverage including:
149
+ // US, UK, DE, FR, JP, CN, HK, AU, CA, IN, etc.
150
+ const supportedMarkets = [
151
+ 'US', 'UK', 'GB', 'DE', 'FR', 'JP', 'CN', 'HK', 'AU', 'CA',
152
+ 'IN', 'BR', 'MX', 'IT', 'ES', 'NL', 'SE', 'CH', 'NO', 'DK'
153
+ ];
154
+ return supportedMarkets.includes(market.toUpperCase());
155
+ }
156
+
157
+ /**
158
+ * Check if a ticker format is supported
159
+ */
160
+ public supportsTicker(ticker: string): boolean {
161
+ // Basic validation - Marketstack supports most standard ticker formats
162
+ return /^[A-Z0-9\.\-]{1,10}$/.test(ticker.toUpperCase());
163
+ }
164
+
165
+ /**
166
+ * Map Marketstack API response to IStockPrice interface
167
+ */
168
+ private mapToStockPrice(data: any): IStockPrice {
169
+ if (!data.close) {
170
+ throw new Error('Missing required price data');
171
+ }
172
+
173
+ // Calculate change and change percent
174
+ // EOD data: previous close is typically open price of the same day
175
+ // For better accuracy, we'd need previous day's close, but that requires another API call
176
+ const currentPrice = data.close;
177
+ const previousClose = data.open;
178
+ const change = currentPrice - previousClose;
179
+ const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
180
+
181
+ // Parse timestamp
182
+ const timestamp = data.date ? new Date(data.date) : new Date();
183
+
184
+ const stockPrice: IStockPrice = {
185
+ ticker: data.symbol.toUpperCase(),
186
+ price: currentPrice,
187
+ currency: data.price_currency || 'USD',
188
+ change: change,
189
+ changePercent: changePercent,
190
+ previousClose: previousClose,
191
+ timestamp: timestamp,
192
+ provider: this.name,
193
+ marketState: 'CLOSED', // EOD data is always for closed markets
194
+ exchange: data.exchange,
195
+ exchangeName: data.exchange_code || data.name
196
+ };
197
+
198
+ return stockPrice;
199
+ }
200
+ }