Jimin Huang commited on
Commit
5cec81c
·
1 Parent(s): a00f4de

Change settings

Browse files
Files changed (1) hide show
  1. 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 your asset code -> provider + symbol
5
- const MAP: Record<string, { p: 'binance'|'stooq'; s: string }> = {
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 (stooq wants .us)
15
- AAPL:{ p: 'stooq', s: 'aapl.us' },
16
- MSFT:{ p: 'stooq', s: 'msft.us' },
17
- BMRN:{ p: 'stooq', s: 'bmrn.us' },
18
- MRNA:{ p: 'stooq', s: 'mrna.us' },
19
- TSLA:{ p: 'stooq', s: 'tsla.us' },
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 fetchStooq(symbol: string) {
39
- const url = `https://stooq.com/q/l/?s=${encodeURIComponent(symbol)}&f=sd2t2ohlcv&h&e=csv`;
40
  const r = await fetch(url);
41
- const txt = await r.text(); // first line is header; second line has data, column 'Close'
42
- const line = txt.split('\n')[1] || '';
43
- const cols = line.split(','); // Symbol,Date,Time,Open,High,Low,Close,Volume
44
- const n = Number(cols[6]);
45
- return Number.isFinite(n) ? n : undefined;
 
 
 
 
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
- await Promise.all(uniq.map(async code => {
52
- const { p, s } = MAP[code];
53
- const px = p === 'binance' ? await fetchBinance(s) : await fetchStooq(s);
54
- if (px !== undefined) latest[code] = px;
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
  }