@spfunctions/cli 1.4.0 → 1.4.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.
@@ -671,6 +671,8 @@ async function agentCommand(thesisId, opts) {
671
671
  const matched = series
672
672
  .filter((s) => keywords.every((kw) => (s.title || '').toLowerCase().includes(kw) ||
673
673
  (s.ticker || '').toLowerCase().includes(kw)))
674
+ .filter((s) => parseFloat(s.volume_fp || '0') > 1000)
675
+ .sort((a, b) => parseFloat(b.volume_fp || '0') - parseFloat(a.volume_fp || '0'))
674
676
  .slice(0, 15);
675
677
  result = matched;
676
678
  }
@@ -2187,7 +2189,7 @@ async function runPlainTextAgent(params) {
2187
2189
  else if (p.query) {
2188
2190
  const series = await (0, client_js_1.kalshiFetchAllSeries)();
2189
2191
  const kws = p.query.toLowerCase().split(/\s+/);
2190
- result = series.filter((s) => kws.every((k) => ((s.title || '') + (s.ticker || '')).toLowerCase().includes(k))).slice(0, 15);
2192
+ result = series.filter((s) => kws.every((k) => ((s.title || '') + (s.ticker || '')).toLowerCase().includes(k))).filter((s) => parseFloat(s.volume_fp || '0') > 1000).sort((a, b) => parseFloat(b.volume_fp || '0') - parseFloat(a.volume_fp || '0')).slice(0, 15);
2191
2193
  }
2192
2194
  else {
2193
2195
  result = { error: 'Provide query, series, or market' };
@@ -8,10 +8,36 @@ const client_js_1 = require("../client.js");
8
8
  const kalshi_js_1 = require("../kalshi.js");
9
9
  const config_js_1 = require("../config.js");
10
10
  const utils_js_1 = require("../utils.js");
11
- /** Abbreviate ticker: KXWTIMAX-26DEC31-T135 -> T135 */
11
+ /** Abbreviate ticker to something readable:
12
+ * KXWTIMAX-26DEC31-T135 → Oil 135
13
+ * KXRECSSNBER-26 → Recsn
14
+ * KXAAAGASM-26MAR31-4.40 → Gas 4.40
15
+ * KXINXY-26DEC31H1600-T4000 → S&P 4000
16
+ */
12
17
  function abbrevTicker(ticker) {
18
+ // Short topic names for performance table
19
+ const SHORT_TOPICS = {
20
+ KXWTIMAX: 'Oil', KXWTI: 'Oil', KXRECSSNBER: 'Recsn',
21
+ KXAAAGASM: 'Gas', KXCPI: 'CPI', KXINXY: 'S&P',
22
+ KXFEDDECISION: 'Fed', KXUNEMPLOYMENT: 'Unemp', KXCLOSEHORMUZ: 'Hormuz',
23
+ };
24
+ const sorted = Object.keys(SHORT_TOPICS).sort((a, b) => b.length - a.length);
25
+ let topic = '';
26
+ for (const prefix of sorted) {
27
+ if (ticker.startsWith(prefix)) {
28
+ topic = SHORT_TOPICS[prefix];
29
+ break;
30
+ }
31
+ }
32
+ // Extract the meaningful suffix (strike/level)
13
33
  const parts = ticker.split('-');
14
- return parts[parts.length - 1] || ticker;
34
+ const last = parts[parts.length - 1] || '';
35
+ const suffix = last.startsWith('T') ? last.slice(1) : '';
36
+ if (topic && suffix)
37
+ return `${topic} ${suffix}`;
38
+ if (topic)
39
+ return topic;
40
+ return last || ticker.slice(0, 8);
15
41
  }
16
42
  /** Format date as "Mar 01" */
17
43
  function fmtDate(d) {
@@ -48,8 +74,8 @@ async function performanceCommand(opts) {
48
74
  continue;
49
75
  const side = fill.side || 'yes';
50
76
  const action = fill.action || 'buy';
51
- const count = fill.count || 0;
52
- const yesPrice = fill.yes_price || 0; // cents int
77
+ const count = Math.round(parseFloat(fill.count_fp || fill.count || '0'));
78
+ const yesPrice = Math.round(parseFloat(fill.yes_price_dollars || '0') * 100); // dollars string → cents int
53
79
  // Determine direction: buy yes = +count, sell yes = -count
54
80
  let delta = count;
55
81
  if (action === 'sell')
@@ -105,7 +131,11 @@ async function performanceCommand(opts) {
105
131
  const priceByDate = new Map();
106
132
  for (const candle of (mc.candlesticks || [])) {
107
133
  // close_dollars is a string like "0.4800"
108
- const closeDollars = parseFloat(candle.close_dollars || candle.close || '0');
134
+ // price object may be empty; use midpoint of yes_bid.close and yes_ask.close
135
+ const bidClose = parseFloat(candle.yes_bid?.close_dollars || '0');
136
+ const askClose = parseFloat(candle.yes_ask?.close_dollars || '0');
137
+ const mid = bidClose > 0 && askClose > 0 ? (bidClose + askClose) / 2 : bidClose || askClose;
138
+ const closeDollars = parseFloat(candle.price?.close_dollars || '0') || mid;
109
139
  const closeCents = Math.round(closeDollars * 100);
110
140
  const ts = candle.end_period_ts || candle.period_end_ts || candle.ts;
111
141
  if (ts) {
@@ -151,12 +181,12 @@ async function performanceCommand(opts) {
151
181
  const config = (0, config_js_1.loadConfig)();
152
182
  const client = new client_js_1.SFClient(config.apiKey, config.apiUrl);
153
183
  const feedData = await client.getFeed(720);
154
- const feedItems = feedData?.items || feedData?.events || feedData || [];
184
+ const feedItems = feedData?.feed || feedData?.items || feedData?.events || feedData || [];
155
185
  if (Array.isArray(feedItems)) {
156
186
  for (const item of feedItems) {
157
- const confDelta = item.confidenceDelta ?? item.confidence_delta ?? 0;
158
- if (Math.abs(confDelta) > 0.03) {
159
- const itemDate = item.createdAt || item.created_at || item.timestamp || '';
187
+ const confDelta = item.delta ?? item.confidenceDelta ?? item.confidence_delta ?? 0;
188
+ if (Math.abs(confDelta) >= 0.02) {
189
+ const itemDate = item.evaluatedAt || item.createdAt || item.created_at || item.timestamp || '';
160
190
  if (itemDate) {
161
191
  events.push({
162
192
  date: dateKey(new Date(itemDate)),
@@ -207,7 +237,7 @@ async function performanceCommand(opts) {
207
237
  console.log();
208
238
  // Column headers
209
239
  const abbrevs = tickers.map(t => abbrevTicker(t.ticker));
210
- const colWidth = 9;
240
+ const colWidth = 11;
211
241
  const dateCol = 'Date'.padEnd(12);
212
242
  const headerCols = abbrevs.map(a => (0, utils_js_1.rpad)(a, colWidth)).join('');
213
243
  const totalCol = (0, utils_js_1.rpad)('Total', colWidth);
@@ -235,6 +265,21 @@ async function performanceCommand(opts) {
235
265
  const totalColor = row.total > 0 ? utils_js_1.c.green : row.total < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
236
266
  console.log(` ${dateStr}${cols}${totalColor}${(0, utils_js_1.rpad)(totalStr, colWidth)}${utils_js_1.c.reset}`);
237
267
  }
268
+ // Sparkline of total P&L
269
+ if (dailyRows.length >= 2) {
270
+ const totals = dailyRows.map(r => r.total);
271
+ const min = Math.min(...totals);
272
+ const max = Math.max(...totals);
273
+ const range = max - min || 1;
274
+ const blocks = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
275
+ const spark = totals.map(v => {
276
+ const idx = Math.round(((v - min) / range) * (blocks.length - 1));
277
+ const ch = blocks[idx];
278
+ return v >= 0 ? `${utils_js_1.c.green}${ch}${utils_js_1.c.reset}` : `${utils_js_1.c.red}${ch}${utils_js_1.c.reset}`;
279
+ }).join('');
280
+ console.log();
281
+ console.log(` ${utils_js_1.c.dim}P&L trend:${utils_js_1.c.reset} ${spark}`);
282
+ }
238
283
  // Events
239
284
  if (events.length > 0) {
240
285
  console.log();
@@ -104,14 +104,16 @@ async function keywordScan(query, json) {
104
104
  score += 3;
105
105
  else if (v > 100_000)
106
106
  score += 1;
107
- if (score > 0)
108
- matches.push({ series: s, score });
107
+ if (score > 0 && v > 1000)
108
+ matches.push({ series: s, score, volume: v });
109
109
  }
110
- matches.sort((a, b) => b.score - a.score);
110
+ // Sort by score first, then by volume as tiebreaker
111
+ matches.sort((a, b) => b.score - a.score || b.volume - a.volume);
111
112
  const topSeries = matches.slice(0, 15);
112
113
  console.log(`\n${utils_js_1.c.bold}Found ${matches.length} relevant series. Top ${topSeries.length}:${utils_js_1.c.reset}\n`);
113
- for (const { series: s, score } of topSeries) {
114
- console.log(` ${utils_js_1.c.dim}[${score}]${utils_js_1.c.reset} ${(0, utils_js_1.pad)(s.ticker, 25)} ${s.title}`);
114
+ for (const { series: s, volume } of topSeries) {
115
+ const volStr = volume >= 1_000_000 ? `$${(volume / 1_000_000).toFixed(1)}M` : volume >= 1000 ? `$${(volume / 1000).toFixed(0)}k` : `$${volume.toFixed(0)}`;
116
+ console.log(` ${(0, utils_js_1.rpad)(volStr, 10)} ${(0, utils_js_1.pad)(s.ticker, 25)} ${s.title}`);
115
117
  }
116
118
  // Fetch live markets for top 10
117
119
  console.log(`\n${utils_js_1.c.dim}Fetching live markets...${utils_js_1.c.reset}\n`);
package/dist/kalshi.js CHANGED
@@ -481,12 +481,12 @@ async function getBatchCandlesticks(params) {
481
481
  return [];
482
482
  try {
483
483
  const searchParams = new URLSearchParams();
484
- searchParams.set('tickers', params.tickers.join(','));
484
+ searchParams.set('market_tickers', params.tickers.join(','));
485
485
  searchParams.set('start_ts', params.startTs.toString());
486
486
  searchParams.set('end_ts', params.endTs.toString());
487
487
  searchParams.set('period_interval', (params.periodInterval ?? 1440).toString());
488
488
  const data = await kalshiAuthGet(`/markets/candlesticks?${searchParams.toString()}`);
489
- return data.candlesticks || [];
489
+ return data.markets || [];
490
490
  }
491
491
  catch (err) {
492
492
  console.warn('[Kalshi] Failed to fetch candlesticks:', err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
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"