@spfunctions/cli 1.4.5 → 1.5.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.
@@ -0,0 +1,424 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const polymarket_js_1 = require("./polymarket.js");
5
+ const mockFetch = vitest_1.vi.fn();
6
+ vitest_1.vi.stubGlobal('fetch', mockFetch);
7
+ (0, vitest_1.beforeEach)(() => {
8
+ mockFetch.mockReset();
9
+ });
10
+ // ============================================================================
11
+ // PURE FUNCTIONS (no network)
12
+ // ============================================================================
13
+ (0, vitest_1.describe)('computeOrderbookDepth', () => {
14
+ (0, vitest_1.it)('computes best bid/ask and spread in cents', () => {
15
+ const raw = {
16
+ market: 'cond1', asset_id: 'tok1', timestamp: '123', hash: 'h',
17
+ bids: [
18
+ { price: '0.45', size: '200' },
19
+ { price: '0.44', size: '150' },
20
+ { price: '0.43', size: '100' },
21
+ ],
22
+ asks: [
23
+ { price: '0.48', size: '300' },
24
+ { price: '0.49', size: '250' },
25
+ { price: '0.50', size: '100' },
26
+ ],
27
+ };
28
+ const depth = (0, polymarket_js_1.computeOrderbookDepth)(raw);
29
+ (0, vitest_1.expect)(depth.bestBid).toBe(45);
30
+ (0, vitest_1.expect)(depth.bestAsk).toBe(48);
31
+ (0, vitest_1.expect)(depth.spread).toBe(3);
32
+ });
33
+ (0, vitest_1.it)('computes top-3 depth', () => {
34
+ const raw = {
35
+ market: 'c', asset_id: 't', timestamp: '0', hash: '',
36
+ bids: [
37
+ { price: '0.50', size: '100' },
38
+ { price: '0.49', size: '200' },
39
+ { price: '0.48', size: '300' },
40
+ { price: '0.47', size: '400' }, // 4th level, not counted
41
+ ],
42
+ asks: [
43
+ { price: '0.52', size: '50' },
44
+ { price: '0.53', size: '60' },
45
+ { price: '0.54', size: '70' },
46
+ ],
47
+ };
48
+ const depth = (0, polymarket_js_1.computeOrderbookDepth)(raw);
49
+ (0, vitest_1.expect)(depth.bidDepthTop3).toBe(600); // 100+200+300
50
+ (0, vitest_1.expect)(depth.askDepthTop3).toBe(180); // 50+60+70
51
+ (0, vitest_1.expect)(depth.totalBidDepth).toBe(1000); // includes 4th level
52
+ (0, vitest_1.expect)(depth.totalAskDepth).toBe(180);
53
+ });
54
+ (0, vitest_1.it)('sorts bids descending and asks ascending', () => {
55
+ const raw = {
56
+ market: 'c', asset_id: 't', timestamp: '0', hash: '',
57
+ bids: [
58
+ { price: '0.40', size: '100' },
59
+ { price: '0.50', size: '200' }, // best bid (highest)
60
+ ],
61
+ asks: [
62
+ { price: '0.60', size: '100' },
63
+ { price: '0.55', size: '200' }, // best ask (lowest)
64
+ ],
65
+ };
66
+ const depth = (0, polymarket_js_1.computeOrderbookDepth)(raw);
67
+ (0, vitest_1.expect)(depth.bestBid).toBe(50);
68
+ (0, vitest_1.expect)(depth.bestAsk).toBe(55);
69
+ (0, vitest_1.expect)(depth.spread).toBe(5);
70
+ });
71
+ (0, vitest_1.it)('handles empty orderbook', () => {
72
+ const raw = {
73
+ market: 'c', asset_id: 't', timestamp: '0', hash: '',
74
+ bids: [], asks: [],
75
+ };
76
+ const depth = (0, polymarket_js_1.computeOrderbookDepth)(raw);
77
+ (0, vitest_1.expect)(depth.bestBid).toBe(0);
78
+ (0, vitest_1.expect)(depth.bestAsk).toBe(100);
79
+ (0, vitest_1.expect)(depth.spread).toBe(100);
80
+ (0, vitest_1.expect)(depth.bidDepthTop3).toBe(0);
81
+ (0, vitest_1.expect)(depth.askDepthTop3).toBe(0);
82
+ (0, vitest_1.expect)(depth.liquidityScore).toBe('low');
83
+ });
84
+ (0, vitest_1.it)('assigns correct liquidity scores', () => {
85
+ // high: spread ≤ 2¢, depth ≥ 500
86
+ const high = {
87
+ market: 'c', asset_id: 't', timestamp: '0', hash: '',
88
+ bids: [{ price: '0.49', size: '300' }],
89
+ asks: [{ price: '0.51', size: '300' }],
90
+ };
91
+ (0, vitest_1.expect)((0, polymarket_js_1.computeOrderbookDepth)(high).liquidityScore).toBe('high');
92
+ // medium: spread ≤ 5¢, depth ≥ 100
93
+ const medium = {
94
+ market: 'c', asset_id: 't', timestamp: '0', hash: '',
95
+ bids: [{ price: '0.47', size: '60' }],
96
+ asks: [{ price: '0.52', size: '60' }],
97
+ };
98
+ (0, vitest_1.expect)((0, polymarket_js_1.computeOrderbookDepth)(medium).liquidityScore).toBe('medium');
99
+ // low: wide spread
100
+ const low = {
101
+ market: 'c', asset_id: 't', timestamp: '0', hash: '',
102
+ bids: [{ price: '0.30', size: '10' }],
103
+ asks: [{ price: '0.70', size: '10' }],
104
+ };
105
+ (0, vitest_1.expect)((0, polymarket_js_1.computeOrderbookDepth)(low).liquidityScore).toBe('low');
106
+ });
107
+ });
108
+ (0, vitest_1.describe)('scoreLiquidity', () => {
109
+ (0, vitest_1.it)('returns high for tight spread + deep book', () => {
110
+ (0, vitest_1.expect)((0, polymarket_js_1.scoreLiquidity)(1, 600)).toBe('high');
111
+ (0, vitest_1.expect)((0, polymarket_js_1.scoreLiquidity)(2, 500)).toBe('high');
112
+ });
113
+ (0, vitest_1.it)('returns medium for moderate spread + depth', () => {
114
+ (0, vitest_1.expect)((0, polymarket_js_1.scoreLiquidity)(3, 200)).toBe('medium');
115
+ (0, vitest_1.expect)((0, polymarket_js_1.scoreLiquidity)(5, 100)).toBe('medium');
116
+ });
117
+ (0, vitest_1.it)('returns low for wide spread or thin book', () => {
118
+ (0, vitest_1.expect)((0, polymarket_js_1.scoreLiquidity)(6, 1000)).toBe('low');
119
+ (0, vitest_1.expect)((0, polymarket_js_1.scoreLiquidity)(1, 50)).toBe('low');
120
+ (0, vitest_1.expect)((0, polymarket_js_1.scoreLiquidity)(10, 10)).toBe('low');
121
+ });
122
+ });
123
+ (0, vitest_1.describe)('parseClobTokenIds', () => {
124
+ (0, vitest_1.it)('parses valid JSON array', () => {
125
+ const result = (0, polymarket_js_1.parseClobTokenIds)('["tok_yes_123","tok_no_456"]');
126
+ (0, vitest_1.expect)(result).toEqual(['tok_yes_123', 'tok_no_456']);
127
+ });
128
+ (0, vitest_1.it)('returns null for invalid JSON', () => {
129
+ (0, vitest_1.expect)((0, polymarket_js_1.parseClobTokenIds)('not json')).toBeNull();
130
+ });
131
+ (0, vitest_1.it)('returns null for array with < 2 elements', () => {
132
+ (0, vitest_1.expect)((0, polymarket_js_1.parseClobTokenIds)('["only_one"]')).toBeNull();
133
+ });
134
+ (0, vitest_1.it)('returns null for empty string', () => {
135
+ (0, vitest_1.expect)((0, polymarket_js_1.parseClobTokenIds)('')).toBeNull();
136
+ });
137
+ });
138
+ (0, vitest_1.describe)('parseOutcomes', () => {
139
+ (0, vitest_1.it)('parses yes/no outcomes', () => {
140
+ (0, vitest_1.expect)((0, polymarket_js_1.parseOutcomes)('["Yes","No"]')).toEqual(['Yes', 'No']);
141
+ });
142
+ (0, vitest_1.it)('parses multi-outcome', () => {
143
+ (0, vitest_1.expect)((0, polymarket_js_1.parseOutcomes)('["Trump","Biden","Other"]')).toEqual(['Trump', 'Biden', 'Other']);
144
+ });
145
+ (0, vitest_1.it)('returns empty for invalid', () => {
146
+ (0, vitest_1.expect)((0, polymarket_js_1.parseOutcomes)('bad')).toEqual([]);
147
+ });
148
+ });
149
+ (0, vitest_1.describe)('parseOutcomePrices', () => {
150
+ (0, vitest_1.it)('parses price array', () => {
151
+ (0, vitest_1.expect)((0, polymarket_js_1.parseOutcomePrices)('[0.65, 0.35]')).toEqual([0.65, 0.35]);
152
+ });
153
+ (0, vitest_1.it)('returns empty for invalid', () => {
154
+ (0, vitest_1.expect)((0, polymarket_js_1.parseOutcomePrices)('')).toEqual([]);
155
+ });
156
+ });
157
+ (0, vitest_1.describe)('toCents', () => {
158
+ (0, vitest_1.it)('converts dollars to cents', () => {
159
+ (0, vitest_1.expect)((0, polymarket_js_1.toCents)(0.55)).toBe(55);
160
+ (0, vitest_1.expect)((0, polymarket_js_1.toCents)(1.0)).toBe(100);
161
+ (0, vitest_1.expect)((0, polymarket_js_1.toCents)(0)).toBe(0);
162
+ (0, vitest_1.expect)((0, polymarket_js_1.toCents)(0.123)).toBe(12); // rounds
163
+ });
164
+ });
165
+ (0, vitest_1.describe)('polymarketUrl', () => {
166
+ (0, vitest_1.it)('builds correct URL', () => {
167
+ (0, vitest_1.expect)((0, polymarket_js_1.polymarketUrl)('fed-decision')).toBe('https://polymarket.com/event/fed-decision');
168
+ });
169
+ });
170
+ // ============================================================================
171
+ // API FUNCTIONS (mocked fetch)
172
+ // ============================================================================
173
+ (0, vitest_1.describe)('polymarketSearch', () => {
174
+ (0, vitest_1.it)('calls Gamma API with query params', async () => {
175
+ mockFetch.mockResolvedValue({
176
+ ok: true,
177
+ json: () => Promise.resolve({ events: [{ id: '1', title: 'Test' }] }),
178
+ });
179
+ const events = await (0, polymarket_js_1.polymarketSearch)('recession', 5);
180
+ (0, vitest_1.expect)(events).toHaveLength(1);
181
+ (0, vitest_1.expect)(events[0].title).toBe('Test');
182
+ const url = mockFetch.mock.calls[0][0];
183
+ (0, vitest_1.expect)(url).toContain('gamma-api.polymarket.com/public-search');
184
+ (0, vitest_1.expect)(url).toContain('q=recession');
185
+ (0, vitest_1.expect)(url).toContain('limit_per_type=5');
186
+ });
187
+ (0, vitest_1.it)('throws on error response', async () => {
188
+ mockFetch.mockResolvedValue({ ok: false, status: 500 });
189
+ await (0, vitest_1.expect)((0, polymarket_js_1.polymarketSearch)('test')).rejects.toThrow('500');
190
+ });
191
+ });
192
+ (0, vitest_1.describe)('polymarketListEvents', () => {
193
+ (0, vitest_1.it)('fetches active events with pagination', async () => {
194
+ mockFetch.mockResolvedValue({
195
+ ok: true,
196
+ json: () => Promise.resolve([{ id: '1' }, { id: '2' }]),
197
+ });
198
+ const events = await (0, polymarket_js_1.polymarketListEvents)({ limit: 10, offset: 20, order: 'volume_24hr' });
199
+ const url = mockFetch.mock.calls[0][0];
200
+ (0, vitest_1.expect)(url).toContain('active=true');
201
+ (0, vitest_1.expect)(url).toContain('limit=10');
202
+ (0, vitest_1.expect)(url).toContain('offset=20');
203
+ (0, vitest_1.expect)(url).toContain('order=volume_24hr');
204
+ (0, vitest_1.expect)(events).toHaveLength(2);
205
+ });
206
+ });
207
+ (0, vitest_1.describe)('polymarketGetEvent', () => {
208
+ (0, vitest_1.it)('fetches single event by ID', async () => {
209
+ mockFetch.mockResolvedValue({
210
+ ok: true,
211
+ json: () => Promise.resolve({ id: '42', title: 'Fed Decision' }),
212
+ });
213
+ const event = await (0, polymarket_js_1.polymarketGetEvent)('42');
214
+ (0, vitest_1.expect)(event.id).toBe('42');
215
+ (0, vitest_1.expect)(mockFetch.mock.calls[0][0]).toContain('/events/42');
216
+ });
217
+ });
218
+ (0, vitest_1.describe)('polymarketGetMarket', () => {
219
+ (0, vitest_1.it)('fetches single market', async () => {
220
+ mockFetch.mockResolvedValue({
221
+ ok: true,
222
+ json: () => Promise.resolve({ id: 'm1', question: 'Will X happen?' }),
223
+ });
224
+ const market = await (0, polymarket_js_1.polymarketGetMarket)('m1');
225
+ (0, vitest_1.expect)(market.question).toBe('Will X happen?');
226
+ });
227
+ });
228
+ (0, vitest_1.describe)('polymarketListTags', () => {
229
+ (0, vitest_1.it)('returns tags array', async () => {
230
+ mockFetch.mockResolvedValue({
231
+ ok: true,
232
+ json: () => Promise.resolve([
233
+ { id: '1', label: 'Politics', slug: 'politics' },
234
+ { id: '2', label: 'Crypto', slug: 'crypto' },
235
+ ]),
236
+ });
237
+ const tags = await (0, polymarket_js_1.polymarketListTags)();
238
+ (0, vitest_1.expect)(tags).toHaveLength(2);
239
+ (0, vitest_1.expect)(tags[0].label).toBe('Politics');
240
+ });
241
+ });
242
+ (0, vitest_1.describe)('polymarketGetOrderbook', () => {
243
+ (0, vitest_1.it)('fetches raw orderbook', async () => {
244
+ mockFetch.mockResolvedValue({
245
+ ok: true,
246
+ json: () => Promise.resolve({
247
+ market: 'cond1', asset_id: 'tok1', timestamp: '123', hash: 'h',
248
+ bids: [{ price: '0.45', size: '100' }],
249
+ asks: [{ price: '0.55', size: '100' }],
250
+ }),
251
+ });
252
+ const ob = await (0, polymarket_js_1.polymarketGetOrderbook)('tok1');
253
+ (0, vitest_1.expect)(ob).not.toBeNull();
254
+ (0, vitest_1.expect)(ob.bids).toHaveLength(1);
255
+ (0, vitest_1.expect)(mockFetch.mock.calls[0][0]).toContain('clob.polymarket.com/book?token_id=tok1');
256
+ });
257
+ (0, vitest_1.it)('returns null on error', async () => {
258
+ mockFetch.mockResolvedValue({ ok: false, status: 404 });
259
+ const ob = await (0, polymarket_js_1.polymarketGetOrderbook)('bad');
260
+ (0, vitest_1.expect)(ob).toBeNull();
261
+ });
262
+ (0, vitest_1.it)('returns null on API error response', async () => {
263
+ mockFetch.mockResolvedValue({
264
+ ok: true,
265
+ json: () => Promise.resolve({ error: 'No orderbook exists' }),
266
+ });
267
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetOrderbook)('bad')).toBeNull();
268
+ });
269
+ (0, vitest_1.it)('returns null on network error', async () => {
270
+ mockFetch.mockRejectedValue(new Error('network'));
271
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetOrderbook)('bad')).toBeNull();
272
+ });
273
+ });
274
+ (0, vitest_1.describe)('polymarketGetOrderbookWithDepth', () => {
275
+ (0, vitest_1.it)('fetches and computes depth in one call', async () => {
276
+ mockFetch.mockResolvedValue({
277
+ ok: true,
278
+ json: () => Promise.resolve({
279
+ market: 'c', asset_id: 't', timestamp: '0', hash: '',
280
+ bids: [{ price: '0.49', size: '300' }],
281
+ asks: [{ price: '0.51', size: '400' }],
282
+ }),
283
+ });
284
+ const depth = await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)('tok1');
285
+ (0, vitest_1.expect)(depth).not.toBeNull();
286
+ (0, vitest_1.expect)(depth.bestBid).toBe(49);
287
+ (0, vitest_1.expect)(depth.bestAsk).toBe(51);
288
+ (0, vitest_1.expect)(depth.spread).toBe(2);
289
+ (0, vitest_1.expect)(depth.liquidityScore).toBe('high');
290
+ });
291
+ (0, vitest_1.it)('returns null when orderbook unavailable', async () => {
292
+ mockFetch.mockResolvedValue({ ok: false });
293
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)('bad')).toBeNull();
294
+ });
295
+ });
296
+ (0, vitest_1.describe)('polymarketGetMidpoint', () => {
297
+ (0, vitest_1.it)('returns midpoint as number', async () => {
298
+ mockFetch.mockResolvedValue({
299
+ ok: true,
300
+ json: () => Promise.resolve({ mid: '0.52' }),
301
+ });
302
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetMidpoint)('tok1')).toBe(0.52);
303
+ });
304
+ (0, vitest_1.it)('returns null on error', async () => {
305
+ mockFetch.mockResolvedValue({ ok: false });
306
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetMidpoint)('bad')).toBeNull();
307
+ });
308
+ });
309
+ (0, vitest_1.describe)('polymarketGetSpread', () => {
310
+ (0, vitest_1.it)('returns spread as number', async () => {
311
+ mockFetch.mockResolvedValue({
312
+ ok: true,
313
+ json: () => Promise.resolve({ spread: '0.03' }),
314
+ });
315
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetSpread)('tok1')).toBe(0.03);
316
+ });
317
+ });
318
+ (0, vitest_1.describe)('polymarketGetMidpoints', () => {
319
+ (0, vitest_1.it)('posts batch of token IDs', async () => {
320
+ mockFetch.mockResolvedValue({
321
+ ok: true,
322
+ json: () => Promise.resolve({ tok1: 0.5, tok2: 0.7 }),
323
+ });
324
+ const result = await (0, polymarket_js_1.polymarketGetMidpoints)(['tok1', 'tok2']);
325
+ (0, vitest_1.expect)(result.tok1).toBe(0.5);
326
+ (0, vitest_1.expect)(result.tok2).toBe(0.7);
327
+ (0, vitest_1.expect)(mockFetch.mock.calls[0][1].method).toBe('POST');
328
+ });
329
+ (0, vitest_1.it)('returns empty for empty input', async () => {
330
+ const result = await (0, polymarket_js_1.polymarketGetMidpoints)([]);
331
+ (0, vitest_1.expect)(result).toEqual({});
332
+ (0, vitest_1.expect)(mockFetch).not.toHaveBeenCalled();
333
+ });
334
+ });
335
+ (0, vitest_1.describe)('polymarketGetOHLC', () => {
336
+ (0, vitest_1.it)('fetches candlestick data', async () => {
337
+ const candles = [
338
+ { t: 1000, o: 0.5, h: 0.55, l: 0.48, c: 0.52, v: 100 },
339
+ ];
340
+ mockFetch.mockResolvedValue({
341
+ ok: true,
342
+ json: () => Promise.resolve(candles),
343
+ });
344
+ const result = await (0, polymarket_js_1.polymarketGetOHLC)({
345
+ tokenId: 'tok1',
346
+ startTs: 1000,
347
+ fidelity: '1h',
348
+ });
349
+ (0, vitest_1.expect)(result).toHaveLength(1);
350
+ (0, vitest_1.expect)(result[0].c).toBe(0.52);
351
+ const url = mockFetch.mock.calls[0][0];
352
+ (0, vitest_1.expect)(url).toContain('clob.polymarket.com/ohlc');
353
+ (0, vitest_1.expect)(url).toContain('asset_id=tok1');
354
+ (0, vitest_1.expect)(url).toContain('fidelity=1h');
355
+ });
356
+ (0, vitest_1.it)('returns empty array on error', async () => {
357
+ mockFetch.mockResolvedValue({ ok: false, status: 400 });
358
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetOHLC)({ tokenId: 'bad', startTs: 0 })).toEqual([]);
359
+ });
360
+ });
361
+ (0, vitest_1.describe)('polymarketGetPriceHistory', () => {
362
+ (0, vitest_1.it)('fetches price history with interval', async () => {
363
+ mockFetch.mockResolvedValue({
364
+ ok: true,
365
+ json: () => Promise.resolve({
366
+ history: [{ t: 1000, p: 0.5 }, { t: 2000, p: 0.55 }],
367
+ }),
368
+ });
369
+ const result = await (0, polymarket_js_1.polymarketGetPriceHistory)({
370
+ tokenId: 'tok1',
371
+ interval: '1d',
372
+ });
373
+ (0, vitest_1.expect)(result).toHaveLength(2);
374
+ const url = mockFetch.mock.calls[0][0];
375
+ (0, vitest_1.expect)(url).toContain('prices-history');
376
+ (0, vitest_1.expect)(url).toContain('interval=1d');
377
+ });
378
+ (0, vitest_1.it)('fetches with absolute timestamps', async () => {
379
+ mockFetch.mockResolvedValue({
380
+ ok: true,
381
+ json: () => Promise.resolve({ history: [] }),
382
+ });
383
+ await (0, polymarket_js_1.polymarketGetPriceHistory)({
384
+ tokenId: 'tok1',
385
+ startTs: 1000,
386
+ endTs: 2000,
387
+ fidelity: 60,
388
+ });
389
+ const url = mockFetch.mock.calls[0][0];
390
+ (0, vitest_1.expect)(url).toContain('startTs=1000');
391
+ (0, vitest_1.expect)(url).toContain('endTs=2000');
392
+ (0, vitest_1.expect)(url).toContain('fidelity=60');
393
+ (0, vitest_1.expect)(url).not.toContain('interval=');
394
+ });
395
+ });
396
+ (0, vitest_1.describe)('polymarketGetPositions', () => {
397
+ (0, vitest_1.it)('fetches positions for wallet address', async () => {
398
+ mockFetch.mockResolvedValue({
399
+ ok: true,
400
+ json: () => Promise.resolve([
401
+ { asset: 'tok1', size: 100, outcome: 'Yes' },
402
+ ]),
403
+ });
404
+ const positions = await (0, polymarket_js_1.polymarketGetPositions)('0xabc123');
405
+ (0, vitest_1.expect)(positions).toHaveLength(1);
406
+ (0, vitest_1.expect)(positions[0].outcome).toBe('Yes');
407
+ (0, vitest_1.expect)(mockFetch.mock.calls[0][0]).toContain('data-api.polymarket.com/positions?user=0xabc123');
408
+ });
409
+ (0, vitest_1.it)('returns empty on error', async () => {
410
+ mockFetch.mockResolvedValue({ ok: false });
411
+ (0, vitest_1.expect)(await (0, polymarket_js_1.polymarketGetPositions)('bad')).toEqual([]);
412
+ });
413
+ });
414
+ (0, vitest_1.describe)('polymarketGetClosedPositions', () => {
415
+ (0, vitest_1.it)('fetches closed positions', async () => {
416
+ mockFetch.mockResolvedValue({
417
+ ok: true,
418
+ json: () => Promise.resolve([{ asset: 'tok1', outcome: 'No' }]),
419
+ });
420
+ const positions = await (0, polymarket_js_1.polymarketGetClosedPositions)('0xabc');
421
+ (0, vitest_1.expect)(positions).toHaveLength(1);
422
+ (0, vitest_1.expect)(mockFetch.mock.calls[0][0]).toContain('closed-positions');
423
+ });
424
+ });
package/dist/topics.d.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * Shared between dashboard, liquidity scanner, and other commands
5
5
  * that need to categorize markets by topic.
6
+ *
7
+ * Sourced from Kalshi's top series by volume (non-sports).
8
+ * Run `sf scan` to discover new series.
6
9
  */
7
10
  export declare const TOPIC_SERIES: Record<string, string[]>;
8
11
  /** Map a series prefix to a human-readable category name (for dashboard display) */
package/dist/topics.js CHANGED
@@ -4,29 +4,87 @@
4
4
  *
5
5
  * Shared between dashboard, liquidity scanner, and other commands
6
6
  * that need to categorize markets by topic.
7
+ *
8
+ * Sourced from Kalshi's top series by volume (non-sports).
9
+ * Run `sf scan` to discover new series.
7
10
  */
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.RISK_CATEGORIES = exports.TOPIC_SERIES = void 0;
10
13
  exports.tickerToTopic = tickerToTopic;
11
14
  exports.TOPIC_SERIES = {
12
- oil: ['KXWTIMAX', 'KXWTIW', 'KXWTID'],
15
+ oil: ['KXWTIMAX', 'KXWTIW', 'KXWTID', 'KXWTI'],
16
+ gas: ['KXAAAGASM', 'KXAAAGASW', 'KXCPIGAS'],
17
+ fed: ['KXFEDDECISION', 'KXFED', 'KXRATECUT', 'KXRATECUTCOUNT'],
18
+ cpi: ['KXCPI', 'KXCPIYOY'],
13
19
  recession: ['KXRECSSNBER'],
14
- fed: ['KXFEDDECISION'],
15
- cpi: ['KXCPI'],
16
- gas: ['KXAAAGASM'],
17
- sp500: ['KXINXY'],
20
+ sp500: ['KXINXY', 'KXINXU', 'KXINX'],
21
+ nasdaq: ['KXNASDAQ100', 'KXNASDAQ100U', 'KXNASDAQ100Y'],
22
+ crypto: ['KXBTCD', 'KXBTC', 'KXBTC15M', 'KXBTCMAXY', 'KXBTCMINY', 'KXBTCY',
23
+ 'KXETHD', 'KXETH', 'KXETH15M', 'KXETHMAXY', 'KXETHMINY',
24
+ 'KXSOL15M', 'KXXRP15M'],
25
+ unemployment: ['KXU3', 'KXPAYROLLS'],
26
+ gdp: ['KXGDP'],
27
+ treasury: ['KXTNOTEW', 'KXTNOTED'],
28
+ geopolitics: ['KXCLOSEHORMUZ', 'KXHORMUZTRAFFICW', 'KXHORMUZTRAFFIC', 'KXHORMUZNORM',
29
+ 'KXLEADERSOUT', 'KXLEADEROUT', 'KXMADUROOUT', 'KXKHAMENEIOUT'],
30
+ elections: ['PRES', 'KXFEDCHAIRNOM', 'KXPRESNOMD', 'KXPRESNOMR', 'KXPRESPERSON',
31
+ 'KXNEXTPOPE', 'KXTRUMPOUT', 'KXCANADAPM'],
32
+ politics: ['KXGOVSHUT', 'KXGOVTSHUTDOWN', 'KXGOVSHUTLENGTH', 'KXGOVTCUTS',
33
+ 'KXTRUMPMENTION', 'KXEOWEEK', 'KXGREENLAND', 'KXCANCOALITION'],
34
+ centralbanks: ['KXCBDECISIONJAPAN', 'KXCBDECISIONENGLAND', 'KXCBDECISIONEU',
35
+ 'KXCBDECISIONAUSTRALIA', 'KXCBDECISIONCANADA', 'KXCBDECISIONCHINA',
36
+ 'KXCBDECISIONMEXICO', 'KXCBDECISIONKOREA'],
37
+ forex: ['KXUSDJPY'],
38
+ tariffs: ['KXTARIFFRATEPRC', 'KXTARIFFRATECAN', 'KXTARIFFRATECA',
39
+ 'KXTARIFFRATEINDIA', 'KXTARIFFRATEBR', 'KXTARIFFRATEEU',
40
+ 'KXTARIFFRATEJP', 'KXTARIFFRATEKR'],
41
+ tech: ['KXLLM1', 'KXTOPMODEL', 'KXALIENS'],
18
42
  };
19
43
  /** Map a series prefix to a human-readable category name (for dashboard display) */
20
44
  exports.RISK_CATEGORIES = {
21
45
  KXWTIMAX: 'Oil',
46
+ KXWTIW: 'Oil',
47
+ KXWTID: 'Oil',
22
48
  KXWTI: 'Oil',
23
- KXRECSSNBER: 'Recession',
24
49
  KXAAAGASM: 'Gas',
50
+ KXAAAGASW: 'Gas',
51
+ KXCPIGAS: 'Gas',
52
+ KXRECSSNBER: 'Recession',
25
53
  KXCPI: 'Inflation',
54
+ KXCPIYOY: 'Inflation',
26
55
  KXINXY: 'S&P 500',
56
+ KXINXU: 'S&P 500',
57
+ KXINX: 'S&P 500',
58
+ KXNASDAQ100: 'Nasdaq',
59
+ KXNASDAQ100U: 'Nasdaq',
60
+ KXNASDAQ100Y: 'Nasdaq',
27
61
  KXFEDDECISION: 'Fed Rate',
28
- KXUNEMPLOYMENT: 'Unemployment',
62
+ KXFED: 'Fed Rate',
63
+ KXRATECUT: 'Fed Rate',
64
+ KXRATECUTCOUNT: 'Fed Rate',
65
+ KXBTCD: 'Bitcoin',
66
+ KXBTC: 'Bitcoin',
67
+ KXBTC15M: 'Bitcoin',
68
+ KXETHD: 'Ethereum',
69
+ KXETH: 'Ethereum',
70
+ KXETH15M: 'Ethereum',
71
+ KXU3: 'Unemployment',
72
+ KXPAYROLLS: 'Jobs',
73
+ KXGDP: 'GDP',
74
+ KXTNOTEW: 'Treasury',
75
+ KXTNOTED: 'Treasury',
29
76
  KXCLOSEHORMUZ: 'Hormuz',
77
+ KXHORMUZTRAFFICW: 'Hormuz',
78
+ KXHORMUZTRAFFIC: 'Hormuz',
79
+ KXUSDJPY: 'USD/JPY',
80
+ KXGOVSHUT: 'Govt Shutdown',
81
+ KXGOVTSHUTDOWN: 'Govt Shutdown',
82
+ PRES: 'Elections',
83
+ KXFEDCHAIRNOM: 'Elections',
84
+ KXTARIFFRATEPRC: 'Tariffs',
85
+ KXCBDECISIONJAPAN: 'Central Banks',
86
+ KXCBDECISIONENGLAND: 'Central Banks',
87
+ KXCBDECISIONEU: 'Central Banks',
30
88
  };
31
89
  /**
32
90
  * Given a ticker string, return the topic name (uppercased).
@@ -7,21 +7,85 @@ const topics_js_1 = require("./topics.js");
7
7
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXWTIMAX-26DEC31-T135')).toBe('OIL');
8
8
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXWTID-something')).toBe('OIL');
9
9
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXWTIW-2026')).toBe('OIL');
10
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXWTI-26MAR24-T100')).toBe('OIL');
10
11
  });
11
12
  (0, vitest_1.it)('matches recession', () => {
12
13
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXRECSSNBER-26')).toBe('RECESSION');
13
14
  });
14
15
  (0, vitest_1.it)('matches fed', () => {
15
16
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXFEDDECISION-2026')).toBe('FED');
17
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXFED-something')).toBe('FED');
18
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXRATECUT-2026')).toBe('FED');
19
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXRATECUTCOUNT-2026')).toBe('FED');
16
20
  });
17
21
  (0, vitest_1.it)('matches cpi', () => {
18
22
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXCPI-26MAY-T0.4')).toBe('CPI');
23
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXCPIYOY-2026')).toBe('CPI');
19
24
  });
20
25
  (0, vitest_1.it)('matches gas', () => {
21
26
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXAAAGASM-26MAR31-4.40')).toBe('GAS');
27
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXAAAGASW-2026')).toBe('GAS');
22
28
  });
23
29
  (0, vitest_1.it)('matches sp500', () => {
24
30
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXINXY-26DEC31H1600-T4000')).toBe('SP500');
31
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXINXU-2026')).toBe('SP500');
32
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXINX-2026')).toBe('SP500');
33
+ });
34
+ (0, vitest_1.it)('matches nasdaq', () => {
35
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXNASDAQ100-2026')).toBe('NASDAQ');
36
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXNASDAQ100U-2026')).toBe('NASDAQ');
37
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXNASDAQ100Y-2026')).toBe('NASDAQ');
38
+ });
39
+ (0, vitest_1.it)('matches crypto', () => {
40
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXBTCD-2026')).toBe('CRYPTO');
41
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXBTC15M-2026')).toBe('CRYPTO');
42
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXBTCMAXY-2026')).toBe('CRYPTO');
43
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXETHD-2026')).toBe('CRYPTO');
44
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXETH15M-2026')).toBe('CRYPTO');
45
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXSOL15M-2026')).toBe('CRYPTO');
46
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXXRP15M-2026')).toBe('CRYPTO');
47
+ });
48
+ (0, vitest_1.it)('matches unemployment & jobs', () => {
49
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXU3-2026')).toBe('UNEMPLOYMENT');
50
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXPAYROLLS-2026')).toBe('UNEMPLOYMENT');
51
+ });
52
+ (0, vitest_1.it)('matches gdp', () => {
53
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXGDP-Q1-2026')).toBe('GDP');
54
+ });
55
+ (0, vitest_1.it)('matches geopolitics', () => {
56
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXCLOSEHORMUZ-2026')).toBe('GEOPOLITICS');
57
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXHORMUZTRAFFICW-26MAR')).toBe('GEOPOLITICS');
58
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXKHAMENEIOUT-2026')).toBe('GEOPOLITICS');
59
+ });
60
+ (0, vitest_1.it)('matches elections', () => {
61
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('PRES-2028')).toBe('ELECTIONS');
62
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXFEDCHAIRNOM-2026')).toBe('ELECTIONS');
63
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXTRUMPOUT-2026')).toBe('ELECTIONS');
64
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXNEXTPOPE-2026')).toBe('ELECTIONS');
65
+ });
66
+ (0, vitest_1.it)('matches politics', () => {
67
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXGOVSHUT-2026')).toBe('POLITICS');
68
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXGOVTSHUTDOWN-2026')).toBe('POLITICS');
69
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXGREENLAND-2026')).toBe('POLITICS');
70
+ });
71
+ (0, vitest_1.it)('matches central banks', () => {
72
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXCBDECISIONJAPAN-2026')).toBe('CENTRALBANKS');
73
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXCBDECISIONEU-2026')).toBe('CENTRALBANKS');
74
+ });
75
+ (0, vitest_1.it)('matches tariffs', () => {
76
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXTARIFFRATEPRC-2026')).toBe('TARIFFS');
77
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXTARIFFRATECA-2026')).toBe('TARIFFS');
78
+ });
79
+ (0, vitest_1.it)('matches treasury', () => {
80
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXTNOTEW-2026')).toBe('TREASURY');
81
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXTNOTED-2026')).toBe('TREASURY');
82
+ });
83
+ (0, vitest_1.it)('matches forex', () => {
84
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXUSDJPY-2026')).toBe('FOREX');
85
+ });
86
+ (0, vitest_1.it)('matches tech', () => {
87
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXLLM1-2026')).toBe('TECH');
88
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('KXTOPMODEL-2026')).toBe('TECH');
25
89
  });
26
90
  (0, vitest_1.it)('returns OTHER for unknown tickers', () => {
27
91
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('UNKNOWN-TICKER')).toBe('OTHER');
@@ -29,11 +93,20 @@ const topics_js_1 = require("./topics.js");
29
93
  });
30
94
  (0, vitest_1.it)('is case-insensitive', () => {
31
95
  (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('kxwtimax-lower')).toBe('OIL');
96
+ (0, vitest_1.expect)((0, topics_js_1.tickerToTopic)('kxbtcd-lower')).toBe('CRYPTO');
32
97
  });
33
98
  });
34
99
  (0, vitest_1.describe)('TOPIC_SERIES', () => {
35
- (0, vitest_1.it)('has expected topics', () => {
36
- (0, vitest_1.expect)(Object.keys(topics_js_1.TOPIC_SERIES)).toEqual(vitest_1.expect.arrayContaining(['oil', 'recession', 'fed', 'cpi', 'gas', 'sp500']));
100
+ (0, vitest_1.it)('has all expected topics', () => {
101
+ const topics = Object.keys(topics_js_1.TOPIC_SERIES);
102
+ (0, vitest_1.expect)(topics).toEqual(vitest_1.expect.arrayContaining([
103
+ 'oil', 'gas', 'fed', 'cpi', 'recession', 'sp500', 'nasdaq',
104
+ 'crypto', 'unemployment', 'gdp', 'treasury', 'geopolitics',
105
+ 'elections', 'politics', 'centralbanks', 'forex', 'tariffs', 'tech',
106
+ ]));
107
+ });
108
+ (0, vitest_1.it)('has at least 18 topics', () => {
109
+ (0, vitest_1.expect)(Object.keys(topics_js_1.TOPIC_SERIES).length).toBeGreaterThanOrEqual(18);
37
110
  });
38
111
  (0, vitest_1.it)('each topic has at least one series', () => {
39
112
  for (const [, series] of Object.entries(topics_js_1.TOPIC_SERIES)) {
@@ -42,13 +115,17 @@ const topics_js_1 = require("./topics.js");
42
115
  });
43
116
  });
44
117
  (0, vitest_1.describe)('RISK_CATEGORIES', () => {
45
- (0, vitest_1.it)('maps KXWTIMAX to Oil', () => {
118
+ (0, vitest_1.it)('maps oil series to Oil', () => {
46
119
  (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXWTIMAX']).toBe('Oil');
120
+ (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXWTI']).toBe('Oil');
47
121
  });
48
- (0, vitest_1.it)('maps KXRECSSNBER to Recession', () => {
49
- (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXRECSSNBER']).toBe('Recession');
122
+ (0, vitest_1.it)('maps crypto series', () => {
123
+ (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXBTCD']).toBe('Bitcoin');
124
+ (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXETHD']).toBe('Ethereum');
50
125
  });
51
- (0, vitest_1.it)('maps KXFEDDECISION to Fed Rate', () => {
126
+ (0, vitest_1.it)('maps financial series', () => {
127
+ (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXINXY']).toBe('S&P 500');
128
+ (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXNASDAQ100']).toBe('Nasdaq');
52
129
  (0, vitest_1.expect)(topics_js_1.RISK_CATEGORIES['KXFEDDECISION']).toBe('Fed Rate');
53
130
  });
54
131
  });