horizon-code 0.2.0 → 0.3.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/package.json +1 -1
- package/src/ai/client.ts +33 -8
- package/src/ai/system-prompt.ts +48 -6
- package/src/app.ts +82 -3
- package/src/components/code-panel.ts +82 -3
- package/src/components/footer.ts +3 -0
- package/src/components/settings-panel.ts +14 -0
- package/src/platform/exchanges.ts +154 -0
- package/src/research/apis.ts +208 -11
- package/src/research/stock-apis.ts +117 -0
- package/src/research/tools.ts +929 -17
- package/src/research/widgets.ts +1042 -29
package/src/research/apis.ts
CHANGED
|
@@ -229,28 +229,225 @@ export async function polymarketTrades(slug: string): Promise<any> {
|
|
|
229
229
|
};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
// ── Polymarket DATA API — wallet/trade deep analysis ──
|
|
233
|
+
|
|
234
|
+
/** Get recent trades for a market (up to 500) */
|
|
235
|
+
export async function getMarketTrades(conditionId: string, limit = 200): Promise<any[]> {
|
|
236
|
+
const data = await get(`${DATA_API}/trades?market=${conditionId}&limit=${Math.min(limit, 500)}`).catch(() => []);
|
|
237
|
+
return data ?? [];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Get trades for a specific wallet (up to 500) */
|
|
241
|
+
export async function getWalletTrades(address: string, limit = 200, conditionId?: string): Promise<any[]> {
|
|
242
|
+
let url = `${DATA_API}/trades?maker_address=${address}&limit=${Math.min(limit, 500)}`;
|
|
243
|
+
if (conditionId) url += `&market=${conditionId}`;
|
|
244
|
+
const data = await get(url).catch(() => []);
|
|
245
|
+
return data ?? [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Get open positions for a wallet */
|
|
249
|
+
export async function getWalletPositions(address: string, limit = 100): Promise<any[]> {
|
|
250
|
+
const data = await get(`${DATA_API}/positions?user=${address}&limit=${Math.min(limit, 500)}&sort_by=TOKENS`).catch(() => []);
|
|
251
|
+
return data ?? [];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Get wallet profile from Gamma API */
|
|
255
|
+
export async function getWalletProfile(address: string): Promise<any> {
|
|
256
|
+
const data = await get(`${GAMMA}/users?address=${address}`).catch(() => null);
|
|
257
|
+
const user = Array.isArray(data) ? data[0] : data;
|
|
258
|
+
return user ? {
|
|
259
|
+
address, name: user.pseudonym ?? user.name ?? "Anonymous",
|
|
260
|
+
bio: user.bio ?? "", profileImage: user.profile_image,
|
|
261
|
+
xUsername: user.x_username, createdAt: user.created_at,
|
|
262
|
+
} : { address, name: "Anonymous" };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Resolve market slug → conditionId */
|
|
266
|
+
export async function resolveConditionId(slug: string): Promise<{ conditionId: string; title: string; market: any }> {
|
|
267
|
+
const events = await get(`${GAMMA}/events?slug=${slug}`);
|
|
268
|
+
const market = events?.[0]?.markets?.[0];
|
|
269
|
+
if (!market) throw new Error(`Market not found: ${slug}`);
|
|
270
|
+
return { conditionId: market.conditionId, title: events[0].title, market };
|
|
271
|
+
}
|
|
272
|
+
|
|
232
273
|
// ── Kalshi ──
|
|
274
|
+
// API v2 returns prices as dollar strings: "yes_bid_dollars": "0.09"
|
|
275
|
+
// Volumes as float strings: "volume_fp": "55642.00"
|
|
276
|
+
|
|
277
|
+
/** Parse Kalshi dollar string to number (e.g. "0.09" → 0.09, null → null) */
|
|
278
|
+
function kPrice(v: any): number | null {
|
|
279
|
+
if (v == null || v === "" || v === "0.0000") return null;
|
|
280
|
+
const n = typeof v === "string" ? parseFloat(v) : v;
|
|
281
|
+
return isNaN(n) ? null : +n.toFixed(4);
|
|
282
|
+
}
|
|
233
283
|
|
|
234
|
-
|
|
284
|
+
/** Parse Kalshi integer/float volume string */
|
|
285
|
+
function kVol(v: any): number {
|
|
286
|
+
if (v == null || v === "") return 0;
|
|
287
|
+
const n = typeof v === "string" ? parseFloat(v) : v;
|
|
288
|
+
return isNaN(n) ? 0 : Math.round(n);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Parse a Kalshi market from the API response */
|
|
292
|
+
function parseKalshiMarket(m: any): any {
|
|
293
|
+
const yesBid = kPrice(m.yes_bid_dollars ?? m.yes_bid);
|
|
294
|
+
const yesAsk = kPrice(m.yes_ask_dollars ?? m.yes_ask);
|
|
295
|
+
const lastPrice = kPrice(m.last_price_dollars ?? m.last_price);
|
|
296
|
+
return {
|
|
297
|
+
ticker: m.ticker,
|
|
298
|
+
name: m.title ?? m.yes_sub_title ?? m.subtitle ?? m.ticker,
|
|
299
|
+
title: m.title ?? m.yes_sub_title ?? m.subtitle ?? m.ticker,
|
|
300
|
+
yesBid, yesAsk, lastPrice,
|
|
301
|
+
noBid: kPrice(m.no_bid_dollars ?? m.no_bid),
|
|
302
|
+
noAsk: kPrice(m.no_ask_dollars ?? m.no_ask),
|
|
303
|
+
volume: kVol(m.volume_fp ?? m.volume),
|
|
304
|
+
volume24h: kVol(m.volume_24h_fp ?? m.volume_24h),
|
|
305
|
+
openInterest: kVol(m.open_interest_fp ?? m.open_interest),
|
|
306
|
+
spread: yesBid != null && yesAsk != null ? +((yesAsk - yesBid)).toFixed(4) : null,
|
|
307
|
+
closeTime: m.close_time, expirationTime: m.expiration_time,
|
|
308
|
+
status: m.status,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let _kalshiCache: { data: any[]; ts: number } = { data: [], ts: 0 };
|
|
313
|
+
const KALSHI_CACHE_TTL = 60_000;
|
|
314
|
+
|
|
315
|
+
async function fetchAllKalshiEvents(limit = 100): Promise<any[]> {
|
|
316
|
+
if (_kalshiCache.data.length > 0 && Date.now() - _kalshiCache.ts < KALSHI_CACHE_TTL) {
|
|
317
|
+
return _kalshiCache.data;
|
|
318
|
+
}
|
|
235
319
|
const params = new URLSearchParams({
|
|
236
320
|
status: "open", with_nested_markets: "true",
|
|
237
|
-
limit: String(
|
|
321
|
+
limit: String(limit),
|
|
238
322
|
});
|
|
239
|
-
|
|
323
|
+
const data = await get(`${KALSHI}/events?${params}`).catch(() => ({ events: [] }));
|
|
324
|
+
const events = data?.events ?? [];
|
|
325
|
+
_kalshiCache = { data: events, ts: Date.now() };
|
|
326
|
+
return events;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export async function kalshiEvents(opts: { query?: string; limit?: number } = {}): Promise<any[]> {
|
|
330
|
+
const limit = Math.min(opts.limit ?? 10, 20);
|
|
331
|
+
const query = (opts.query ?? "").toLowerCase().trim();
|
|
332
|
+
|
|
333
|
+
const allEvents = await fetchAllKalshiEvents();
|
|
334
|
+
let events: any[];
|
|
335
|
+
|
|
336
|
+
if (!query) {
|
|
337
|
+
events = allEvents.slice(0, limit);
|
|
338
|
+
} else {
|
|
339
|
+
// Client-side text search (series_ticker only matches exact tickers)
|
|
340
|
+
const words = query.split(/\s+/).filter(w => w.length > 1);
|
|
341
|
+
events = allEvents.filter((e: any) => {
|
|
342
|
+
const text = ((e.title ?? "") + " " + (e.sub_title ?? "") + " " +
|
|
343
|
+
(e.category ?? "") + " " + (e.series_ticker ?? "") + " " + (e.event_ticker ?? "")).toLowerCase();
|
|
344
|
+
return words.every(w => text.includes(w));
|
|
345
|
+
});
|
|
346
|
+
// Fallback: any word match
|
|
347
|
+
if (events.length === 0 && words.length > 1) {
|
|
348
|
+
events = allEvents.filter((e: any) => {
|
|
349
|
+
const text = ((e.title ?? "") + " " + (e.sub_title ?? "") + " " + (e.category ?? "")).toLowerCase();
|
|
350
|
+
return words.some(w => text.includes(w));
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
events = events.slice(0, limit);
|
|
354
|
+
}
|
|
240
355
|
|
|
241
|
-
|
|
242
|
-
return (data?.events ?? []).map((e: any) => ({
|
|
356
|
+
return events.map((e: any) => ({
|
|
243
357
|
ticker: e.event_ticker, title: e.title,
|
|
244
358
|
category: e.category, subtitle: e.sub_title,
|
|
245
|
-
url: `https://kalshi.com/markets/${e.event_ticker
|
|
246
|
-
markets: (e.markets ?? []).
|
|
247
|
-
name: m.title ?? m.subtitle, yesBid: m.yes_bid, yesAsk: m.yes_ask,
|
|
248
|
-
lastPrice: m.last_price, volume: m.volume,
|
|
249
|
-
openInterest: m.open_interest, closeTime: m.close_time,
|
|
250
|
-
})),
|
|
359
|
+
url: `https://kalshi.com/markets/${(e.event_ticker ?? "").toLowerCase()}`,
|
|
360
|
+
markets: (e.markets ?? []).slice(0, 5).map(parseKalshiMarket),
|
|
251
361
|
}));
|
|
252
362
|
}
|
|
253
363
|
|
|
364
|
+
export async function kalshiEventDetail(ticker: string): Promise<any> {
|
|
365
|
+
const data = await get(`${KALSHI}/events/${ticker}?with_nested_markets=true`);
|
|
366
|
+
const event = data?.event ?? data;
|
|
367
|
+
if (!event) throw new Error(`Kalshi event not found: ${ticker}`);
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
ticker: event.event_ticker, title: event.title,
|
|
371
|
+
category: event.category, subtitle: event.sub_title,
|
|
372
|
+
description: (event.strike_description ?? event.sub_title ?? "").slice(0, 500),
|
|
373
|
+
url: `https://kalshi.com/markets/${(event.event_ticker ?? "").toLowerCase()}`,
|
|
374
|
+
markets: (event.markets ?? []).map(parseKalshiMarket),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function kalshiOrderBook(ticker: string): Promise<any> {
|
|
379
|
+
const book = await get(`${KALSHI}/markets/${ticker}/orderbook`);
|
|
380
|
+
const ob = book?.orderbook ?? {};
|
|
381
|
+
// yes array: [[price_cents, size], ...] — bids for YES outcome
|
|
382
|
+
// no array: [[price_cents, size], ...] — asks (complement pricing)
|
|
383
|
+
const bids = (ob.yes ?? []).map((l: any) => {
|
|
384
|
+
const price = Array.isArray(l) ? l[0] / 100 : (l.price ?? 0) / 100;
|
|
385
|
+
const size = Array.isArray(l) ? l[1] : l.size ?? 0;
|
|
386
|
+
return { price: +price.toFixed(4), size };
|
|
387
|
+
});
|
|
388
|
+
const asks = (ob.no ?? []).map((l: any) => {
|
|
389
|
+
const price = Array.isArray(l) ? 1 - l[0] / 100 : 1 - (l.price ?? 0) / 100;
|
|
390
|
+
const size = Array.isArray(l) ? l[1] : l.size ?? 0;
|
|
391
|
+
return { price: +price.toFixed(4), size };
|
|
392
|
+
});
|
|
393
|
+
bids.sort((a: any, b: any) => b.price - a.price);
|
|
394
|
+
asks.sort((a: any, b: any) => a.price - b.price);
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
ticker, title: ticker,
|
|
398
|
+
url: `https://kalshi.com/markets/${ticker.toLowerCase()}`,
|
|
399
|
+
bestBid: bids[0]?.price ?? null,
|
|
400
|
+
bestAsk: asks[0]?.price ?? null,
|
|
401
|
+
spread: bids[0] && asks[0] ? +(asks[0].price - bids[0].price).toFixed(4) : null,
|
|
402
|
+
bids: bids.slice(0, 15),
|
|
403
|
+
asks: asks.slice(0, 15),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export async function kalshiPriceHistory(ticker: string, period = "1w"): Promise<any> {
|
|
408
|
+
const intervalMap: Record<string, number> = { "1h": 1, "6h": 1, "1d": 60, "1w": 60, "1m": 1440, "max": 1440 };
|
|
409
|
+
const periodInterval = intervalMap[period] ?? 60;
|
|
410
|
+
|
|
411
|
+
const data = await get(`${KALSHI}/markets/${ticker}/candlesticks?period_interval=${periodInterval}`).catch(() => ({ candlesticks: [] }));
|
|
412
|
+
const candles = data?.candlesticks ?? [];
|
|
413
|
+
|
|
414
|
+
// Prices can be dollar strings or cent integers — normalize to decimal
|
|
415
|
+
const normalize = (v: any): number => {
|
|
416
|
+
if (v == null) return 0;
|
|
417
|
+
const n = typeof v === "string" ? parseFloat(v) : v;
|
|
418
|
+
// If > 1, it's cents (old format); if <= 1, it's dollars (new format)
|
|
419
|
+
return n > 1 ? n / 100 : n;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const prices = candles.map((c: any) => {
|
|
423
|
+
const bid = normalize(c.yes_bid_dollars ?? c.yes_bid ?? c.yes_price);
|
|
424
|
+
const ask = normalize(c.yes_ask_dollars ?? c.yes_ask);
|
|
425
|
+
return ask > 0 ? (bid + ask) / 2 : bid;
|
|
426
|
+
}).filter((p: number) => p > 0);
|
|
427
|
+
|
|
428
|
+
const timestamps = candles.map((c: any) => {
|
|
429
|
+
const ts = c.end_period_ts ?? c.ts;
|
|
430
|
+
return typeof ts === "string" ? new Date(ts).getTime() / 1000 : ts;
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
ticker, title: ticker,
|
|
435
|
+
url: `https://kalshi.com/markets/${ticker.toLowerCase()}`,
|
|
436
|
+
interval: period,
|
|
437
|
+
dataPoints: prices.length,
|
|
438
|
+
current: prices.length > 0 ? +prices[prices.length - 1].toFixed(4) : null,
|
|
439
|
+
high: prices.length > 0 ? +Math.max(...prices).toFixed(4) : null,
|
|
440
|
+
low: prices.length > 0 ? +Math.min(...prices).toFixed(4) : null,
|
|
441
|
+
change: prices.length > 1 ? +((prices[prices.length - 1] - prices[0]) / (prices[0] || 1)).toFixed(4) : null,
|
|
442
|
+
priceHistory: candles.map((c: any, i: number) => ({
|
|
443
|
+
t: timestamps[i], p: prices[i] ?? 0,
|
|
444
|
+
volume: kVol(c.volume_fp ?? c.volume),
|
|
445
|
+
open: normalize(c.open_dollars ?? c.open),
|
|
446
|
+
close: normalize(c.close_dollars ?? c.close),
|
|
447
|
+
})).filter((p: any) => p.p > 0),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
254
451
|
// ── Exa (web search + sentiment) — via server proxy ──
|
|
255
452
|
|
|
256
453
|
import { getAuthUrl, loadConfig } from "../platform/config.ts";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Yahoo Finance API — free, no auth required
|
|
2
|
+
// Uses v8 chart API (still public) + search API
|
|
3
|
+
// Quote data extracted from chart metadata (v7 quote endpoint now requires crumb auth)
|
|
4
|
+
|
|
5
|
+
const YF_CHART = "https://query1.finance.yahoo.com/v8/finance/chart";
|
|
6
|
+
const YF_SEARCH = "https://query2.finance.yahoo.com/v1/finance/search";
|
|
7
|
+
|
|
8
|
+
async function yfGet(url: string): Promise<any> {
|
|
9
|
+
const res = await fetch(url, {
|
|
10
|
+
headers: {
|
|
11
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
|
|
12
|
+
"Accept": "application/json",
|
|
13
|
+
},
|
|
14
|
+
signal: AbortSignal.timeout(10000),
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) throw new Error(`Yahoo Finance ${res.status}`);
|
|
17
|
+
return res.json();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Get real-time quote for one or more symbols — uses chart endpoint meta */
|
|
21
|
+
export async function yahooQuote(symbols: string[]): Promise<any[]> {
|
|
22
|
+
const results = await Promise.all(
|
|
23
|
+
symbols.map(async (sym) => {
|
|
24
|
+
try {
|
|
25
|
+
const data = await yfGet(`${YF_CHART}/${sym.toUpperCase()}?range=5d&interval=1d&includePrePost=false`);
|
|
26
|
+
const result = data?.chart?.result?.[0];
|
|
27
|
+
if (!result) return null;
|
|
28
|
+
const meta = result.meta ?? {};
|
|
29
|
+
const closes: number[] = (result.indicators?.quote?.[0]?.close ?? []).filter((p: any) => p != null);
|
|
30
|
+
const volumes: number[] = (result.indicators?.quote?.[0]?.volume ?? []).filter((v: any) => v != null);
|
|
31
|
+
const prevClose = meta.chartPreviousClose ?? meta.previousClose ?? (closes.length > 1 ? closes[closes.length - 2] : 0);
|
|
32
|
+
const price = meta.regularMarketPrice ?? (closes.length > 0 ? closes[closes.length - 1] : 0);
|
|
33
|
+
const change = prevClose > 0 ? price - prevClose : 0;
|
|
34
|
+
const changePct = prevClose > 0 ? (change / prevClose) * 100 : 0;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
symbol: meta.symbol ?? sym.toUpperCase(),
|
|
38
|
+
name: meta.shortName ?? meta.longName ?? sym.toUpperCase(),
|
|
39
|
+
price: +price.toFixed(2),
|
|
40
|
+
change: +change.toFixed(2),
|
|
41
|
+
changePct: +changePct.toFixed(2),
|
|
42
|
+
previousClose: +prevClose.toFixed(2),
|
|
43
|
+
open: meta.regularMarketOpen ?? 0,
|
|
44
|
+
dayHigh: meta.regularMarketDayHigh ?? meta.dayHigh ?? 0,
|
|
45
|
+
dayLow: meta.regularMarketDayLow ?? meta.dayLow ?? 0,
|
|
46
|
+
volume: meta.regularMarketVolume ?? (volumes.length > 0 ? volumes[volumes.length - 1] : 0),
|
|
47
|
+
avgVolume: meta.averageDailyVolume3Month ?? 0,
|
|
48
|
+
marketCap: meta.marketCap ?? 0,
|
|
49
|
+
peRatio: meta.trailingPE ?? null,
|
|
50
|
+
forwardPE: meta.forwardPE ?? null,
|
|
51
|
+
dividendYield: meta.dividendYield ?? null,
|
|
52
|
+
fiftyTwoWeekHigh: meta.fiftyTwoWeekHigh ?? null,
|
|
53
|
+
fiftyTwoWeekLow: meta.fiftyTwoWeekLow ?? null,
|
|
54
|
+
fiftyDayAvg: meta.fiftyDayAverage ?? null,
|
|
55
|
+
twoHundredDayAvg: meta.twoHundredDayAverage ?? null,
|
|
56
|
+
beta: meta.beta ?? null,
|
|
57
|
+
exchange: meta.exchangeName ?? meta.exchange ?? "",
|
|
58
|
+
quoteType: meta.instrumentType ?? "",
|
|
59
|
+
currency: meta.currency ?? "USD",
|
|
60
|
+
marketState: meta.marketState ?? "CLOSED",
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
return { symbol: sym.toUpperCase(), name: sym.toUpperCase(), price: 0, error: "Failed to fetch" };
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
return results.filter(Boolean) as any[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Get price history (OHLCV) for a symbol */
|
|
71
|
+
export async function yahooChart(symbol: string, range = "1mo", interval = "1d"): Promise<any> {
|
|
72
|
+
const data = await yfGet(`${YF_CHART}/${symbol.toUpperCase()}?range=${range}&interval=${interval}&includePrePost=false`);
|
|
73
|
+
const result = data?.chart?.result?.[0];
|
|
74
|
+
if (!result) throw new Error(`No chart data for ${symbol}`);
|
|
75
|
+
|
|
76
|
+
const timestamps = result.timestamp ?? [];
|
|
77
|
+
const quote = result.indicators?.quote?.[0] ?? {};
|
|
78
|
+
const closes: number[] = quote.close ?? [];
|
|
79
|
+
const opens: number[] = quote.open ?? [];
|
|
80
|
+
const highs: number[] = quote.high ?? [];
|
|
81
|
+
const lows: number[] = quote.low ?? [];
|
|
82
|
+
const volumes: number[] = quote.volume ?? [];
|
|
83
|
+
|
|
84
|
+
const meta = result.meta ?? {};
|
|
85
|
+
const prices = closes.filter((p: any) => p !== null && p !== undefined);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
symbol: meta.symbol ?? symbol.toUpperCase(),
|
|
89
|
+
name: meta.shortName ?? meta.longName ?? symbol,
|
|
90
|
+
currency: meta.currency ?? "USD",
|
|
91
|
+
exchange: meta.exchangeName ?? "",
|
|
92
|
+
range, interval,
|
|
93
|
+
dataPoints: prices.length,
|
|
94
|
+
current: prices.length > 0 ? +prices[prices.length - 1].toFixed(2) : null,
|
|
95
|
+
high: prices.length > 0 ? +Math.max(...prices).toFixed(2) : null,
|
|
96
|
+
low: prices.length > 0 ? +Math.min(...prices).toFixed(2) : null,
|
|
97
|
+
change: prices.length > 1 ? +((prices[prices.length - 1] - prices[0]) / prices[0]).toFixed(4) : null,
|
|
98
|
+
priceHistory: timestamps.map((t: number, i: number) => ({
|
|
99
|
+
t, p: closes[i], o: opens[i], h: highs[i], l: lows[i], v: volumes[i],
|
|
100
|
+
})).filter((p: any) => p.p !== null && p.p !== undefined),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Search for tickers */
|
|
105
|
+
export async function yahooSearch(query: string, limit = 8): Promise<any[]> {
|
|
106
|
+
const data = await yfGet(`${YF_SEARCH}?q=${encodeURIComponent(query)}"esCount=${limit}&newsCount=0`);
|
|
107
|
+
const quotes = data?.quotes ?? [];
|
|
108
|
+
|
|
109
|
+
return quotes.map((q: any) => ({
|
|
110
|
+
symbol: q.symbol,
|
|
111
|
+
name: q.shortname ?? q.longname ?? q.symbol,
|
|
112
|
+
type: q.quoteType ?? "",
|
|
113
|
+
exchange: q.exchange ?? "",
|
|
114
|
+
sector: q.sector ?? "",
|
|
115
|
+
industry: q.industry ?? "",
|
|
116
|
+
}));
|
|
117
|
+
}
|