@spfunctions/cli 1.1.5 → 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 +4 -0
- package/dist/client.js +14 -0
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +891 -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/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.js +21 -2
- 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 +154 -3
- package/dist/kalshi.d.ts +71 -0
- package/dist/kalshi.js +257 -17
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scheduleCommand = scheduleCommand;
|
|
4
|
+
const utils_js_1 = require("../utils.js");
|
|
5
|
+
const KALSHI_API_BASE = 'https://api.elections.kalshi.com/trade-api/v2';
|
|
6
|
+
async function scheduleCommand(opts) {
|
|
7
|
+
const statusRes = await fetch(`${KALSHI_API_BASE}/exchange/status`, {
|
|
8
|
+
headers: { 'Accept': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
if (!statusRes.ok)
|
|
11
|
+
throw new Error(`Exchange API ${statusRes.status}`);
|
|
12
|
+
const status = await statusRes.json();
|
|
13
|
+
let schedule = null;
|
|
14
|
+
try {
|
|
15
|
+
const schedRes = await fetch(`${KALSHI_API_BASE}/exchange/schedule`, {
|
|
16
|
+
headers: { 'Accept': 'application/json' },
|
|
17
|
+
});
|
|
18
|
+
if (schedRes.ok)
|
|
19
|
+
schedule = await schedRes.json();
|
|
20
|
+
}
|
|
21
|
+
catch { /* schedule endpoint may not exist */ }
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
console.log(JSON.stringify({ status, schedule }, null, 2));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const trading = status.exchange_active ? `${utils_js_1.c.green}OPEN${utils_js_1.c.reset}` : `${utils_js_1.c.red}CLOSED${utils_js_1.c.reset}`;
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(` ${utils_js_1.c.bold}${utils_js_1.c.cyan}Exchange Status${utils_js_1.c.reset}`);
|
|
29
|
+
console.log(` ${utils_js_1.c.dim}${'─'.repeat(30)}${utils_js_1.c.reset}`);
|
|
30
|
+
console.log(` Trading: ${trading}`);
|
|
31
|
+
if (status.trading_active !== undefined) {
|
|
32
|
+
console.log(` Trading Active: ${status.trading_active ? 'yes' : 'no'}`);
|
|
33
|
+
}
|
|
34
|
+
if (schedule?.schedule) {
|
|
35
|
+
console.log(` Schedule: ${JSON.stringify(schedule.schedule).slice(0, 100)}`);
|
|
36
|
+
}
|
|
37
|
+
console.log();
|
|
38
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.settlementsCommand = settlementsCommand;
|
|
4
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
5
|
+
const client_js_1 = require("../client.js");
|
|
6
|
+
const utils_js_1 = require("../utils.js");
|
|
7
|
+
async function settlementsCommand(opts) {
|
|
8
|
+
// Paginate through all settlements
|
|
9
|
+
const all = [];
|
|
10
|
+
let cursor = '';
|
|
11
|
+
do {
|
|
12
|
+
const result = await (0, kalshi_js_1.getSettlements)({ limit: 200, cursor: cursor || undefined });
|
|
13
|
+
if (!result)
|
|
14
|
+
throw new Error('Kalshi not configured. Set KALSHI_API_KEY_ID + KALSHI_PRIVATE_KEY_PATH.');
|
|
15
|
+
all.push(...result.settlements);
|
|
16
|
+
cursor = result.cursor;
|
|
17
|
+
} while (cursor);
|
|
18
|
+
let filtered = all;
|
|
19
|
+
if (opts.thesis) {
|
|
20
|
+
const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
|
|
21
|
+
const ctx = await client.getContext(opts.thesis);
|
|
22
|
+
const edgeTickers = new Set((ctx.edges || []).map((e) => e.marketId));
|
|
23
|
+
filtered = all.filter((s) => edgeTickers.has(s.ticker));
|
|
24
|
+
}
|
|
25
|
+
if (opts.json) {
|
|
26
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (filtered.length === 0) {
|
|
30
|
+
console.log(`${utils_js_1.c.dim}No settlements found.${utils_js_1.c.reset}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Settlements${utils_js_1.c.reset}`);
|
|
34
|
+
console.log(`${utils_js_1.c.dim}${'─'.repeat(80)}${utils_js_1.c.reset}`);
|
|
35
|
+
console.log(`${utils_js_1.c.bold}${'Ticker'.padEnd(35)} ${'Result'.padEnd(8)} ${'Revenue'.padEnd(10)} ${'Cost'.padEnd(10)} P&L${utils_js_1.c.reset}`);
|
|
36
|
+
let totalPnl = 0;
|
|
37
|
+
for (const s of filtered.slice(0, 50)) {
|
|
38
|
+
const revenue = parseFloat(s.revenue || s.revenue_dollars || '0');
|
|
39
|
+
const cost = parseFloat(s.yes_total_cost || s.yes_total_cost_dollars || '0') +
|
|
40
|
+
parseFloat(s.no_total_cost || s.no_total_cost_dollars || '0');
|
|
41
|
+
const pnl = revenue - cost;
|
|
42
|
+
totalPnl += pnl;
|
|
43
|
+
const pnlStr = pnl >= 0 ? `${utils_js_1.c.green}+$${pnl.toFixed(2)}${utils_js_1.c.reset}` : `${utils_js_1.c.red}-$${Math.abs(pnl).toFixed(2)}${utils_js_1.c.reset}`;
|
|
44
|
+
const result = s.market_result || '-';
|
|
45
|
+
console.log(` ${(s.ticker || '').slice(0, 33).padEnd(35)} ${result.padEnd(8)} $${revenue.toFixed(2).padEnd(9)} $${cost.toFixed(2).padEnd(9)} ${pnlStr}`);
|
|
46
|
+
}
|
|
47
|
+
console.log(`${utils_js_1.c.dim}${'─'.repeat(80)}${utils_js_1.c.reset}`);
|
|
48
|
+
const totalStr = totalPnl >= 0 ? `${utils_js_1.c.green}+$${totalPnl.toFixed(2)}${utils_js_1.c.reset}` : `${utils_js_1.c.red}-$${Math.abs(totalPnl).toFixed(2)}${utils_js_1.c.reset}`;
|
|
49
|
+
console.log(` Total: ${totalStr} (${filtered.length} settlements)`);
|
|
50
|
+
}
|
package/dist/commands/setup.d.ts
CHANGED
package/dist/commands/setup.js
CHANGED
|
@@ -147,6 +147,21 @@ async function setupCommand(opts) {
|
|
|
147
147
|
ok(`保存到 ${(0, config_js_1.getConfigPath)()}`);
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
|
+
// ── sf setup --enable-trading / --disable-trading ────────────────────────
|
|
151
|
+
if (opts.enableTrading) {
|
|
152
|
+
const existing = (0, config_js_1.loadFileConfig)();
|
|
153
|
+
(0, config_js_1.saveConfig)({ ...existing, tradingEnabled: true });
|
|
154
|
+
ok('Trading enabled. sf buy / sf sell / sf cancel now available.');
|
|
155
|
+
blank();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (opts.disableTrading) {
|
|
159
|
+
const existing = (0, config_js_1.loadFileConfig)();
|
|
160
|
+
(0, config_js_1.saveConfig)({ ...existing, tradingEnabled: false });
|
|
161
|
+
ok('Trading disabled.');
|
|
162
|
+
blank();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
150
165
|
// ── Full interactive wizard ───────────────────────────────────────────────
|
|
151
166
|
return runWizard();
|
|
152
167
|
}
|
|
@@ -185,6 +200,13 @@ async function showCheck() {
|
|
|
185
200
|
else {
|
|
186
201
|
info(`${dim('○')} TAVILY ${dim('跳过')}`);
|
|
187
202
|
}
|
|
203
|
+
// Trading
|
|
204
|
+
if (config.tradingEnabled) {
|
|
205
|
+
ok('TRADING 已启用');
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
info(`${dim('○')} TRADING ${dim('未启用 — sf setup --enable-trading')}`);
|
|
209
|
+
}
|
|
188
210
|
blank();
|
|
189
211
|
console.log(` ${dim('配置文件: ' + (0, config_js_1.getConfigPath)())}`);
|
|
190
212
|
blank();
|
|
@@ -303,6 +325,26 @@ async function runWizard() {
|
|
|
303
325
|
if (config.tavilyKey)
|
|
304
326
|
process.env.TAVILY_API_KEY = config.tavilyKey;
|
|
305
327
|
// ════════════════════════════════════════════════════════════════════════════
|
|
328
|
+
// Step 5: Trading
|
|
329
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
330
|
+
if (config.kalshiKeyId) {
|
|
331
|
+
console.log(` ${bold('第 5 步:交易功能(可选)')}`);
|
|
332
|
+
blank();
|
|
333
|
+
info('⚠️ 启用后 sf buy / sf sell / sf cancel 可用。');
|
|
334
|
+
info('你的 Kalshi API key 必须有 read+write 权限。');
|
|
335
|
+
blank();
|
|
336
|
+
const enableTrading = await promptYN(' 启用交易功能?(y/N) ', false);
|
|
337
|
+
config.tradingEnabled = enableTrading;
|
|
338
|
+
if (enableTrading) {
|
|
339
|
+
ok('交易功能已启用');
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
info(dim('跳过。之后可以 sf setup --enable-trading 启用。'));
|
|
343
|
+
}
|
|
344
|
+
blank();
|
|
345
|
+
(0, config_js_1.saveConfig)(config);
|
|
346
|
+
}
|
|
347
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
306
348
|
// Summary
|
|
307
349
|
// ════════════════════════════════════════════════════════════════════════════
|
|
308
350
|
console.log(` ${dim('─'.repeat(25))}`);
|
|
@@ -433,7 +475,7 @@ async function promptForTavily() {
|
|
|
433
475
|
blank();
|
|
434
476
|
return answer;
|
|
435
477
|
}
|
|
436
|
-
// ─── Step
|
|
478
|
+
// ─── Step 6: Thesis ──────────────────────────────────────────────────────────
|
|
437
479
|
async function handleThesisStep(config) {
|
|
438
480
|
try {
|
|
439
481
|
const client = new client_js_1.SFClient(config.apiKey, config.apiUrl);
|
|
@@ -441,7 +483,7 @@ async function handleThesisStep(config) {
|
|
|
441
483
|
const theses = data.theses || [];
|
|
442
484
|
const activeTheses = theses.filter((t) => t.status === 'active');
|
|
443
485
|
if (activeTheses.length > 0) {
|
|
444
|
-
console.log(` ${bold('第
|
|
486
|
+
console.log(` ${bold('第 6 步:论文')}`);
|
|
445
487
|
blank();
|
|
446
488
|
ok(`已有 ${activeTheses.length} 个活跃论文:`);
|
|
447
489
|
for (const t of activeTheses.slice(0, 5)) {
|
|
@@ -482,7 +524,7 @@ async function handleThesisStep(config) {
|
|
|
482
524
|
return;
|
|
483
525
|
}
|
|
484
526
|
// No theses — offer to create one
|
|
485
|
-
console.log(` ${bold('第
|
|
527
|
+
console.log(` ${bold('第 6 步:创建你的第一个论文')}`);
|
|
486
528
|
blank();
|
|
487
529
|
info('论文是你对市场的一个核心判断。系统会基于它构建因果模型,');
|
|
488
530
|
info('然后持续扫描预测市场寻找被错误定价的合约。');
|
package/dist/commands/signal.js
CHANGED
|
@@ -14,5 +14,16 @@ async function signalCommand(id, content, opts) {
|
|
|
14
14
|
if (result.signalId) {
|
|
15
15
|
console.log(` ${utils_js_1.c.bold}ID:${utils_js_1.c.reset} ${result.signalId}`);
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
// Calculate minutes until next 15-min cron cycle (runs at :00, :15, :30, :45)
|
|
18
|
+
const now = new Date();
|
|
19
|
+
const minute = now.getMinutes();
|
|
20
|
+
const nextCycleMin = Math.ceil((minute + 1) / 15) * 15;
|
|
21
|
+
const minutesUntil = nextCycleMin - minute;
|
|
22
|
+
const nextRun = new Date(now);
|
|
23
|
+
nextRun.setMinutes(nextCycleMin % 60, 0, 0);
|
|
24
|
+
if (nextCycleMin >= 60)
|
|
25
|
+
nextRun.setHours(nextRun.getHours() + 1);
|
|
26
|
+
const timeStr = nextRun.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
27
|
+
console.log(`\n${utils_js_1.c.dim}Signal queued. Next monitor cycle in ~${minutesUntil}min (${timeStr}).${utils_js_1.c.reset}`);
|
|
28
|
+
console.log(`${utils_js_1.c.dim}Or run ${utils_js_1.c.reset}sf evaluate ${id}${utils_js_1.c.dim} to consume immediately.${utils_js_1.c.reset}`);
|
|
18
29
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sf strategies — List strategies across theses
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* sf strategies — all active strategies across all theses
|
|
6
|
+
* sf strategies f582bf76 — strategies for a specific thesis
|
|
7
|
+
* sf strategies --status executed — filter by status
|
|
8
|
+
* sf strategies --all — all statuses
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
export declare function registerStrategies(program: Command): void;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* sf strategies — List strategies across theses
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* sf strategies — all active strategies across all theses
|
|
7
|
+
* sf strategies f582bf76 — strategies for a specific thesis
|
|
8
|
+
* sf strategies --status executed — filter by status
|
|
9
|
+
* sf strategies --all — all statuses
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.registerStrategies = registerStrategies;
|
|
13
|
+
const client_js_1 = require("../client.js");
|
|
14
|
+
const STATUS_COLORS = {
|
|
15
|
+
active: '\x1b[32m', // green
|
|
16
|
+
watching: '\x1b[33m', // yellow
|
|
17
|
+
executed: '\x1b[36m', // cyan
|
|
18
|
+
cancelled: '\x1b[90m', // gray
|
|
19
|
+
review: '\x1b[31m', // red
|
|
20
|
+
};
|
|
21
|
+
const RESET = '\x1b[0m';
|
|
22
|
+
const DIM = '\x1b[2m';
|
|
23
|
+
const BOLD = '\x1b[1m';
|
|
24
|
+
function formatStrategy(s, showThesis = false) {
|
|
25
|
+
const statusColor = STATUS_COLORS[s.status] || '';
|
|
26
|
+
const lines = [];
|
|
27
|
+
// Header line
|
|
28
|
+
const thesisPrefix = showThesis ? `${DIM}${s.thesisTitle || s.thesisId?.slice(0, 8)}${RESET} ` : '';
|
|
29
|
+
lines.push(` ${thesisPrefix}${statusColor}[${s.status}]${RESET} ${BOLD}${s.marketId}${RESET} ${s.direction.toUpperCase()} ${DIM}${s.horizon}${RESET} priority ${s.priority || 0}`);
|
|
30
|
+
// Entry conditions
|
|
31
|
+
const entryParts = [];
|
|
32
|
+
if (s.entryBelow != null)
|
|
33
|
+
entryParts.push(`ask ≤ ${s.entryBelow}¢`);
|
|
34
|
+
if (s.entryAbove != null)
|
|
35
|
+
entryParts.push(`ask ≥ ${s.entryAbove}¢`);
|
|
36
|
+
const stopPart = s.stopLoss != null ? `Stop: ${s.stopLoss}¢` : '';
|
|
37
|
+
const tpPart = s.takeProfit != null ? `TP: ${s.takeProfit}¢` : '';
|
|
38
|
+
const maxPart = `Max: ${s.maxQuantity || 500}`;
|
|
39
|
+
const filledPart = `Filled: ${s.executedQuantity || 0}/${s.maxQuantity || 500}`;
|
|
40
|
+
const conditionLine = [
|
|
41
|
+
entryParts.length > 0 ? `Entry: ${entryParts.join(', ')}` : null,
|
|
42
|
+
stopPart || null,
|
|
43
|
+
tpPart || null,
|
|
44
|
+
maxPart,
|
|
45
|
+
filledPart,
|
|
46
|
+
].filter(Boolean).join(' | ');
|
|
47
|
+
lines.push(` ${conditionLine}`);
|
|
48
|
+
// Soft conditions
|
|
49
|
+
if (s.softConditions) {
|
|
50
|
+
lines.push(` ${DIM}Soft: ${s.softConditions}${RESET}`);
|
|
51
|
+
}
|
|
52
|
+
// Review warning
|
|
53
|
+
if (s.status === 'review') {
|
|
54
|
+
lines.push(` \x1b[31m⚠️ Needs review${RESET}`);
|
|
55
|
+
}
|
|
56
|
+
// Rationale (truncated)
|
|
57
|
+
if (s.rationale) {
|
|
58
|
+
const truncated = s.rationale.length > 120 ? s.rationale.slice(0, 117) + '...' : s.rationale;
|
|
59
|
+
lines.push(` ${DIM}${truncated}${RESET}`);
|
|
60
|
+
}
|
|
61
|
+
// Footer
|
|
62
|
+
const createdBy = s.createdBy || 'user';
|
|
63
|
+
const date = s.createdAt ? new Date(s.createdAt).toLocaleDateString('en-US', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
|
|
64
|
+
lines.push(` ${DIM}created by ${createdBy} · ${date}${RESET}`);
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
function registerStrategies(program) {
|
|
68
|
+
program
|
|
69
|
+
.command('strategies')
|
|
70
|
+
.argument('[thesisId]', 'thesis ID or prefix (omit for all theses)')
|
|
71
|
+
.option('--status <status>', 'filter by status (active|watching|executed|cancelled|review)')
|
|
72
|
+
.option('--all', 'show all statuses (default: active only)')
|
|
73
|
+
.description('List strategies across theses')
|
|
74
|
+
.action(async (thesisId, opts) => {
|
|
75
|
+
try {
|
|
76
|
+
const client = new client_js_1.SFClient();
|
|
77
|
+
if (thesisId) {
|
|
78
|
+
// Strategies for a specific thesis
|
|
79
|
+
const statusParam = opts?.all ? '' : (opts?.status || 'active');
|
|
80
|
+
const url = statusParam
|
|
81
|
+
? `/api/thesis/${thesisId}/strategies?status=${statusParam}`
|
|
82
|
+
: `/api/thesis/${thesisId}/strategies`;
|
|
83
|
+
const data = await client.getStrategies(thesisId, opts?.all ? undefined : (opts?.status || 'active'));
|
|
84
|
+
const strategies = data.strategies || [];
|
|
85
|
+
if (strategies.length === 0) {
|
|
86
|
+
console.log(`\n No strategies found for thesis ${thesisId}`);
|
|
87
|
+
if (!opts?.all && !opts?.status) {
|
|
88
|
+
console.log(` ${DIM}Try --all to see all statuses${RESET}`);
|
|
89
|
+
}
|
|
90
|
+
console.log();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.log(`\n Strategies for ${thesisId}\n`);
|
|
94
|
+
for (const s of strategies) {
|
|
95
|
+
console.log(formatStrategy(s));
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// All strategies across all theses
|
|
101
|
+
const { theses } = await client.listTheses();
|
|
102
|
+
let totalStrategies = 0;
|
|
103
|
+
for (const thesis of theses) {
|
|
104
|
+
const statusFilter = opts?.all ? undefined : (opts?.status || 'active');
|
|
105
|
+
const data = await client.getStrategies(thesis.id, statusFilter);
|
|
106
|
+
const strategies = data.strategies || [];
|
|
107
|
+
if (strategies.length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
totalStrategies += strategies.length;
|
|
110
|
+
console.log(`\n ${BOLD}${thesis.title}${RESET} ${DIM}(${thesis.id.slice(0, 8)})${RESET}\n`);
|
|
111
|
+
for (const s of strategies) {
|
|
112
|
+
console.log(formatStrategy(s));
|
|
113
|
+
console.log();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (totalStrategies === 0) {
|
|
117
|
+
console.log(`\n No strategies found`);
|
|
118
|
+
if (!opts?.all && !opts?.status) {
|
|
119
|
+
console.log(` ${DIM}Try --all to see all statuses${RESET}`);
|
|
120
|
+
}
|
|
121
|
+
console.log();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
console.error(`\x1b[31mError:\x1b[0m ${err.message}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function buyCommand(ticker: string, qty: string, opts: {
|
|
2
|
+
price?: string;
|
|
3
|
+
market?: boolean;
|
|
4
|
+
side?: string;
|
|
5
|
+
yesIAmSure?: boolean;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function sellCommand(ticker: string, qty: string, opts: {
|
|
8
|
+
price?: string;
|
|
9
|
+
market?: boolean;
|
|
10
|
+
side?: string;
|
|
11
|
+
yesIAmSure?: boolean;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buyCommand = buyCommand;
|
|
4
|
+
exports.sellCommand = sellCommand;
|
|
5
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
6
|
+
const config_js_1 = require("../config.js");
|
|
7
|
+
const utils_js_1 = require("../utils.js");
|
|
8
|
+
async function buyCommand(ticker, qty, opts) {
|
|
9
|
+
(0, config_js_1.requireTrading)();
|
|
10
|
+
await executeOrder(ticker, qty, 'buy', opts);
|
|
11
|
+
}
|
|
12
|
+
async function sellCommand(ticker, qty, opts) {
|
|
13
|
+
(0, config_js_1.requireTrading)();
|
|
14
|
+
await executeOrder(ticker, qty, 'sell', opts);
|
|
15
|
+
}
|
|
16
|
+
async function executeOrder(ticker, qty, action, opts) {
|
|
17
|
+
const quantity = parseInt(qty);
|
|
18
|
+
if (isNaN(quantity) || quantity <= 0)
|
|
19
|
+
throw new Error('Quantity must be a positive integer');
|
|
20
|
+
const side = (opts.side || 'yes');
|
|
21
|
+
const orderType = opts.market ? 'market' : 'limit';
|
|
22
|
+
if (orderType === 'limit' && !opts.price) {
|
|
23
|
+
throw new Error('Limit order requires --price <cents>. Use --market for market orders.');
|
|
24
|
+
}
|
|
25
|
+
const priceCents = opts.price ? parseInt(opts.price) : undefined;
|
|
26
|
+
if (priceCents !== undefined && (priceCents < 1 || priceCents > 99)) {
|
|
27
|
+
throw new Error('Price must be 1-99 cents.');
|
|
28
|
+
}
|
|
29
|
+
const priceDollars = priceCents ? (priceCents / 100).toFixed(2) : undefined;
|
|
30
|
+
const maxCost = ((priceCents || 99) * quantity / 100).toFixed(2);
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(` ${utils_js_1.c.bold}${utils_js_1.c.cyan}${action.toUpperCase()} Order${utils_js_1.c.reset}`);
|
|
33
|
+
console.log(` ${utils_js_1.c.dim}${'─'.repeat(35)}${utils_js_1.c.reset}`);
|
|
34
|
+
console.log(` Ticker: ${ticker}`);
|
|
35
|
+
console.log(` Side: ${side === 'yes' ? utils_js_1.c.green + 'YES' + utils_js_1.c.reset : utils_js_1.c.red + 'NO' + utils_js_1.c.reset}`);
|
|
36
|
+
console.log(` Quantity: ${quantity}`);
|
|
37
|
+
console.log(` Type: ${orderType}`);
|
|
38
|
+
if (priceDollars)
|
|
39
|
+
console.log(` Price: ${priceCents}¢`);
|
|
40
|
+
console.log(` Max cost: $${maxCost}`);
|
|
41
|
+
console.log();
|
|
42
|
+
if (!opts.yesIAmSure) {
|
|
43
|
+
for (let i = 3; i > 0; i--) {
|
|
44
|
+
process.stdout.write(` Executing in ${i}... (Ctrl+C to cancel)\r`);
|
|
45
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write(' Executing... \n');
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const result = await (0, kalshi_js_1.createOrder)({
|
|
51
|
+
ticker,
|
|
52
|
+
side,
|
|
53
|
+
action,
|
|
54
|
+
type: orderType,
|
|
55
|
+
count: quantity,
|
|
56
|
+
...(priceDollars ? { yes_price: priceDollars } : {}),
|
|
57
|
+
});
|
|
58
|
+
const order = result.order || result;
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(` ${utils_js_1.c.green}✓${utils_js_1.c.reset} Order placed: ${order.order_id || 'OK'}`);
|
|
61
|
+
if (order.status)
|
|
62
|
+
console.log(` Status: ${order.status}`);
|
|
63
|
+
if (order.fill_count_fp)
|
|
64
|
+
console.log(` Filled: ${order.fill_count_fp}/${order.initial_count_fp || quantity}`);
|
|
65
|
+
console.log();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const msg = err.message || String(err);
|
|
69
|
+
if (msg.includes('403')) {
|
|
70
|
+
console.error(`\n ${utils_js_1.c.red}✗${utils_js_1.c.reset} 403 Forbidden — your Kalshi key lacks write permission.`);
|
|
71
|
+
console.error(` Get a read+write key at https://kalshi.com/account/api-keys\n`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.error(`\n ${utils_js_1.c.red}✗${utils_js_1.c.reset} ${msg}\n`);
|
|
75
|
+
}
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface SFConfig {
|
|
|
15
15
|
kalshiPrivateKeyPath?: string;
|
|
16
16
|
tavilyKey?: string;
|
|
17
17
|
model?: string;
|
|
18
|
+
tradingEnabled?: boolean;
|
|
18
19
|
configuredAt?: string;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -47,4 +48,5 @@ export declare function applyConfig(): void;
|
|
|
47
48
|
* Check if SF API key is configured (from any source).
|
|
48
49
|
*/
|
|
49
50
|
export declare function isConfigured(): boolean;
|
|
51
|
+
export declare function requireTrading(): void;
|
|
50
52
|
export declare function getConfigPath(): string;
|
package/dist/config.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.saveConfig = saveConfig;
|
|
|
18
18
|
exports.resetConfig = resetConfig;
|
|
19
19
|
exports.applyConfig = applyConfig;
|
|
20
20
|
exports.isConfigured = isConfigured;
|
|
21
|
+
exports.requireTrading = requireTrading;
|
|
21
22
|
exports.getConfigPath = getConfigPath;
|
|
22
23
|
const fs_1 = __importDefault(require("fs"));
|
|
23
24
|
const path_1 = __importDefault(require("path"));
|
|
@@ -51,6 +52,7 @@ function loadConfig() {
|
|
|
51
52
|
kalshiPrivateKeyPath: process.env.KALSHI_PRIVATE_KEY_PATH || file.kalshiPrivateKeyPath,
|
|
52
53
|
tavilyKey: process.env.TAVILY_API_KEY || file.tavilyKey,
|
|
53
54
|
model: process.env.SF_MODEL || file.model || DEFAULT_MODEL,
|
|
55
|
+
tradingEnabled: file.tradingEnabled || false,
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
@@ -112,6 +114,17 @@ function isConfigured() {
|
|
|
112
114
|
const config = loadConfig();
|
|
113
115
|
return !!config.apiKey;
|
|
114
116
|
}
|
|
117
|
+
function requireTrading() {
|
|
118
|
+
const config = loadConfig();
|
|
119
|
+
if (!config.tradingEnabled) {
|
|
120
|
+
console.error('\n ⚠️ Trading is disabled. Run: sf setup --enable-trading\n');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
if (!config.kalshiKeyId && !process.env.KALSHI_API_KEY_ID) {
|
|
124
|
+
console.error('\n ⚠️ Kalshi API key not configured. Run: sf setup\n');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
115
128
|
function getConfigPath() {
|
|
116
129
|
return CONFIG_PATH;
|
|
117
130
|
}
|