market-data-analyzer 2.1.0 → 2.1.1
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/index.js +0 -1
- package/dist/license.d.ts +6 -13
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +19 -36
- package/dist/tools/analyze_portfolio.js +0 -1
- package/dist/tools/analyze_stock.js +0 -1
- package/dist/tools/compare_assets.js +0 -1
- package/dist/tools/crypto_analysis.js +0 -1
- package/dist/tools/market_overview.js +0 -1
- package/dist/tools/screen_stocks.js +0 -1
- package/dist/types.js +0 -1
- package/dist/utils/api.js +0 -1
- package/dist/utils/cache.js +0 -1
- package/dist/utils/math.js +0 -1
- package/package.json +4 -1
- package/dist/index.js.map +0 -1
- package/dist/license.js.map +0 -1
- package/dist/tools/analyze_portfolio.js.map +0 -1
- package/dist/tools/analyze_stock.js.map +0 -1
- package/dist/tools/compare_assets.js.map +0 -1
- package/dist/tools/crypto_analysis.js.map +0 -1
- package/dist/tools/market_overview.js.map +0 -1
- package/dist/tools/screen_stocks.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/api.js.map +0 -1
- package/dist/utils/cache.js.map +0 -1
- package/dist/utils/math.js.map +0 -1
- package/src/index.ts +0 -393
- package/src/license.ts +0 -143
- package/src/tools/analyze_portfolio.ts +0 -207
- package/src/tools/analyze_stock.ts +0 -204
- package/src/tools/compare_assets.ts +0 -183
- package/src/tools/crypto_analysis.ts +0 -221
- package/src/tools/market_overview.ts +0 -236
- package/src/tools/screen_stocks.ts +0 -156
- package/src/types.ts +0 -175
- package/src/utils/api.ts +0 -396
- package/src/utils/cache.ts +0 -65
- package/src/utils/math.ts +0 -342
- package/tsconfig.json +0 -19
package/src/types.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared types for the Market Data Analyzer MCP server.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Yahoo Finance response shapes (partial -- only what we use)
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
export interface YahooQuote {
|
|
10
|
-
symbol: string;
|
|
11
|
-
shortName?: string;
|
|
12
|
-
longName?: string;
|
|
13
|
-
regularMarketPrice?: number;
|
|
14
|
-
regularMarketChange?: number;
|
|
15
|
-
regularMarketChangePercent?: number;
|
|
16
|
-
regularMarketVolume?: number;
|
|
17
|
-
regularMarketDayHigh?: number;
|
|
18
|
-
regularMarketDayLow?: number;
|
|
19
|
-
regularMarketOpen?: number;
|
|
20
|
-
regularMarketPreviousClose?: number;
|
|
21
|
-
fiftyTwoWeekHigh?: number;
|
|
22
|
-
fiftyTwoWeekLow?: number;
|
|
23
|
-
fiftyDayAverage?: number;
|
|
24
|
-
twoHundredDayAverage?: number;
|
|
25
|
-
marketCap?: number;
|
|
26
|
-
trailingPE?: number;
|
|
27
|
-
forwardPE?: number;
|
|
28
|
-
priceToBook?: number;
|
|
29
|
-
trailingAnnualDividendYield?: number;
|
|
30
|
-
sector?: string;
|
|
31
|
-
industry?: string;
|
|
32
|
-
averageDailyVolume3Month?: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface YahooChartPoint {
|
|
36
|
-
date: number;
|
|
37
|
-
open: number;
|
|
38
|
-
high: number;
|
|
39
|
-
low: number;
|
|
40
|
-
close: number;
|
|
41
|
-
volume: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// CoinGecko response shapes (partial)
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
export interface CoinGeckoMarketData {
|
|
49
|
-
id: string;
|
|
50
|
-
symbol: string;
|
|
51
|
-
name: string;
|
|
52
|
-
current_price: number;
|
|
53
|
-
market_cap: number;
|
|
54
|
-
market_cap_rank: number;
|
|
55
|
-
total_volume: number;
|
|
56
|
-
high_24h: number;
|
|
57
|
-
low_24h: number;
|
|
58
|
-
price_change_24h: number;
|
|
59
|
-
price_change_percentage_24h: number;
|
|
60
|
-
price_change_percentage_7d_in_currency?: number;
|
|
61
|
-
price_change_percentage_30d_in_currency?: number;
|
|
62
|
-
market_cap_change_percentage_24h: number;
|
|
63
|
-
circulating_supply: number;
|
|
64
|
-
total_supply: number | null;
|
|
65
|
-
max_supply: number | null;
|
|
66
|
-
ath: number;
|
|
67
|
-
ath_change_percentage: number;
|
|
68
|
-
ath_date: string;
|
|
69
|
-
atl: number;
|
|
70
|
-
atl_change_percentage: number;
|
|
71
|
-
atl_date: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface CoinGeckoGlobalData {
|
|
75
|
-
total_market_cap: Record<string, number>;
|
|
76
|
-
total_volume: Record<string, number>;
|
|
77
|
-
market_cap_percentage: Record<string, number>;
|
|
78
|
-
market_cap_change_percentage_24h_usd: number;
|
|
79
|
-
active_cryptocurrencies: number;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Tool output shapes
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
export interface StockAnalysis {
|
|
87
|
-
symbol: string;
|
|
88
|
-
name: string;
|
|
89
|
-
price: number;
|
|
90
|
-
change: number;
|
|
91
|
-
changePercent: number;
|
|
92
|
-
volume: number;
|
|
93
|
-
marketCap: number;
|
|
94
|
-
peRatio: number | null;
|
|
95
|
-
high52w: number;
|
|
96
|
-
low52w: number;
|
|
97
|
-
sma20: number | null;
|
|
98
|
-
sma50: number | null;
|
|
99
|
-
sma200: number | null;
|
|
100
|
-
rsi14: number | null;
|
|
101
|
-
macd: { line: number; signal: number; histogram: number } | null;
|
|
102
|
-
supportLevels: number[];
|
|
103
|
-
resistanceLevels: number[];
|
|
104
|
-
priceHistory: YahooChartPoint[];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface PortfolioPosition {
|
|
108
|
-
symbol: string;
|
|
109
|
-
shares: number;
|
|
110
|
-
avgCost: number;
|
|
111
|
-
currentPrice: number;
|
|
112
|
-
marketValue: number;
|
|
113
|
-
costBasis: number;
|
|
114
|
-
pnl: number;
|
|
115
|
-
pnlPercent: number;
|
|
116
|
-
allocationPercent: number;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface PortfolioAnalysis {
|
|
120
|
-
totalValue: number;
|
|
121
|
-
totalCost: number;
|
|
122
|
-
totalPnl: number;
|
|
123
|
-
totalPnlPercent: number;
|
|
124
|
-
positions: PortfolioPosition[];
|
|
125
|
-
diversificationScore: number;
|
|
126
|
-
riskMetrics: {
|
|
127
|
-
concentrationRisk: string;
|
|
128
|
-
largestPosition: string;
|
|
129
|
-
largestPositionPct: number;
|
|
130
|
-
sectorBreakdown: Record<string, number>;
|
|
131
|
-
};
|
|
132
|
-
warnings: string[];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface ScreenerResult {
|
|
136
|
-
symbol: string;
|
|
137
|
-
name: string;
|
|
138
|
-
price: number;
|
|
139
|
-
changePercent: number;
|
|
140
|
-
marketCap: number;
|
|
141
|
-
peRatio: number | null;
|
|
142
|
-
volume: number;
|
|
143
|
-
sector?: string;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface AssetComparison {
|
|
147
|
-
symbol: string;
|
|
148
|
-
name: string;
|
|
149
|
-
totalReturn: number;
|
|
150
|
-
annualizedReturn: number;
|
|
151
|
-
volatility: number;
|
|
152
|
-
sharpeRatio: number;
|
|
153
|
-
maxDrawdown: number;
|
|
154
|
-
currentPrice: number;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export interface CryptoAnalysisResult {
|
|
158
|
-
id: string;
|
|
159
|
-
symbol: string;
|
|
160
|
-
name: string;
|
|
161
|
-
price: number;
|
|
162
|
-
change24h: number;
|
|
163
|
-
changePct24h: number;
|
|
164
|
-
volume24h: number;
|
|
165
|
-
marketCap: number;
|
|
166
|
-
marketCapRank: number;
|
|
167
|
-
circulatingSupply: number;
|
|
168
|
-
totalSupply: number | null;
|
|
169
|
-
maxSupply: number | null;
|
|
170
|
-
ath: number;
|
|
171
|
-
athChangePct: number;
|
|
172
|
-
athDate: string;
|
|
173
|
-
dominance: number | null;
|
|
174
|
-
fearGreedApprox: string;
|
|
175
|
-
}
|
package/src/utils/api.ts
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API helpers for Yahoo Finance and CoinGecko free endpoints.
|
|
3
|
-
*
|
|
4
|
-
* Yahoo Finance: Uses the v7/v8 finance endpoints with cookie+crumb auth.
|
|
5
|
-
* CoinGecko: Uses the free /api/v3 endpoints (no API key needed, rate-limited).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { priceCache } from "./cache.js";
|
|
9
|
-
import type {
|
|
10
|
-
YahooQuote,
|
|
11
|
-
YahooChartPoint,
|
|
12
|
-
CoinGeckoMarketData,
|
|
13
|
-
CoinGeckoGlobalData,
|
|
14
|
-
} from "../types.js";
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// User-Agent to avoid blocks
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
const UA =
|
|
21
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Yahoo Finance cookie + crumb authentication
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
let yahooCookie: string | null = null;
|
|
28
|
-
let yahooCrumb: string | null = null;
|
|
29
|
-
let yahooAuthExpiry = 0;
|
|
30
|
-
|
|
31
|
-
/** Sleep helper for retry backoff. */
|
|
32
|
-
function sleep(ms: number): Promise<void> {
|
|
33
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Obtain a cookie + crumb pair for Yahoo Finance API access.
|
|
38
|
-
* Cookies are cached for 1 hour. Retries up to 3 times on rate limits.
|
|
39
|
-
*/
|
|
40
|
-
async function ensureYahooAuth(): Promise<{ cookie: string; crumb: string }> {
|
|
41
|
-
if (yahooCookie && yahooCrumb && Date.now() < yahooAuthExpiry) {
|
|
42
|
-
return { cookie: yahooCookie, crumb: yahooCrumb };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const maxRetries = 3;
|
|
46
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
47
|
-
if (attempt > 0) {
|
|
48
|
-
await sleep(2000 * attempt);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
// Step 1: Get cookies from fc.yahoo.com
|
|
53
|
-
const resp1 = await fetch("https://fc.yahoo.com", {
|
|
54
|
-
headers: { "User-Agent": UA },
|
|
55
|
-
redirect: "manual",
|
|
56
|
-
});
|
|
57
|
-
const setCookies = resp1.headers.getSetCookie?.() ?? [];
|
|
58
|
-
const cookieStr = setCookies.map((c) => c.split(";")[0]).join("; ");
|
|
59
|
-
if (!cookieStr) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Step 2: Get crumb using the cookies
|
|
64
|
-
const resp2 = await fetch(
|
|
65
|
-
"https://query2.finance.yahoo.com/v1/test/getcrumb",
|
|
66
|
-
{
|
|
67
|
-
headers: { "User-Agent": UA, Cookie: cookieStr },
|
|
68
|
-
},
|
|
69
|
-
);
|
|
70
|
-
if (!resp2.ok) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const crumb = await resp2.text();
|
|
74
|
-
if (!crumb || crumb.includes("Too Many")) {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
yahooCookie = cookieStr;
|
|
79
|
-
yahooCrumb = crumb;
|
|
80
|
-
yahooAuthExpiry = Date.now() + 3_600_000; // 1 hour
|
|
81
|
-
|
|
82
|
-
return { cookie: cookieStr, crumb };
|
|
83
|
-
} catch {
|
|
84
|
-
// Network error, retry
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
throw new Error(
|
|
90
|
-
"Failed to authenticate with Yahoo Finance after multiple attempts. " +
|
|
91
|
-
"The API may be rate-limiting requests. Try again in a few minutes.",
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// Generic fetch helpers
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
|
|
99
|
-
async function fetchJSON<T>(url: string): Promise<T> {
|
|
100
|
-
const res = await fetch(url, {
|
|
101
|
-
headers: {
|
|
102
|
-
"User-Agent": UA,
|
|
103
|
-
Accept: "application/json",
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
if (!res.ok) {
|
|
107
|
-
throw new Error(`HTTP ${res.status} from ${url}: ${res.statusText}`);
|
|
108
|
-
}
|
|
109
|
-
return res.json() as Promise<T>;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function fetchYahooJSON<T>(url: string): Promise<T> {
|
|
113
|
-
const maxRetries = 2;
|
|
114
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
115
|
-
const { cookie, crumb } = await ensureYahooAuth();
|
|
116
|
-
const separator = url.includes("?") ? "&" : "?";
|
|
117
|
-
const fullUrl = `${url}${separator}crumb=${encodeURIComponent(crumb)}`;
|
|
118
|
-
const res = await fetch(fullUrl, {
|
|
119
|
-
headers: {
|
|
120
|
-
"User-Agent": UA,
|
|
121
|
-
Accept: "application/json",
|
|
122
|
-
Cookie: cookie,
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
if (res.status === 429 && attempt < maxRetries) {
|
|
126
|
-
// Invalidate cached auth and retry
|
|
127
|
-
yahooAuthExpiry = 0;
|
|
128
|
-
await sleep(2000 * (attempt + 1));
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (!res.ok) {
|
|
132
|
-
throw new Error(`HTTP ${res.status} from ${url}: ${res.statusText}`);
|
|
133
|
-
}
|
|
134
|
-
return res.json() as Promise<T>;
|
|
135
|
-
}
|
|
136
|
-
throw new Error(`Failed to fetch ${url} after retries`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
// Yahoo Finance helpers
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Fetch a quote for one or more symbols via Yahoo Finance v7 quote endpoint.
|
|
145
|
-
* Uses cookie+crumb authentication via query2 subdomain.
|
|
146
|
-
*/
|
|
147
|
-
export async function yahooQuote(symbols: string[]): Promise<YahooQuote[]> {
|
|
148
|
-
const cacheKey = `yq:${symbols.sort().join(",")}`;
|
|
149
|
-
const cached = priceCache.get<YahooQuote[]>(cacheKey);
|
|
150
|
-
if (cached) return cached;
|
|
151
|
-
|
|
152
|
-
const joined = symbols.join(",");
|
|
153
|
-
const url = `https://query2.finance.yahoo.com/v7/finance/quote?symbols=${encodeURIComponent(joined)}`;
|
|
154
|
-
|
|
155
|
-
const data = await fetchYahooJSON<any>(url);
|
|
156
|
-
const quotes: YahooQuote[] = data?.quoteResponse?.result ?? [];
|
|
157
|
-
priceCache.set(cacheKey, quotes);
|
|
158
|
-
return quotes;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Fetch historical chart data from Yahoo Finance.
|
|
163
|
-
* @param symbol Ticker symbol
|
|
164
|
-
* @param range "1mo", "3mo", "6mo", "1y", "5y"
|
|
165
|
-
* @param interval "1d", "1wk", "1mo"
|
|
166
|
-
*/
|
|
167
|
-
export async function yahooChart(
|
|
168
|
-
symbol: string,
|
|
169
|
-
range: string = "6mo",
|
|
170
|
-
interval: string = "1d",
|
|
171
|
-
): Promise<YahooChartPoint[]> {
|
|
172
|
-
const cacheKey = `yc:${symbol}:${range}:${interval}`;
|
|
173
|
-
const cached = priceCache.get<YahooChartPoint[]>(cacheKey);
|
|
174
|
-
if (cached) return cached;
|
|
175
|
-
|
|
176
|
-
const url = `https://query2.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?range=${range}&interval=${interval}&includePrePost=false`;
|
|
177
|
-
const data = await fetchYahooJSON<any>(url);
|
|
178
|
-
const result = data?.chart?.result?.[0];
|
|
179
|
-
if (!result) {
|
|
180
|
-
throw new Error(`No chart data returned for ${symbol}`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const timestamps: number[] = result.timestamp ?? [];
|
|
184
|
-
const ohlcv = result.indicators?.quote?.[0] ?? {};
|
|
185
|
-
const adjClose = result.indicators?.adjclose?.[0]?.adjclose;
|
|
186
|
-
|
|
187
|
-
const points: YahooChartPoint[] = [];
|
|
188
|
-
for (let i = 0; i < timestamps.length; i++) {
|
|
189
|
-
const close = adjClose?.[i] ?? ohlcv.close?.[i];
|
|
190
|
-
const open = ohlcv.open?.[i];
|
|
191
|
-
if (close == null || open == null) continue;
|
|
192
|
-
points.push({
|
|
193
|
-
date: timestamps[i]!,
|
|
194
|
-
open,
|
|
195
|
-
high: ohlcv.high?.[i] ?? open,
|
|
196
|
-
low: ohlcv.low?.[i] ?? open,
|
|
197
|
-
close,
|
|
198
|
-
volume: ohlcv.volume?.[i] ?? 0,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
priceCache.set(cacheKey, points);
|
|
203
|
-
return points;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Screen stocks using Yahoo Finance's screener or quote list.
|
|
208
|
-
* Fetches quotes for a predefined set of popular tickers and filters them.
|
|
209
|
-
*/
|
|
210
|
-
export async function yahooScreener(
|
|
211
|
-
symbols: string[],
|
|
212
|
-
): Promise<YahooQuote[]> {
|
|
213
|
-
// Batch into groups of 20 to stay within URL limits
|
|
214
|
-
const results: YahooQuote[] = [];
|
|
215
|
-
for (let i = 0; i < symbols.length; i += 20) {
|
|
216
|
-
const batch = symbols.slice(i, i + 20);
|
|
217
|
-
const quotes = await yahooQuote(batch);
|
|
218
|
-
results.push(...quotes);
|
|
219
|
-
}
|
|
220
|
-
return results;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
// CoinGecko helpers
|
|
225
|
-
// ---------------------------------------------------------------------------
|
|
226
|
-
|
|
227
|
-
const CG_BASE = "https://api.coingecko.com/api/v3";
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Fetch market data for one or more coins by ID.
|
|
231
|
-
*/
|
|
232
|
-
export async function coingeckoMarkets(
|
|
233
|
-
ids: string[],
|
|
234
|
-
vsCurrency: string = "usd",
|
|
235
|
-
): Promise<CoinGeckoMarketData[]> {
|
|
236
|
-
const cacheKey = `cg:${ids.sort().join(",")}:${vsCurrency}`;
|
|
237
|
-
const cached = priceCache.get<CoinGeckoMarketData[]>(cacheKey);
|
|
238
|
-
if (cached) return cached;
|
|
239
|
-
|
|
240
|
-
const url = `${CG_BASE}/coins/markets?vs_currency=${vsCurrency}&ids=${ids.join(",")}&order=market_cap_desc&per_page=100&page=1&sparkline=false&price_change_percentage=7d,30d`;
|
|
241
|
-
const data = await fetchJSON<CoinGeckoMarketData[]>(url);
|
|
242
|
-
priceCache.set(cacheKey, data);
|
|
243
|
-
return data;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Fetch global crypto market data.
|
|
248
|
-
*/
|
|
249
|
-
export async function coingeckoGlobal(): Promise<CoinGeckoGlobalData> {
|
|
250
|
-
const cacheKey = "cg:global";
|
|
251
|
-
const cached = priceCache.get<CoinGeckoGlobalData>(cacheKey);
|
|
252
|
-
if (cached) return cached;
|
|
253
|
-
|
|
254
|
-
const data = await fetchJSON<{ data: CoinGeckoGlobalData }>(`${CG_BASE}/global`);
|
|
255
|
-
priceCache.set(cacheKey, data.data);
|
|
256
|
-
return data.data;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Map common symbols to CoinGecko IDs.
|
|
261
|
-
*/
|
|
262
|
-
export function symbolToCoinGeckoId(symbol: string): string {
|
|
263
|
-
const map: Record<string, string> = {
|
|
264
|
-
BTC: "bitcoin",
|
|
265
|
-
ETH: "ethereum",
|
|
266
|
-
SOL: "solana",
|
|
267
|
-
BNB: "binancecoin",
|
|
268
|
-
XRP: "ripple",
|
|
269
|
-
ADA: "cardano",
|
|
270
|
-
DOGE: "dogecoin",
|
|
271
|
-
DOT: "polkadot",
|
|
272
|
-
AVAX: "avalanche-2",
|
|
273
|
-
MATIC: "matic-network",
|
|
274
|
-
LINK: "chainlink",
|
|
275
|
-
UNI: "uniswap",
|
|
276
|
-
ATOM: "cosmos",
|
|
277
|
-
LTC: "litecoin",
|
|
278
|
-
NEAR: "near",
|
|
279
|
-
APT: "aptos",
|
|
280
|
-
ARB: "arbitrum",
|
|
281
|
-
OP: "optimism",
|
|
282
|
-
SUI: "sui",
|
|
283
|
-
TRX: "tron",
|
|
284
|
-
SHIB: "shiba-inu",
|
|
285
|
-
PEPE: "pepe",
|
|
286
|
-
};
|
|
287
|
-
const upper = symbol.toUpperCase().replace("-USD", "").replace("USD", "");
|
|
288
|
-
return map[upper] ?? symbol.toLowerCase();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ---------------------------------------------------------------------------
|
|
292
|
-
// Popular stock symbols for screening
|
|
293
|
-
// ---------------------------------------------------------------------------
|
|
294
|
-
|
|
295
|
-
export const POPULAR_SYMBOLS = [
|
|
296
|
-
// Mega-cap tech
|
|
297
|
-
"AAPL", "MSFT", "NVDA", "GOOGL", "AMZN", "META", "TSLA", "AVGO", "TSM", "ORCL",
|
|
298
|
-
// More tech
|
|
299
|
-
"CRM", "AMD", "INTC", "ADBE", "NFLX", "CSCO", "QCOM", "TXN", "AMAT", "NOW",
|
|
300
|
-
// Financials
|
|
301
|
-
"JPM", "V", "MA", "BAC", "GS", "MS", "BRK-B", "AXP", "BLK", "SCHW",
|
|
302
|
-
// Healthcare
|
|
303
|
-
"UNH", "JNJ", "LLY", "PFE", "ABBV", "MRK", "TMO", "ABT", "DHR", "BMY",
|
|
304
|
-
// Consumer
|
|
305
|
-
"WMT", "PG", "KO", "PEP", "COST", "MCD", "NKE", "HD", "LOW", "SBUX",
|
|
306
|
-
// Energy
|
|
307
|
-
"XOM", "CVX", "COP", "SLB", "EOG",
|
|
308
|
-
// Industrials
|
|
309
|
-
"CAT", "GE", "UNP", "HON", "BA", "RTX", "DE", "LMT",
|
|
310
|
-
// Other
|
|
311
|
-
"DIS", "NEE", "AMT", "PLD", "LIN", "SPGI",
|
|
312
|
-
];
|
|
313
|
-
|
|
314
|
-
// ---------------------------------------------------------------------------
|
|
315
|
-
// Static sector/industry mapping for popular symbols
|
|
316
|
-
// (v7 quote endpoint does not return sector/industry)
|
|
317
|
-
// ---------------------------------------------------------------------------
|
|
318
|
-
|
|
319
|
-
export const SYMBOL_SECTORS: Record<string, { sector: string; industry: string }> = {
|
|
320
|
-
// Technology
|
|
321
|
-
AAPL: { sector: "Technology", industry: "Consumer Electronics" },
|
|
322
|
-
MSFT: { sector: "Technology", industry: "Software - Infrastructure" },
|
|
323
|
-
NVDA: { sector: "Technology", industry: "Semiconductors" },
|
|
324
|
-
GOOGL: { sector: "Technology", industry: "Internet Content & Information" },
|
|
325
|
-
AMZN: { sector: "Technology", industry: "Internet Retail" },
|
|
326
|
-
META: { sector: "Technology", industry: "Internet Content & Information" },
|
|
327
|
-
TSLA: { sector: "Technology", industry: "Auto Manufacturers" },
|
|
328
|
-
AVGO: { sector: "Technology", industry: "Semiconductors" },
|
|
329
|
-
TSM: { sector: "Technology", industry: "Semiconductors" },
|
|
330
|
-
ORCL: { sector: "Technology", industry: "Software - Infrastructure" },
|
|
331
|
-
CRM: { sector: "Technology", industry: "Software - Application" },
|
|
332
|
-
AMD: { sector: "Technology", industry: "Semiconductors" },
|
|
333
|
-
INTC: { sector: "Technology", industry: "Semiconductors" },
|
|
334
|
-
ADBE: { sector: "Technology", industry: "Software - Application" },
|
|
335
|
-
NFLX: { sector: "Technology", industry: "Entertainment" },
|
|
336
|
-
CSCO: { sector: "Technology", industry: "Communication Equipment" },
|
|
337
|
-
QCOM: { sector: "Technology", industry: "Semiconductors" },
|
|
338
|
-
TXN: { sector: "Technology", industry: "Semiconductors" },
|
|
339
|
-
AMAT: { sector: "Technology", industry: "Semiconductor Equipment" },
|
|
340
|
-
NOW: { sector: "Technology", industry: "Software - Application" },
|
|
341
|
-
// Financials
|
|
342
|
-
JPM: { sector: "Financial Services", industry: "Banks - Diversified" },
|
|
343
|
-
V: { sector: "Financial Services", industry: "Credit Services" },
|
|
344
|
-
MA: { sector: "Financial Services", industry: "Credit Services" },
|
|
345
|
-
BAC: { sector: "Financial Services", industry: "Banks - Diversified" },
|
|
346
|
-
GS: { sector: "Financial Services", industry: "Capital Markets" },
|
|
347
|
-
MS: { sector: "Financial Services", industry: "Capital Markets" },
|
|
348
|
-
"BRK-B": { sector: "Financial Services", industry: "Insurance - Diversified" },
|
|
349
|
-
AXP: { sector: "Financial Services", industry: "Credit Services" },
|
|
350
|
-
BLK: { sector: "Financial Services", industry: "Asset Management" },
|
|
351
|
-
SCHW: { sector: "Financial Services", industry: "Capital Markets" },
|
|
352
|
-
SPGI: { sector: "Financial Services", industry: "Financial Data & Stock Exchanges" },
|
|
353
|
-
// Healthcare
|
|
354
|
-
UNH: { sector: "Healthcare", industry: "Healthcare Plans" },
|
|
355
|
-
JNJ: { sector: "Healthcare", industry: "Drug Manufacturers" },
|
|
356
|
-
LLY: { sector: "Healthcare", industry: "Drug Manufacturers" },
|
|
357
|
-
PFE: { sector: "Healthcare", industry: "Drug Manufacturers" },
|
|
358
|
-
ABBV: { sector: "Healthcare", industry: "Drug Manufacturers" },
|
|
359
|
-
MRK: { sector: "Healthcare", industry: "Drug Manufacturers" },
|
|
360
|
-
TMO: { sector: "Healthcare", industry: "Diagnostics & Research" },
|
|
361
|
-
ABT: { sector: "Healthcare", industry: "Medical Devices" },
|
|
362
|
-
DHR: { sector: "Healthcare", industry: "Diagnostics & Research" },
|
|
363
|
-
BMY: { sector: "Healthcare", industry: "Drug Manufacturers" },
|
|
364
|
-
// Consumer Discretionary / Staples
|
|
365
|
-
WMT: { sector: "Consumer Defensive", industry: "Discount Stores" },
|
|
366
|
-
PG: { sector: "Consumer Defensive", industry: "Household & Personal Products" },
|
|
367
|
-
KO: { sector: "Consumer Defensive", industry: "Beverages - Non-Alcoholic" },
|
|
368
|
-
PEP: { sector: "Consumer Defensive", industry: "Beverages - Non-Alcoholic" },
|
|
369
|
-
COST: { sector: "Consumer Defensive", industry: "Discount Stores" },
|
|
370
|
-
MCD: { sector: "Consumer Cyclical", industry: "Restaurants" },
|
|
371
|
-
NKE: { sector: "Consumer Cyclical", industry: "Footwear & Accessories" },
|
|
372
|
-
HD: { sector: "Consumer Cyclical", industry: "Home Improvement Retail" },
|
|
373
|
-
LOW: { sector: "Consumer Cyclical", industry: "Home Improvement Retail" },
|
|
374
|
-
SBUX: { sector: "Consumer Cyclical", industry: "Restaurants" },
|
|
375
|
-
DIS: { sector: "Communication Services", industry: "Entertainment" },
|
|
376
|
-
// Energy
|
|
377
|
-
XOM: { sector: "Energy", industry: "Oil & Gas Integrated" },
|
|
378
|
-
CVX: { sector: "Energy", industry: "Oil & Gas Integrated" },
|
|
379
|
-
COP: { sector: "Energy", industry: "Oil & Gas E&P" },
|
|
380
|
-
SLB: { sector: "Energy", industry: "Oil & Gas Equipment & Services" },
|
|
381
|
-
EOG: { sector: "Energy", industry: "Oil & Gas E&P" },
|
|
382
|
-
// Industrials
|
|
383
|
-
CAT: { sector: "Industrials", industry: "Farm & Heavy Construction Machinery" },
|
|
384
|
-
GE: { sector: "Industrials", industry: "Specialty Industrial Machinery" },
|
|
385
|
-
UNP: { sector: "Industrials", industry: "Railroads" },
|
|
386
|
-
HON: { sector: "Industrials", industry: "Conglomerates" },
|
|
387
|
-
BA: { sector: "Industrials", industry: "Aerospace & Defense" },
|
|
388
|
-
RTX: { sector: "Industrials", industry: "Aerospace & Defense" },
|
|
389
|
-
DE: { sector: "Industrials", industry: "Farm & Heavy Construction Machinery" },
|
|
390
|
-
LMT: { sector: "Industrials", industry: "Aerospace & Defense" },
|
|
391
|
-
// Real Estate / Utilities / Materials
|
|
392
|
-
NEE: { sector: "Utilities", industry: "Utilities - Regulated Electric" },
|
|
393
|
-
AMT: { sector: "Real Estate", industry: "REIT - Specialty" },
|
|
394
|
-
PLD: { sector: "Real Estate", industry: "REIT - Industrial" },
|
|
395
|
-
LIN: { sector: "Basic Materials", industry: "Specialty Chemicals" },
|
|
396
|
-
};
|
package/src/utils/cache.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory cache with configurable TTL.
|
|
3
|
-
* Default TTL is 5 minutes (300_000 ms) for price data.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
interface CacheEntry<T> {
|
|
7
|
-
value: T;
|
|
8
|
-
expiresAt: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class MemoryCache {
|
|
12
|
-
private store = new Map<string, CacheEntry<unknown>>();
|
|
13
|
-
private defaultTTL: number;
|
|
14
|
-
|
|
15
|
-
constructor(defaultTTLMs: number = 300_000) {
|
|
16
|
-
this.defaultTTL = defaultTTLMs;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
get<T>(key: string): T | undefined {
|
|
20
|
-
const entry = this.store.get(key);
|
|
21
|
-
if (!entry) return undefined;
|
|
22
|
-
if (Date.now() > entry.expiresAt) {
|
|
23
|
-
this.store.delete(key);
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
return entry.value as T;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
set<T>(key: string, value: T, ttlMs?: number): void {
|
|
30
|
-
this.store.set(key, {
|
|
31
|
-
value,
|
|
32
|
-
expiresAt: Date.now() + (ttlMs ?? this.defaultTTL),
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
has(key: string): boolean {
|
|
37
|
-
return this.get(key) !== undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
delete(key: string): void {
|
|
41
|
-
this.store.delete(key);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
clear(): void {
|
|
45
|
-
this.store.clear();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Remove all expired entries. */
|
|
49
|
-
prune(): void {
|
|
50
|
-
const now = Date.now();
|
|
51
|
-
for (const [key, entry] of this.store) {
|
|
52
|
-
if (now > entry.expiresAt) {
|
|
53
|
-
this.store.delete(key);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get size(): number {
|
|
59
|
-
this.prune();
|
|
60
|
-
return this.store.size;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Shared price cache instance -- 5 minute TTL */
|
|
65
|
-
export const priceCache = new MemoryCache(300_000);
|