@spfunctions/cli 1.1.5 → 1.1.7

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.
Files changed (46) hide show
  1. package/dist/client.d.ts +5 -0
  2. package/dist/client.js +17 -0
  3. package/dist/commands/agent.d.ts +1 -0
  4. package/dist/commands/agent.js +1170 -76
  5. package/dist/commands/announcements.d.ts +3 -0
  6. package/dist/commands/announcements.js +28 -0
  7. package/dist/commands/balance.d.ts +3 -0
  8. package/dist/commands/balance.js +17 -0
  9. package/dist/commands/cancel.d.ts +5 -0
  10. package/dist/commands/cancel.js +41 -0
  11. package/dist/commands/dashboard.d.ts +11 -0
  12. package/dist/commands/dashboard.js +195 -0
  13. package/dist/commands/feed.d.ts +13 -0
  14. package/dist/commands/feed.js +73 -0
  15. package/dist/commands/fills.d.ts +4 -0
  16. package/dist/commands/fills.js +29 -0
  17. package/dist/commands/forecast.d.ts +4 -0
  18. package/dist/commands/forecast.js +53 -0
  19. package/dist/commands/history.d.ts +3 -0
  20. package/dist/commands/history.js +38 -0
  21. package/dist/commands/milestones.d.ts +8 -0
  22. package/dist/commands/milestones.js +56 -0
  23. package/dist/commands/orders.d.ts +4 -0
  24. package/dist/commands/orders.js +28 -0
  25. package/dist/commands/publish.js +21 -2
  26. package/dist/commands/rfq.d.ts +5 -0
  27. package/dist/commands/rfq.js +35 -0
  28. package/dist/commands/schedule.d.ts +3 -0
  29. package/dist/commands/schedule.js +38 -0
  30. package/dist/commands/settlements.d.ts +6 -0
  31. package/dist/commands/settlements.js +50 -0
  32. package/dist/commands/setup.d.ts +2 -0
  33. package/dist/commands/setup.js +45 -3
  34. package/dist/commands/signal.js +12 -1
  35. package/dist/commands/strategies.d.ts +11 -0
  36. package/dist/commands/strategies.js +130 -0
  37. package/dist/commands/trade.d.ts +12 -0
  38. package/dist/commands/trade.js +78 -0
  39. package/dist/commands/whatif.d.ts +17 -0
  40. package/dist/commands/whatif.js +209 -0
  41. package/dist/config.d.ts +2 -0
  42. package/dist/config.js +13 -0
  43. package/dist/index.js +177 -3
  44. package/dist/kalshi.d.ts +71 -0
  45. package/dist/kalshi.js +257 -17
  46. package/package.json +1 -1
package/dist/kalshi.js CHANGED
@@ -16,8 +16,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.isKalshiConfigured = isKalshiConfigured;
18
18
  exports.getPositions = getPositions;
19
+ exports.kalshiPriceCents = kalshiPriceCents;
19
20
  exports.getMarketPrice = getMarketPrice;
20
21
  exports.getOrderbook = getOrderbook;
22
+ exports.getSettlements = getSettlements;
23
+ exports.getBalance = getBalance;
24
+ exports.getOrders = getOrders;
25
+ exports.getFills = getFills;
26
+ exports.getForecastHistory = getForecastHistory;
27
+ exports.getExchangeAnnouncements = getExchangeAnnouncements;
28
+ exports.getHistoricalMarket = getHistoricalMarket;
29
+ exports.createOrder = createOrder;
30
+ exports.cancelOrder = cancelOrder;
31
+ exports.batchCancelOrders = batchCancelOrders;
32
+ exports.amendOrder = amendOrder;
33
+ exports.createRFQ = createRFQ;
21
34
  const fs_1 = __importDefault(require("fs"));
22
35
  const path_1 = __importDefault(require("path"));
23
36
  const crypto_1 = __importDefault(require("crypto"));
@@ -87,7 +100,9 @@ async function kalshiAuthGet(apiPath) {
87
100
  throw new Error('Kalshi private key not loaded. Check KALSHI_PRIVATE_KEY_PATH.');
88
101
  }
89
102
  const url = `${KALSHI_API_BASE}${apiPath}`;
90
- const { headers } = signRequest('GET', `/trade-api/v2${apiPath}`, privateKey);
103
+ // Kalshi signing MUST exclude query params — sign only the path portion
104
+ const pathOnly = apiPath.split('?')[0];
105
+ const { headers } = signRequest('GET', `/trade-api/v2${pathOnly}`, privateKey);
91
106
  const res = await fetch(url, { method: 'GET', headers });
92
107
  if (!res.ok) {
93
108
  const text = await res.text();
@@ -95,6 +110,44 @@ async function kalshiAuthGet(apiPath) {
95
110
  }
96
111
  return res.json();
97
112
  }
113
+ async function kalshiAuthPost(apiPath, body) {
114
+ const privateKey = loadPrivateKey();
115
+ if (!privateKey) {
116
+ throw new Error('Kalshi private key not loaded. Check KALSHI_PRIVATE_KEY_PATH.');
117
+ }
118
+ const url = `${KALSHI_API_BASE}${apiPath}`;
119
+ const pathOnly = apiPath.split('?')[0];
120
+ const { headers } = signRequest('POST', `/trade-api/v2${pathOnly}`, privateKey);
121
+ const res = await fetch(url, {
122
+ method: 'POST',
123
+ headers,
124
+ body: JSON.stringify(body),
125
+ });
126
+ if (!res.ok) {
127
+ const text = await res.text();
128
+ throw new Error(`Kalshi API ${res.status}: ${text}`);
129
+ }
130
+ return res.json();
131
+ }
132
+ async function kalshiAuthDelete(apiPath) {
133
+ const privateKey = loadPrivateKey();
134
+ if (!privateKey) {
135
+ throw new Error('Kalshi private key not loaded. Check KALSHI_PRIVATE_KEY_PATH.');
136
+ }
137
+ const url = `${KALSHI_API_BASE}${apiPath}`;
138
+ const pathOnly = apiPath.split('?')[0];
139
+ const { headers } = signRequest('DELETE', `/trade-api/v2${pathOnly}`, privateKey);
140
+ const res = await fetch(url, { method: 'DELETE', headers });
141
+ if (!res.ok) {
142
+ const text = await res.text();
143
+ throw new Error(`Kalshi API ${res.status}: ${text}`);
144
+ }
145
+ const contentType = res.headers.get('content-type');
146
+ if (contentType?.includes('application/json')) {
147
+ return res.json();
148
+ }
149
+ return {};
150
+ }
98
151
  /**
99
152
  * Get user's live positions from Kalshi.
100
153
  * Returns null if Kalshi is not configured.
@@ -138,6 +191,32 @@ async function getPositions() {
138
191
  return null;
139
192
  }
140
193
  }
194
+ /**
195
+ * Extract price in cents (0-100) from a Kalshi market object.
196
+ * Dollars fields first (Kalshi API returns null for integer cents fields).
197
+ */
198
+ function kalshiPriceCents(market) {
199
+ if (market.last_price_dollars) {
200
+ const p = parseFloat(market.last_price_dollars);
201
+ if (!isNaN(p) && p > 0)
202
+ return Math.round(p * 100);
203
+ }
204
+ if (market.yes_bid_dollars) {
205
+ const p = parseFloat(market.yes_bid_dollars);
206
+ if (!isNaN(p) && p > 0)
207
+ return Math.round(p * 100);
208
+ }
209
+ if (market.yes_ask_dollars) {
210
+ const p = parseFloat(market.yes_ask_dollars);
211
+ if (!isNaN(p) && p > 0)
212
+ return Math.round(p * 100);
213
+ }
214
+ if (market.last_price != null && market.last_price > 0)
215
+ return market.last_price;
216
+ if (market.yes_bid != null && market.yes_bid > 0)
217
+ return market.yes_bid;
218
+ return 50;
219
+ }
141
220
  /**
142
221
  * Get the current market price for a given ticker (public, no auth).
143
222
  * Returns price in cents (0-100) or null.
@@ -150,22 +229,8 @@ async function getMarketPrice(ticker) {
150
229
  return null;
151
230
  const data = await res.json();
152
231
  const m = data.market || data;
153
- // Try cents fields first, then dollar string fields
154
- if (m.last_price && m.last_price > 0)
155
- return m.last_price;
156
- if (m.yes_bid && m.yes_bid > 0)
157
- return m.yes_bid;
158
- if (m.last_price_dollars) {
159
- const parsed = parseFloat(m.last_price_dollars);
160
- if (!isNaN(parsed) && parsed > 0)
161
- return Math.round(parsed * 100);
162
- }
163
- if (m.yes_bid_dollars) {
164
- const parsed = parseFloat(m.yes_bid_dollars);
165
- if (!isNaN(parsed) && parsed > 0)
166
- return Math.round(parsed * 100);
167
- }
168
- return null;
232
+ const price = kalshiPriceCents(m);
233
+ return price === 50 && !m.last_price_dollars && !m.yes_bid_dollars ? null : price;
169
234
  }
170
235
  catch {
171
236
  return null;
@@ -213,3 +278,178 @@ async function getOrderbook(ticker) {
213
278
  return null;
214
279
  }
215
280
  }
281
+ // ============================================================================
282
+ // SETTLEMENTS (Authenticated)
283
+ // ============================================================================
284
+ async function getSettlements(params) {
285
+ if (!isKalshiConfigured())
286
+ return null;
287
+ try {
288
+ const searchParams = new URLSearchParams();
289
+ if (params?.limit)
290
+ searchParams.set('limit', params.limit.toString());
291
+ if (params?.cursor)
292
+ searchParams.set('cursor', params.cursor);
293
+ if (params?.ticker)
294
+ searchParams.set('ticker', params.ticker);
295
+ const data = await kalshiAuthGet(`/portfolio/settlements?${searchParams.toString()}`);
296
+ return { settlements: data.settlements || [], cursor: data.cursor || '' };
297
+ }
298
+ catch (err) {
299
+ console.warn('[Kalshi] Failed to fetch settlements:', err);
300
+ return null;
301
+ }
302
+ }
303
+ // ============================================================================
304
+ // BALANCE (Authenticated)
305
+ // ============================================================================
306
+ async function getBalance() {
307
+ if (!isKalshiConfigured())
308
+ return null;
309
+ try {
310
+ const data = await kalshiAuthGet('/portfolio/balance');
311
+ // API returns cents integers; convert to dollars
312
+ const balance = (data.balance || 0) / 100;
313
+ const portfolioValue = (data.portfolio_value || 0) / 100;
314
+ return { balance, portfolioValue };
315
+ }
316
+ catch (err) {
317
+ console.warn('[Kalshi] Failed to fetch balance:', err);
318
+ return null;
319
+ }
320
+ }
321
+ // ============================================================================
322
+ // ORDERS (Authenticated)
323
+ // ============================================================================
324
+ async function getOrders(params) {
325
+ if (!isKalshiConfigured())
326
+ return null;
327
+ try {
328
+ const searchParams = new URLSearchParams();
329
+ if (params?.status)
330
+ searchParams.set('status', params.status);
331
+ if (params?.ticker)
332
+ searchParams.set('ticker', params.ticker);
333
+ if (params?.limit)
334
+ searchParams.set('limit', params.limit.toString());
335
+ if (params?.cursor)
336
+ searchParams.set('cursor', params.cursor);
337
+ const data = await kalshiAuthGet(`/portfolio/orders?${searchParams.toString()}`);
338
+ return { orders: data.orders || [], cursor: data.cursor || '' };
339
+ }
340
+ catch (err) {
341
+ console.warn('[Kalshi] Failed to fetch orders:', err);
342
+ return null;
343
+ }
344
+ }
345
+ // ============================================================================
346
+ // FILLS (Authenticated)
347
+ // ============================================================================
348
+ async function getFills(params) {
349
+ if (!isKalshiConfigured())
350
+ return null;
351
+ try {
352
+ const searchParams = new URLSearchParams();
353
+ if (params?.ticker)
354
+ searchParams.set('ticker', params.ticker);
355
+ if (params?.limit)
356
+ searchParams.set('limit', params.limit.toString());
357
+ if (params?.cursor)
358
+ searchParams.set('cursor', params.cursor);
359
+ const data = await kalshiAuthGet(`/portfolio/fills?${searchParams.toString()}`);
360
+ return { fills: data.fills || [], cursor: data.cursor || '' };
361
+ }
362
+ catch (err) {
363
+ console.warn('[Kalshi] Failed to fetch fills:', err);
364
+ return null;
365
+ }
366
+ }
367
+ // ============================================================================
368
+ // FORECAST PERCENTILE HISTORY (Authenticated)
369
+ // ============================================================================
370
+ async function getForecastHistory(params) {
371
+ if (!isKalshiConfigured())
372
+ return null;
373
+ try {
374
+ const searchParams = new URLSearchParams();
375
+ for (const p of params.percentiles)
376
+ searchParams.append('percentiles', p.toString());
377
+ searchParams.set('start_ts', params.startTs.toString());
378
+ // Kalshi returns 400 if end_ts is past the latest available forecast data.
379
+ // Clamp to start of today UTC to avoid hitting future timestamps.
380
+ const todayMidnight = Math.floor(new Date().setUTCHours(0, 0, 0, 0) / 1000);
381
+ const clampedEnd = Math.min(params.endTs, todayMidnight);
382
+ searchParams.set('end_ts', clampedEnd.toString());
383
+ searchParams.set('period_interval', params.periodInterval.toString());
384
+ const apiPath = `/series/${params.seriesTicker}/events/${params.eventTicker}/forecast_percentile_history?${searchParams.toString()}`;
385
+ const data = await kalshiAuthGet(apiPath);
386
+ return data.forecast_history || [];
387
+ }
388
+ catch (err) {
389
+ console.warn('[Kalshi] Failed to fetch forecast:', err);
390
+ return null;
391
+ }
392
+ }
393
+ // ============================================================================
394
+ // EXCHANGE (Public, no auth)
395
+ // ============================================================================
396
+ async function getExchangeAnnouncements() {
397
+ try {
398
+ const res = await fetch(`${KALSHI_API_BASE}/exchange/announcements`, { headers: { 'Accept': 'application/json' } });
399
+ if (!res.ok)
400
+ return [];
401
+ const data = await res.json();
402
+ return data.announcements || [];
403
+ }
404
+ catch {
405
+ return [];
406
+ }
407
+ }
408
+ async function getHistoricalMarket(ticker) {
409
+ try {
410
+ const res = await fetch(`${KALSHI_API_BASE}/historical/markets/${ticker}`, { headers: { 'Accept': 'application/json' } });
411
+ if (!res.ok)
412
+ return null;
413
+ const data = await res.json();
414
+ return data.market || data || null;
415
+ }
416
+ catch {
417
+ return null;
418
+ }
419
+ }
420
+ // ============================================================================
421
+ // TRADING — ORDER MANAGEMENT (Authenticated, requires write key)
422
+ // ============================================================================
423
+ async function createOrder(params) {
424
+ return kalshiAuthPost('/portfolio/orders', params);
425
+ }
426
+ async function cancelOrder(orderId) {
427
+ await kalshiAuthDelete(`/portfolio/orders/${orderId}`);
428
+ }
429
+ async function batchCancelOrders(orderIds) {
430
+ await kalshiAuthPost('/portfolio/orders/batched', {
431
+ orders: orderIds.map(id => ({ action: 'cancel', order_id: id })),
432
+ });
433
+ }
434
+ async function amendOrder(orderId, params) {
435
+ // PATCH - need to implement kalshiAuthPatch or use POST
436
+ const privateKey = loadPrivateKey();
437
+ if (!privateKey)
438
+ throw new Error('Kalshi private key not loaded.');
439
+ const apiPath = `/portfolio/orders/${orderId}`;
440
+ const url = `${KALSHI_API_BASE}${apiPath}`;
441
+ const { headers } = signRequest('PATCH', `/trade-api/v2${apiPath}`, privateKey);
442
+ const res = await fetch(url, {
443
+ method: 'PATCH',
444
+ headers,
445
+ body: JSON.stringify(params),
446
+ });
447
+ if (!res.ok) {
448
+ const text = await res.text();
449
+ throw new Error(`Kalshi API ${res.status}: ${text}`);
450
+ }
451
+ return res.json();
452
+ }
453
+ async function createRFQ(params) {
454
+ return kalshiAuthPost('/communications/rfqs', params);
455
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
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"