@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.
@@ -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.0",
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"