Spaces:
Running
Running
Jimin Huang
commited on
Commit
·
5cec81c
1
Parent(s):
a00f4de
Change settings
Browse files- src/lib/prices.ts +43 -21
src/lib/prices.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
// src/lib/prices.ts
|
| 2 |
type PriceMap = Record<string, number>
|
| 3 |
|
| 4 |
-
// Map
|
| 5 |
-
const MAP: Record<string, { p: 'binance'|'
|
| 6 |
// crypto (USDT pairs)
|
| 7 |
BTC: { p: 'binance', s: 'BTCUSDT' },
|
| 8 |
ETH: { p: 'binance', s: 'ETHUSDT' },
|
|
@@ -11,15 +11,14 @@ const MAP: Record<string, { p: 'binance'|'stooq'; s: string }> = {
|
|
| 11 |
DOGE:{ p: 'binance', s: 'DOGEUSDT' },
|
| 12 |
XRP: { p: 'binance', s: 'XRPUSDT' },
|
| 13 |
|
| 14 |
-
// equities (
|
| 15 |
-
AAPL:{ p: '
|
| 16 |
-
MSFT:{ p: '
|
| 17 |
-
BMRN:{ p: '
|
| 18 |
-
MRNA:{ p: '
|
| 19 |
-
TSLA:{ p: '
|
| 20 |
};
|
| 21 |
|
| 22 |
-
// --- simple cache + pub/sub
|
| 23 |
let latest: PriceMap = {};
|
| 24 |
const subs = new Set<(m: PriceMap)=>void>();
|
| 25 |
const push = () => subs.forEach(fn => fn({ ...latest }));
|
|
@@ -31,30 +30,53 @@ export function subscribePrices(fn: (m: PriceMap)=>void) {
|
|
| 31 |
|
| 32 |
async function fetchBinance(symbol: string) {
|
| 33 |
const r = await fetch(`https://api.binance.com/api/v3/ticker/price?symbol=${encodeURIComponent(symbol)}`);
|
|
|
|
| 34 |
const j = await r.json(); const n = Number(j?.price);
|
| 35 |
return Number.isFinite(n) ? n : undefined;
|
| 36 |
}
|
| 37 |
|
| 38 |
-
async function
|
| 39 |
-
const url = `https://
|
| 40 |
const r = await fetch(url);
|
| 41 |
-
|
| 42 |
-
const
|
| 43 |
-
const
|
| 44 |
-
const
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
|
| 48 |
export async function pollPrices(codes: string[], intervalMs = 30000) {
|
| 49 |
const uniq = [...new Set(codes)].filter(c => MAP[c]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
async function tick() {
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
push();
|
| 57 |
}
|
|
|
|
| 58 |
await tick();
|
| 59 |
setInterval(tick, intervalMs);
|
| 60 |
}
|
|
|
|
| 1 |
// src/lib/prices.ts
|
| 2 |
type PriceMap = Record<string, number>
|
| 3 |
|
| 4 |
+
// Map asset code -> provider + symbol
|
| 5 |
+
const MAP: Record<string, { p: 'binance'|'yahoo'; s: string }> = {
|
| 6 |
// crypto (USDT pairs)
|
| 7 |
BTC: { p: 'binance', s: 'BTCUSDT' },
|
| 8 |
ETH: { p: 'binance', s: 'ETHUSDT' },
|
|
|
|
| 11 |
DOGE:{ p: 'binance', s: 'DOGEUSDT' },
|
| 12 |
XRP: { p: 'binance', s: 'XRPUSDT' },
|
| 13 |
|
| 14 |
+
// equities (Yahoo uses plain tickers)
|
| 15 |
+
AAPL:{ p: 'yahoo', s: 'AAPL' },
|
| 16 |
+
MSFT:{ p: 'yahoo', s: 'MSFT' },
|
| 17 |
+
BMRN:{ p: 'yahoo', s: 'BMRN' },
|
| 18 |
+
MRNA:{ p: 'yahoo', s: 'MRNA' },
|
| 19 |
+
TSLA:{ p: 'yahoo', s: 'TSLA' },
|
| 20 |
};
|
| 21 |
|
|
|
|
| 22 |
let latest: PriceMap = {};
|
| 23 |
const subs = new Set<(m: PriceMap)=>void>();
|
| 24 |
const push = () => subs.forEach(fn => fn({ ...latest }));
|
|
|
|
| 30 |
|
| 31 |
async function fetchBinance(symbol: string) {
|
| 32 |
const r = await fetch(`https://api.binance.com/api/v3/ticker/price?symbol=${encodeURIComponent(symbol)}`);
|
| 33 |
+
if (!r.ok) throw new Error(`binance ${symbol} ${r.status}`);
|
| 34 |
const j = await r.json(); const n = Number(j?.price);
|
| 35 |
return Number.isFinite(n) ? n : undefined;
|
| 36 |
}
|
| 37 |
|
| 38 |
+
async function fetchYahoo(symbolsCsv: string) {
|
| 39 |
+
const url = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=${encodeURIComponent(symbolsCsv)}`
|
| 40 |
const r = await fetch(url);
|
| 41 |
+
if (!r.ok) throw new Error(`yahoo ${symbolsCsv} ${r.status}`);
|
| 42 |
+
const j = await r.json();
|
| 43 |
+
const out: Record<string, number> = {};
|
| 44 |
+
for (const it of (j?.quoteResponse?.result ?? [])) {
|
| 45 |
+
const sym = it?.symbol;
|
| 46 |
+
const px = Number(it?.regularMarketPrice ?? it?.postMarketPrice ?? it?.preMarketPrice);
|
| 47 |
+
if (sym && Number.isFinite(px)) out[sym] = px;
|
| 48 |
+
}
|
| 49 |
+
return out;
|
| 50 |
}
|
| 51 |
|
| 52 |
export async function pollPrices(codes: string[], intervalMs = 30000) {
|
| 53 |
const uniq = [...new Set(codes)].filter(c => MAP[c]);
|
| 54 |
+
|
| 55 |
+
// split by provider for efficient requests
|
| 56 |
+
const binanceSyms = uniq.filter(c => MAP[c].p === 'binance').map(c => MAP[c].s);
|
| 57 |
+
const yahooSyms = uniq.filter(c => MAP[c].p === 'yahoo').map(c => MAP[c].s);
|
| 58 |
+
|
| 59 |
async function tick() {
|
| 60 |
+
try {
|
| 61 |
+
// Binance: per-symbol (cheap for a handful)
|
| 62 |
+
await Promise.all(binanceSyms.map(async s => {
|
| 63 |
+
const px = await fetchBinance(s);
|
| 64 |
+
const code = Object.entries(MAP).find(([k,v]) => v.s === s)?.[0];
|
| 65 |
+
if (code && px !== undefined) latest[code] = px;
|
| 66 |
+
}));
|
| 67 |
+
|
| 68 |
+
// Yahoo: batch in one call
|
| 69 |
+
if (yahooSyms.length) {
|
| 70 |
+
const batch = await fetchYahoo(yahooSyms.join(','));
|
| 71 |
+
for (const [sym, px] of Object.entries(batch)) {
|
| 72 |
+
const code = Object.entries(MAP).find(([k,v]) => v.s === sym)?.[0];
|
| 73 |
+
if (code && px !== undefined) latest[code] = px;
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
} catch { /* ignore transient errors */ }
|
| 77 |
push();
|
| 78 |
}
|
| 79 |
+
|
| 80 |
await tick();
|
| 81 |
setInterval(tick, intervalMs);
|
| 82 |
}
|