@fin.cx/opendata 1.5.3 → 1.5.4
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/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/stocks/classes.stockservice.d.ts +32 -0
- package/dist_ts/stocks/classes.stockservice.js +221 -0
- package/dist_ts/stocks/index.d.ts +4 -0
- package/dist_ts/stocks/index.js +8 -0
- package/dist_ts/stocks/interfaces/provider.d.ts +30 -0
- package/dist_ts/stocks/interfaces/provider.js +2 -0
- package/dist_ts/stocks/interfaces/stockprice.d.ts +27 -0
- package/dist_ts/stocks/interfaces/stockprice.js +2 -0
- package/dist_ts/stocks/providers/provider.yahoo.d.ts +22 -0
- package/dist_ts/stocks/providers/provider.yahoo.js +139 -0
- package/package.json +10 -10
- package/readme.hints.md +45 -0
- package/readme.plan.md +193 -0
- package/ts/index.ts +1 -0
- package/ts/plugins.ts +2 -0
- package/ts/stocks/classes.stockservice.ts +309 -0
- package/ts/stocks/index.ts +9 -0
- package/ts/stocks/interfaces/provider.ts +36 -0
- package/ts/stocks/interfaces/stockprice.ts +30 -0
- package/ts/stocks/providers/provider.yahoo.ts +159 -0
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
export class YahooFinanceProvider implements IStockProvider {
|
|
6
|
+
public name = 'Yahoo Finance';
|
|
7
|
+
public priority = 100;
|
|
8
|
+
public readonly requiresAuth = false;
|
|
9
|
+
public readonly rateLimit = {
|
|
10
|
+
requestsPerMinute: 100, // Conservative estimate
|
|
11
|
+
requestsPerDay: undefined
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
private logger = console;
|
|
15
|
+
private baseUrl = 'https://query1.finance.yahoo.com';
|
|
16
|
+
private userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
17
|
+
|
|
18
|
+
constructor(private config?: IProviderConfig) {}
|
|
19
|
+
|
|
20
|
+
public async fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice> {
|
|
21
|
+
try {
|
|
22
|
+
const url = `${this.baseUrl}/v8/finance/chart/${request.ticker}`;
|
|
23
|
+
const response = await plugins.smartrequest.getJson(url, {
|
|
24
|
+
headers: {
|
|
25
|
+
'User-Agent': this.userAgent
|
|
26
|
+
},
|
|
27
|
+
timeout: this.config?.timeout || 10000
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const responseData = response.body as any;
|
|
31
|
+
|
|
32
|
+
if (!responseData?.chart?.result?.[0]) {
|
|
33
|
+
throw new Error(`No data found for ticker ${request.ticker}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const data = responseData.chart.result[0];
|
|
37
|
+
const meta = data.meta;
|
|
38
|
+
|
|
39
|
+
if (!meta.regularMarketPrice) {
|
|
40
|
+
throw new Error(`No price data available for ${request.ticker}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const stockPrice: IStockPrice = {
|
|
44
|
+
ticker: request.ticker.toUpperCase(),
|
|
45
|
+
price: meta.regularMarketPrice,
|
|
46
|
+
currency: meta.currency || 'USD',
|
|
47
|
+
change: meta.regularMarketPrice - meta.previousClose,
|
|
48
|
+
changePercent: ((meta.regularMarketPrice - meta.previousClose) / meta.previousClose) * 100,
|
|
49
|
+
previousClose: meta.previousClose,
|
|
50
|
+
timestamp: new Date(meta.regularMarketTime * 1000),
|
|
51
|
+
provider: this.name,
|
|
52
|
+
marketState: this.determineMarketState(meta),
|
|
53
|
+
exchange: meta.exchange,
|
|
54
|
+
exchangeName: meta.exchangeName
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return stockPrice;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`Failed to fetch price for ${request.ticker}:`, error);
|
|
60
|
+
throw new Error(`Yahoo Finance: Failed to fetch price for ${request.ticker}: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public async fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]> {
|
|
65
|
+
try {
|
|
66
|
+
const symbols = request.tickers.join(',');
|
|
67
|
+
const url = `${this.baseUrl}/v8/finance/spark?symbols=${symbols}&range=1d&interval=5m`;
|
|
68
|
+
|
|
69
|
+
const response = await plugins.smartrequest.getJson(url, {
|
|
70
|
+
headers: {
|
|
71
|
+
'User-Agent': this.userAgent
|
|
72
|
+
},
|
|
73
|
+
timeout: this.config?.timeout || 15000
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const responseData = response.body as any;
|
|
77
|
+
const prices: IStockPrice[] = [];
|
|
78
|
+
|
|
79
|
+
for (const [ticker, data] of Object.entries(responseData)) {
|
|
80
|
+
if (!data || typeof data !== 'object') continue;
|
|
81
|
+
|
|
82
|
+
const sparkData = data as any;
|
|
83
|
+
if (!sparkData.previousClose || !sparkData.close?.length) {
|
|
84
|
+
console.warn(`Incomplete data for ${ticker}, skipping`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const currentPrice = sparkData.close[sparkData.close.length - 1];
|
|
89
|
+
const timestamp = sparkData.timestamp?.[sparkData.timestamp.length - 1];
|
|
90
|
+
|
|
91
|
+
prices.push({
|
|
92
|
+
ticker: ticker.toUpperCase(),
|
|
93
|
+
price: currentPrice,
|
|
94
|
+
currency: sparkData.currency || 'USD',
|
|
95
|
+
change: currentPrice - sparkData.previousClose,
|
|
96
|
+
changePercent: ((currentPrice - sparkData.previousClose) / sparkData.previousClose) * 100,
|
|
97
|
+
previousClose: sparkData.previousClose,
|
|
98
|
+
timestamp: timestamp ? new Date(timestamp * 1000) : new Date(),
|
|
99
|
+
provider: this.name,
|
|
100
|
+
marketState: sparkData.marketState || 'REGULAR',
|
|
101
|
+
exchange: sparkData.exchange,
|
|
102
|
+
exchangeName: sparkData.exchangeName
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (prices.length === 0) {
|
|
107
|
+
throw new Error('No valid price data received from batch request');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return prices;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`Failed to fetch batch prices:`, error);
|
|
113
|
+
throw new Error(`Yahoo Finance: Failed to fetch batch prices: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public async isAvailable(): Promise<boolean> {
|
|
118
|
+
try {
|
|
119
|
+
// Test with a well-known ticker
|
|
120
|
+
await this.fetchPrice({ ticker: 'AAPL' });
|
|
121
|
+
return true;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn('Yahoo Finance provider is not available:', error);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public supportsMarket(market: string): boolean {
|
|
129
|
+
// Yahoo Finance supports most major markets
|
|
130
|
+
const supportedMarkets = ['US', 'UK', 'DE', 'FR', 'JP', 'CN', 'HK', 'AU', 'CA'];
|
|
131
|
+
return supportedMarkets.includes(market.toUpperCase());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public supportsTicker(ticker: string): boolean {
|
|
135
|
+
// Basic validation - Yahoo supports most tickers
|
|
136
|
+
return /^[A-Z0-9\.\-]{1,10}$/.test(ticker.toUpperCase());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private determineMarketState(meta: any): 'PRE' | 'REGULAR' | 'POST' | 'CLOSED' {
|
|
140
|
+
const marketState = meta.marketState?.toUpperCase();
|
|
141
|
+
|
|
142
|
+
switch (marketState) {
|
|
143
|
+
case 'PRE':
|
|
144
|
+
return 'PRE';
|
|
145
|
+
case 'POST':
|
|
146
|
+
return 'POST';
|
|
147
|
+
case 'REGULAR':
|
|
148
|
+
return 'REGULAR';
|
|
149
|
+
default:
|
|
150
|
+
// Check if market is currently open based on timestamps
|
|
151
|
+
const now = Date.now() / 1000;
|
|
152
|
+
const regularMarketTime = meta.regularMarketTime;
|
|
153
|
+
const timeDiff = now - regularMarketTime;
|
|
154
|
+
|
|
155
|
+
// If last update was more than 1 hour ago, market is likely closed
|
|
156
|
+
return timeDiff > 3600 ? 'CLOSED' : 'REGULAR';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|