horizon-code 0.6.0 → 0.6.2
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 +13 -6
- package/src/ai/system-prompt.ts +1 -1
- package/src/app.ts +47 -16
- package/src/chat/renderer.ts +106 -24
- package/src/components/code-panel.ts +15 -8
- package/src/platform/auth.ts +3 -3
- package/src/platform/session-sync.ts +3 -3
- package/src/platform/supabase.ts +10 -9
- package/src/platform/sync.ts +9 -1
- package/src/research/apis.ts +70 -25
- package/src/research/widgets.ts +34 -30
- package/src/strategy/prompts.ts +80 -557
package/src/platform/sync.ts
CHANGED
|
@@ -8,8 +8,11 @@ const pnlHistories: Map<string, number[]> = new Map();
|
|
|
8
8
|
|
|
9
9
|
export class PlatformSync {
|
|
10
10
|
private timer: ReturnType<typeof setInterval> | null = null;
|
|
11
|
+
private _polling = false;
|
|
11
12
|
|
|
12
13
|
async start(intervalMs = 30000): Promise<void> {
|
|
14
|
+
if (this.timer) return; // Prevent duplicate timers
|
|
15
|
+
|
|
13
16
|
const loggedIn = await isLoggedIn();
|
|
14
17
|
if (!loggedIn && !platform.authenticated) return;
|
|
15
18
|
|
|
@@ -22,6 +25,8 @@ export class PlatformSync {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
private async poll(): Promise<void> {
|
|
28
|
+
if (this._polling) return;
|
|
29
|
+
this._polling = true;
|
|
25
30
|
try {
|
|
26
31
|
const strategies = await platform.listStrategies();
|
|
27
32
|
|
|
@@ -88,8 +93,11 @@ export class PlatformSync {
|
|
|
88
93
|
});
|
|
89
94
|
|
|
90
95
|
store.update({ deployments, connection: "connected" });
|
|
91
|
-
} catch {
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error("[sync] poll failed:", e);
|
|
92
98
|
store.update({ connection: "disconnected" });
|
|
99
|
+
} finally {
|
|
100
|
+
this._polling = false;
|
|
93
101
|
}
|
|
94
102
|
}
|
|
95
103
|
}
|
package/src/research/apis.ts
CHANGED
|
@@ -6,12 +6,29 @@ const KALSHI = "https://api.elections.kalshi.com/trade-api/v2";
|
|
|
6
6
|
const DATA_API = "https://data-api.polymarket.com";
|
|
7
7
|
// EXA and CALA now proxied through API server — keys held server-side
|
|
8
8
|
|
|
9
|
-
async function get(url: string): Promise<any> {
|
|
10
|
-
const res = await fetch(url, {
|
|
9
|
+
async function get(url: string, timeoutMs = 10000): Promise<any> {
|
|
10
|
+
const res = await fetch(url, {
|
|
11
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
12
|
+
headers: { "Accept": "application/json", "User-Agent": "HorizonCode/0.6" },
|
|
13
|
+
});
|
|
11
14
|
if (!res.ok) throw new Error(`${res.status} ${url}`);
|
|
12
15
|
return res.json();
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
/** GET with retry — for flaky APIs like Kalshi */
|
|
19
|
+
async function getWithRetry(url: string, retries = 2, timeoutMs = 15000): Promise<any> {
|
|
20
|
+
let lastErr: Error | null = null;
|
|
21
|
+
for (let i = 0; i <= retries; i++) {
|
|
22
|
+
try {
|
|
23
|
+
return await get(url, timeoutMs);
|
|
24
|
+
} catch (e: any) {
|
|
25
|
+
lastErr = e;
|
|
26
|
+
if (i < retries) await new Promise((r) => setTimeout(r, 500 * (i + 1)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
throw lastErr;
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
async function post(url: string, body: any, headers: Record<string, string> = {}): Promise<any> {
|
|
16
33
|
const res = await fetch(url, {
|
|
17
34
|
method: "POST",
|
|
@@ -32,6 +49,28 @@ async function post(url: string, body: any, headers: Record<string, string> = {}
|
|
|
32
49
|
let _eventsCache: { data: any[]; ts: number } = { data: [], ts: 0 };
|
|
33
50
|
const CACHE_TTL = 60_000; // 60 seconds
|
|
34
51
|
|
|
52
|
+
/** Strip Gamma event to only the fields we use — reduces 16MB → ~200KB in memory */
|
|
53
|
+
function slimEvent(e: any): any {
|
|
54
|
+
return {
|
|
55
|
+
title: e.title, slug: e.slug, id: e.id,
|
|
56
|
+
description: (e.description ?? "").slice(0, 500),
|
|
57
|
+
volume: e.volume, liquidity: e.liquidity,
|
|
58
|
+
volume24hr: e.volume24hr, openInterest: e.openInterest,
|
|
59
|
+
startDate: e.startDate, endDate: e.endDate,
|
|
60
|
+
oneDayPriceChange: e.oneDayPriceChange,
|
|
61
|
+
tags: e.tags,
|
|
62
|
+
markets: (e.markets ?? []).map((m: any) => ({
|
|
63
|
+
question: m.question, conditionId: m.conditionId,
|
|
64
|
+
outcomePrices: m.outcomePrices,
|
|
65
|
+
volume: m.volume, liquidity: m.liquidity,
|
|
66
|
+
bestBid: m.bestBid, bestAsk: m.bestAsk,
|
|
67
|
+
lastTradePrice: m.lastTradePrice,
|
|
68
|
+
oneDayPriceChange: m.oneDayPriceChange,
|
|
69
|
+
clobTokenIds: m.clobTokenIds,
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
35
74
|
async function fetchAllEvents(): Promise<any[]> {
|
|
36
75
|
if (_eventsCache.data.length > 0 && Date.now() - _eventsCache.ts < CACHE_TTL) {
|
|
37
76
|
return _eventsCache.data;
|
|
@@ -39,11 +78,14 @@ async function fetchAllEvents(): Promise<any[]> {
|
|
|
39
78
|
const params = new URLSearchParams({
|
|
40
79
|
active: "true", closed: "false",
|
|
41
80
|
order: "volume24hr", ascending: "false",
|
|
42
|
-
limit: "
|
|
81
|
+
limit: "100",
|
|
43
82
|
});
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
83
|
+
const raw = await get(`${GAMMA}/events?${params}`).catch(() => []);
|
|
84
|
+
const data = (raw ?? []).map(slimEvent);
|
|
85
|
+
if (data.length > 0) {
|
|
86
|
+
_eventsCache = { data, ts: Date.now() };
|
|
87
|
+
}
|
|
88
|
+
return data;
|
|
47
89
|
}
|
|
48
90
|
|
|
49
91
|
export async function gammaEvents(opts: { query?: string; limit?: number } = {}): Promise<any[]> {
|
|
@@ -111,7 +153,7 @@ export async function gammaEvents(opts: { query?: string; limit?: number } = {})
|
|
|
111
153
|
}
|
|
112
154
|
|
|
113
155
|
export async function gammaEventDetail(slug: string): Promise<any> {
|
|
114
|
-
const events = await get(`${GAMMA}/events?slug=${slug}`);
|
|
156
|
+
const events = await get(`${GAMMA}/events?slug=${encodeURIComponent(slug)}`);
|
|
115
157
|
const event = events?.[0];
|
|
116
158
|
if (!event) throw new Error(`Event not found: ${slug}`);
|
|
117
159
|
|
|
@@ -149,7 +191,7 @@ export async function gammaEventDetail(slug: string): Promise<any> {
|
|
|
149
191
|
}
|
|
150
192
|
|
|
151
193
|
export async function clobPriceHistory(slug: string, interval = "1w", fidelity = 60): Promise<any> {
|
|
152
|
-
const events = await get(`${GAMMA}/events?slug=${slug}`);
|
|
194
|
+
const events = await get(`${GAMMA}/events?slug=${encodeURIComponent(slug)}`);
|
|
153
195
|
const market = events?.[0]?.markets?.[0];
|
|
154
196
|
if (!market) throw new Error(`Market not found: ${slug}`);
|
|
155
197
|
|
|
@@ -174,7 +216,7 @@ export async function clobPriceHistory(slug: string, interval = "1w", fidelity =
|
|
|
174
216
|
}
|
|
175
217
|
|
|
176
218
|
export async function clobOrderBook(slug: string, marketIndex = 0, outcomeIndex = 0): Promise<any> {
|
|
177
|
-
const events = await get(`${GAMMA}/events?slug=${slug}`);
|
|
219
|
+
const events = await get(`${GAMMA}/events?slug=${encodeURIComponent(slug)}`);
|
|
178
220
|
const market = events?.[0]?.markets?.[marketIndex];
|
|
179
221
|
if (!market) throw new Error(`Market not found: ${slug}`);
|
|
180
222
|
|
|
@@ -198,7 +240,7 @@ export async function clobOrderBook(slug: string, marketIndex = 0, outcomeIndex
|
|
|
198
240
|
}
|
|
199
241
|
|
|
200
242
|
export async function polymarketTrades(slug: string): Promise<any> {
|
|
201
|
-
const events = await get(`${GAMMA}/events?slug=${slug}`);
|
|
243
|
+
const events = await get(`${GAMMA}/events?slug=${encodeURIComponent(slug)}`);
|
|
202
244
|
const market = events?.[0]?.markets?.[0];
|
|
203
245
|
if (!market) throw new Error(`Market not found: ${slug}`);
|
|
204
246
|
|
|
@@ -233,27 +275,27 @@ export async function polymarketTrades(slug: string): Promise<any> {
|
|
|
233
275
|
|
|
234
276
|
/** Get recent trades for a market (up to 500) */
|
|
235
277
|
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(() => []);
|
|
278
|
+
const data = await get(`${DATA_API}/trades?market=${encodeURIComponent(conditionId)}&limit=${Math.min(limit, 500)}`).catch(() => []);
|
|
237
279
|
return data ?? [];
|
|
238
280
|
}
|
|
239
281
|
|
|
240
282
|
/** Get trades for a specific wallet (up to 500) */
|
|
241
283
|
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}`;
|
|
284
|
+
let url = `${DATA_API}/trades?maker_address=${encodeURIComponent(address)}&limit=${Math.min(limit, 500)}`;
|
|
285
|
+
if (conditionId) url += `&market=${encodeURIComponent(conditionId)}`;
|
|
244
286
|
const data = await get(url).catch(() => []);
|
|
245
287
|
return data ?? [];
|
|
246
288
|
}
|
|
247
289
|
|
|
248
290
|
/** Get open positions for a wallet */
|
|
249
291
|
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(() => []);
|
|
292
|
+
const data = await get(`${DATA_API}/positions?user=${encodeURIComponent(address)}&limit=${Math.min(limit, 500)}&sort_by=TOKENS`).catch(() => []);
|
|
251
293
|
return data ?? [];
|
|
252
294
|
}
|
|
253
295
|
|
|
254
296
|
/** Get wallet profile from Gamma API */
|
|
255
297
|
export async function getWalletProfile(address: string): Promise<any> {
|
|
256
|
-
const data = await get(`${GAMMA}/users?address=${address}`).catch(() => null);
|
|
298
|
+
const data = await get(`${GAMMA}/users?address=${encodeURIComponent(address)}`).catch(() => null);
|
|
257
299
|
const user = Array.isArray(data) ? data[0] : data;
|
|
258
300
|
return user ? {
|
|
259
301
|
address, name: user.pseudonym ?? user.name ?? "Anonymous",
|
|
@@ -264,7 +306,7 @@ export async function getWalletProfile(address: string): Promise<any> {
|
|
|
264
306
|
|
|
265
307
|
/** Resolve market slug → conditionId */
|
|
266
308
|
export async function resolveConditionId(slug: string): Promise<{ conditionId: string; title: string; market: any }> {
|
|
267
|
-
const events = await get(`${GAMMA}/events?slug=${slug}`);
|
|
309
|
+
const events = await get(`${GAMMA}/events?slug=${encodeURIComponent(slug)}`);
|
|
268
310
|
const market = events?.[0]?.markets?.[0];
|
|
269
311
|
if (!market) throw new Error(`Market not found: ${slug}`);
|
|
270
312
|
return { conditionId: market.conditionId, title: events[0].title, market };
|
|
@@ -312,8 +354,8 @@ function parseKalshiMarket(m: any): any {
|
|
|
312
354
|
let _kalshiCache: { data: any[]; ts: number } = { data: [], ts: 0 };
|
|
313
355
|
const KALSHI_CACHE_TTL = 60_000;
|
|
314
356
|
|
|
315
|
-
/** Fetch events from Kalshi —
|
|
316
|
-
async function fetchKalshiEvents(limit =
|
|
357
|
+
/** Fetch events from Kalshi — single page to avoid slow pagination */
|
|
358
|
+
async function fetchKalshiEvents(limit = 100): Promise<any[]> {
|
|
317
359
|
if (_kalshiCache.data.length > 0 && Date.now() - _kalshiCache.ts < KALSHI_CACHE_TTL) {
|
|
318
360
|
return _kalshiCache.data;
|
|
319
361
|
}
|
|
@@ -330,7 +372,7 @@ async function fetchKalshiEvents(limit = 200): Promise<any[]> {
|
|
|
330
372
|
});
|
|
331
373
|
if (cursor) params.set("cursor", cursor);
|
|
332
374
|
|
|
333
|
-
const data = await
|
|
375
|
+
const data = await getWithRetry(`${KALSHI}/events?${params}`).catch(() => ({ events: [], cursor: "" }));
|
|
334
376
|
const events = data?.events ?? [];
|
|
335
377
|
if (events.length === 0) break;
|
|
336
378
|
|
|
@@ -339,7 +381,10 @@ async function fetchKalshiEvents(limit = 200): Promise<any[]> {
|
|
|
339
381
|
if (!cursor) break;
|
|
340
382
|
}
|
|
341
383
|
|
|
342
|
-
|
|
384
|
+
// Only cache successful non-empty results — don't pollute cache with failures
|
|
385
|
+
if (all.length > 0) {
|
|
386
|
+
_kalshiCache = { data: all, ts: Date.now() };
|
|
387
|
+
}
|
|
343
388
|
return all;
|
|
344
389
|
}
|
|
345
390
|
|
|
@@ -383,7 +428,7 @@ export async function kalshiEvents(opts: { query?: string; limit?: number } = {}
|
|
|
383
428
|
status: "open", with_nested_markets: "true",
|
|
384
429
|
series_ticker: seriesTicker, limit: String(limit),
|
|
385
430
|
});
|
|
386
|
-
const data = await
|
|
431
|
+
const data = await getWithRetry(`${KALSHI}/events?${params}`);
|
|
387
432
|
const events = data?.events ?? [];
|
|
388
433
|
if (events.length > 0) return events.slice(0, limit).map(formatKalshiEvent);
|
|
389
434
|
} catch {}
|
|
@@ -413,7 +458,7 @@ export async function kalshiEvents(opts: { query?: string; limit?: number } = {}
|
|
|
413
458
|
status: "open", with_nested_markets: "true",
|
|
414
459
|
series_ticker: query.toUpperCase(), limit: String(limit),
|
|
415
460
|
});
|
|
416
|
-
const data = await
|
|
461
|
+
const data = await getWithRetry(`${KALSHI}/events?${params}`);
|
|
417
462
|
events = data?.events ?? [];
|
|
418
463
|
} catch {}
|
|
419
464
|
}
|
|
@@ -443,7 +488,7 @@ function formatKalshiEvent(e: any): any {
|
|
|
443
488
|
}
|
|
444
489
|
|
|
445
490
|
export async function kalshiEventDetail(ticker: string): Promise<any> {
|
|
446
|
-
const data = await
|
|
491
|
+
const data = await getWithRetry(`${KALSHI}/events/${encodeURIComponent(ticker)}?with_nested_markets=true`);
|
|
447
492
|
const event = data?.event ?? data;
|
|
448
493
|
if (!event) throw new Error(`Kalshi event not found: ${ticker}`);
|
|
449
494
|
|
|
@@ -457,7 +502,7 @@ export async function kalshiEventDetail(ticker: string): Promise<any> {
|
|
|
457
502
|
}
|
|
458
503
|
|
|
459
504
|
export async function kalshiOrderBook(ticker: string): Promise<any> {
|
|
460
|
-
const book = await
|
|
505
|
+
const book = await getWithRetry(`${KALSHI}/markets/${encodeURIComponent(ticker)}/orderbook`);
|
|
461
506
|
const ob = book?.orderbook ?? {};
|
|
462
507
|
// yes array: [[price_cents, size], ...] — bids for YES outcome
|
|
463
508
|
// no array: [[price_cents, size], ...] — asks (complement pricing)
|
|
@@ -489,7 +534,7 @@ export async function kalshiPriceHistory(ticker: string, period = "1w"): Promise
|
|
|
489
534
|
const intervalMap: Record<string, number> = { "1h": 1, "6h": 1, "1d": 60, "1w": 60, "1m": 1440, "max": 1440 };
|
|
490
535
|
const periodInterval = intervalMap[period] ?? 60;
|
|
491
536
|
|
|
492
|
-
const data = await
|
|
537
|
+
const data = await getWithRetry(`${KALSHI}/markets/${encodeURIComponent(ticker)}/candlesticks?period_interval=${periodInterval}`).catch(() => ({ candlesticks: [] }));
|
|
493
538
|
const candles = data?.candlesticks ?? [];
|
|
494
539
|
|
|
495
540
|
// Prices can be dollar strings or cent integers — normalize to decimal
|
package/src/research/widgets.ts
CHANGED
|
@@ -601,26 +601,28 @@ function renderKalshiList(data: any, renderer: CliRenderer): BoxRenderable {
|
|
|
601
601
|
exchangeBadge(box, renderer, "kalshi", "Markets");
|
|
602
602
|
const events = Array.isArray(data) ? data : (data?.events ?? []);
|
|
603
603
|
|
|
604
|
+
// Helper: truncate with ellipsis
|
|
605
|
+
const trunc = (s: string, max: number) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
606
|
+
// Helper: format price as cents
|
|
607
|
+
const fmtCents = (v: any) => v != null ? `${(+v * 100).toFixed(0)}c` : "--";
|
|
608
|
+
|
|
604
609
|
for (let idx = 0; idx < Math.min(events.length, 8); idx++) {
|
|
605
610
|
const e = events[idx];
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
const
|
|
611
|
+
const m = e.markets?.[0];
|
|
612
|
+
const title = trunc(e.title ?? "", 36);
|
|
613
|
+
const bid = fmtCents(m?.yesBid ?? m?.lastPrice);
|
|
614
|
+
const ask = fmtCents(m?.yesAsk);
|
|
615
|
+
const vol = m?.volume ? `${(m.volume / 1000).toFixed(0)}k` : "";
|
|
616
|
+
const cat = trunc(e.category ?? "", 10);
|
|
610
617
|
|
|
611
|
-
const row = new BoxRenderable(renderer, { id: uid(), flexDirection: "row"
|
|
612
|
-
row.add(new TextRenderable(renderer, { id: uid(), content: `${(idx + 1).toString().padStart(2)}
|
|
613
|
-
row.add(new TextRenderable(renderer, { id: uid(), content:
|
|
614
|
-
row.add(new TextRenderable(renderer, { id: uid(), content:
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
row.add(new
|
|
618
|
-
row.add(new TextRenderable(renderer, { id: uid(), content: e.category ?? "", fg: COLORS.textMuted }));
|
|
618
|
+
const row = new BoxRenderable(renderer, { id: uid(), flexDirection: "row" });
|
|
619
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: `${(idx + 1).toString().padStart(2)} `, fg: COLORS.textMuted }));
|
|
620
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: title.padEnd(37), fg: COLORS.text }));
|
|
621
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: bid.padStart(5), fg: COLORS.success }));
|
|
622
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: `/${ask.padEnd(5)}`, fg: COLORS.error }));
|
|
623
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: vol.padStart(6), fg: COLORS.textMuted }));
|
|
624
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: ` ${cat}`, fg: COLORS.borderDim }));
|
|
619
625
|
box.add(row);
|
|
620
|
-
|
|
621
|
-
if (e.ticker) {
|
|
622
|
-
box.add(new TextRenderable(renderer, { id: uid(), content: ` ${e.ticker}`, fg: COLORS.borderDim }));
|
|
623
|
-
}
|
|
624
626
|
}
|
|
625
627
|
|
|
626
628
|
return box;
|
|
@@ -773,26 +775,28 @@ function renderProbability(data: any, renderer: CliRenderer): BoxRenderable {
|
|
|
773
775
|
function renderKalshiDetail(data: any, renderer: CliRenderer): BoxRenderable {
|
|
774
776
|
const box = new BoxRenderable(renderer, { id: uid(), flexDirection: "column", marginBottom: 1 });
|
|
775
777
|
|
|
776
|
-
|
|
778
|
+
const trunc = (s: string, max: number) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
779
|
+
const fmtCents = (v: any) => v != null ? `${(+v * 100).toFixed(0)}c` : "--";
|
|
780
|
+
|
|
781
|
+
exchangeBadge(box, renderer, "kalshi", trunc(data.title ?? data.ticker ?? "", 50));
|
|
777
782
|
if (data.category) {
|
|
778
783
|
box.add(new TextRenderable(renderer, { id: uid(), content: ` ${data.category} ${data.ticker ?? ""}`, fg: COLORS.textMuted }));
|
|
779
784
|
}
|
|
780
785
|
sep(box, renderer);
|
|
781
786
|
|
|
782
787
|
for (const m of (data.markets ?? []).slice(0, 8)) {
|
|
783
|
-
const row = new BoxRenderable(renderer, { id: uid(), flexDirection: "row"
|
|
784
|
-
const title = (m.title ?? m.ticker ?? ""
|
|
785
|
-
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
}
|
|
788
|
+
const row = new BoxRenderable(renderer, { id: uid(), flexDirection: "row" });
|
|
789
|
+
const title = trunc(m.title ?? m.ticker ?? "", 32);
|
|
790
|
+
const bid = fmtCents(m.yesBid ?? m.lastPrice);
|
|
791
|
+
const ask = fmtCents(m.yesAsk);
|
|
792
|
+
const spread = m.spread != null ? `${(+m.spread * 100).toFixed(0)}c` : "";
|
|
793
|
+
const vol = m.volume ? `${(m.volume / 1000).toFixed(0)}k` : "";
|
|
794
|
+
|
|
795
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: title.padEnd(33), fg: COLORS.text }));
|
|
796
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: bid.padStart(5), fg: COLORS.success }));
|
|
797
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: `/${ask.padEnd(5)}`, fg: COLORS.error }));
|
|
798
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: spread.padStart(5), fg: COLORS.textMuted }));
|
|
799
|
+
row.add(new TextRenderable(renderer, { id: uid(), content: vol.padStart(6), fg: COLORS.textMuted }));
|
|
796
800
|
box.add(row);
|
|
797
801
|
}
|
|
798
802
|
|