@spfunctions/cli 1.1.4 → 1.1.6
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.
- package/dist/client.d.ts +6 -0
- package/dist/client.js +20 -0
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +914 -14
- package/dist/commands/announcements.d.ts +3 -0
- package/dist/commands/announcements.js +28 -0
- package/dist/commands/balance.d.ts +3 -0
- package/dist/commands/balance.js +17 -0
- package/dist/commands/cancel.d.ts +5 -0
- package/dist/commands/cancel.js +41 -0
- package/dist/commands/dashboard.d.ts +11 -0
- package/dist/commands/dashboard.js +195 -0
- package/dist/commands/explore.d.ts +13 -0
- package/dist/commands/explore.js +110 -0
- package/dist/commands/fills.d.ts +4 -0
- package/dist/commands/fills.js +29 -0
- package/dist/commands/forecast.d.ts +4 -0
- package/dist/commands/forecast.js +53 -0
- package/dist/commands/history.d.ts +3 -0
- package/dist/commands/history.js +38 -0
- package/dist/commands/milestones.d.ts +8 -0
- package/dist/commands/milestones.js +56 -0
- package/dist/commands/orders.d.ts +4 -0
- package/dist/commands/orders.js +28 -0
- package/dist/commands/publish.d.ts +15 -0
- package/dist/commands/publish.js +39 -0
- package/dist/commands/rfq.d.ts +5 -0
- package/dist/commands/rfq.js +35 -0
- package/dist/commands/schedule.d.ts +3 -0
- package/dist/commands/schedule.js +38 -0
- package/dist/commands/settlements.d.ts +6 -0
- package/dist/commands/settlements.js +50 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +45 -3
- package/dist/commands/signal.js +12 -1
- package/dist/commands/strategies.d.ts +11 -0
- package/dist/commands/strategies.js +130 -0
- package/dist/commands/trade.d.ts +12 -0
- package/dist/commands/trade.js +78 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +13 -0
- package/dist/index.js +182 -3
- package/dist/kalshi.d.ts +71 -0
- package/dist/kalshi.js +257 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,22 @@ const positions_js_1 = require("./commands/positions.js");
|
|
|
32
32
|
const edges_js_1 = require("./commands/edges.js");
|
|
33
33
|
const agent_js_1 = require("./commands/agent.js");
|
|
34
34
|
const setup_js_1 = require("./commands/setup.js");
|
|
35
|
+
const publish_js_1 = require("./commands/publish.js");
|
|
36
|
+
const explore_js_1 = require("./commands/explore.js");
|
|
37
|
+
const dashboard_js_1 = require("./commands/dashboard.js");
|
|
38
|
+
const strategies_js_1 = require("./commands/strategies.js");
|
|
39
|
+
const milestones_js_1 = require("./commands/milestones.js");
|
|
40
|
+
const forecast_js_1 = require("./commands/forecast.js");
|
|
41
|
+
const settlements_js_1 = require("./commands/settlements.js");
|
|
42
|
+
const balance_js_1 = require("./commands/balance.js");
|
|
43
|
+
const orders_js_1 = require("./commands/orders.js");
|
|
44
|
+
const fills_js_1 = require("./commands/fills.js");
|
|
45
|
+
const trade_js_1 = require("./commands/trade.js");
|
|
46
|
+
const cancel_js_1 = require("./commands/cancel.js");
|
|
47
|
+
const schedule_js_1 = require("./commands/schedule.js");
|
|
48
|
+
const rfq_js_1 = require("./commands/rfq.js");
|
|
49
|
+
const announcements_js_1 = require("./commands/announcements.js");
|
|
50
|
+
const history_js_1 = require("./commands/history.js");
|
|
35
51
|
const utils_js_1 = require("./utils.js");
|
|
36
52
|
// ── Apply ~/.sf/config.json to process.env BEFORE any command ────────────────
|
|
37
53
|
// This means client.ts, kalshi.ts, agent.ts keep reading process.env and just work.
|
|
@@ -44,7 +60,7 @@ program
|
|
|
44
60
|
.option('--api-key <key>', 'API key (or set SF_API_KEY env var)')
|
|
45
61
|
.option('--api-url <url>', 'API base URL (or set SF_API_URL env var)');
|
|
46
62
|
// ── Pre-action guard: check configuration ────────────────────────────────────
|
|
47
|
-
const NO_CONFIG_COMMANDS = new Set(['setup', 'help', 'scan']);
|
|
63
|
+
const NO_CONFIG_COMMANDS = new Set(['setup', 'help', 'scan', 'explore', 'milestones', 'forecast', 'settlements', 'balance', 'orders', 'fills', 'schedule', 'announcements', 'history']);
|
|
48
64
|
program.hook('preAction', (thisCommand) => {
|
|
49
65
|
const cmdName = thisCommand.name();
|
|
50
66
|
if (NO_CONFIG_COMMANDS.has(cmdName))
|
|
@@ -67,8 +83,10 @@ program
|
|
|
67
83
|
.option('--check', 'Show current configuration status')
|
|
68
84
|
.option('--reset', 'Delete config and start over')
|
|
69
85
|
.option('--key <key>', 'Set SF API key (non-interactive, for CI)')
|
|
86
|
+
.option('--enable-trading', 'Enable trading (sf buy/sell/cancel)')
|
|
87
|
+
.option('--disable-trading', 'Disable trading')
|
|
70
88
|
.action(async (opts) => {
|
|
71
|
-
await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key }));
|
|
89
|
+
await run(() => (0, setup_js_1.setupCommand)({ check: opts.check, reset: opts.reset, key: opts.key, enableTrading: opts.enableTrading, disableTrading: opts.disableTrading }));
|
|
72
90
|
});
|
|
73
91
|
// ── sf list ──────────────────────────────────────────────────────────────────
|
|
74
92
|
program
|
|
@@ -181,10 +199,171 @@ program
|
|
|
181
199
|
.option('--model <model>', 'Model via OpenRouter (default: anthropic/claude-sonnet-4.6)')
|
|
182
200
|
.option('--model-key <key>', 'OpenRouter API key (or set OPENROUTER_API_KEY)')
|
|
183
201
|
.option('--new', 'Start a fresh session (default: continue last session)')
|
|
202
|
+
.option('--plain', 'Plain text mode (no TUI, works in pipes and scripts)')
|
|
184
203
|
.action(async (thesisId, opts, cmd) => {
|
|
185
204
|
const g = cmd.optsWithGlobals();
|
|
186
|
-
await run(() => (0, agent_js_1.agentCommand)(thesisId, { model: opts.model, modelKey: opts.modelKey, newSession: opts.new }));
|
|
205
|
+
await run(() => (0, agent_js_1.agentCommand)(thesisId, { model: opts.model, modelKey: opts.modelKey, newSession: opts.new, noTui: opts.plain }));
|
|
187
206
|
});
|
|
207
|
+
// ── sf publish <thesisId> ─────────────────────────────────────────────────────
|
|
208
|
+
program
|
|
209
|
+
.command('publish <thesisId>')
|
|
210
|
+
.description('Publish a thesis for public viewing')
|
|
211
|
+
.requiredOption('--slug <slug>', 'URL slug (lowercase, hyphens, 3-60 chars)')
|
|
212
|
+
.option('--description <desc>', 'Short description')
|
|
213
|
+
.action(async (thesisId, opts, cmd) => {
|
|
214
|
+
const g = cmd.optsWithGlobals();
|
|
215
|
+
await run(() => (0, publish_js_1.publishCommand)(thesisId, { slug: opts.slug, description: opts.description, apiKey: g.apiKey, apiUrl: g.apiUrl }));
|
|
216
|
+
});
|
|
217
|
+
// ── sf unpublish <thesisId> ───────────────────────────────────────────────────
|
|
218
|
+
program
|
|
219
|
+
.command('unpublish <thesisId>')
|
|
220
|
+
.description('Remove a thesis from public viewing')
|
|
221
|
+
.action(async (thesisId, _opts, cmd) => {
|
|
222
|
+
const g = cmd.optsWithGlobals();
|
|
223
|
+
await run(() => (0, publish_js_1.unpublishCommand)(thesisId, { apiKey: g.apiKey, apiUrl: g.apiUrl }));
|
|
224
|
+
});
|
|
225
|
+
// ── sf explore [slug] ─────────────────────────────────────────────────────────
|
|
226
|
+
program
|
|
227
|
+
.command('explore [slug]')
|
|
228
|
+
.description('Browse public theses (no auth required)')
|
|
229
|
+
.option('--json', 'JSON output')
|
|
230
|
+
.action(async (slug, opts) => {
|
|
231
|
+
await run(() => (0, explore_js_1.exploreCommand)(slug, { json: opts.json }));
|
|
232
|
+
});
|
|
233
|
+
// ── sf dashboard ──────────────────────────────────────────────────────────────
|
|
234
|
+
program
|
|
235
|
+
.command('dashboard')
|
|
236
|
+
.description('Portfolio overview — theses, positions, risk, unpositioned edges')
|
|
237
|
+
.option('--json', 'JSON output')
|
|
238
|
+
.action(async (opts, cmd) => {
|
|
239
|
+
const g = cmd.optsWithGlobals();
|
|
240
|
+
await run(() => (0, dashboard_js_1.dashboardCommand)({ json: opts.json, apiKey: g.apiKey, apiUrl: g.apiUrl }));
|
|
241
|
+
});
|
|
242
|
+
// ── sf milestones ────────────────────────────────────────────────────────────
|
|
243
|
+
program
|
|
244
|
+
.command('milestones')
|
|
245
|
+
.description('Upcoming events from Kalshi calendar')
|
|
246
|
+
.option('--category <cat>', 'Filter by category')
|
|
247
|
+
.option('--thesis <id>', 'Show milestones matching thesis edges')
|
|
248
|
+
.option('--hours <n>', 'Hours ahead (default 168)', '168')
|
|
249
|
+
.option('--json', 'JSON output')
|
|
250
|
+
.action(async (opts, cmd) => {
|
|
251
|
+
const g = cmd.optsWithGlobals();
|
|
252
|
+
await run(() => (0, milestones_js_1.milestonesCommand)({ ...opts, apiKey: g.apiKey, apiUrl: g.apiUrl }));
|
|
253
|
+
});
|
|
254
|
+
// ── sf forecast <eventTicker> ────────────────────────────────────────────────
|
|
255
|
+
program
|
|
256
|
+
.command('forecast <eventTicker>')
|
|
257
|
+
.description('Market distribution forecast (P50/P75/P90 history)')
|
|
258
|
+
.option('--days <n>', 'Days of history (default 7)', '7')
|
|
259
|
+
.option('--json', 'JSON output')
|
|
260
|
+
.action(async (eventTicker, opts) => {
|
|
261
|
+
await run(() => (0, forecast_js_1.forecastCommand)(eventTicker, opts));
|
|
262
|
+
});
|
|
263
|
+
// ── sf settlements ───────────────────────────────────────────────────────────
|
|
264
|
+
program
|
|
265
|
+
.command('settlements')
|
|
266
|
+
.description('Settled (resolved) contracts with P&L')
|
|
267
|
+
.option('--thesis <id>', 'Filter to thesis edge tickers')
|
|
268
|
+
.option('--json', 'JSON output')
|
|
269
|
+
.action(async (opts, cmd) => {
|
|
270
|
+
const g = cmd.optsWithGlobals();
|
|
271
|
+
await run(() => (0, settlements_js_1.settlementsCommand)({ ...opts, apiKey: g.apiKey, apiUrl: g.apiUrl }));
|
|
272
|
+
});
|
|
273
|
+
// ── sf balance ───────────────────────────────────────────────────────────────
|
|
274
|
+
program
|
|
275
|
+
.command('balance')
|
|
276
|
+
.description('Kalshi account balance')
|
|
277
|
+
.option('--json', 'JSON output')
|
|
278
|
+
.action(async (opts) => {
|
|
279
|
+
await run(() => (0, balance_js_1.balanceCommand)(opts));
|
|
280
|
+
});
|
|
281
|
+
// ── sf orders ────────────────────────────────────────────────────────────────
|
|
282
|
+
program
|
|
283
|
+
.command('orders')
|
|
284
|
+
.description('Kalshi resting orders')
|
|
285
|
+
.option('--status <status>', 'Order status filter (default: resting)', 'resting')
|
|
286
|
+
.option('--json', 'JSON output')
|
|
287
|
+
.action(async (opts) => {
|
|
288
|
+
await run(() => (0, orders_js_1.ordersCommand)(opts));
|
|
289
|
+
});
|
|
290
|
+
// ── sf fills ─────────────────────────────────────────────────────────────────
|
|
291
|
+
program
|
|
292
|
+
.command('fills')
|
|
293
|
+
.description('Recent trade fills')
|
|
294
|
+
.option('--ticker <ticker>', 'Filter by market ticker')
|
|
295
|
+
.option('--json', 'JSON output')
|
|
296
|
+
.action(async (opts) => {
|
|
297
|
+
await run(() => (0, fills_js_1.fillsCommand)(opts));
|
|
298
|
+
});
|
|
299
|
+
// ── sf schedule ──────────────────────────────────────────────────────────────
|
|
300
|
+
program
|
|
301
|
+
.command('schedule')
|
|
302
|
+
.description('Exchange status and trading hours')
|
|
303
|
+
.option('--json', 'JSON output')
|
|
304
|
+
.action(async (opts) => {
|
|
305
|
+
await run(() => (0, schedule_js_1.scheduleCommand)(opts));
|
|
306
|
+
});
|
|
307
|
+
// ── sf buy <ticker> <qty> ───────────────────────────────────────────────────
|
|
308
|
+
program
|
|
309
|
+
.command('buy <ticker> <qty>')
|
|
310
|
+
.description('Buy contracts (requires --enable-trading)')
|
|
311
|
+
.option('--price <cents>', 'Limit price in cents (required for limit orders)')
|
|
312
|
+
.option('--market', 'Market order (no price needed)')
|
|
313
|
+
.option('--side <s>', 'yes or no', 'yes')
|
|
314
|
+
.option('--yes-i-am-sure', 'Skip 3-second countdown')
|
|
315
|
+
.action(async (ticker, qty, opts) => {
|
|
316
|
+
await run(() => (0, trade_js_1.buyCommand)(ticker, qty, opts));
|
|
317
|
+
});
|
|
318
|
+
// ── sf sell <ticker> <qty> ───────────────────────────────────────────────────
|
|
319
|
+
program
|
|
320
|
+
.command('sell <ticker> <qty>')
|
|
321
|
+
.description('Sell contracts (requires --enable-trading)')
|
|
322
|
+
.option('--price <cents>', 'Limit price in cents')
|
|
323
|
+
.option('--market', 'Market order')
|
|
324
|
+
.option('--side <s>', 'yes or no', 'yes')
|
|
325
|
+
.option('--yes-i-am-sure', 'Skip 3-second countdown')
|
|
326
|
+
.action(async (ticker, qty, opts) => {
|
|
327
|
+
await run(() => (0, trade_js_1.sellCommand)(ticker, qty, opts));
|
|
328
|
+
});
|
|
329
|
+
// ── sf cancel [orderId] ─────────────────────────────────────────────────────
|
|
330
|
+
program
|
|
331
|
+
.command('cancel [orderId]')
|
|
332
|
+
.description('Cancel order(s) (requires --enable-trading)')
|
|
333
|
+
.option('--all', 'Cancel all resting orders')
|
|
334
|
+
.option('--ticker <t>', 'Cancel orders matching ticker prefix (with --all)')
|
|
335
|
+
.option('--yes-i-am-sure', 'Skip countdown')
|
|
336
|
+
.action(async (orderId, opts) => {
|
|
337
|
+
await run(() => (0, cancel_js_1.cancelCommand)(orderId, opts));
|
|
338
|
+
});
|
|
339
|
+
// ── sf rfq <ticker> <qty> ───────────────────────────────────────────────────
|
|
340
|
+
program
|
|
341
|
+
.command('rfq <ticker> <qty>')
|
|
342
|
+
.description('Request for quote — large order pricing (requires --enable-trading)')
|
|
343
|
+
.option('--target-cost <cents>', 'Target cost per contract in cents')
|
|
344
|
+
.option('--rest-remainder', 'Rest unfilled portion as limit order')
|
|
345
|
+
.option('--json', 'JSON output')
|
|
346
|
+
.action(async (ticker, qty, opts) => {
|
|
347
|
+
await run(() => (0, rfq_js_1.rfqCommand)(ticker, qty, opts));
|
|
348
|
+
});
|
|
349
|
+
// ── sf announcements ─────────────────────────────────────────────────────────
|
|
350
|
+
program
|
|
351
|
+
.command('announcements')
|
|
352
|
+
.description('Exchange announcements (rule changes, maintenance)')
|
|
353
|
+
.option('--json', 'JSON output')
|
|
354
|
+
.action(async (opts) => {
|
|
355
|
+
await run(() => (0, announcements_js_1.announcementsCommand)(opts));
|
|
356
|
+
});
|
|
357
|
+
// ── sf history <ticker> ──────────────────────────────────────────────────────
|
|
358
|
+
program
|
|
359
|
+
.command('history <ticker>')
|
|
360
|
+
.description('Historical market data (settled/closed)')
|
|
361
|
+
.option('--json', 'JSON output')
|
|
362
|
+
.action(async (ticker, opts) => {
|
|
363
|
+
await run(() => (0, history_js_1.historyCommand)(ticker, opts));
|
|
364
|
+
});
|
|
365
|
+
// ── sf strategies ─────────────────────────────────────────────────────────────
|
|
366
|
+
(0, strategies_js_1.registerStrategies)(program);
|
|
188
367
|
// ── Error wrapper ─────────────────────────────────────────────────────────────
|
|
189
368
|
async function run(fn) {
|
|
190
369
|
try {
|
package/dist/kalshi.d.ts
CHANGED
|
@@ -35,6 +35,11 @@ export interface KalshiPortfolio {
|
|
|
35
35
|
* Returns null if Kalshi is not configured.
|
|
36
36
|
*/
|
|
37
37
|
export declare function getPositions(): Promise<KalshiPosition[] | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Extract price in cents (0-100) from a Kalshi market object.
|
|
40
|
+
* Dollars fields first (Kalshi API returns null for integer cents fields).
|
|
41
|
+
*/
|
|
42
|
+
export declare function kalshiPriceCents(market: any): number;
|
|
38
43
|
/**
|
|
39
44
|
* Get the current market price for a given ticker (public, no auth).
|
|
40
45
|
* Returns price in cents (0-100) or null.
|
|
@@ -53,3 +58,69 @@ export interface LocalOrderbook {
|
|
|
53
58
|
liquidityScore: 'high' | 'medium' | 'low';
|
|
54
59
|
}
|
|
55
60
|
export declare function getOrderbook(ticker: string): Promise<LocalOrderbook | null>;
|
|
61
|
+
export declare function getSettlements(params?: {
|
|
62
|
+
limit?: number;
|
|
63
|
+
cursor?: string;
|
|
64
|
+
ticker?: string;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
settlements: any[];
|
|
67
|
+
cursor: string;
|
|
68
|
+
} | null>;
|
|
69
|
+
export declare function getBalance(): Promise<{
|
|
70
|
+
balance: number;
|
|
71
|
+
portfolioValue: number;
|
|
72
|
+
} | null>;
|
|
73
|
+
export declare function getOrders(params?: {
|
|
74
|
+
status?: string;
|
|
75
|
+
ticker?: string;
|
|
76
|
+
limit?: number;
|
|
77
|
+
cursor?: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
orders: any[];
|
|
80
|
+
cursor: string;
|
|
81
|
+
} | null>;
|
|
82
|
+
export declare function getFills(params?: {
|
|
83
|
+
ticker?: string;
|
|
84
|
+
limit?: number;
|
|
85
|
+
cursor?: string;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
fills: any[];
|
|
88
|
+
cursor: string;
|
|
89
|
+
} | null>;
|
|
90
|
+
export declare function getForecastHistory(params: {
|
|
91
|
+
seriesTicker: string;
|
|
92
|
+
eventTicker: string;
|
|
93
|
+
percentiles: number[];
|
|
94
|
+
startTs: number;
|
|
95
|
+
endTs: number;
|
|
96
|
+
periodInterval: number;
|
|
97
|
+
}): Promise<any[] | null>;
|
|
98
|
+
export declare function getExchangeAnnouncements(): Promise<any[]>;
|
|
99
|
+
export declare function getHistoricalMarket(ticker: string): Promise<any | null>;
|
|
100
|
+
export declare function createOrder(params: {
|
|
101
|
+
ticker: string;
|
|
102
|
+
side: 'yes' | 'no';
|
|
103
|
+
action: 'buy' | 'sell';
|
|
104
|
+
type: 'limit' | 'market';
|
|
105
|
+
count: number;
|
|
106
|
+
yes_price?: string;
|
|
107
|
+
client_order_id?: string;
|
|
108
|
+
}): Promise<{
|
|
109
|
+
order: any;
|
|
110
|
+
}>;
|
|
111
|
+
export declare function cancelOrder(orderId: string): Promise<void>;
|
|
112
|
+
export declare function batchCancelOrders(orderIds: string[]): Promise<void>;
|
|
113
|
+
export declare function amendOrder(orderId: string, params: {
|
|
114
|
+
price?: string;
|
|
115
|
+
count?: number;
|
|
116
|
+
}): Promise<{
|
|
117
|
+
order: any;
|
|
118
|
+
}>;
|
|
119
|
+
export declare function createRFQ(params: {
|
|
120
|
+
market_ticker: string;
|
|
121
|
+
contracts: number;
|
|
122
|
+
rest_remainder: boolean;
|
|
123
|
+
target_cost_centi_cents?: number;
|
|
124
|
+
}): Promise<{
|
|
125
|
+
id: string;
|
|
126
|
+
}>;
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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.
|
|
3
|
+
"version": "1.1.6",
|
|
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"
|