@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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.announcementsCommand = announcementsCommand;
|
|
4
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
async function announcementsCommand(opts) {
|
|
7
|
+
const announcements = await (0, kalshi_js_1.getExchangeAnnouncements)();
|
|
8
|
+
if (opts.json) {
|
|
9
|
+
console.log(JSON.stringify(announcements, null, 2));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (announcements.length === 0) {
|
|
13
|
+
console.log(`${utils_js_1.c.dim}No announcements.${utils_js_1.c.reset}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Exchange Announcements${utils_js_1.c.reset}`);
|
|
17
|
+
console.log(`${utils_js_1.c.dim}${'─'.repeat(70)}${utils_js_1.c.reset}`);
|
|
18
|
+
for (const a of announcements.slice(0, 20)) {
|
|
19
|
+
const time = a.created_time ? new Date(a.created_time).toLocaleDateString() : '';
|
|
20
|
+
const type = a.type ? `[${a.type}]` : '';
|
|
21
|
+
console.log(` ${utils_js_1.c.dim}${time}${utils_js_1.c.reset} ${type} ${a.title || a.message || ''}`);
|
|
22
|
+
if (a.body) {
|
|
23
|
+
const body = String(a.body).slice(0, 120);
|
|
24
|
+
console.log(` ${utils_js_1.c.dim}${body}${utils_js_1.c.reset}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
console.log(`\n${utils_js_1.c.dim}${announcements.length} announcement(s)${utils_js_1.c.reset}`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.balanceCommand = balanceCommand;
|
|
4
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
async function balanceCommand(opts) {
|
|
7
|
+
const result = await (0, kalshi_js_1.getBalance)();
|
|
8
|
+
if (!result)
|
|
9
|
+
throw new Error('Kalshi not configured. Set KALSHI_API_KEY_ID + KALSHI_PRIVATE_KEY_PATH.');
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify(result, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Kalshi Account${utils_js_1.c.reset}`);
|
|
15
|
+
console.log(` Balance: $${result.balance.toFixed(2)}`);
|
|
16
|
+
console.log(` Portfolio Value: $${result.portfolioValue.toFixed(2)}`);
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cancelCommand = cancelCommand;
|
|
4
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
5
|
+
const config_js_1 = require("../config.js");
|
|
6
|
+
const utils_js_1 = require("../utils.js");
|
|
7
|
+
async function cancelCommand(orderId, opts) {
|
|
8
|
+
(0, config_js_1.requireTrading)();
|
|
9
|
+
if (opts.all) {
|
|
10
|
+
const result = await (0, kalshi_js_1.getOrders)({ status: 'resting', limit: 200 });
|
|
11
|
+
if (!result)
|
|
12
|
+
throw new Error('Kalshi not configured.');
|
|
13
|
+
let toCancel = result.orders;
|
|
14
|
+
if (opts.ticker) {
|
|
15
|
+
toCancel = toCancel.filter((o) => (o.ticker || '').startsWith(opts.ticker));
|
|
16
|
+
}
|
|
17
|
+
if (toCancel.length === 0) {
|
|
18
|
+
console.log(`\n ${utils_js_1.c.dim}No resting orders to cancel.${utils_js_1.c.reset}\n`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log(`\n Cancelling ${toCancel.length} order(s)...`);
|
|
22
|
+
if (!opts.yesIAmSure) {
|
|
23
|
+
for (let i = 3; i > 0; i--) {
|
|
24
|
+
process.stdout.write(` Executing in ${i}... (Ctrl+C to cancel)\r`);
|
|
25
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
26
|
+
}
|
|
27
|
+
process.stdout.write(' Executing... \n');
|
|
28
|
+
}
|
|
29
|
+
for (let i = 0; i < toCancel.length; i += 20) {
|
|
30
|
+
const batch = toCancel.slice(i, i + 20).map((o) => o.order_id);
|
|
31
|
+
await (0, kalshi_js_1.batchCancelOrders)(batch);
|
|
32
|
+
}
|
|
33
|
+
console.log(`\n ${utils_js_1.c.green}✓${utils_js_1.c.reset} Cancelled ${toCancel.length} order(s).\n`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!orderId) {
|
|
37
|
+
throw new Error('Usage: sf cancel <orderId> or sf cancel --all');
|
|
38
|
+
}
|
|
39
|
+
await (0, kalshi_js_1.cancelOrder)(orderId);
|
|
40
|
+
console.log(`\n ${utils_js_1.c.green}✓${utils_js_1.c.reset} Order ${orderId} cancelled.\n`);
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sf dashboard — Portfolio overview
|
|
3
|
+
*
|
|
4
|
+
* One-screen summary: theses, positions, risk exposure, top unpositioned edges.
|
|
5
|
+
* Uses existing APIs + local Kalshi positions.
|
|
6
|
+
*/
|
|
7
|
+
export declare function dashboardCommand(opts?: {
|
|
8
|
+
json?: boolean;
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* sf dashboard — Portfolio overview
|
|
4
|
+
*
|
|
5
|
+
* One-screen summary: theses, positions, risk exposure, top unpositioned edges.
|
|
6
|
+
* Uses existing APIs + local Kalshi positions.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.dashboardCommand = dashboardCommand;
|
|
10
|
+
const client_js_1 = require("../client.js");
|
|
11
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
12
|
+
// ── Risk category mapping by Kalshi ticker prefix ────────────────────────────
|
|
13
|
+
const RISK_CATEGORIES = {
|
|
14
|
+
KXWTIMAX: 'Oil',
|
|
15
|
+
KXWTI: 'Oil',
|
|
16
|
+
KXRECSSNBER: 'Recession',
|
|
17
|
+
KXAAAGASM: 'Gas',
|
|
18
|
+
KXCPI: 'Inflation',
|
|
19
|
+
KXINXY: 'S&P 500',
|
|
20
|
+
KXFEDDECISION: 'Fed Rate',
|
|
21
|
+
KXUNEMPLOYMENT: 'Unemployment',
|
|
22
|
+
KXCLOSEHORMUZ: 'Hormuz',
|
|
23
|
+
};
|
|
24
|
+
function categorize(ticker) {
|
|
25
|
+
// Match longest prefix first
|
|
26
|
+
const sorted = Object.keys(RISK_CATEGORIES).sort((a, b) => b.length - a.length);
|
|
27
|
+
for (const prefix of sorted) {
|
|
28
|
+
if (ticker.startsWith(prefix))
|
|
29
|
+
return RISK_CATEGORIES[prefix];
|
|
30
|
+
}
|
|
31
|
+
return 'Other';
|
|
32
|
+
}
|
|
33
|
+
function timeAgo(dateStr) {
|
|
34
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
35
|
+
const mins = Math.floor(diff / 60000);
|
|
36
|
+
if (mins < 60)
|
|
37
|
+
return `${mins}m ago`;
|
|
38
|
+
const hrs = Math.floor(mins / 60);
|
|
39
|
+
if (hrs < 24)
|
|
40
|
+
return `${hrs}h ago`;
|
|
41
|
+
const days = Math.floor(hrs / 24);
|
|
42
|
+
return `${days}d ago`;
|
|
43
|
+
}
|
|
44
|
+
async function dashboardCommand(opts) {
|
|
45
|
+
const client = new client_js_1.SFClient(opts?.apiKey, opts?.apiUrl);
|
|
46
|
+
// ── Fetch data in parallel ─────────────────────────────────────────────────
|
|
47
|
+
const [thesesResult, positions] = await Promise.all([
|
|
48
|
+
client.listTheses(),
|
|
49
|
+
(0, kalshi_js_1.getPositions)().catch(() => null),
|
|
50
|
+
]);
|
|
51
|
+
const theses = thesesResult.theses || thesesResult;
|
|
52
|
+
// Fetch context for each thesis (edges)
|
|
53
|
+
const contexts = [];
|
|
54
|
+
for (const t of theses) {
|
|
55
|
+
try {
|
|
56
|
+
const ctx = await client.getContext(t.id);
|
|
57
|
+
contexts.push(ctx);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
contexts.push(null);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Enrich positions with live prices
|
|
64
|
+
if (positions) {
|
|
65
|
+
for (const pos of positions) {
|
|
66
|
+
const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
|
|
67
|
+
if (livePrice !== null) {
|
|
68
|
+
pos.current_value = livePrice;
|
|
69
|
+
pos.unrealized_pnl = Math.round((livePrice - pos.average_price_paid) * pos.quantity);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ── Collect all edges across all theses ────────────────────────────────────
|
|
74
|
+
const allEdges = [];
|
|
75
|
+
for (const ctx of contexts) {
|
|
76
|
+
if (!ctx?.edges)
|
|
77
|
+
continue;
|
|
78
|
+
for (const e of ctx.edges) {
|
|
79
|
+
allEdges.push(e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Dedupe edges by marketId (keep highest absolute edge)
|
|
83
|
+
const edgeMap = new Map();
|
|
84
|
+
for (const e of allEdges) {
|
|
85
|
+
const existing = edgeMap.get(e.marketId);
|
|
86
|
+
if (!existing || Math.abs(e.edge) > Math.abs(existing.edge)) {
|
|
87
|
+
edgeMap.set(e.marketId, e);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Find positioned tickers
|
|
91
|
+
const positionedTickers = new Set(positions?.map((p) => p.ticker) || []);
|
|
92
|
+
// Unpositioned edges = edges where no position exists on that marketId
|
|
93
|
+
const unpositionedEdges = [...edgeMap.values()]
|
|
94
|
+
.filter(e => !positionedTickers.has(e.marketId))
|
|
95
|
+
.sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge))
|
|
96
|
+
.slice(0, 10);
|
|
97
|
+
// ── JSON output ────────────────────────────────────────────────────────────
|
|
98
|
+
if (opts?.json) {
|
|
99
|
+
console.log(JSON.stringify({
|
|
100
|
+
theses,
|
|
101
|
+
positions,
|
|
102
|
+
unpositionedEdges,
|
|
103
|
+
}, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// ── Formatted output ───────────────────────────────────────────────────────
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(' SimpleFunctions Dashboard');
|
|
109
|
+
console.log(' ' + '─'.repeat(50));
|
|
110
|
+
console.log();
|
|
111
|
+
// ── Theses ─────────────────────────────────────────────────────────────────
|
|
112
|
+
console.log(' Theses');
|
|
113
|
+
if (theses.length === 0) {
|
|
114
|
+
console.log(' (none)');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
for (let i = 0; i < theses.length; i++) {
|
|
118
|
+
const t = theses[i];
|
|
119
|
+
const ctx = contexts[i];
|
|
120
|
+
const id = t.id.slice(0, 8);
|
|
121
|
+
const title = (t.title || '').slice(0, 35).padEnd(35);
|
|
122
|
+
const conf = t.confidence != null ? `${Math.round(t.confidence * 100)}%` : '?%';
|
|
123
|
+
const edgeCount = ctx?.edges?.length || 0;
|
|
124
|
+
const updated = t.updatedAt ? timeAgo(t.updatedAt) : '?';
|
|
125
|
+
console.log(` ${id} ${title} ${conf.padStart(4)} ${String(edgeCount).padStart(2)} edges updated ${updated}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.log();
|
|
129
|
+
// ── Positions ──────────────────────────────────────────────────────────────
|
|
130
|
+
console.log(' Positions');
|
|
131
|
+
if (!positions || positions.length === 0) {
|
|
132
|
+
console.log(' (no Kalshi positions or Kalshi not configured)');
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
let totalCost = 0;
|
|
136
|
+
let totalPnl = 0;
|
|
137
|
+
for (const p of positions) {
|
|
138
|
+
const ticker = (p.ticker || '').padEnd(22);
|
|
139
|
+
const qty = String(p.quantity || 0).padStart(5);
|
|
140
|
+
const avg = `${p.average_price_paid || 0}¢`;
|
|
141
|
+
const now = typeof p.current_value === 'number' ? `${p.current_value}¢` : '?¢';
|
|
142
|
+
const pnlCents = p.unrealized_pnl || 0;
|
|
143
|
+
const pnlDollars = (pnlCents / 100).toFixed(2);
|
|
144
|
+
const pnlStr = pnlCents >= 0 ? `+$${pnlDollars}` : `-$${Math.abs(parseFloat(pnlDollars)).toFixed(2)}`;
|
|
145
|
+
const cost = (p.average_price_paid || 0) * (p.quantity || 0);
|
|
146
|
+
totalCost += cost;
|
|
147
|
+
totalPnl += pnlCents;
|
|
148
|
+
console.log(` ${ticker} ${qty} @ ${avg.padEnd(5)} now ${now.padEnd(5)} ${pnlStr}`);
|
|
149
|
+
}
|
|
150
|
+
console.log(' ' + '─'.repeat(45));
|
|
151
|
+
const totalCostDollars = (totalCost / 100).toFixed(0);
|
|
152
|
+
const totalPnlDollars = (totalPnl / 100).toFixed(2);
|
|
153
|
+
const pnlDisplay = totalPnl >= 0 ? `+$${totalPnlDollars}` : `-$${Math.abs(parseFloat(totalPnlDollars)).toFixed(2)}`;
|
|
154
|
+
console.log(` Total cost: $${totalCostDollars} | P&L: ${pnlDisplay}`);
|
|
155
|
+
}
|
|
156
|
+
console.log();
|
|
157
|
+
// ── Risk Exposure ──────────────────────────────────────────────────────────
|
|
158
|
+
if (positions && positions.length > 0) {
|
|
159
|
+
console.log(' Risk Exposure');
|
|
160
|
+
const riskGroups = new Map();
|
|
161
|
+
for (const p of positions) {
|
|
162
|
+
const cat = categorize(p.ticker || '');
|
|
163
|
+
const existing = riskGroups.get(cat) || { cost: 0, contracts: 0, tickers: [] };
|
|
164
|
+
const cost = (p.average_price_paid || 0) * (p.quantity || 0);
|
|
165
|
+
existing.cost += cost;
|
|
166
|
+
existing.contracts += p.quantity || 0;
|
|
167
|
+
if (!existing.tickers.includes(p.ticker))
|
|
168
|
+
existing.tickers.push(p.ticker);
|
|
169
|
+
riskGroups.set(cat, existing);
|
|
170
|
+
}
|
|
171
|
+
// Sort by cost descending
|
|
172
|
+
const sorted = [...riskGroups.entries()].sort((a, b) => b[1].cost - a[1].cost);
|
|
173
|
+
for (const [category, data] of sorted) {
|
|
174
|
+
const costDollars = `$${(data.cost / 100).toFixed(0)}`;
|
|
175
|
+
const tickerSummary = data.tickers.length <= 2
|
|
176
|
+
? ` (${data.tickers.join('+')})`
|
|
177
|
+
: ` (${data.tickers.length} markets)`;
|
|
178
|
+
console.log(` ${(category + tickerSummary + ':').padEnd(35)} ${costDollars.padStart(7)} cost | ${String(data.contracts).padStart(5)} contracts`);
|
|
179
|
+
}
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
182
|
+
// ── Top Unpositioned Edges ─────────────────────────────────────────────────
|
|
183
|
+
if (unpositionedEdges.length > 0) {
|
|
184
|
+
console.log(' Top Unpositioned Edges');
|
|
185
|
+
for (const e of unpositionedEdges) {
|
|
186
|
+
const name = (e.market || e.marketId || '').slice(0, 25).padEnd(25);
|
|
187
|
+
const mkt = `${e.marketPrice}¢`;
|
|
188
|
+
const thesis = `${e.thesisPrice}¢`;
|
|
189
|
+
const edge = e.edge > 0 ? `+${e.edge}` : `${e.edge}`;
|
|
190
|
+
const liq = e.orderbook?.liquidityScore || '?';
|
|
191
|
+
console.log(` ${name} ${mkt.padStart(5)} → ${thesis.padStart(5)} edge ${edge.padStart(4)} ${liq}`);
|
|
192
|
+
}
|
|
193
|
+
console.log();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sf explore [slug]
|
|
3
|
+
*
|
|
4
|
+
* Browse public theses. No authentication required.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* sf explore — List all public theses
|
|
8
|
+
* sf explore us-iran-war — View a specific public thesis
|
|
9
|
+
* sf explore us-iran-war --json — JSON output (for agents)
|
|
10
|
+
*/
|
|
11
|
+
export declare function exploreCommand(slug?: string, opts?: {
|
|
12
|
+
json?: boolean;
|
|
13
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* sf explore [slug]
|
|
4
|
+
*
|
|
5
|
+
* Browse public theses. No authentication required.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* sf explore — List all public theses
|
|
9
|
+
* sf explore us-iran-war — View a specific public thesis
|
|
10
|
+
* sf explore us-iran-war --json — JSON output (for agents)
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.exploreCommand = exploreCommand;
|
|
14
|
+
const BASE_URL = 'https://simplefunctions.dev';
|
|
15
|
+
async function exploreCommand(slug, opts) {
|
|
16
|
+
if (!slug) {
|
|
17
|
+
// List all public theses
|
|
18
|
+
const res = await fetch(`${BASE_URL}/api/public/theses`);
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
console.error(` Error: ${res.status} ${await res.text()}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { theses } = await res.json();
|
|
24
|
+
if (opts?.json) {
|
|
25
|
+
console.log(JSON.stringify(theses, null, 2));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
console.log('\n Public Theses\n');
|
|
29
|
+
if (theses.length === 0) {
|
|
30
|
+
console.log(' No public theses yet.\n');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const t of theses) {
|
|
34
|
+
const conf = t.confidence != null ? Math.round(t.confidence * 100) : '?';
|
|
35
|
+
const ret = t.impliedReturn != null ? `${t.impliedReturn > 0 ? '+' : ''}${t.impliedReturn}%` : '';
|
|
36
|
+
console.log(` ${(t.slug || '').padEnd(35)} ${String(conf).padStart(3)}% ${ret.padStart(8)} ${(t.status || '').padEnd(8)} ${(t.title || '').slice(0, 45)}`);
|
|
37
|
+
}
|
|
38
|
+
console.log(`\n ${theses.length} public theses. Use: sf explore <slug>\n`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// View a specific public thesis
|
|
42
|
+
const res = await fetch(`${BASE_URL}/api/public/thesis/${slug}`);
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
if (res.status === 404) {
|
|
45
|
+
console.error(` Not found: ${slug}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.error(` Error: ${res.status} ${await res.text()}`);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
if (opts?.json) {
|
|
54
|
+
console.log(JSON.stringify(data, null, 2));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Formatted output
|
|
58
|
+
const t = data.thesis;
|
|
59
|
+
const ir = data.impliedReturns;
|
|
60
|
+
console.log(`\n ${t.title}`);
|
|
61
|
+
console.log(` ${t.slug} | ${t.confidence != null ? Math.round(t.confidence * 100) : '?'}% | ${t.status} | published ${t.publishedAt?.slice(0, 10) || '?'}`);
|
|
62
|
+
if (t.description)
|
|
63
|
+
console.log(` ${t.description}`);
|
|
64
|
+
console.log('');
|
|
65
|
+
// Causal tree
|
|
66
|
+
if (data.causalTree?.nodes?.length) {
|
|
67
|
+
console.log(' Causal Tree');
|
|
68
|
+
for (const n of data.causalTree.nodes) {
|
|
69
|
+
const bar = '█'.repeat(Math.round((n.probability || 0) * 10)) + '░'.repeat(10 - Math.round((n.probability || 0) * 10));
|
|
70
|
+
console.log(` ${n.id} ${(n.label || '').slice(0, 35).padEnd(35)} ${Math.round((n.probability || 0) * 100)}% ${bar}`);
|
|
71
|
+
if (n.children) {
|
|
72
|
+
for (const c of n.children) {
|
|
73
|
+
const cbar = '█'.repeat(Math.round((c.probability || 0) * 10)) + '░'.repeat(10 - Math.round((c.probability || 0) * 10));
|
|
74
|
+
console.log(` ${c.id} ${(c.label || '').slice(0, 33).padEnd(33)} ${Math.round((c.probability || 0) * 100)}% ${cbar}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
// Implied returns
|
|
81
|
+
if (ir && ir.edges?.length > 0) {
|
|
82
|
+
console.log(` Implied Returns (equal-weight, since ${ir.trackedSince?.slice(0, 10) || '?'})`);
|
|
83
|
+
console.log(` Avg: ${ir.avgReturnPct > 0 ? '+' : ''}${ir.avgReturnPct}% | Win rate: ${ir.winRate}% (${ir.winners}W ${ir.losers}L)`);
|
|
84
|
+
console.log('');
|
|
85
|
+
for (const e of ir.edges.slice(0, 10)) {
|
|
86
|
+
const ret = e.returnPct > 0 ? `+${e.returnPct}%` : `${e.returnPct}%`;
|
|
87
|
+
console.log(` ${(e.market || '').slice(0, 35).padEnd(35)} ${e.entryPrice}¢ → ${e.currentPrice}¢ ${ret}`);
|
|
88
|
+
}
|
|
89
|
+
console.log('');
|
|
90
|
+
}
|
|
91
|
+
// Recent evaluations
|
|
92
|
+
if (data.confidenceHistory?.length > 0) {
|
|
93
|
+
console.log(' Recent Evaluations');
|
|
94
|
+
for (const h of data.confidenceHistory.slice(-5)) {
|
|
95
|
+
const d = h.delta > 0 ? `+${Math.round(h.delta * 100)}` : `${Math.round(h.delta * 100)}`;
|
|
96
|
+
console.log(` ${(h.evaluatedAt || '').slice(0, 16)} ${Math.round(h.confidence * 100)}% (${d}) ${(h.summary || '').slice(0, 60)}`);
|
|
97
|
+
}
|
|
98
|
+
console.log('');
|
|
99
|
+
}
|
|
100
|
+
// Top edges
|
|
101
|
+
if (data.edges?.length > 0) {
|
|
102
|
+
console.log(' Top Edges');
|
|
103
|
+
const sorted = [...data.edges].sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge)).slice(0, 10);
|
|
104
|
+
for (const e of sorted) {
|
|
105
|
+
const liq = e.orderbook?.liquidityScore || '?';
|
|
106
|
+
console.log(` ${(e.market || '').slice(0, 35).padEnd(35)} ${e.marketPrice}¢ → ${e.thesisPrice}¢ edge ${e.edge > 0 ? '+' : ''}${e.edge} ${liq}`);
|
|
107
|
+
}
|
|
108
|
+
console.log('');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fillsCommand = fillsCommand;
|
|
4
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
async function fillsCommand(opts) {
|
|
7
|
+
const result = await (0, kalshi_js_1.getFills)({ ticker: opts.ticker, limit: 50 });
|
|
8
|
+
if (!result)
|
|
9
|
+
throw new Error('Kalshi not configured. Set KALSHI_API_KEY_ID + KALSHI_PRIVATE_KEY_PATH.');
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify(result.fills, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (result.fills.length === 0) {
|
|
15
|
+
console.log(`${utils_js_1.c.dim}No fills.${utils_js_1.c.reset}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Recent Fills${utils_js_1.c.reset}`);
|
|
19
|
+
console.log(`${utils_js_1.c.dim}${'─'.repeat(80)}${utils_js_1.c.reset}`);
|
|
20
|
+
for (const f of result.fills) {
|
|
21
|
+
const price = f.yes_price_dollars ? `${parseFloat(f.yes_price_dollars) * 100}¢` : `${f.yes_price || '?'}¢`;
|
|
22
|
+
const side = f.side === 'yes' ? `${utils_js_1.c.green}YES${utils_js_1.c.reset}` : `${utils_js_1.c.red}NO${utils_js_1.c.reset}`;
|
|
23
|
+
const action = f.action || 'buy';
|
|
24
|
+
const count = f.count_fp || f.count || '?';
|
|
25
|
+
const time = f.created_time ? new Date(f.created_time).toLocaleString() : '';
|
|
26
|
+
console.log(` ${(f.ticker || '').padEnd(35)} ${action.padEnd(5)} ${side} ${price.padEnd(8)} x${count} ${utils_js_1.c.dim}${time}${utils_js_1.c.reset}`);
|
|
27
|
+
}
|
|
28
|
+
console.log(`\n${utils_js_1.c.dim}${result.fills.length} fill(s)${utils_js_1.c.reset}`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.forecastCommand = forecastCommand;
|
|
4
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const KALSHI_API_BASE = 'https://api.elections.kalshi.com/trade-api/v2';
|
|
7
|
+
async function forecastCommand(eventTicker, opts) {
|
|
8
|
+
const days = parseInt(opts.days || '7');
|
|
9
|
+
// Get series ticker from event
|
|
10
|
+
const evtRes = await fetch(`${KALSHI_API_BASE}/events/${eventTicker}`, {
|
|
11
|
+
headers: { 'Accept': 'application/json' },
|
|
12
|
+
});
|
|
13
|
+
if (!evtRes.ok)
|
|
14
|
+
throw new Error(`Event not found: ${eventTicker}`);
|
|
15
|
+
const evtData = await evtRes.json();
|
|
16
|
+
const seriesTicker = evtData.event?.series_ticker;
|
|
17
|
+
if (!seriesTicker)
|
|
18
|
+
throw new Error(`No series_ticker for ${eventTicker}`);
|
|
19
|
+
// Align timestamps to midnight UTC — Kalshi API rejects unaligned/future timestamps
|
|
20
|
+
const todayMidnight = new Date();
|
|
21
|
+
todayMidnight.setUTCHours(0, 0, 0, 0);
|
|
22
|
+
const endTs = Math.floor(todayMidnight.getTime() / 1000);
|
|
23
|
+
const startTs = endTs - days * 86400;
|
|
24
|
+
const history = await (0, kalshi_js_1.getForecastHistory)({
|
|
25
|
+
seriesTicker,
|
|
26
|
+
eventTicker,
|
|
27
|
+
percentiles: [5000, 7500, 9000],
|
|
28
|
+
startTs,
|
|
29
|
+
endTs,
|
|
30
|
+
periodInterval: 1440,
|
|
31
|
+
});
|
|
32
|
+
if (!history || history.length === 0) {
|
|
33
|
+
console.log(`${utils_js_1.c.dim}No forecast data for ${eventTicker}${utils_js_1.c.reset}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (opts.json) {
|
|
37
|
+
console.log(JSON.stringify(history, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Forecast: ${evtData.event?.title || eventTicker}${utils_js_1.c.reset}`);
|
|
41
|
+
console.log(`${utils_js_1.c.dim}Series: ${seriesTicker} | ${days} days${utils_js_1.c.reset}`);
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(`${utils_js_1.c.bold}${'Date'.padEnd(14)} ${'P50'.padEnd(12)} ${'P75'.padEnd(12)} P90${utils_js_1.c.reset}`);
|
|
44
|
+
console.log(`${utils_js_1.c.dim}${'─'.repeat(52)}${utils_js_1.c.reset}`);
|
|
45
|
+
for (const point of history) {
|
|
46
|
+
const date = new Date(point.end_period_ts * 1000).toISOString().slice(0, 10);
|
|
47
|
+
const pts = point.percentile_points || [];
|
|
48
|
+
const p50 = pts.find((p) => p.percentile === 5000)?.formatted_forecast || '-';
|
|
49
|
+
const p75 = pts.find((p) => p.percentile === 7500)?.formatted_forecast || '-';
|
|
50
|
+
const p90 = pts.find((p) => p.percentile === 9000)?.formatted_forecast || '-';
|
|
51
|
+
console.log(` ${date.padEnd(14)} ${p50.padEnd(12)} ${p75.padEnd(12)} ${p90}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.historyCommand = historyCommand;
|
|
4
|
+
const kalshi_js_1 = require("../kalshi.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
async function historyCommand(ticker, opts) {
|
|
7
|
+
const market = await (0, kalshi_js_1.getHistoricalMarket)(ticker);
|
|
8
|
+
if (!market) {
|
|
9
|
+
console.log(`${utils_js_1.c.dim}No historical data for ${ticker}${utils_js_1.c.reset}`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (opts.json) {
|
|
13
|
+
console.log(JSON.stringify(market, null, 2));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}${market.title || ticker}${utils_js_1.c.reset}`);
|
|
17
|
+
console.log(`${utils_js_1.c.dim}${'─'.repeat(60)}${utils_js_1.c.reset}`);
|
|
18
|
+
console.log(` Ticker: ${market.ticker || ticker}`);
|
|
19
|
+
console.log(` Event: ${market.event_ticker || '-'}`);
|
|
20
|
+
console.log(` Status: ${market.status || '-'}`);
|
|
21
|
+
console.log(` Result: ${market.result || market.market_result || '-'}`);
|
|
22
|
+
if (market.last_price_dollars) {
|
|
23
|
+
console.log(` Last Price: ${Math.round(parseFloat(market.last_price_dollars) * 100)}¢`);
|
|
24
|
+
}
|
|
25
|
+
if (market.settlement_value !== undefined) {
|
|
26
|
+
console.log(` Settlement: ${market.settlement_value}`);
|
|
27
|
+
}
|
|
28
|
+
if (market.volume) {
|
|
29
|
+
console.log(` Volume: ${market.volume}`);
|
|
30
|
+
}
|
|
31
|
+
if (market.open_interest) {
|
|
32
|
+
console.log(` Open Int: ${market.open_interest}`);
|
|
33
|
+
}
|
|
34
|
+
if (market.expiration_time) {
|
|
35
|
+
console.log(` Expired: ${market.expiration_time}`);
|
|
36
|
+
}
|
|
37
|
+
console.log();
|
|
38
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.milestonesCommand = milestonesCommand;
|
|
4
|
+
const client_js_1 = require("../client.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const KALSHI_API_BASE = 'https://api.elections.kalshi.com/trade-api/v2';
|
|
7
|
+
async function milestonesCommand(opts) {
|
|
8
|
+
const hours = parseInt(opts.hours || '168');
|
|
9
|
+
const now = new Date();
|
|
10
|
+
const cutoff = new Date(now.getTime() + hours * 3600000);
|
|
11
|
+
// Fetch milestones (public endpoint)
|
|
12
|
+
const url = `${KALSHI_API_BASE}/milestones?limit=200&minimum_start_date=${now.toISOString()}` +
|
|
13
|
+
(opts.category ? `&category=${opts.category}` : '');
|
|
14
|
+
const res = await fetch(url, { headers: { 'Accept': 'application/json' } });
|
|
15
|
+
if (!res.ok)
|
|
16
|
+
throw new Error(`Kalshi API ${res.status}`);
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
let milestones = (data.milestones || []).filter((m) => new Date(m.start_date).getTime() <= cutoff.getTime());
|
|
19
|
+
// If thesis specified, filter to matching event tickers
|
|
20
|
+
if (opts.thesis) {
|
|
21
|
+
const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
|
|
22
|
+
const ctx = await client.getContext(opts.thesis);
|
|
23
|
+
const edgeEventTickers = new Set((ctx.edges || []).map((e) => e.eventTicker).filter(Boolean));
|
|
24
|
+
// Also match by related_event_tickers overlap
|
|
25
|
+
const edgeSeriesTickers = new Set((ctx.edges || []).map((e) => e.seriesTicker).filter(Boolean));
|
|
26
|
+
milestones = milestones.filter((m) => {
|
|
27
|
+
const related = m.related_event_tickers || m.primary_event_tickers || [];
|
|
28
|
+
return related.some((t) => edgeEventTickers.has(t) || edgeSeriesTickers.has(t.split('-')[0]));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (opts.json) {
|
|
32
|
+
console.log(JSON.stringify(milestones, null, 2));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (milestones.length === 0) {
|
|
36
|
+
console.log(`${utils_js_1.c.dim}No milestones in the next ${hours} hours.${utils_js_1.c.reset}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Sort by date
|
|
40
|
+
milestones.sort((a, b) => new Date(a.start_date).getTime() - new Date(b.start_date).getTime());
|
|
41
|
+
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}Upcoming Milestones (next ${hours}h)${utils_js_1.c.reset}`);
|
|
42
|
+
console.log(`${utils_js_1.c.dim}${'─'.repeat(80)}${utils_js_1.c.reset}`);
|
|
43
|
+
for (const m of milestones) {
|
|
44
|
+
const date = new Date(m.start_date);
|
|
45
|
+
const hoursUntil = Math.round((date.getTime() - now.getTime()) / 3600000);
|
|
46
|
+
const timeStr = hoursUntil <= 24
|
|
47
|
+
? `${utils_js_1.c.bold}${hoursUntil}h${utils_js_1.c.reset}`
|
|
48
|
+
: `${utils_js_1.c.dim}${Math.round(hoursUntil / 24)}d${utils_js_1.c.reset}`;
|
|
49
|
+
const cat = `${utils_js_1.c.dim}[${m.category}]${utils_js_1.c.reset}`;
|
|
50
|
+
const tickers = (m.related_event_tickers || []).slice(0, 3).join(', ');
|
|
51
|
+
console.log(` ${timeStr.padEnd(12)} ${cat.padEnd(25)} ${m.title}`);
|
|
52
|
+
if (tickers)
|
|
53
|
+
console.log(` ${' '.repeat(10)} ${utils_js_1.c.dim}${tickers}${utils_js_1.c.reset}`);
|
|
54
|
+
}
|
|
55
|
+
console.log(`\n${utils_js_1.c.dim}${milestones.length} milestone(s)${utils_js_1.c.reset}`);
|
|
56
|
+
}
|