@fin.cx/opendata 2.0.0 → 3.0.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 +40 -4
- package/dist_ts/stocks/classes.stockservice.js +126 -69
- package/dist_ts/stocks/interfaces/provider.d.ts +2 -3
- package/dist_ts/stocks/interfaces/stockprice.d.ts +47 -4
- package/dist_ts/stocks/interfaces/stockprice.js +2 -2
- package/dist_ts/stocks/providers/provider.marketstack.d.ts +40 -11
- package/dist_ts/stocks/providers/provider.marketstack.js +167 -22
- package/dist_ts/stocks/providers/provider.yahoo.d.ts +13 -3
- package/dist_ts/stocks/providers/provider.yahoo.js +33 -6
- package/package.json +1 -1
- package/readme.md +249 -22
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/stocks/classes.stockservice.ts +162 -98
- package/ts/stocks/interfaces/provider.ts +5 -6
- package/ts/stocks/interfaces/stockprice.ts +68 -10
- package/ts/stocks/providers/provider.marketstack.ts +198 -22
- package/ts/stocks/providers/provider.yahoo.ts +39 -6
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import type { IStockPrice,
|
|
1
|
+
import type { IStockPrice, IStockDataRequest } from './stockprice.js';
|
|
2
2
|
|
|
3
3
|
export interface IStockProvider {
|
|
4
4
|
name: string;
|
|
5
5
|
priority: number;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]>;
|
|
6
|
+
|
|
7
|
+
fetchData(request: IStockDataRequest): Promise<IStockPrice | IStockPrice[]>;
|
|
9
8
|
isAvailable(): Promise<boolean>;
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
supportsMarket?(market: string): boolean;
|
|
12
11
|
supportsTicker?(ticker: string): boolean;
|
|
13
|
-
|
|
12
|
+
|
|
14
13
|
readonly requiresAuth: boolean;
|
|
15
14
|
readonly rateLimit?: {
|
|
16
15
|
requestsPerMinute: number;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Enhanced stock price interface with additional OHLCV data
|
|
3
2
|
export interface IStockPrice {
|
|
4
3
|
ticker: string;
|
|
5
4
|
price: number;
|
|
@@ -12,11 +11,20 @@ export interface IStockPrice {
|
|
|
12
11
|
marketState: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED';
|
|
13
12
|
exchange?: string;
|
|
14
13
|
exchangeName?: string;
|
|
14
|
+
|
|
15
|
+
// Phase 1 enhancements
|
|
16
|
+
volume?: number; // Trading volume
|
|
17
|
+
open?: number; // Opening price
|
|
18
|
+
high?: number; // Day high
|
|
19
|
+
low?: number; // Day low
|
|
20
|
+
adjusted?: boolean; // If price is split/dividend adjusted
|
|
21
|
+
dataType: 'eod' | 'intraday' | 'live'; // What kind of data this is
|
|
22
|
+
fetchedAt: Date; // When we fetched (vs data timestamp)
|
|
23
|
+
|
|
24
|
+
// Company identification
|
|
25
|
+
companyName?: string; // Company name (e.g., "Apple Inc.")
|
|
26
|
+
companyFullName?: string; // Full company name with exchange (e.g., "Apple Inc. (NASDAQ:AAPL)")
|
|
15
27
|
}
|
|
16
|
-
type CheckStockPrice = plugins.tsclass.typeFest.IsEqual<
|
|
17
|
-
IStockPrice,
|
|
18
|
-
plugins.tsclass.finance.IStockPrice
|
|
19
|
-
>;
|
|
20
28
|
|
|
21
29
|
export interface IStockPriceError {
|
|
22
30
|
ticker: string;
|
|
@@ -25,12 +33,62 @@ export interface IStockPriceError {
|
|
|
25
33
|
timestamp: Date;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
// Pagination support for large datasets
|
|
37
|
+
export interface IPaginatedResponse<T> {
|
|
38
|
+
data: T[];
|
|
39
|
+
pagination: {
|
|
40
|
+
currentPage: number;
|
|
41
|
+
totalPages: number;
|
|
42
|
+
totalRecords: number;
|
|
43
|
+
hasMore: boolean;
|
|
44
|
+
limit: number;
|
|
45
|
+
offset: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Phase 1: Discriminated union types for different request types
|
|
50
|
+
export type TIntervalType = '1min' | '5min' | '10min' | '15min' | '30min' | '1hour';
|
|
51
|
+
export type TSortOrder = 'ASC' | 'DESC';
|
|
52
|
+
|
|
53
|
+
// Current price request (latest EOD or live)
|
|
54
|
+
export interface IStockCurrentRequest {
|
|
55
|
+
type: 'current';
|
|
56
|
+
ticker: string;
|
|
57
|
+
exchange?: string; // MIC code like 'XNAS', 'XNYS', 'XLON'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Historical price request (date range)
|
|
61
|
+
export interface IStockHistoricalRequest {
|
|
62
|
+
type: 'historical';
|
|
63
|
+
ticker: string;
|
|
64
|
+
from: Date;
|
|
65
|
+
to: Date;
|
|
66
|
+
exchange?: string;
|
|
67
|
+
sort?: TSortOrder;
|
|
68
|
+
limit?: number; // Max results per page (default 1000)
|
|
69
|
+
offset?: number; // For pagination
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Intraday price request (real-time intervals)
|
|
73
|
+
export interface IStockIntradayRequest {
|
|
74
|
+
type: 'intraday';
|
|
29
75
|
ticker: string;
|
|
30
|
-
|
|
76
|
+
interval: TIntervalType;
|
|
77
|
+
exchange?: string;
|
|
78
|
+
limit?: number; // Number of bars to return
|
|
79
|
+
date?: Date; // Specific date for historical intraday
|
|
31
80
|
}
|
|
32
81
|
|
|
33
|
-
|
|
82
|
+
// Batch current prices request
|
|
83
|
+
export interface IStockBatchCurrentRequest {
|
|
84
|
+
type: 'batch';
|
|
34
85
|
tickers: string[];
|
|
35
|
-
|
|
86
|
+
exchange?: string;
|
|
36
87
|
}
|
|
88
|
+
|
|
89
|
+
// Union type for all stock data requests
|
|
90
|
+
export type IStockDataRequest =
|
|
91
|
+
| IStockCurrentRequest
|
|
92
|
+
| IStockHistoricalRequest
|
|
93
|
+
| IStockIntradayRequest
|
|
94
|
+
| IStockBatchCurrentRequest;
|
|
@@ -1,22 +1,37 @@
|
|
|
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
|
+
IStockDataRequest,
|
|
6
|
+
IStockCurrentRequest,
|
|
7
|
+
IStockHistoricalRequest,
|
|
8
|
+
IStockIntradayRequest,
|
|
9
|
+
IStockBatchCurrentRequest
|
|
10
|
+
} from '../interfaces/stockprice.js';
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
|
-
* Marketstack API v2 Provider
|
|
7
|
-
* Documentation: https://
|
|
13
|
+
* Marketstack API v2 Provider - Enhanced
|
|
14
|
+
* Documentation: https://docs.apilayer.com/marketstack/docs/marketstack-api-v2-v-2-0-0
|
|
8
15
|
*
|
|
9
16
|
* Features:
|
|
10
|
-
* - End-of-Day (EOD) stock prices
|
|
11
|
-
* -
|
|
17
|
+
* - End-of-Day (EOD) stock prices with historical data
|
|
18
|
+
* - Intraday pricing with multiple intervals (1min, 5min, 15min, 30min, 1hour)
|
|
19
|
+
* - Exchange filtering via MIC codes (XNAS, XNYS, XLON, etc.)
|
|
20
|
+
* - Supports 500,000+ tickers across 72+ exchanges worldwide
|
|
21
|
+
* - OHLCV data (Open, High, Low, Close, Volume)
|
|
22
|
+
* - Pagination for large datasets
|
|
12
23
|
* - Requires API key authentication
|
|
13
24
|
*
|
|
14
25
|
* Rate Limits:
|
|
15
26
|
* - Free Plan: 100 requests/month (EOD only)
|
|
16
27
|
* - Basic Plan: 10,000 requests/month
|
|
17
|
-
* - Professional Plan: 100,000 requests/month
|
|
28
|
+
* - Professional Plan: 100,000 requests/month (intraday access)
|
|
18
29
|
*
|
|
19
|
-
*
|
|
30
|
+
* Phase 1 Enhancements:
|
|
31
|
+
* - Historical data retrieval with date ranges
|
|
32
|
+
* - Exchange filtering
|
|
33
|
+
* - OHLCV data support
|
|
34
|
+
* - Pagination handling
|
|
20
35
|
*/
|
|
21
36
|
export class MarketstackProvider implements IStockProvider {
|
|
22
37
|
public name = 'Marketstack';
|
|
@@ -40,11 +55,34 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
/**
|
|
43
|
-
*
|
|
58
|
+
* Unified data fetching method supporting all request types
|
|
44
59
|
*/
|
|
45
|
-
public async
|
|
60
|
+
public async fetchData(request: IStockDataRequest): Promise<IStockPrice[] | IStockPrice> {
|
|
61
|
+
switch (request.type) {
|
|
62
|
+
case 'current':
|
|
63
|
+
return this.fetchCurrentPrice(request);
|
|
64
|
+
case 'historical':
|
|
65
|
+
return this.fetchHistoricalPrices(request);
|
|
66
|
+
case 'intraday':
|
|
67
|
+
return this.fetchIntradayPrices(request);
|
|
68
|
+
case 'batch':
|
|
69
|
+
return this.fetchBatchCurrentPrices(request);
|
|
70
|
+
default:
|
|
71
|
+
throw new Error(`Unsupported request type: ${(request as any).type}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch current/latest EOD price for a single ticker (new API)
|
|
77
|
+
*/
|
|
78
|
+
private async fetchCurrentPrice(request: IStockCurrentRequest): Promise<IStockPrice> {
|
|
46
79
|
try {
|
|
47
|
-
|
|
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}`;
|
|
85
|
+
}
|
|
48
86
|
|
|
49
87
|
const response = await plugins.smartrequest.SmartRequest.create()
|
|
50
88
|
.url(url)
|
|
@@ -63,20 +101,101 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
63
101
|
throw new Error(`No data found for ticker ${request.ticker}`);
|
|
64
102
|
}
|
|
65
103
|
|
|
66
|
-
return this.mapToStockPrice(responseData);
|
|
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}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Fetch historical EOD prices for a ticker with date range
|
|
113
|
+
*/
|
|
114
|
+
private async fetchHistoricalPrices(request: IStockHistoricalRequest): Promise<IStockPrice[]> {
|
|
115
|
+
try {
|
|
116
|
+
const allPrices: IStockPrice[] = [];
|
|
117
|
+
let offset = request.offset || 0;
|
|
118
|
+
const limit = request.limit || 1000; // Max per page
|
|
119
|
+
const maxRecords = 10000; // Safety limit
|
|
120
|
+
|
|
121
|
+
while (true) {
|
|
122
|
+
let url = `${this.baseUrl}/eod?access_key=${this.apiKey}`;
|
|
123
|
+
url += `&symbols=${request.ticker}`;
|
|
124
|
+
url += `&date_from=${this.formatDate(request.from)}`;
|
|
125
|
+
url += `&date_to=${this.formatDate(request.to)}`;
|
|
126
|
+
url += `&limit=${limit}`;
|
|
127
|
+
url += `&offset=${offset}`;
|
|
128
|
+
|
|
129
|
+
if (request.exchange) {
|
|
130
|
+
url += `&exchange=${request.exchange}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (request.sort) {
|
|
134
|
+
url += `&sort=${request.sort}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const response = await plugins.smartrequest.SmartRequest.create()
|
|
138
|
+
.url(url)
|
|
139
|
+
.timeout(this.config?.timeout || 15000)
|
|
140
|
+
.get();
|
|
141
|
+
|
|
142
|
+
const responseData = await response.json() as any;
|
|
143
|
+
|
|
144
|
+
// Check for API errors
|
|
145
|
+
if (responseData.error) {
|
|
146
|
+
throw new Error(`Marketstack API error: ${responseData.error.message || JSON.stringify(responseData.error)}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!responseData?.data || !Array.isArray(responseData.data)) {
|
|
150
|
+
throw new Error('Invalid response format from Marketstack API');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Map data to stock prices
|
|
154
|
+
for (const data of responseData.data) {
|
|
155
|
+
try {
|
|
156
|
+
allPrices.push(this.mapToStockPrice(data, 'eod'));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.logger.warn(`Failed to parse historical data for ${data.symbol}:`, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check if we have more pages
|
|
163
|
+
const pagination = responseData.pagination;
|
|
164
|
+
const hasMore = pagination && offset + limit < pagination.total;
|
|
165
|
+
|
|
166
|
+
// Safety check: don't fetch more than maxRecords
|
|
167
|
+
if (!hasMore || allPrices.length >= maxRecords) {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
offset += limit;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return allPrices;
|
|
67
175
|
} catch (error) {
|
|
68
|
-
this.logger.error(`Failed to fetch
|
|
69
|
-
throw new Error(`Marketstack: Failed to fetch
|
|
176
|
+
this.logger.error(`Failed to fetch historical prices for ${request.ticker}:`, error);
|
|
177
|
+
throw new Error(`Marketstack: Failed to fetch historical prices for ${request.ticker}: ${error.message}`);
|
|
70
178
|
}
|
|
71
179
|
}
|
|
72
180
|
|
|
73
181
|
/**
|
|
74
|
-
* Fetch
|
|
182
|
+
* Fetch intraday prices with specified interval (Phase 2 placeholder)
|
|
183
|
+
*/
|
|
184
|
+
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"');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Fetch current prices for multiple tickers (new API)
|
|
75
190
|
*/
|
|
76
|
-
|
|
191
|
+
private async fetchBatchCurrentPrices(request: IStockBatchCurrentRequest): Promise<IStockPrice[]> {
|
|
77
192
|
try {
|
|
78
193
|
const symbols = request.tickers.join(',');
|
|
79
|
-
|
|
194
|
+
let url = `${this.baseUrl}/eod/latest?access_key=${this.apiKey}&symbols=${symbols}`;
|
|
195
|
+
|
|
196
|
+
if (request.exchange) {
|
|
197
|
+
url += `&exchange=${request.exchange}`;
|
|
198
|
+
}
|
|
80
199
|
|
|
81
200
|
const response = await plugins.smartrequest.SmartRequest.create()
|
|
82
201
|
.url(url)
|
|
@@ -98,7 +217,7 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
98
217
|
|
|
99
218
|
for (const data of responseData.data) {
|
|
100
219
|
try {
|
|
101
|
-
prices.push(this.mapToStockPrice(data));
|
|
220
|
+
prices.push(this.mapToStockPrice(data, 'eod'));
|
|
102
221
|
} catch (error) {
|
|
103
222
|
this.logger.warn(`Failed to parse data for ${data.symbol}:`, error);
|
|
104
223
|
// Continue processing other tickers
|
|
@@ -111,8 +230,8 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
111
230
|
|
|
112
231
|
return prices;
|
|
113
232
|
} catch (error) {
|
|
114
|
-
this.logger.error(`Failed to fetch batch prices:`, error);
|
|
115
|
-
throw new Error(`Marketstack: Failed to fetch batch prices: ${error.message}`);
|
|
233
|
+
this.logger.error(`Failed to fetch batch current prices:`, error);
|
|
234
|
+
throw new Error(`Marketstack: Failed to fetch batch current prices: ${error.message}`);
|
|
116
235
|
}
|
|
117
236
|
}
|
|
118
237
|
|
|
@@ -165,7 +284,7 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
165
284
|
/**
|
|
166
285
|
* Map Marketstack API response to IStockPrice interface
|
|
167
286
|
*/
|
|
168
|
-
private mapToStockPrice(data: any): IStockPrice {
|
|
287
|
+
private mapToStockPrice(data: any, dataType: 'eod' | 'intraday' | 'live' = 'eod'): IStockPrice {
|
|
169
288
|
if (!data.close) {
|
|
170
289
|
throw new Error('Missing required price data');
|
|
171
290
|
}
|
|
@@ -174,12 +293,13 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
174
293
|
// EOD data: previous close is typically open price of the same day
|
|
175
294
|
// For better accuracy, we'd need previous day's close, but that requires another API call
|
|
176
295
|
const currentPrice = data.close;
|
|
177
|
-
const previousClose = data.open;
|
|
296
|
+
const previousClose = data.open || currentPrice;
|
|
178
297
|
const change = currentPrice - previousClose;
|
|
179
298
|
const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
|
|
180
299
|
|
|
181
300
|
// Parse timestamp
|
|
182
301
|
const timestamp = data.date ? new Date(data.date) : new Date();
|
|
302
|
+
const fetchedAt = new Date();
|
|
183
303
|
|
|
184
304
|
const stockPrice: IStockPrice = {
|
|
185
305
|
ticker: data.symbol.toUpperCase(),
|
|
@@ -192,9 +312,65 @@ export class MarketstackProvider implements IStockProvider {
|
|
|
192
312
|
provider: this.name,
|
|
193
313
|
marketState: 'CLOSED', // EOD data is always for closed markets
|
|
194
314
|
exchange: data.exchange,
|
|
195
|
-
exchangeName: data.exchange_code || data.name
|
|
315
|
+
exchangeName: data.exchange_code || data.name,
|
|
316
|
+
|
|
317
|
+
// Phase 1 enhancements: OHLCV data
|
|
318
|
+
volume: data.volume,
|
|
319
|
+
open: data.open,
|
|
320
|
+
high: data.high,
|
|
321
|
+
low: data.low,
|
|
322
|
+
adjusted: data.adj_close !== undefined, // If adj_close exists, price is adjusted
|
|
323
|
+
dataType: dataType,
|
|
324
|
+
fetchedAt: fetchedAt,
|
|
325
|
+
|
|
326
|
+
// Company identification
|
|
327
|
+
companyName: data.company_name || data.name || undefined,
|
|
328
|
+
companyFullName: this.buildCompanyFullName(data)
|
|
196
329
|
};
|
|
197
330
|
|
|
198
331
|
return stockPrice;
|
|
199
332
|
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Build full company name with exchange and ticker information
|
|
336
|
+
* Example: "Apple Inc (NASDAQ:AAPL)"
|
|
337
|
+
*/
|
|
338
|
+
private buildCompanyFullName(data: any): string | undefined {
|
|
339
|
+
// Check if API already provides full name
|
|
340
|
+
if (data.full_name || data.long_name) {
|
|
341
|
+
return data.full_name || data.long_name;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Build from available data
|
|
345
|
+
const companyName = data.company_name || data.name;
|
|
346
|
+
const exchangeCode = data.exchange_code; // e.g., "NASDAQ"
|
|
347
|
+
const symbol = data.symbol; // e.g., "AAPL"
|
|
348
|
+
|
|
349
|
+
if (!companyName) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// If we have exchange and symbol, build full name: "Apple Inc (NASDAQ:AAPL)"
|
|
354
|
+
if (exchangeCode && symbol) {
|
|
355
|
+
return `${companyName} (${exchangeCode}:${symbol})`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// If we only have symbol: "Apple Inc (AAPL)"
|
|
359
|
+
if (symbol) {
|
|
360
|
+
return `${companyName} (${symbol})`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Otherwise just return company name
|
|
364
|
+
return companyName;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Format date to YYYY-MM-DD for API requests
|
|
369
|
+
*/
|
|
370
|
+
private formatDate(date: Date): string {
|
|
371
|
+
const year = date.getFullYear();
|
|
372
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
373
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
374
|
+
return `${year}-${month}-${day}`;
|
|
375
|
+
}
|
|
200
376
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
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
|
+
IStockDataRequest,
|
|
6
|
+
IStockCurrentRequest,
|
|
7
|
+
IStockBatchCurrentRequest
|
|
8
|
+
} from '../interfaces/stockprice.js';
|
|
4
9
|
|
|
5
10
|
export class YahooFinanceProvider implements IStockProvider {
|
|
6
11
|
public name = 'Yahoo Finance';
|
|
@@ -17,7 +22,28 @@ export class YahooFinanceProvider implements IStockProvider {
|
|
|
17
22
|
|
|
18
23
|
constructor(private config?: IProviderConfig) {}
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Unified data fetching method
|
|
27
|
+
*/
|
|
28
|
+
public async fetchData(request: IStockDataRequest): Promise<IStockPrice | IStockPrice[]> {
|
|
29
|
+
switch (request.type) {
|
|
30
|
+
case 'current':
|
|
31
|
+
return this.fetchCurrentPrice(request);
|
|
32
|
+
case 'batch':
|
|
33
|
+
return this.fetchBatchCurrentPrices(request);
|
|
34
|
+
case 'historical':
|
|
35
|
+
throw new Error('Yahoo Finance provider does not support historical data. Use Marketstack provider instead.');
|
|
36
|
+
case 'intraday':
|
|
37
|
+
throw new Error('Yahoo Finance provider does not support intraday data yet. Use Marketstack provider instead.');
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`Unsupported request type: ${(request as any).type}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fetch current price for a single ticker
|
|
45
|
+
*/
|
|
46
|
+
private async fetchCurrentPrice(request: IStockCurrentRequest): Promise<IStockPrice> {
|
|
21
47
|
try {
|
|
22
48
|
const url = `${this.baseUrl}/v8/finance/chart/${request.ticker}`;
|
|
23
49
|
const response = await plugins.smartrequest.SmartRequest.create()
|
|
@@ -52,7 +78,9 @@ export class YahooFinanceProvider implements IStockProvider {
|
|
|
52
78
|
provider: this.name,
|
|
53
79
|
marketState: this.determineMarketState(meta),
|
|
54
80
|
exchange: meta.exchange,
|
|
55
|
-
exchangeName: meta.exchangeName
|
|
81
|
+
exchangeName: meta.exchangeName,
|
|
82
|
+
dataType: 'live', // Yahoo provides real-time/near real-time data
|
|
83
|
+
fetchedAt: new Date()
|
|
56
84
|
};
|
|
57
85
|
|
|
58
86
|
return stockPrice;
|
|
@@ -62,7 +90,10 @@ export class YahooFinanceProvider implements IStockProvider {
|
|
|
62
90
|
}
|
|
63
91
|
}
|
|
64
92
|
|
|
65
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Fetch batch current prices
|
|
95
|
+
*/
|
|
96
|
+
private async fetchBatchCurrentPrices(request: IStockBatchCurrentRequest): Promise<IStockPrice[]> {
|
|
66
97
|
try {
|
|
67
98
|
const symbols = request.tickers.join(',');
|
|
68
99
|
const url = `${this.baseUrl}/v8/finance/spark?symbols=${symbols}&range=1d&interval=5m`;
|
|
@@ -101,7 +132,9 @@ export class YahooFinanceProvider implements IStockProvider {
|
|
|
101
132
|
provider: this.name,
|
|
102
133
|
marketState: sparkData.marketState || 'REGULAR',
|
|
103
134
|
exchange: sparkData.exchange,
|
|
104
|
-
exchangeName: sparkData.exchangeName
|
|
135
|
+
exchangeName: sparkData.exchangeName,
|
|
136
|
+
dataType: 'live', // Yahoo provides real-time/near real-time data
|
|
137
|
+
fetchedAt: new Date()
|
|
105
138
|
});
|
|
106
139
|
}
|
|
107
140
|
|
|
@@ -119,7 +152,7 @@ export class YahooFinanceProvider implements IStockProvider {
|
|
|
119
152
|
public async isAvailable(): Promise<boolean> {
|
|
120
153
|
try {
|
|
121
154
|
// Test with a well-known ticker
|
|
122
|
-
await this.
|
|
155
|
+
await this.fetchData({ type: 'current', ticker: 'AAPL' });
|
|
123
156
|
return true;
|
|
124
157
|
} catch (error) {
|
|
125
158
|
console.warn('Yahoo Finance provider is not available:', error);
|