@fin.cx/opendata 3.3.0 → 3.4.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: '3.3.0',
6
+ version: '3.4.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=
@@ -1,28 +1,35 @@
1
1
  import type { IStockProvider, IProviderConfig } from '../interfaces/provider.js';
2
2
  import type { IStockPrice, IStockDataRequest } from '../interfaces/stockprice.js';
3
3
  /**
4
- * Marketstack API v2 Provider - Enhanced
4
+ * Marketstack API v2 Provider - Professional Plan with Intelligent Intraday Support
5
5
  * Documentation: https://docs.apilayer.com/marketstack/docs/marketstack-api-v2-v-2-0-0
6
6
  *
7
7
  * Features:
8
+ * - Intelligent endpoint selection based on market hours
9
+ * - Real-time intraday pricing with multiple intervals (1min, 5min, 10min, 15min, 30min, 1hour)
8
10
  * - End-of-Day (EOD) stock prices with historical data
9
- * - Intraday pricing with multiple intervals (1min, 5min, 15min, 30min, 1hour)
11
+ * - Market state detection (PRE, REGULAR, POST, CLOSED)
10
12
  * - Exchange filtering via MIC codes (XNAS, XNYS, XLON, etc.)
11
13
  * - Supports 500,000+ tickers across 72+ exchanges worldwide
12
14
  * - OHLCV data (Open, High, Low, Close, Volume)
13
15
  * - Pagination for large datasets
16
+ * - Automatic fallback from intraday to EOD on errors
14
17
  * - Requires API key authentication
15
18
  *
19
+ * Intelligent Endpoint Selection:
20
+ * - During market hours (PRE/REGULAR/POST): Uses intraday endpoints for fresh data
21
+ * - After market close (CLOSED): Uses EOD endpoints to save API credits
22
+ * - Automatic fallback to EOD if intraday fails (rate limits, plan restrictions, etc.)
23
+ *
16
24
  * Rate Limits:
17
25
  * - Free Plan: 100 requests/month (EOD only)
18
- * - Basic Plan: 10,000 requests/month
19
- * - Professional Plan: 100,000 requests/month (intraday access)
26
+ * - Basic Plan: 10,000 requests/month (EOD only)
27
+ * - Professional Plan: 100,000 requests/month (intraday + EOD)
20
28
  *
21
- * Phase 1 Enhancements:
22
- * - Historical data retrieval with date ranges
23
- * - Exchange filtering
24
- * - OHLCV data support
25
- * - Pagination handling
29
+ * Intraday Access:
30
+ * - Intervals below 15min (1min, 5min, 10min) require Professional Plan or higher
31
+ * - Real-time data from IEX Exchange for US tickers
32
+ * - Symbol formatting: Periods replaced with hyphens for intraday (BRK.B → BRK-B)
26
33
  */
27
34
  export declare class MarketstackProvider implements IStockProvider {
28
35
  private config?;
@@ -42,21 +49,44 @@ export declare class MarketstackProvider implements IStockProvider {
42
49
  */
43
50
  fetchData(request: IStockDataRequest): Promise<IStockPrice[] | IStockPrice>;
44
51
  /**
45
- * Fetch current/latest EOD price for a single ticker (new API)
52
+ * Fetch current/latest price with intelligent endpoint selection
53
+ * Uses intraday during market hours (PRE, REGULAR, POST) for fresh data
54
+ * Uses EOD after market close (CLOSED) to save API credits
46
55
  */
47
56
  private fetchCurrentPrice;
57
+ /**
58
+ * Fetch current price using intraday endpoint (during market hours)
59
+ * Uses 1min interval for most recent data
60
+ */
61
+ private fetchCurrentPriceIntraday;
62
+ /**
63
+ * Fetch current price using EOD endpoint (after market close)
64
+ */
65
+ private fetchCurrentPriceEod;
48
66
  /**
49
67
  * Fetch historical EOD prices for a ticker with date range
50
68
  */
51
69
  private fetchHistoricalPrices;
52
70
  /**
53
- * Fetch intraday prices with specified interval (Phase 2 placeholder)
71
+ * Fetch intraday prices with specified interval
72
+ * Supports intervals: 1min, 5min, 10min, 15min, 30min, 1hour
73
+ * Note: Intervals below 15min require Professional Plan or higher
54
74
  */
55
75
  private fetchIntradayPrices;
56
76
  /**
57
- * Fetch current prices for multiple tickers (new API)
77
+ * Fetch current prices for multiple tickers with intelligent endpoint selection
78
+ * Uses intraday during market hours (PRE, REGULAR, POST) for fresh data
79
+ * Uses EOD after market close (CLOSED) to save API credits
58
80
  */
59
81
  private fetchBatchCurrentPrices;
82
+ /**
83
+ * Fetch batch current prices using intraday endpoint
84
+ */
85
+ private fetchBatchCurrentPricesIntraday;
86
+ /**
87
+ * Fetch batch current prices using EOD endpoint
88
+ */
89
+ private fetchBatchCurrentPricesEod;
60
90
  /**
61
91
  * Check if the Marketstack API is available and accessible
62
92
  */
@@ -72,6 +102,9 @@ export declare class MarketstackProvider implements IStockProvider {
72
102
  supportsTicker(ticker: string): boolean;
73
103
  /**
74
104
  * Map Marketstack API response to IStockPrice interface
105
+ * @param data - API response data
106
+ * @param dataType - Type of data (eod, intraday, live)
107
+ * @param explicitMarketState - Override market state (used for intraday data fetched during known market hours)
75
108
  */
76
109
  private mapToStockPrice;
77
110
  /**
@@ -83,4 +116,32 @@ export declare class MarketstackProvider implements IStockProvider {
83
116
  * Format date to YYYY-MM-DD for API requests
84
117
  */
85
118
  private formatDate;
119
+ /**
120
+ * Get US market state based on Eastern Time
121
+ * Regular hours: 9:30 AM - 4:00 PM ET
122
+ * Pre-market: 4:00 AM - 9:30 AM ET
123
+ * After-hours: 4:00 PM - 8:00 PM ET
124
+ *
125
+ * @param timestampMs - Optional timestamp in milliseconds (defaults to current time)
126
+ * @returns Market state: PRE, REGULAR, POST, or CLOSED
127
+ */
128
+ private getUsMarketState;
129
+ /**
130
+ * Determine if intraday endpoint should be used based on market state
131
+ * Uses intraday for PRE, REGULAR, and POST market states
132
+ * Uses EOD for CLOSED state to save API credits
133
+ *
134
+ * @param marketState - Current market state
135
+ * @returns true if intraday endpoint should be used
136
+ */
137
+ private shouldUseIntradayEndpoint;
138
+ /**
139
+ * Format ticker symbol for intraday endpoints
140
+ * Marketstack intraday API requires periods to be replaced with hyphens
141
+ * Example: BRK.B → BRK-B
142
+ *
143
+ * @param symbol - Original ticker symbol
144
+ * @returns Formatted symbol for intraday endpoints
145
+ */
146
+ private formatSymbolForIntraday;
86
147
  }
@@ -1,33 +1,40 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  /**
3
- * Marketstack API v2 Provider - Enhanced
3
+ * Marketstack API v2 Provider - Professional Plan with Intelligent Intraday Support
4
4
  * Documentation: https://docs.apilayer.com/marketstack/docs/marketstack-api-v2-v-2-0-0
5
5
  *
6
6
  * Features:
7
+ * - Intelligent endpoint selection based on market hours
8
+ * - Real-time intraday pricing with multiple intervals (1min, 5min, 10min, 15min, 30min, 1hour)
7
9
  * - End-of-Day (EOD) stock prices with historical data
8
- * - Intraday pricing with multiple intervals (1min, 5min, 15min, 30min, 1hour)
10
+ * - Market state detection (PRE, REGULAR, POST, CLOSED)
9
11
  * - Exchange filtering via MIC codes (XNAS, XNYS, XLON, etc.)
10
12
  * - Supports 500,000+ tickers across 72+ exchanges worldwide
11
13
  * - OHLCV data (Open, High, Low, Close, Volume)
12
14
  * - Pagination for large datasets
15
+ * - Automatic fallback from intraday to EOD on errors
13
16
  * - Requires API key authentication
14
17
  *
18
+ * Intelligent Endpoint Selection:
19
+ * - During market hours (PRE/REGULAR/POST): Uses intraday endpoints for fresh data
20
+ * - After market close (CLOSED): Uses EOD endpoints to save API credits
21
+ * - Automatic fallback to EOD if intraday fails (rate limits, plan restrictions, etc.)
22
+ *
15
23
  * Rate Limits:
16
24
  * - Free Plan: 100 requests/month (EOD only)
17
- * - Basic Plan: 10,000 requests/month
18
- * - Professional Plan: 100,000 requests/month (intraday access)
25
+ * - Basic Plan: 10,000 requests/month (EOD only)
26
+ * - Professional Plan: 100,000 requests/month (intraday + EOD)
19
27
  *
20
- * Phase 1 Enhancements:
21
- * - Historical data retrieval with date ranges
22
- * - Exchange filtering
23
- * - OHLCV data support
24
- * - Pagination handling
28
+ * Intraday Access:
29
+ * - Intervals below 15min (1min, 5min, 10min) require Professional Plan or higher
30
+ * - Real-time data from IEX Exchange for US tickers
31
+ * - Symbol formatting: Periods replaced with hyphens for intraday (BRK.B → BRK-B)
25
32
  */
26
33
  export class MarketstackProvider {
27
34
  constructor(apiKey, config) {
28
35
  this.config = config;
29
36
  this.name = 'Marketstack';
30
- this.priority = 80; // Lower than Yahoo (100) due to rate limits and EOD-only data
37
+ this.priority = 90; // Increased from 80 - now supports real-time intraday data during market hours
31
38
  this.requiresAuth = true;
32
39
  this.rateLimit = {
33
40
  requestsPerMinute: undefined, // No per-minute limit specified
@@ -58,35 +65,89 @@ export class MarketstackProvider {
58
65
  }
59
66
  }
60
67
  /**
61
- * Fetch current/latest EOD price for a single ticker (new API)
68
+ * Fetch current/latest price with intelligent endpoint selection
69
+ * Uses intraday during market hours (PRE, REGULAR, POST) for fresh data
70
+ * Uses EOD after market close (CLOSED) to save API credits
62
71
  */
63
72
  async fetchCurrentPrice(request) {
64
73
  try {
65
- let url = `${this.baseUrl}/tickers/${request.ticker}/eod/latest?access_key=${this.apiKey}`;
66
- // Add exchange filter if specified
67
- if (request.exchange) {
68
- url += `&exchange=${request.exchange}`;
74
+ // Determine current market state
75
+ const marketState = this.getUsMarketState();
76
+ const useIntraday = this.shouldUseIntradayEndpoint(marketState);
77
+ if (useIntraday) {
78
+ // Use intraday endpoint for fresh data during market hours
79
+ return await this.fetchCurrentPriceIntraday(request, marketState);
69
80
  }
70
- const response = await plugins.smartrequest.SmartRequest.create()
71
- .url(url)
72
- .timeout(this.config?.timeout || 10000)
73
- .get();
74
- const responseData = await response.json();
75
- // Check for API errors
76
- if (responseData.error) {
77
- throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
78
- }
79
- // For single ticker endpoint, response is direct object (not wrapped in data field)
80
- if (!responseData || !responseData.close) {
81
- throw new Error(`No data found for ticker ${request.ticker}`);
81
+ else {
82
+ // Use EOD endpoint for after-close data
83
+ return await this.fetchCurrentPriceEod(request);
82
84
  }
83
- return this.mapToStockPrice(responseData, 'eod');
84
85
  }
85
86
  catch (error) {
86
- this.logger.error(`Failed to fetch current price for ${request.ticker}:`, error);
87
- throw new Error(`Marketstack: Failed to fetch current price for ${request.ticker}: ${error.message}`);
87
+ // If intraday fails, fallback to EOD with warning
88
+ if (error.message?.includes('intraday') || error.message?.includes('Marketstack API error')) {
89
+ this.logger.warn(`Intraday endpoint failed for ${request.ticker}, falling back to EOD:`, error.message);
90
+ try {
91
+ return await this.fetchCurrentPriceEod(request);
92
+ }
93
+ catch (eodError) {
94
+ // Both failed, throw original error
95
+ throw error;
96
+ }
97
+ }
98
+ throw error;
88
99
  }
89
100
  }
101
+ /**
102
+ * Fetch current price using intraday endpoint (during market hours)
103
+ * Uses 1min interval for most recent data
104
+ */
105
+ async fetchCurrentPriceIntraday(request, marketState) {
106
+ const formattedSymbol = this.formatSymbolForIntraday(request.ticker);
107
+ let url = `${this.baseUrl}/tickers/${formattedSymbol}/intraday/latest?access_key=${this.apiKey}`;
108
+ url += `&interval=1min`; // Use 1min for most recent data
109
+ // Add exchange filter if specified
110
+ if (request.exchange) {
111
+ url += `&exchange=${request.exchange}`;
112
+ }
113
+ const response = await plugins.smartrequest.SmartRequest.create()
114
+ .url(url)
115
+ .timeout(this.config?.timeout || 10000)
116
+ .get();
117
+ const responseData = await response.json();
118
+ // Check for API errors
119
+ if (responseData.error) {
120
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
121
+ }
122
+ if (!responseData || !responseData.close) {
123
+ throw new Error(`No intraday data found for ticker ${request.ticker}`);
124
+ }
125
+ return this.mapToStockPrice(responseData, 'intraday', marketState);
126
+ }
127
+ /**
128
+ * Fetch current price using EOD endpoint (after market close)
129
+ */
130
+ async fetchCurrentPriceEod(request) {
131
+ let url = `${this.baseUrl}/tickers/${request.ticker}/eod/latest?access_key=${this.apiKey}`;
132
+ // Add exchange filter if specified
133
+ if (request.exchange) {
134
+ url += `&exchange=${request.exchange}`;
135
+ }
136
+ const response = await plugins.smartrequest.SmartRequest.create()
137
+ .url(url)
138
+ .timeout(this.config?.timeout || 10000)
139
+ .get();
140
+ const responseData = await response.json();
141
+ // Check for API errors
142
+ if (responseData.error) {
143
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
144
+ }
145
+ // For single ticker endpoint, response is direct object (not wrapped in data field)
146
+ if (!responseData || !responseData.close) {
147
+ throw new Error(`No data found for ticker ${request.ticker}`);
148
+ }
149
+ return this.mapToStockPrice(responseData, 'eod');
150
+ }
90
151
  /**
91
152
  * Fetch historical EOD prices for a ticker with date range
92
153
  */
@@ -147,52 +208,177 @@ export class MarketstackProvider {
147
208
  }
148
209
  }
149
210
  /**
150
- * Fetch intraday prices with specified interval (Phase 2 placeholder)
211
+ * Fetch intraday prices with specified interval
212
+ * Supports intervals: 1min, 5min, 10min, 15min, 30min, 1hour
213
+ * Note: Intervals below 15min require Professional Plan or higher
151
214
  */
152
215
  async fetchIntradayPrices(request) {
153
- throw new Error('Intraday data support coming in Phase 2. For now, use EOD data with type: "current" or "historical"');
216
+ try {
217
+ const allPrices = [];
218
+ let offset = 0;
219
+ const limit = 1000; // Max per page for intraday
220
+ const maxRecords = 10000; // Safety limit
221
+ // Format symbol for intraday endpoint (replace . with -)
222
+ const formattedSymbol = this.formatSymbolForIntraday(request.ticker);
223
+ while (true) {
224
+ let url = `${this.baseUrl}/tickers/${formattedSymbol}/intraday?access_key=${this.apiKey}`;
225
+ url += `&interval=${request.interval}`;
226
+ url += `&limit=${limit}`;
227
+ url += `&offset=${offset}`;
228
+ // Add date filter if specified
229
+ if (request.date) {
230
+ url += `&date_from=${this.formatDate(request.date)}`;
231
+ url += `&date_to=${this.formatDate(request.date)}`;
232
+ }
233
+ // Add exchange filter if specified
234
+ if (request.exchange) {
235
+ url += `&exchange=${request.exchange}`;
236
+ }
237
+ const response = await plugins.smartrequest.SmartRequest.create()
238
+ .url(url)
239
+ .timeout(this.config?.timeout || 15000)
240
+ .get();
241
+ const responseData = await response.json();
242
+ // Check for API errors
243
+ if (responseData.error) {
244
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
245
+ }
246
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
247
+ throw new Error('Invalid response format from Marketstack API');
248
+ }
249
+ // Map data to stock prices
250
+ for (const data of responseData.data) {
251
+ try {
252
+ allPrices.push(this.mapToStockPrice(data, 'intraday'));
253
+ }
254
+ catch (error) {
255
+ this.logger.warn(`Failed to parse intraday data for ${data.symbol}:`, error);
256
+ }
257
+ }
258
+ // Check if we have more pages
259
+ const pagination = responseData.pagination;
260
+ const hasMore = pagination && offset + limit < pagination.total;
261
+ // Honor limit from request if specified, or safety limit
262
+ if (!hasMore || (request.limit && allPrices.length >= request.limit) || allPrices.length >= maxRecords) {
263
+ break;
264
+ }
265
+ offset += limit;
266
+ }
267
+ // Apply limit if specified
268
+ if (request.limit && allPrices.length > request.limit) {
269
+ return allPrices.slice(0, request.limit);
270
+ }
271
+ return allPrices;
272
+ }
273
+ catch (error) {
274
+ this.logger.error(`Failed to fetch intraday prices for ${request.ticker}:`, error);
275
+ throw new Error(`Marketstack: Failed to fetch intraday prices for ${request.ticker}: ${error.message}`);
276
+ }
154
277
  }
155
278
  /**
156
- * Fetch current prices for multiple tickers (new API)
279
+ * Fetch current prices for multiple tickers with intelligent endpoint selection
280
+ * Uses intraday during market hours (PRE, REGULAR, POST) for fresh data
281
+ * Uses EOD after market close (CLOSED) to save API credits
157
282
  */
158
283
  async fetchBatchCurrentPrices(request) {
159
284
  try {
160
- const symbols = request.tickers.join(',');
161
- let url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
162
- if (request.exchange) {
163
- url += `&exchange=${request.exchange}`;
164
- }
165
- const response = await plugins.smartrequest.SmartRequest.create()
166
- .url(url)
167
- .timeout(this.config?.timeout || 15000)
168
- .get();
169
- const responseData = await response.json();
170
- // Check for API errors
171
- if (responseData.error) {
172
- throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
285
+ // Determine current market state
286
+ const marketState = this.getUsMarketState();
287
+ const useIntraday = this.shouldUseIntradayEndpoint(marketState);
288
+ if (useIntraday) {
289
+ return await this.fetchBatchCurrentPricesIntraday(request, marketState);
173
290
  }
174
- if (!responseData?.data || !Array.isArray(responseData.data)) {
175
- throw new Error('Invalid response format from Marketstack API');
291
+ else {
292
+ return await this.fetchBatchCurrentPricesEod(request);
176
293
  }
177
- const prices = [];
178
- for (const data of responseData.data) {
294
+ }
295
+ catch (error) {
296
+ // Fallback to EOD if intraday fails
297
+ if (error.message?.includes('intraday') || error.message?.includes('Marketstack API error')) {
298
+ this.logger.warn(`Intraday batch endpoint failed, falling back to EOD:`, error.message);
179
299
  try {
180
- prices.push(this.mapToStockPrice(data, 'eod'));
300
+ return await this.fetchBatchCurrentPricesEod(request);
181
301
  }
182
- catch (error) {
183
- this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
184
- // Continue processing other tickers
302
+ catch (eodError) {
303
+ // Both failed, throw original error
304
+ throw error;
185
305
  }
186
306
  }
187
- if (prices.length === 0) {
188
- throw new Error('No valid price data received from batch request');
307
+ throw error;
308
+ }
309
+ }
310
+ /**
311
+ * Fetch batch current prices using intraday endpoint
312
+ */
313
+ async fetchBatchCurrentPricesIntraday(request, marketState) {
314
+ // Format symbols for intraday (replace . with -)
315
+ const formattedSymbols = request.tickers.map(t => this.formatSymbolForIntraday(t)).join(',');
316
+ let url = `${this.baseUrl}/intraday/latest?access_key=${this.apiKey}`;
317
+ url += `&symbols=${formattedSymbols}`;
318
+ url += `&interval=1min`; // Use 1min for most recent data
319
+ if (request.exchange) {
320
+ url += `&exchange=${request.exchange}`;
321
+ }
322
+ const response = await plugins.smartrequest.SmartRequest.create()
323
+ .url(url)
324
+ .timeout(this.config?.timeout || 15000)
325
+ .get();
326
+ const responseData = await response.json();
327
+ if (responseData.error) {
328
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
329
+ }
330
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
331
+ throw new Error('Invalid response format from Marketstack API');
332
+ }
333
+ const prices = [];
334
+ for (const data of responseData.data) {
335
+ try {
336
+ prices.push(this.mapToStockPrice(data, 'intraday', marketState));
337
+ }
338
+ catch (error) {
339
+ this.logger.warn(`Failed to parse intraday data for ${data.symbol}:`, error);
189
340
  }
190
- return prices;
191
341
  }
192
- catch (error) {
193
- this.logger.error(`Failed to fetch batch current prices:`, error);
194
- throw new Error(`Marketstack: Failed to fetch batch current prices: ${error.message}`);
342
+ if (prices.length === 0) {
343
+ throw new Error('No valid price data received from batch intraday request');
195
344
  }
345
+ return prices;
346
+ }
347
+ /**
348
+ * Fetch batch current prices using EOD endpoint
349
+ */
350
+ async fetchBatchCurrentPricesEod(request) {
351
+ const symbols = request.tickers.join(',');
352
+ let url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
353
+ if (request.exchange) {
354
+ url += `&exchange=${request.exchange}`;
355
+ }
356
+ const response = await plugins.smartrequest.SmartRequest.create()
357
+ .url(url)
358
+ .timeout(this.config?.timeout || 15000)
359
+ .get();
360
+ const responseData = await response.json();
361
+ // Check for API errors
362
+ if (responseData.error) {
363
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
364
+ }
365
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
366
+ throw new Error('Invalid response format from Marketstack API');
367
+ }
368
+ const prices = [];
369
+ for (const data of responseData.data) {
370
+ try {
371
+ prices.push(this.mapToStockPrice(data, 'eod'));
372
+ }
373
+ catch (error) {
374
+ this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
375
+ // Continue processing other tickers
376
+ }
377
+ }
378
+ if (prices.length === 0) {
379
+ throw new Error('No valid price data received from batch request');
380
+ }
381
+ return prices;
196
382
  }
197
383
  /**
198
384
  * Check if the Marketstack API is available and accessible
@@ -237,8 +423,11 @@ export class MarketstackProvider {
237
423
  }
238
424
  /**
239
425
  * Map Marketstack API response to IStockPrice interface
426
+ * @param data - API response data
427
+ * @param dataType - Type of data (eod, intraday, live)
428
+ * @param explicitMarketState - Override market state (used for intraday data fetched during known market hours)
240
429
  */
241
- mapToStockPrice(data, dataType = 'eod') {
430
+ mapToStockPrice(data, dataType = 'eod', explicitMarketState) {
242
431
  if (!data.close) {
243
432
  throw new Error('Missing required price data');
244
433
  }
@@ -252,6 +441,24 @@ export class MarketstackProvider {
252
441
  // Parse timestamp
253
442
  const timestamp = data.date ? new Date(data.date) : new Date();
254
443
  const fetchedAt = new Date();
444
+ // Determine market state intelligently
445
+ let marketState;
446
+ if (explicitMarketState) {
447
+ // Use provided market state (for intraday data fetched during known market hours)
448
+ marketState = explicitMarketState;
449
+ }
450
+ else if (dataType === 'eod') {
451
+ // EOD data is always for closed markets
452
+ marketState = 'CLOSED';
453
+ }
454
+ else if (dataType === 'intraday') {
455
+ // For intraday data without explicit state, determine from timestamp
456
+ marketState = this.getUsMarketState(timestamp.getTime());
457
+ }
458
+ else {
459
+ // Default fallback
460
+ marketState = 'CLOSED';
461
+ }
255
462
  const stockPrice = {
256
463
  ticker: data.symbol.toUpperCase(),
257
464
  price: currentPrice,
@@ -261,7 +468,7 @@ export class MarketstackProvider {
261
468
  previousClose: previousClose,
262
469
  timestamp: timestamp,
263
470
  provider: this.name,
264
- marketState: 'CLOSED', // EOD data is always for closed markets
471
+ marketState: marketState, // Now dynamic based on data type, timestamp, and explicit state
265
472
  exchange: data.exchange,
266
473
  exchangeName: data.exchange_code || data.name,
267
474
  // Phase 1 enhancements: OHLCV data
@@ -314,5 +521,71 @@ export class MarketstackProvider {
314
521
  const day = String(date.getDate()).padStart(2, '0');
315
522
  return `${year}-${month}-${day}`;
316
523
  }
524
+ /**
525
+ * Get US market state based on Eastern Time
526
+ * Regular hours: 9:30 AM - 4:00 PM ET
527
+ * Pre-market: 4:00 AM - 9:30 AM ET
528
+ * After-hours: 4:00 PM - 8:00 PM ET
529
+ *
530
+ * @param timestampMs - Optional timestamp in milliseconds (defaults to current time)
531
+ * @returns Market state: PRE, REGULAR, POST, or CLOSED
532
+ */
533
+ getUsMarketState(timestampMs) {
534
+ const now = timestampMs ? new Date(timestampMs) : new Date();
535
+ // Convert to ET (UTC-5 or UTC-4 depending on DST)
536
+ // For simplicity, we'll use a rough approximation
537
+ // TODO: Add proper timezone library for production use
538
+ const etOffset = -5; // Standard time, adjust for DST if needed
539
+ const etTime = new Date(now.getTime() + (etOffset * 60 * 60 * 1000));
540
+ // Get day of week (0 = Sunday, 6 = Saturday)
541
+ const dayOfWeek = etTime.getUTCDay();
542
+ // Check if weekend
543
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
544
+ return 'CLOSED';
545
+ }
546
+ // Get hour and minute in ET
547
+ const hours = etTime.getUTCHours();
548
+ const minutes = etTime.getUTCMinutes();
549
+ const timeInMinutes = hours * 60 + minutes;
550
+ // Define market hours in minutes
551
+ const preMarketStart = 4 * 60; // 4:00 AM
552
+ const regularMarketStart = 9 * 60 + 30; // 9:30 AM
553
+ const regularMarketEnd = 16 * 60; // 4:00 PM
554
+ const afterHoursEnd = 20 * 60; // 8:00 PM
555
+ if (timeInMinutes >= preMarketStart && timeInMinutes < regularMarketStart) {
556
+ return 'PRE';
557
+ }
558
+ else if (timeInMinutes >= regularMarketStart && timeInMinutes < regularMarketEnd) {
559
+ return 'REGULAR';
560
+ }
561
+ else if (timeInMinutes >= regularMarketEnd && timeInMinutes < afterHoursEnd) {
562
+ return 'POST';
563
+ }
564
+ else {
565
+ return 'CLOSED';
566
+ }
567
+ }
568
+ /**
569
+ * Determine if intraday endpoint should be used based on market state
570
+ * Uses intraday for PRE, REGULAR, and POST market states
571
+ * Uses EOD for CLOSED state to save API credits
572
+ *
573
+ * @param marketState - Current market state
574
+ * @returns true if intraday endpoint should be used
575
+ */
576
+ shouldUseIntradayEndpoint(marketState) {
577
+ return marketState !== 'CLOSED';
578
+ }
579
+ /**
580
+ * Format ticker symbol for intraday endpoints
581
+ * Marketstack intraday API requires periods to be replaced with hyphens
582
+ * Example: BRK.B → BRK-B
583
+ *
584
+ * @param symbol - Original ticker symbol
585
+ * @returns Formatted symbol for intraday endpoints
586
+ */
587
+ formatSymbolForIntraday(symbol) {
588
+ return symbol.replace(/\./g, '-');
589
+ }
317
590
  }
318
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXIubWFya2V0c3RhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9zdG9ja3MvcHJvdmlkZXJzL3Byb3ZpZGVyLm1hcmtldHN0YWNrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFXNUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBdUJHO0FBQ0gsTUFBTSxPQUFPLG1CQUFtQjtJQWE5QixZQUFZLE1BQWMsRUFBVSxNQUF3QjtRQUF4QixXQUFNLEdBQU4sTUFBTSxDQUFrQjtRQVpyRCxTQUFJLEdBQUcsYUFBYSxDQUFDO1FBQ3JCLGFBQVEsR0FBRyxFQUFFLENBQUMsQ0FBQyw4REFBOEQ7UUFDcEUsaUJBQVksR0FBRyxJQUFJLENBQUM7UUFDcEIsY0FBUyxHQUFHO1lBQzFCLGlCQUFpQixFQUFFLFNBQVMsRUFBRSxnQ0FBZ0M7WUFDOUQsY0FBYyxFQUFFLFNBQVMsQ0FBQyxpQkFBaUI7U0FDNUMsQ0FBQztRQUVNLFdBQU0sR0FBRyxPQUFPLENBQUM7UUFDakIsWUFBTyxHQUFHLGdDQUFnQyxDQUFDO1FBSWpELElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNaLE1BQU0sSUFBSSxLQUFLLENBQUMsOENBQThDLENBQUMsQ0FBQztRQUNsRSxDQUFDO1FBRUQsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7SUFDdkIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFNBQVMsQ0FBQyxPQUEwQjtRQUMvQyxRQUFRLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNyQixLQUFLLFNBQVM7Z0JBQ1osT0FBTyxJQUFJLENBQUMsaUJBQWlCLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDekMsS0FBSyxZQUFZO2dCQUNmLE9BQU8sSUFBSSxDQUFDLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQzdDLEtBQUssVUFBVTtnQkFDYixPQUFP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
591
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXIubWFya2V0c3RhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9zdG9ja3MvcHJvdmlkZXJzL3Byb3ZpZGVyLm1hcmtldHN0YWNrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFXNUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQThCRztBQUNILE1BQU0sT0FBTyxtQkFBbUI7SUFhOUIsWUFBWSxNQUFjLEVBQVUsTUFBd0I7UUFBeEIsV0FBTSxHQUFOLE1BQU0sQ0FBa0I7UUFackQsU0FBSSxHQUFHLGFBQWEsQ0FBQztRQUNyQixhQUFRLEdBQUcsRUFBRSxDQUFDLENBQUMsK0VBQStFO1FBQ3JGLGlCQUFZLEdBQUcsSUFBSSxDQUFDO1FBQ3BCLGNBQVMsR0FBRztZQUMxQixpQkFBaUIsRUFBRSxTQUFTLEVBQUUsZ0NBQWdDO1lBQzlELGNBQWMsRUFBRSxTQUFTLENBQUMsaUJBQWlCO1NBQzVDLENBQUM7UUFFTSxXQUFNLEdBQUcsT0FBTyxDQUFDO1FBQ2pCLFlBQU8sR0FBRyxnQ0FBZ0MsQ0FBQztRQUlqRCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLElBQUksS0FBSyxDQUFDLDhDQUE4QyxDQUFDLENBQUM7UUFDbEUsQ0FBQztRQUVELElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO0lBQ3ZCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxTQUFTLENBQUMsT0FBMEI7UUFDL0MsUUFBUSxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDckIsS0FBSyxTQUFTO2dCQUNaLE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ3pDLEtBQUssWUFBWTtnQkFDZixPQUFPLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUM3QyxLQUFLLFVBQVU7Z0JB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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fin.cx/opendata",
3
- "version": "3.3.0",
3
+ "version": "3.4.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",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/opendata',
6
- version: '3.3.0',
6
+ version: '3.4.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
  }
@@ -10,32 +10,39 @@ import type {
10
10
  } from '../interfaces/stockprice.js';
11
11
 
12
12
  /**
13
- * Marketstack API v2 Provider - Enhanced
13
+ * Marketstack API v2 Provider - Professional Plan with Intelligent Intraday Support
14
14
  * Documentation: https://docs.apilayer.com/marketstack/docs/marketstack-api-v2-v-2-0-0
15
15
  *
16
16
  * Features:
17
+ * - Intelligent endpoint selection based on market hours
18
+ * - Real-time intraday pricing with multiple intervals (1min, 5min, 10min, 15min, 30min, 1hour)
17
19
  * - End-of-Day (EOD) stock prices with historical data
18
- * - Intraday pricing with multiple intervals (1min, 5min, 15min, 30min, 1hour)
20
+ * - Market state detection (PRE, REGULAR, POST, CLOSED)
19
21
  * - Exchange filtering via MIC codes (XNAS, XNYS, XLON, etc.)
20
22
  * - Supports 500,000+ tickers across 72+ exchanges worldwide
21
23
  * - OHLCV data (Open, High, Low, Close, Volume)
22
24
  * - Pagination for large datasets
25
+ * - Automatic fallback from intraday to EOD on errors
23
26
  * - Requires API key authentication
24
27
  *
28
+ * Intelligent Endpoint Selection:
29
+ * - During market hours (PRE/REGULAR/POST): Uses intraday endpoints for fresh data
30
+ * - After market close (CLOSED): Uses EOD endpoints to save API credits
31
+ * - Automatic fallback to EOD if intraday fails (rate limits, plan restrictions, etc.)
32
+ *
25
33
  * Rate Limits:
26
34
  * - Free Plan: 100 requests/month (EOD only)
27
- * - Basic Plan: 10,000 requests/month
28
- * - Professional Plan: 100,000 requests/month (intraday access)
35
+ * - Basic Plan: 10,000 requests/month (EOD only)
36
+ * - Professional Plan: 100,000 requests/month (intraday + EOD)
29
37
  *
30
- * Phase 1 Enhancements:
31
- * - Historical data retrieval with date ranges
32
- * - Exchange filtering
33
- * - OHLCV data support
34
- * - Pagination handling
38
+ * Intraday Access:
39
+ * - Intervals below 15min (1min, 5min, 10min) require Professional Plan or higher
40
+ * - Real-time data from IEX Exchange for US tickers
41
+ * - Symbol formatting: Periods replaced with hyphens for intraday (BRK.B → BRK-B)
35
42
  */
36
43
  export class MarketstackProvider implements IStockProvider {
37
44
  public name = 'Marketstack';
38
- public priority = 80; // Lower than Yahoo (100) due to rate limits and EOD-only data
45
+ public priority = 90; // Increased from 80 - now supports real-time intraday data during market hours
39
46
  public readonly requiresAuth = true;
40
47
  public readonly rateLimit = {
41
48
  requestsPerMinute: undefined, // No per-minute limit specified
@@ -73,39 +80,103 @@ export class MarketstackProvider implements IStockProvider {
73
80
  }
74
81
 
75
82
  /**
76
- * Fetch current/latest EOD price for a single ticker (new API)
83
+ * Fetch current/latest price with intelligent endpoint selection
84
+ * Uses intraday during market hours (PRE, REGULAR, POST) for fresh data
85
+ * Uses EOD after market close (CLOSED) to save API credits
77
86
  */
78
87
  private async fetchCurrentPrice(request: IStockCurrentRequest): Promise<IStockPrice> {
79
88
  try {
80
- let url = `${this.baseUrl}/tickers/${request.ticker}/eod/latest?access_key=${this.apiKey}`;
81
-
82
- // Add exchange filter if specified
83
- if (request.exchange) {
84
- url += `&exchange=${request.exchange}`;
89
+ // Determine current market state
90
+ const marketState = this.getUsMarketState();
91
+ const useIntraday = this.shouldUseIntradayEndpoint(marketState);
92
+
93
+ if (useIntraday) {
94
+ // Use intraday endpoint for fresh data during market hours
95
+ return await this.fetchCurrentPriceIntraday(request, marketState);
96
+ } else {
97
+ // Use EOD endpoint for after-close data
98
+ return await this.fetchCurrentPriceEod(request);
99
+ }
100
+ } catch (error) {
101
+ // If intraday fails, fallback to EOD with warning
102
+ if (error.message?.includes('intraday') || error.message?.includes('Marketstack API error')) {
103
+ this.logger.warn(`Intraday endpoint failed for ${request.ticker}, falling back to EOD:`, error.message);
104
+ try {
105
+ return await this.fetchCurrentPriceEod(request);
106
+ } catch (eodError) {
107
+ // Both failed, throw original error
108
+ throw error;
109
+ }
85
110
  }
111
+ throw error;
112
+ }
113
+ }
86
114
 
87
- const response = await plugins.smartrequest.SmartRequest.create()
88
- .url(url)
89
- .timeout(this.config?.timeout || 10000)
90
- .get();
115
+ /**
116
+ * Fetch current price using intraday endpoint (during market hours)
117
+ * Uses 1min interval for most recent data
118
+ */
119
+ private async fetchCurrentPriceIntraday(
120
+ request: IStockCurrentRequest,
121
+ marketState: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED'
122
+ ): Promise<IStockPrice> {
123
+ const formattedSymbol = this.formatSymbolForIntraday(request.ticker);
124
+ let url = `${this.baseUrl}/tickers/${formattedSymbol}/intraday/latest?access_key=${this.apiKey}`;
125
+ url += `&interval=1min`; // Use 1min for most recent data
126
+
127
+ // Add exchange filter if specified
128
+ if (request.exchange) {
129
+ url += `&exchange=${request.exchange}`;
130
+ }
91
131
 
92
- const responseData = await response.json() as any;
132
+ const response = await plugins.smartrequest.SmartRequest.create()
133
+ .url(url)
134
+ .timeout(this.config?.timeout || 10000)
135
+ .get();
93
136
 
94
- // Check for API errors
95
- if (responseData.error) {
96
- throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
97
- }
137
+ const responseData = await response.json() as any;
98
138
 
99
- // For single ticker endpoint, response is direct object (not wrapped in data field)
100
- if (!responseData || !responseData.close) {
101
- throw new Error(`No data found for ticker ${request.ticker}`);
102
- }
139
+ // Check for API errors
140
+ if (responseData.error) {
141
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
142
+ }
103
143
 
104
- return this.mapToStockPrice(responseData, 'eod');
105
- } catch (error) {
106
- this.logger.error(`Failed to fetch current price for ${request.ticker}:`, error);
107
- throw new Error(`Marketstack: Failed to fetch current price for ${request.ticker}: ${error.message}`);
144
+ if (!responseData || !responseData.close) {
145
+ throw new Error(`No intraday data found for ticker ${request.ticker}`);
108
146
  }
147
+
148
+ return this.mapToStockPrice(responseData, 'intraday', marketState);
149
+ }
150
+
151
+ /**
152
+ * Fetch current price using EOD endpoint (after market close)
153
+ */
154
+ private async fetchCurrentPriceEod(request: IStockCurrentRequest): Promise<IStockPrice> {
155
+ let url = `${this.baseUrl}/tickers/${request.ticker}/eod/latest?access_key=${this.apiKey}`;
156
+
157
+ // Add exchange filter if specified
158
+ if (request.exchange) {
159
+ url += `&exchange=${request.exchange}`;
160
+ }
161
+
162
+ const response = await plugins.smartrequest.SmartRequest.create()
163
+ .url(url)
164
+ .timeout(this.config?.timeout || 10000)
165
+ .get();
166
+
167
+ const responseData = await response.json() as any;
168
+
169
+ // Check for API errors
170
+ if (responseData.error) {
171
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
172
+ }
173
+
174
+ // For single ticker endpoint, response is direct object (not wrapped in data field)
175
+ if (!responseData || !responseData.close) {
176
+ throw new Error(`No data found for ticker ${request.ticker}`);
177
+ }
178
+
179
+ return this.mapToStockPrice(responseData, 'eod');
109
180
  }
110
181
 
111
182
  /**
@@ -179,60 +250,210 @@ export class MarketstackProvider implements IStockProvider {
179
250
  }
180
251
 
181
252
  /**
182
- * Fetch intraday prices with specified interval (Phase 2 placeholder)
253
+ * Fetch intraday prices with specified interval
254
+ * Supports intervals: 1min, 5min, 10min, 15min, 30min, 1hour
255
+ * Note: Intervals below 15min require Professional Plan or higher
183
256
  */
184
257
  private async fetchIntradayPrices(request: IStockIntradayRequest): Promise<IStockPrice[]> {
185
- throw new Error('Intraday data support coming in Phase 2. For now, use EOD data with type: "current" or "historical"');
258
+ try {
259
+ const allPrices: IStockPrice[] = [];
260
+ let offset = 0;
261
+ const limit = 1000; // Max per page for intraday
262
+ const maxRecords = 10000; // Safety limit
263
+
264
+ // Format symbol for intraday endpoint (replace . with -)
265
+ const formattedSymbol = this.formatSymbolForIntraday(request.ticker);
266
+
267
+ while (true) {
268
+ let url = `${this.baseUrl}/tickers/${formattedSymbol}/intraday?access_key=${this.apiKey}`;
269
+ url += `&interval=${request.interval}`;
270
+ url += `&limit=${limit}`;
271
+ url += `&offset=${offset}`;
272
+
273
+ // Add date filter if specified
274
+ if (request.date) {
275
+ url += `&date_from=${this.formatDate(request.date)}`;
276
+ url += `&date_to=${this.formatDate(request.date)}`;
277
+ }
278
+
279
+ // Add exchange filter if specified
280
+ if (request.exchange) {
281
+ url += `&exchange=${request.exchange}`;
282
+ }
283
+
284
+ const response = await plugins.smartrequest.SmartRequest.create()
285
+ .url(url)
286
+ .timeout(this.config?.timeout || 15000)
287
+ .get();
288
+
289
+ const responseData = await response.json() as any;
290
+
291
+ // Check for API errors
292
+ if (responseData.error) {
293
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
294
+ }
295
+
296
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
297
+ throw new Error('Invalid response format from Marketstack API');
298
+ }
299
+
300
+ // Map data to stock prices
301
+ for (const data of responseData.data) {
302
+ try {
303
+ allPrices.push(this.mapToStockPrice(data, 'intraday'));
304
+ } catch (error) {
305
+ this.logger.warn(`Failed to parse intraday data for ${data.symbol}:`, error);
306
+ }
307
+ }
308
+
309
+ // Check if we have more pages
310
+ const pagination = responseData.pagination;
311
+ const hasMore = pagination && offset + limit < pagination.total;
312
+
313
+ // Honor limit from request if specified, or safety limit
314
+ if (!hasMore || (request.limit && allPrices.length >= request.limit) || allPrices.length >= maxRecords) {
315
+ break;
316
+ }
317
+
318
+ offset += limit;
319
+ }
320
+
321
+ // Apply limit if specified
322
+ if (request.limit && allPrices.length > request.limit) {
323
+ return allPrices.slice(0, request.limit);
324
+ }
325
+
326
+ return allPrices;
327
+ } catch (error) {
328
+ this.logger.error(`Failed to fetch intraday prices for ${request.ticker}:`, error);
329
+ throw new Error(`Marketstack: Failed to fetch intraday prices for ${request.ticker}: ${error.message}`);
330
+ }
186
331
  }
187
332
 
188
333
  /**
189
- * Fetch current prices for multiple tickers (new API)
334
+ * Fetch current prices for multiple tickers with intelligent endpoint selection
335
+ * Uses intraday during market hours (PRE, REGULAR, POST) for fresh data
336
+ * Uses EOD after market close (CLOSED) to save API credits
190
337
  */
191
338
  private async fetchBatchCurrentPrices(request: IStockBatchCurrentRequest): Promise<IStockPrice[]> {
192
339
  try {
193
- const symbols = request.tickers.join(',');
194
- let url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
195
-
196
- if (request.exchange) {
197
- url += `&exchange=${request.exchange}`;
340
+ // Determine current market state
341
+ const marketState = this.getUsMarketState();
342
+ const useIntraday = this.shouldUseIntradayEndpoint(marketState);
343
+
344
+ if (useIntraday) {
345
+ return await this.fetchBatchCurrentPricesIntraday(request, marketState);
346
+ } else {
347
+ return await this.fetchBatchCurrentPricesEod(request);
198
348
  }
349
+ } catch (error) {
350
+ // Fallback to EOD if intraday fails
351
+ if (error.message?.includes('intraday') || error.message?.includes('Marketstack API error')) {
352
+ this.logger.warn(`Intraday batch endpoint failed, falling back to EOD:`, error.message);
353
+ try {
354
+ return await this.fetchBatchCurrentPricesEod(request);
355
+ } catch (eodError) {
356
+ // Both failed, throw original error
357
+ throw error;
358
+ }
359
+ }
360
+ throw error;
361
+ }
362
+ }
199
363
 
200
- const response = await plugins.smartrequest.SmartRequest.create()
201
- .url(url)
202
- .timeout(this.config?.timeout || 15000)
203
- .get();
364
+ /**
365
+ * Fetch batch current prices using intraday endpoint
366
+ */
367
+ private async fetchBatchCurrentPricesIntraday(
368
+ request: IStockBatchCurrentRequest,
369
+ marketState: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED'
370
+ ): Promise<IStockPrice[]> {
371
+ // Format symbols for intraday (replace . with -)
372
+ const formattedSymbols = request.tickers.map(t => this.formatSymbolForIntraday(t)).join(',');
373
+
374
+ let url = `${this.baseUrl}/intraday/latest?access_key=${this.apiKey}`;
375
+ url += `&symbols=${formattedSymbols}`;
376
+ url += `&interval=1min`; // Use 1min for most recent data
377
+
378
+ if (request.exchange) {
379
+ url += `&exchange=${request.exchange}`;
380
+ }
204
381
 
205
- const responseData = await response.json() as any;
382
+ const response = await plugins.smartrequest.SmartRequest.create()
383
+ .url(url)
384
+ .timeout(this.config?.timeout || 15000)
385
+ .get();
206
386
 
207
- // Check for API errors
208
- if (responseData.error) {
209
- throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
210
- }
387
+ const responseData = await response.json() as any;
211
388
 
212
- if (!responseData?.data || !Array.isArray(responseData.data)) {
213
- throw new Error('Invalid response format from Marketstack API');
214
- }
389
+ if (responseData.error) {
390
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
391
+ }
215
392
 
216
- const prices: IStockPrice[] = [];
393
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
394
+ throw new Error('Invalid response format from Marketstack API');
395
+ }
217
396
 
218
- for (const data of responseData.data) {
219
- try {
220
- prices.push(this.mapToStockPrice(data, 'eod'));
221
- } catch (error) {
222
- this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
223
- // Continue processing other tickers
224
- }
397
+ const prices: IStockPrice[] = [];
398
+
399
+ for (const data of responseData.data) {
400
+ try {
401
+ prices.push(this.mapToStockPrice(data, 'intraday', marketState));
402
+ } catch (error) {
403
+ this.logger.warn(`Failed to parse intraday data for ${data.symbol}:`, error);
225
404
  }
405
+ }
406
+
407
+ if (prices.length === 0) {
408
+ throw new Error('No valid price data received from batch intraday request');
409
+ }
226
410
 
227
- if (prices.length === 0) {
228
- throw new Error('No valid price data received from batch request');
411
+ return prices;
412
+ }
413
+
414
+ /**
415
+ * Fetch batch current prices using EOD endpoint
416
+ */
417
+ private async fetchBatchCurrentPricesEod(request: IStockBatchCurrentRequest): Promise<IStockPrice[]> {
418
+ const symbols = request.tickers.join(',');
419
+ let url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
420
+
421
+ if (request.exchange) {
422
+ url += `&exchange=${request.exchange}`;
423
+ }
424
+
425
+ const response = await plugins.smartrequest.SmartRequest.create()
426
+ .url(url)
427
+ .timeout(this.config?.timeout || 15000)
428
+ .get();
429
+
430
+ const responseData = await response.json() as any;
431
+
432
+ // Check for API errors
433
+ if (responseData.error) {
434
+ throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
435
+ }
436
+
437
+ if (!responseData?.data || !Array.isArray(responseData.data)) {
438
+ throw new Error('Invalid response format from Marketstack API');
439
+ }
440
+
441
+ const prices: IStockPrice[] = [];
442
+
443
+ for (const data of responseData.data) {
444
+ try {
445
+ prices.push(this.mapToStockPrice(data, 'eod'));
446
+ } catch (error) {
447
+ this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
448
+ // Continue processing other tickers
229
449
  }
450
+ }
230
451
 
231
- return prices;
232
- } catch (error) {
233
- this.logger.error(`Failed to fetch batch current prices:`, error);
234
- throw new Error(`Marketstack: Failed to fetch batch current prices: ${error.message}`);
452
+ if (prices.length === 0) {
453
+ throw new Error('No valid price data received from batch request');
235
454
  }
455
+
456
+ return prices;
236
457
  }
237
458
 
238
459
  /**
@@ -283,8 +504,15 @@ export class MarketstackProvider implements IStockProvider {
283
504
 
284
505
  /**
285
506
  * Map Marketstack API response to IStockPrice interface
507
+ * @param data - API response data
508
+ * @param dataType - Type of data (eod, intraday, live)
509
+ * @param explicitMarketState - Override market state (used for intraday data fetched during known market hours)
286
510
  */
287
- private mapToStockPrice(data: any, dataType: 'eod' | 'intraday' | 'live' = 'eod'): IStockPrice {
511
+ private mapToStockPrice(
512
+ data: any,
513
+ dataType: 'eod' | 'intraday' | 'live' = 'eod',
514
+ explicitMarketState?: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED'
515
+ ): IStockPrice {
288
516
  if (!data.close) {
289
517
  throw new Error('Missing required price data');
290
518
  }
@@ -301,6 +529,23 @@ export class MarketstackProvider implements IStockProvider {
301
529
  const timestamp = data.date ? new Date(data.date) : new Date();
302
530
  const fetchedAt = new Date();
303
531
 
532
+ // Determine market state intelligently
533
+ let marketState: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED';
534
+
535
+ if (explicitMarketState) {
536
+ // Use provided market state (for intraday data fetched during known market hours)
537
+ marketState = explicitMarketState;
538
+ } else if (dataType === 'eod') {
539
+ // EOD data is always for closed markets
540
+ marketState = 'CLOSED';
541
+ } else if (dataType === 'intraday') {
542
+ // For intraday data without explicit state, determine from timestamp
543
+ marketState = this.getUsMarketState(timestamp.getTime());
544
+ } else {
545
+ // Default fallback
546
+ marketState = 'CLOSED';
547
+ }
548
+
304
549
  const stockPrice: IStockPrice = {
305
550
  ticker: data.symbol.toUpperCase(),
306
551
  price: currentPrice,
@@ -310,7 +555,7 @@ export class MarketstackProvider implements IStockProvider {
310
555
  previousClose: previousClose,
311
556
  timestamp: timestamp,
312
557
  provider: this.name,
313
- marketState: 'CLOSED', // EOD data is always for closed markets
558
+ marketState: marketState, // Now dynamic based on data type, timestamp, and explicit state
314
559
  exchange: data.exchange,
315
560
  exchangeName: data.exchange_code || data.name,
316
561
 
@@ -373,4 +618,76 @@ export class MarketstackProvider implements IStockProvider {
373
618
  const day = String(date.getDate()).padStart(2, '0');
374
619
  return `${year}-${month}-${day}`;
375
620
  }
621
+
622
+ /**
623
+ * Get US market state based on Eastern Time
624
+ * Regular hours: 9:30 AM - 4:00 PM ET
625
+ * Pre-market: 4:00 AM - 9:30 AM ET
626
+ * After-hours: 4:00 PM - 8:00 PM ET
627
+ *
628
+ * @param timestampMs - Optional timestamp in milliseconds (defaults to current time)
629
+ * @returns Market state: PRE, REGULAR, POST, or CLOSED
630
+ */
631
+ private getUsMarketState(timestampMs?: number): 'PRE' | 'REGULAR' | 'POST' | 'CLOSED' {
632
+ const now = timestampMs ? new Date(timestampMs) : new Date();
633
+
634
+ // Convert to ET (UTC-5 or UTC-4 depending on DST)
635
+ // For simplicity, we'll use a rough approximation
636
+ // TODO: Add proper timezone library for production use
637
+ const etOffset = -5; // Standard time, adjust for DST if needed
638
+ const etTime = new Date(now.getTime() + (etOffset * 60 * 60 * 1000));
639
+
640
+ // Get day of week (0 = Sunday, 6 = Saturday)
641
+ const dayOfWeek = etTime.getUTCDay();
642
+
643
+ // Check if weekend
644
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
645
+ return 'CLOSED';
646
+ }
647
+
648
+ // Get hour and minute in ET
649
+ const hours = etTime.getUTCHours();
650
+ const minutes = etTime.getUTCMinutes();
651
+ const timeInMinutes = hours * 60 + minutes;
652
+
653
+ // Define market hours in minutes
654
+ const preMarketStart = 4 * 60; // 4:00 AM
655
+ const regularMarketStart = 9 * 60 + 30; // 9:30 AM
656
+ const regularMarketEnd = 16 * 60; // 4:00 PM
657
+ const afterHoursEnd = 20 * 60; // 8:00 PM
658
+
659
+ if (timeInMinutes >= preMarketStart && timeInMinutes < regularMarketStart) {
660
+ return 'PRE';
661
+ } else if (timeInMinutes >= regularMarketStart && timeInMinutes < regularMarketEnd) {
662
+ return 'REGULAR';
663
+ } else if (timeInMinutes >= regularMarketEnd && timeInMinutes < afterHoursEnd) {
664
+ return 'POST';
665
+ } else {
666
+ return 'CLOSED';
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Determine if intraday endpoint should be used based on market state
672
+ * Uses intraday for PRE, REGULAR, and POST market states
673
+ * Uses EOD for CLOSED state to save API credits
674
+ *
675
+ * @param marketState - Current market state
676
+ * @returns true if intraday endpoint should be used
677
+ */
678
+ private shouldUseIntradayEndpoint(marketState: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED'): boolean {
679
+ return marketState !== 'CLOSED';
680
+ }
681
+
682
+ /**
683
+ * Format ticker symbol for intraday endpoints
684
+ * Marketstack intraday API requires periods to be replaced with hyphens
685
+ * Example: BRK.B → BRK-B
686
+ *
687
+ * @param symbol - Original ticker symbol
688
+ * @returns Formatted symbol for intraday endpoints
689
+ */
690
+ private formatSymbolForIntraday(symbol: string): string {
691
+ return symbol.replace(/\./g, '-');
692
+ }
376
693
  }