@fin.cx/opendata 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/stocks/classes.stockservice.d.ts +28 -1
- package/dist_ts/stocks/classes.stockservice.js +147 -9
- package/dist_ts/stocks/interfaces/stockprice.d.ts +51 -0
- package/dist_ts/stocks/providers/provider.marketstack.d.ts +43 -9
- package/dist_ts/stocks/providers/provider.marketstack.js +159 -22
- package/dist_ts/stocks/providers/provider.yahoo.js +7 -3
- package/package.json +1 -1
- package/readme.md +232 -22
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/stocks/classes.stockservice.ts +204 -28
- package/ts/stocks/interfaces/stockprice.ts +73 -0
- package/ts/stocks/providers/provider.marketstack.ts +189 -22
- package/ts/stocks/providers/provider.yahoo.ts +6 -2
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import type { IStockProvider, IProviderConfig } from '../interfaces/provider.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
IStockPrice,
|
|
5
|
+
IStockQuoteRequest,
|
|
6
|
+
IStockBatchQuoteRequest,
|
|
7
|
+
IStockDataRequest,
|
|
8
|
+
IStockCurrentRequest,
|
|
9
|
+
IStockHistoricalRequest,
|
|
10
|
+
IStockIntradayRequest,
|
|
11
|
+
IStockBatchCurrentRequest,
|
|
12
|
+
IPaginatedResponse,
|
|
13
|
+
TSortOrder
|
|
14
|
+
} from '../interfaces/stockprice.js';
|
|
4
15
|
|
|
5
16
|
/**
|
|
6
|
-
* Marketstack API v2 Provider
|
|
7
|
-
* Documentation: https://
|
|
17
|
+
* Marketstack API v2 Provider - Enhanced
|
|
18
|
+
* Documentation: https://docs.apilayer.com/marketstack/docs/marketstack-api-v2-v-2-0-0
|
|
8
19
|
*
|
|
9
20
|
* Features:
|
|
10
|
-
* - End-of-Day (EOD) stock prices
|
|
11
|
-
* -
|
|
21
|
+
* - End-of-Day (EOD) stock prices with historical data
|
|
22
|
+
* - Intraday pricing with multiple intervals (1min, 5min, 15min, 30min, 1hour)
|
|
23
|
+
* - Exchange filtering via MIC codes (XNAS, XNYS, XLON, etc.)
|
|
24
|
+
* - Supports 500,000+ tickers across 72+ exchanges worldwide
|
|
25
|
+
* - OHLCV data (Open, High, Low, Close, Volume)
|
|
26
|
+
* - Pagination for large datasets
|
|
12
27
|
* - Requires API key authentication
|
|
13
28
|
*
|
|
14
29
|
* Rate Limits:
|
|
15
30
|
* - Free Plan: 100 requests/month (EOD only)
|
|
16
31
|
* - Basic Plan: 10,000 requests/month
|
|
17
|
-
* - Professional Plan: 100,000 requests/month
|
|
32
|
+
* - Professional Plan: 100,000 requests/month (intraday access)
|
|
18
33
|
*
|
|
19
|
-
*
|
|
34
|
+
* Phase 1 Enhancements:
|
|
35
|
+
* - Historical data retrieval with date ranges
|
|
36
|
+
* - Exchange filtering
|
|
37
|
+
* - OHLCV data support
|
|
38
|
+
* - Pagination handling
|
|
20
39
|
*/
|
|
21
40
|
export class MarketstackProvider implements IStockProvider {
|
|
22
41
|
public name = 'Marketstack';
|
|
@@ -40,11 +59,34 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
40
59
|
}
|
|
41
60
|
|
|
42
61
|
/**
|
|
43
|
-
*
|
|
62
|
+
* Unified data fetching method supporting all request types
|
|
44
63
|
*/
|
|
45
|
-
public async
|
|
64
|
+
public async fetchData(request: IStockDataRequest): Promise<IStockPrice[] | IStockPrice> {
|
|
65
|
+
switch (request.type) {
|
|
66
|
+
case 'current':
|
|
67
|
+
return this.fetchCurrentPrice(request);
|
|
68
|
+
case 'historical':
|
|
69
|
+
return this.fetchHistoricalPrices(request);
|
|
70
|
+
case 'intraday':
|
|
71
|
+
return this.fetchIntradayPrices(request);
|
|
72
|
+
case 'batch':
|
|
73
|
+
return this.fetchBatchCurrentPrices(request);
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`Unsupported request type: ${(request as any).type}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fetch current/latest EOD price for a single ticker (new API)
|
|
81
|
+
*/
|
|
82
|
+
private async fetchCurrentPrice(request: IStockCurrentRequest): Promise<IStockPrice> {
|
|
46
83
|
try {
|
|
47
|
-
|
|
84
|
+
let url = `${this.baseUrl}/tickers/${request.ticker}/eod/latest?access_key=${this.apiKey}`;
|
|
85
|
+
|
|
86
|
+
// Add exchange filter if specified
|
|
87
|
+
if (request.exchange) {
|
|
88
|
+
url += `&exchange=${request.exchange}`;
|
|
89
|
+
}
|
|
48
90
|
|
|
49
91
|
const response = await plugins.smartrequest.SmartRequest.create()
|
|
50
92
|
.url(url)
|
|
@@ -63,20 +105,101 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
63
105
|
throw new Error(`No data found for ticker ${request.ticker}`);
|
|
64
106
|
}
|
|
65
107
|
|
|
66
|
-
return this.mapToStockPrice(responseData);
|
|
108
|
+
return this.mapToStockPrice(responseData, 'eod');
|
|
67
109
|
} 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}`);
|
|
110
|
+
this.logger.error(`Failed to fetch current price for ${request.ticker}:`, error);
|
|
111
|
+
throw new Error(`Marketstack: Failed to fetch current price for ${request.ticker}: ${error.message}`);
|
|
70
112
|
}
|
|
71
113
|
}
|
|
72
114
|
|
|
73
115
|
/**
|
|
74
|
-
* Fetch
|
|
116
|
+
* Fetch historical EOD prices for a ticker with date range
|
|
75
117
|
*/
|
|
76
|
-
|
|
118
|
+
private async fetchHistoricalPrices(request: IStockHistoricalRequest): Promise<IStockPrice[]> {
|
|
119
|
+
try {
|
|
120
|
+
const allPrices: IStockPrice[] = [];
|
|
121
|
+
let offset = request.offset || 0;
|
|
122
|
+
const limit = request.limit || 1000; // Max per page
|
|
123
|
+
const maxRecords = 10000; // Safety limit
|
|
124
|
+
|
|
125
|
+
while (true) {
|
|
126
|
+
let url = `${this.baseUrl}/eod?access_key=${this.apiKey}`;
|
|
127
|
+
url += `&symbols=${request.ticker}`;
|
|
128
|
+
url += `&date_from=${this.formatDate(request.from)}`;
|
|
129
|
+
url += `&date_to=${this.formatDate(request.to)}`;
|
|
130
|
+
url += `&limit=${limit}`;
|
|
131
|
+
url += `&offset=${offset}`;
|
|
132
|
+
|
|
133
|
+
if (request.exchange) {
|
|
134
|
+
url += `&exchange=${request.exchange}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (request.sort) {
|
|
138
|
+
url += `&sort=${request.sort}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const response = await plugins.smartrequest.SmartRequest.create()
|
|
142
|
+
.url(url)
|
|
143
|
+
.timeout(this.config?.timeout || 15000)
|
|
144
|
+
.get();
|
|
145
|
+
|
|
146
|
+
const responseData = await response.json() as any;
|
|
147
|
+
|
|
148
|
+
// Check for API errors
|
|
149
|
+
if (responseData.error) {
|
|
150
|
+
throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!responseData?.data || !Array.isArray(responseData.data)) {
|
|
154
|
+
throw new Error('Invalid response format from Marketstack API');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Map data to stock prices
|
|
158
|
+
for (const data of responseData.data) {
|
|
159
|
+
try {
|
|
160
|
+
allPrices.push(this.mapToStockPrice(data, 'eod'));
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.logger.warn(`Failed to parse historical data for ${data.symbol}:`, error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check if we have more pages
|
|
167
|
+
const pagination = responseData.pagination;
|
|
168
|
+
const hasMore = pagination && offset + limit < pagination.total;
|
|
169
|
+
|
|
170
|
+
// Safety check: don't fetch more than maxRecords
|
|
171
|
+
if (!hasMore || allPrices.length >= maxRecords) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
offset += limit;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return allPrices;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
this.logger.error(`Failed to fetch historical prices for ${request.ticker}:`, error);
|
|
181
|
+
throw new Error(`Marketstack: Failed to fetch historical prices for ${request.ticker}: ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Fetch intraday prices with specified interval (Phase 2 placeholder)
|
|
187
|
+
*/
|
|
188
|
+
private async fetchIntradayPrices(request: IStockIntradayRequest): Promise<IStockPrice[]> {
|
|
189
|
+
throw new Error('Intraday data support coming in Phase 2. For now, use EOD data with type: "current" or "historical"');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Fetch current prices for multiple tickers (new API)
|
|
194
|
+
*/
|
|
195
|
+
private async fetchBatchCurrentPrices(request: IStockBatchCurrentRequest): Promise<IStockPrice[]> {
|
|
77
196
|
try {
|
|
78
197
|
const symbols = request.tickers.join(',');
|
|
79
|
-
|
|
198
|
+
let url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
|
|
199
|
+
|
|
200
|
+
if (request.exchange) {
|
|
201
|
+
url += `&exchange=${request.exchange}`;
|
|
202
|
+
}
|
|
80
203
|
|
|
81
204
|
const response = await plugins.smartrequest.SmartRequest.create()
|
|
82
205
|
.url(url)
|
|
@@ -98,7 +221,7 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
98
221
|
|
|
99
222
|
for (const data of responseData.data) {
|
|
100
223
|
try {
|
|
101
|
-
prices.push(this.mapToStockPrice(data));
|
|
224
|
+
prices.push(this.mapToStockPrice(data, 'eod'));
|
|
102
225
|
} catch (error) {
|
|
103
226
|
this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
|
|
104
227
|
// Continue processing other tickers
|
|
@@ -111,11 +234,35 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
111
234
|
|
|
112
235
|
return prices;
|
|
113
236
|
} catch (error) {
|
|
114
|
-
this.logger.error(`Failed to fetch batch prices:`, error);
|
|
115
|
-
throw new Error(`Marketstack: Failed to fetch batch prices: ${error.message}`);
|
|
237
|
+
this.logger.error(`Failed to fetch batch current prices:`, error);
|
|
238
|
+
throw new Error(`Marketstack: Failed to fetch batch current prices: ${error.message}`);
|
|
116
239
|
}
|
|
117
240
|
}
|
|
118
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Legacy: Fetch latest EOD price for a single ticker
|
|
244
|
+
* @deprecated Use fetchData with IStockDataRequest instead
|
|
245
|
+
*/
|
|
246
|
+
public async fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice> {
|
|
247
|
+
// Map legacy request to new format
|
|
248
|
+
return this.fetchCurrentPrice({
|
|
249
|
+
type: 'current',
|
|
250
|
+
ticker: request.ticker
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Legacy: Fetch latest EOD prices for multiple tickers
|
|
256
|
+
* @deprecated Use fetchData with IStockDataRequest instead
|
|
257
|
+
*/
|
|
258
|
+
public async fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]> {
|
|
259
|
+
// Map legacy request to new format
|
|
260
|
+
return this.fetchBatchCurrentPrices({
|
|
261
|
+
type: 'batch',
|
|
262
|
+
tickers: request.tickers
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
119
266
|
/**
|
|
120
267
|
* Check if the Marketstack API is available and accessible
|
|
121
268
|
*/
|
|
@@ -165,7 +312,7 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
165
312
|
/**
|
|
166
313
|
* Map Marketstack API response to IStockPrice interface
|
|
167
314
|
*/
|
|
168
|
-
private mapToStockPrice(data: any): IStockPrice {
|
|
315
|
+
private mapToStockPrice(data: any, dataType: 'eod' | 'intraday' | 'live' = 'eod'): IStockPrice {
|
|
169
316
|
if (!data.close) {
|
|
170
317
|
throw new Error('Missing required price data');
|
|
171
318
|
}
|
|
@@ -174,12 +321,13 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
174
321
|
// EOD data: previous close is typically open price of the same day
|
|
175
322
|
// For better accuracy, we'd need previous day's close, but that requires another API call
|
|
176
323
|
const currentPrice = data.close;
|
|
177
|
-
const previousClose = data.open;
|
|
324
|
+
const previousClose = data.open || currentPrice;
|
|
178
325
|
const change = currentPrice - previousClose;
|
|
179
326
|
const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
|
|
180
327
|
|
|
181
328
|
// Parse timestamp
|
|
182
329
|
const timestamp = data.date ? new Date(data.date) : new Date();
|
|
330
|
+
const fetchedAt = new Date();
|
|
183
331
|
|
|
184
332
|
const stockPrice: IStockPrice = {
|
|
185
333
|
ticker: data.symbol.toUpperCase(),
|
|
@@ -192,9 +340,28 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
192
340
|
provider: this.name,
|
|
193
341
|
marketState: 'CLOSED', // EOD data is always for closed markets
|
|
194
342
|
exchange: data.exchange,
|
|
195
|
-
exchangeName: data.exchange_code || data.name
|
|
343
|
+
exchangeName: data.exchange_code || data.name,
|
|
344
|
+
|
|
345
|
+
// Phase 1 enhancements: OHLCV data
|
|
346
|
+
volume: data.volume,
|
|
347
|
+
open: data.open,
|
|
348
|
+
high: data.high,
|
|
349
|
+
low: data.low,
|
|
350
|
+
adjusted: data.adj_close !== undefined, // If adj_close exists, price is adjusted
|
|
351
|
+
dataType: dataType,
|
|
352
|
+
fetchedAt: fetchedAt
|
|
196
353
|
};
|
|
197
354
|
|
|
198
355
|
return stockPrice;
|
|
199
356
|
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Format date to YYYY-MM-DD for API requests
|
|
360
|
+
*/
|
|
361
|
+
private formatDate(date: Date): string {
|
|
362
|
+
const year = date.getFullYear();
|
|
363
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
364
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
365
|
+
return `${year}-${month}-${day}`;
|
|
366
|
+
}
|
|
200
367
|
}
|
|
@@ -52,7 +52,9 @@ export class YahooFinanceProvider implements IStockProvider {
|
|
|
52
52
|
provider: this.name,
|
|
53
53
|
marketState: this.determineMarketState(meta),
|
|
54
54
|
exchange: meta.exchange,
|
|
55
|
-
exchangeName: meta.exchangeName
|
|
55
|
+
exchangeName: meta.exchangeName,
|
|
56
|
+
dataType: 'live', // Yahoo provides real-time/near real-time data
|
|
57
|
+
fetchedAt: new Date()
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
return stockPrice;
|
|
@@ -101,7 +103,9 @@ export class YahooFinanceProvider implements IStockProvider {
|
|
|
101
103
|
provider: this.name,
|
|
102
104
|
marketState: sparkData.marketState || 'REGULAR',
|
|
103
105
|
exchange: sparkData.exchange,
|
|
104
|
-
exchangeName: sparkData.exchangeName
|
|
106
|
+
exchangeName: sparkData.exchangeName,
|
|
107
|
+
dataType: 'live', // Yahoo provides real-time/near real-time data
|
|
108
|
+
fetchedAt: new Date()
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
|