@spfunctions/cli 1.4.5 → 1.5.1

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.
@@ -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
  });
@@ -17,6 +17,7 @@ const config_js_1 = require("../config.js");
17
17
  const client_js_1 = require("../client.js");
18
18
  const kalshi_js_1 = require("../kalshi.js");
19
19
  const topics_js_1 = require("../topics.js");
20
+ const polymarket_js_1 = require("../polymarket.js");
20
21
  // Widget renderers
21
22
  const portfolio_js_1 = require("./widgets/portfolio.js");
22
23
  const thesis_js_1 = require("./widgets/thesis.js");
@@ -78,6 +79,29 @@ async function loadAllData(state) {
78
79
  }
79
80
  state.positions = positions;
80
81
  }
82
+ // Polymarket positions (merge into state.positions with venue tag)
83
+ const config = (0, config_js_1.loadConfig)();
84
+ if (config.polymarketWalletAddress) {
85
+ try {
86
+ const polyPos = await (0, cache_js_1.cached)('poly-positions', REFRESH_POSITIONS, () => (0, polymarket_js_1.polymarketGetPositions)(config.polymarketWalletAddress));
87
+ if (polyPos && Array.isArray(polyPos) && polyPos.length > 0) {
88
+ for (const p of polyPos) {
89
+ state.positions.push({
90
+ ticker: (p.title || p.slug || p.asset || '').slice(0, 25),
91
+ quantity: p.size || 0,
92
+ average_price_paid: Math.round((p.avgPrice || 0) * 100),
93
+ current_value: Math.round((p.curPrice || p.currentPrice || 0) * 100),
94
+ unrealized_pnl: Math.round((p.cashPnl || 0) * 100),
95
+ side: (p.outcome || 'yes').toLowerCase(),
96
+ venue: 'polymarket',
97
+ });
98
+ }
99
+ }
100
+ }
101
+ catch {
102
+ // skip
103
+ }
104
+ }
81
105
  // Theses
82
106
  if (thesesResult.status === 'fulfilled' && thesesResult.value) {
83
107
  const raw = thesesResult.value;
@@ -585,39 +609,50 @@ async function loadLiquidityData(state) {
585
609
  if (!seriesList)
586
610
  return;
587
611
  try {
588
- // Fetch markets for each series in the topic
612
+ // Phase 1: Fetch market lists in parallel (fast — just metadata)
613
+ const seriesResults = await Promise.allSettled(seriesList.map(series => (0, cache_js_1.cached)(`liq:${series}`, 30_000, async () => {
614
+ const url = `https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${series}&status=open&limit=200`;
615
+ const res = await fetch(url, { headers: { Accept: 'application/json' } });
616
+ if (!res.ok)
617
+ return [];
618
+ const data = await res.json();
619
+ return data.markets || [];
620
+ })));
589
621
  const allMarkets = [];
590
- for (const series of seriesList) {
591
- const markets = await (0, cache_js_1.cached)(`liq:${series}`, 30_000, async () => {
592
- const url = `https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${series}&status=open&limit=200`;
593
- const res = await fetch(url, { headers: { Accept: 'application/json' } });
594
- if (!res.ok)
595
- return [];
596
- const data = await res.json();
597
- return data.markets || [];
598
- });
599
- // Enrich with orderbook data
600
- for (const mkt of markets) {
601
- const ob = await (0, cache_js_1.cached)(`ob:${mkt.ticker}`, 30_000, () => (0, kalshi_js_1.getPublicOrderbook)(mkt.ticker));
602
- if (ob) {
603
- const yes = (ob.yes_dollars || []).map((l) => ({
604
- price: Math.round(parseFloat(l[0]) * 100),
605
- qty: parseFloat(l[1]),
606
- })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
607
- const no = (ob.no_dollars || []).map((l) => ({
608
- price: Math.round(parseFloat(l[0]) * 100),
609
- qty: parseFloat(l[1]),
610
- })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
611
- mkt.bestBid = yes.length > 0 ? yes[0].price : 0;
612
- mkt.bestAsk = no.length > 0 ? (100 - no[0].price) : 100;
613
- mkt.spread = mkt.bestAsk - mkt.bestBid;
614
- mkt.totalDepth = yes.slice(0, 3).reduce((s, l) => s + l.qty, 0)
615
- + no.slice(0, 3).reduce((s, l) => s + l.qty, 0);
616
- }
617
- allMarkets.push(mkt);
622
+ for (const r of seriesResults) {
623
+ if (r.status === 'fulfilled' && Array.isArray(r.value)) {
624
+ allMarkets.push(...r.value);
625
+ }
626
+ }
627
+ // Show markets immediately (before orderbooks load)
628
+ state.liquidityData.set(topic, [...allMarkets]);
629
+ // Phase 2: Fetch orderbooks in parallel batches of 8
630
+ const BATCH = 8;
631
+ for (let i = 0; i < allMarkets.length; i += BATCH) {
632
+ const batch = allMarkets.slice(i, i + BATCH);
633
+ const obResults = await Promise.allSettled(batch.map(mkt => (0, cache_js_1.cached)(`ob:${mkt.ticker}`, 30_000, () => (0, kalshi_js_1.getPublicOrderbook)(mkt.ticker))
634
+ .then(ob => ({ mkt, ob }))));
635
+ for (const r of obResults) {
636
+ if (r.status !== 'fulfilled' || !r.value.ob)
637
+ continue;
638
+ const { mkt, ob } = r.value;
639
+ const yes = (ob.yes_dollars || []).map((l) => ({
640
+ price: Math.round(parseFloat(l[0]) * 100),
641
+ qty: parseFloat(l[1]),
642
+ })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
643
+ const no = (ob.no_dollars || []).map((l) => ({
644
+ price: Math.round(parseFloat(l[0]) * 100),
645
+ qty: parseFloat(l[1]),
646
+ })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
647
+ mkt.bestBid = yes.length > 0 ? yes[0].price : 0;
648
+ mkt.bestAsk = no.length > 0 ? (100 - no[0].price) : 100;
649
+ mkt.spread = mkt.bestAsk - mkt.bestBid;
650
+ mkt.totalDepth = yes.slice(0, 3).reduce((s, l) => s + l.qty, 0)
651
+ + no.slice(0, 3).reduce((s, l) => s + l.qty, 0);
618
652
  }
653
+ // Progressive update: refresh display after each batch
654
+ state.liquidityData.set(topic, [...allMarkets]);
619
655
  }
620
- state.liquidityData.set(topic, allMarkets);
621
656
  }
622
657
  catch (err) {
623
658
  state.error = 'Failed to load liquidity data';
@@ -25,9 +25,10 @@ function renderEdges(screen, region, state) {
25
25
  const edgeStr = `+${edgeVal.toFixed(1)}¢`;
26
26
  const liq = e.orderbook?.liquidityScore || '?';
27
27
  const liqColor = liq === 'high' ? border_js_1.CLR.green : liq === 'low' ? border_js_1.CLR.yellow : border_js_1.CLR.dim;
28
- const nameWidth = Math.max(w - 18, 10);
29
- screen.write(y, x, `${marker} ${(0, border_js_1.fit)(name, nameWidth)}`, selected ? border_js_1.CLR.white : border_js_1.CLR.text);
30
- screen.write(y, x + nameWidth + 2, (0, border_js_1.fit)(edgeStr, 8, 'right'), border_js_1.CLR.emerald);
31
- screen.write(y, x + nameWidth + 11, (0, border_js_1.fit)(liq, 5, 'right'), liqColor);
28
+ const venueTag = e.venue === 'polymarket' ? 'P ' : 'K ';
29
+ const nameWidth = Math.max(w - 20, 10);
30
+ screen.write(y, x, `${marker}${venueTag}${(0, border_js_1.fit)(name, nameWidth)}`, selected ? border_js_1.CLR.white : border_js_1.CLR.text);
31
+ screen.write(y, x + nameWidth + 3, (0, border_js_1.fit)(edgeStr, 8, 'right'), border_js_1.CLR.emerald);
32
+ screen.write(y, x + nameWidth + 12, (0, border_js_1.fit)(liq, 5, 'right'), liqColor);
32
33
  }
33
34
  }
@@ -26,8 +26,9 @@ function renderPortfolio(screen, region, state) {
26
26
  const selected = state.focusArea === 'positions' && state.selectedIndex === idx;
27
27
  const marker = selected ? '▸' : ' ';
28
28
  const ticker = pos.ticker || '???';
29
- // Line 1: marker + ticker
30
- screen.write(y, x, (0, border_js_1.fit)(`${marker} ${ticker}`, w), selected ? border_js_1.CLR.white : border_js_1.CLR.text);
29
+ const venueTag = pos.venue === 'polymarket' ? 'P ' : 'K ';
30
+ // Line 1: marker + venue + ticker
31
+ screen.write(y, x, (0, border_js_1.fit)(`${marker}${venueTag}${ticker}`, w), selected ? border_js_1.CLR.white : border_js_1.CLR.text);
31
32
  // Line 2: qty @ entry → current P&L sparkline
32
33
  if (line + 1 < maxRows) {
33
34
  const qty = pos.quantity ?? 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.4.5",
3
+ "version": "1.5.1",
4
4
  "description": "Prediction market intelligence CLI. Causal thesis model, 24/7 Kalshi/Polymarket scan, live orderbook, edge detection. Interactive agent mode with tool calling.",
5
5
  "bin": {
6
6
  "sf": "./dist/index.js"