@spfunctions/cli 1.4.4 → 1.5.0
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/README.md +205 -48
- package/dist/cache.d.ts +6 -0
- package/dist/cache.js +31 -0
- package/dist/cache.test.d.ts +1 -0
- package/dist/cache.test.js +73 -0
- package/dist/client.test.d.ts +1 -0
- package/dist/client.test.js +89 -0
- package/dist/commands/agent.js +594 -106
- package/dist/commands/book.d.ts +17 -0
- package/dist/commands/book.js +220 -0
- package/dist/commands/dashboard.d.ts +6 -3
- package/dist/commands/dashboard.js +53 -22
- package/dist/commands/liquidity.d.ts +2 -0
- package/dist/commands/liquidity.js +128 -43
- package/dist/commands/performance.js +9 -2
- package/dist/commands/positions.js +50 -0
- package/dist/commands/scan.d.ts +1 -0
- package/dist/commands/scan.js +66 -15
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +71 -6
- package/dist/commands/telegram.d.ts +15 -0
- package/dist/commands/telegram.js +125 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +9 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +138 -0
- package/dist/index.js +107 -9
- package/dist/polymarket.d.ts +237 -0
- package/dist/polymarket.js +353 -0
- package/dist/polymarket.test.d.ts +1 -0
- package/dist/polymarket.test.js +424 -0
- package/dist/telegram/agent-bridge.d.ts +15 -0
- package/dist/telegram/agent-bridge.js +368 -0
- package/dist/telegram/bot.d.ts +10 -0
- package/dist/telegram/bot.js +297 -0
- package/dist/telegram/commands.d.ts +11 -0
- package/dist/telegram/commands.js +120 -0
- package/dist/telegram/format.d.ts +11 -0
- package/dist/telegram/format.js +51 -0
- package/dist/telegram/format.test.d.ts +1 -0
- package/dist/telegram/format.test.js +73 -0
- package/dist/telegram/poller.d.ts +6 -0
- package/dist/telegram/poller.js +32 -0
- package/dist/topics.d.ts +3 -0
- package/dist/topics.js +65 -7
- package/dist/topics.test.d.ts +1 -0
- package/dist/topics.test.js +131 -0
- package/dist/tui/border.d.ts +33 -0
- package/dist/tui/border.js +87 -0
- package/dist/tui/chart.d.ts +19 -0
- package/dist/tui/chart.js +117 -0
- package/dist/tui/dashboard.d.ts +9 -0
- package/dist/tui/dashboard.js +814 -0
- package/dist/tui/layout.d.ts +16 -0
- package/dist/tui/layout.js +41 -0
- package/dist/tui/screen.d.ts +33 -0
- package/dist/tui/screen.js +102 -0
- package/dist/tui/state.d.ts +40 -0
- package/dist/tui/state.js +36 -0
- package/dist/tui/widgets/commandbar.d.ts +8 -0
- package/dist/tui/widgets/commandbar.js +82 -0
- package/dist/tui/widgets/detail.d.ts +9 -0
- package/dist/tui/widgets/detail.js +151 -0
- package/dist/tui/widgets/edges.d.ts +4 -0
- package/dist/tui/widgets/edges.js +34 -0
- package/dist/tui/widgets/liquidity.d.ts +9 -0
- package/dist/tui/widgets/liquidity.js +142 -0
- package/dist/tui/widgets/orders.d.ts +4 -0
- package/dist/tui/widgets/orders.js +37 -0
- package/dist/tui/widgets/portfolio.d.ts +4 -0
- package/dist/tui/widgets/portfolio.js +59 -0
- package/dist/tui/widgets/signals.d.ts +4 -0
- package/dist/tui/widgets/signals.js +31 -0
- package/dist/tui/widgets/statusbar.d.ts +8 -0
- package/dist/tui/widgets/statusbar.js +72 -0
- package/dist/tui/widgets/thesis.d.ts +4 -0
- package/dist/tui/widgets/thesis.js +66 -0
- package/dist/tui/widgets/trade.d.ts +9 -0
- package/dist/tui/widgets/trade.js +117 -0
- package/dist/tui/widgets/upcoming.d.ts +4 -0
- package/dist/tui/widgets/upcoming.js +41 -0
- package/dist/tui/widgets/whatif.d.ts +7 -0
- package/dist/tui/widgets/whatif.js +113 -0
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +111 -0
- package/package.json +6 -2
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.liquidityCommand = liquidityCommand;
|
|
10
10
|
const client_js_1 = require("../client.js");
|
|
11
11
|
const kalshi_js_1 = require("../kalshi.js");
|
|
12
|
+
const polymarket_js_1 = require("../polymarket.js");
|
|
12
13
|
const topics_js_1 = require("../topics.js");
|
|
13
14
|
const utils_js_1 = require("../utils.js");
|
|
14
15
|
// ── Horizon classification ───────────────────────────────────────────────────
|
|
@@ -78,12 +79,28 @@ async function batchProcess(items, fn, batchSize, delayMs) {
|
|
|
78
79
|
}
|
|
79
80
|
// ── Main command ─────────────────────────────────────────────────────────────
|
|
80
81
|
async function liquidityCommand(opts) {
|
|
81
|
-
// Determine which topics to scan
|
|
82
82
|
const allTopics = Object.keys(topics_js_1.TOPIC_SERIES);
|
|
83
|
+
// If no topic and no --all, show available topics
|
|
84
|
+
if (!opts.topic && !opts.all) {
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(`${utils_js_1.c.bold}Available Topics${utils_js_1.c.reset} ${utils_js_1.c.dim}(${allTopics.length} topics)${utils_js_1.c.reset}`);
|
|
87
|
+
console.log(utils_js_1.c.dim + '─'.repeat(50) + utils_js_1.c.reset);
|
|
88
|
+
console.log();
|
|
89
|
+
for (const topic of allTopics) {
|
|
90
|
+
const series = topics_js_1.TOPIC_SERIES[topic];
|
|
91
|
+
console.log(` ${utils_js_1.c.cyan}${(0, utils_js_1.pad)(topic, 14)}${utils_js_1.c.reset} ${utils_js_1.c.dim}${series.slice(0, 3).join(', ')}${series.length > 3 ? ` +${series.length - 3} more` : ''}${utils_js_1.c.reset}`);
|
|
92
|
+
}
|
|
93
|
+
console.log();
|
|
94
|
+
console.log(`${utils_js_1.c.dim}Usage: sf liquidity <topic> (e.g. sf liquidity oil)${utils_js_1.c.reset}`);
|
|
95
|
+
console.log(`${utils_js_1.c.dim} sf liquidity --all (scan all topics)${utils_js_1.c.reset}`);
|
|
96
|
+
console.log();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Determine which topics to scan
|
|
83
100
|
const topics = opts.topic
|
|
84
101
|
? allTopics.filter(t => t.toLowerCase() === opts.topic.toLowerCase())
|
|
85
102
|
: allTopics;
|
|
86
|
-
if (topics.length === 0) {
|
|
103
|
+
if (opts.topic && topics.length === 0) {
|
|
87
104
|
const valid = allTopics.join(', ');
|
|
88
105
|
console.error(`Unknown topic: ${opts.topic}. Valid topics: ${valid}`);
|
|
89
106
|
process.exit(1);
|
|
@@ -119,6 +136,37 @@ async function liquidityCommand(opts) {
|
|
|
119
136
|
topicMarkets[topic] = markets;
|
|
120
137
|
}
|
|
121
138
|
}
|
|
139
|
+
// ── Polymarket: search for matching markets ──
|
|
140
|
+
const scanPoly = opts.venue !== 'kalshi';
|
|
141
|
+
if (scanPoly) {
|
|
142
|
+
for (const topic of topics) {
|
|
143
|
+
try {
|
|
144
|
+
const events = await (0, polymarket_js_1.polymarketSearch)(topic, 5);
|
|
145
|
+
for (const event of events) {
|
|
146
|
+
for (const m of (event.markets || [])) {
|
|
147
|
+
if (!m.active || m.closed || !m.enableOrderBook)
|
|
148
|
+
continue;
|
|
149
|
+
if (!topicMarkets[topic])
|
|
150
|
+
topicMarkets[topic] = [];
|
|
151
|
+
topicMarkets[topic].push({
|
|
152
|
+
ticker: m.conditionId?.slice(0, 16) || m.id,
|
|
153
|
+
close_time: m.endDateIso || '',
|
|
154
|
+
venue: 'polymarket',
|
|
155
|
+
question: m.question || event.title,
|
|
156
|
+
clobTokenIds: m.clobTokenIds,
|
|
157
|
+
bestBid: m.bestBid,
|
|
158
|
+
bestAsk: m.bestAsk,
|
|
159
|
+
spread: m.spread,
|
|
160
|
+
liquidityNum: m.liquidityNum,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// skip Polymarket errors
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
122
170
|
// Filter by horizon if specified, classify all markets
|
|
123
171
|
const horizonFilter = opts.horizon;
|
|
124
172
|
const marketInfos = [];
|
|
@@ -130,33 +178,54 @@ async function liquidityCommand(opts) {
|
|
|
130
178
|
const horizon = classifyHorizon(closeTime);
|
|
131
179
|
if (horizonFilter && horizon !== horizonFilter)
|
|
132
180
|
continue;
|
|
133
|
-
marketInfos.push({
|
|
181
|
+
marketInfos.push({
|
|
182
|
+
ticker: m.ticker,
|
|
183
|
+
closeTime,
|
|
184
|
+
topic,
|
|
185
|
+
horizon,
|
|
186
|
+
venue: m.venue || 'kalshi',
|
|
187
|
+
question: m.question,
|
|
188
|
+
clobTokenIds: m.clobTokenIds,
|
|
189
|
+
});
|
|
134
190
|
}
|
|
135
191
|
}
|
|
136
192
|
if (marketInfos.length === 0) {
|
|
137
193
|
console.log('No markets found matching filters.');
|
|
138
194
|
return;
|
|
139
195
|
}
|
|
140
|
-
//
|
|
141
|
-
const
|
|
196
|
+
// Split by venue
|
|
197
|
+
const kalshiInfos = marketInfos.filter(m => m.venue === 'kalshi');
|
|
198
|
+
const polyInfos = marketInfos.filter(m => m.venue === 'polymarket');
|
|
199
|
+
// Fetch Kalshi orderbooks in batches
|
|
200
|
+
const kalshiResults = await batchProcess(kalshiInfos, async (info) => {
|
|
142
201
|
const ob = await (0, kalshi_js_1.getPublicOrderbook)(info.ticker);
|
|
143
202
|
return { info, ob };
|
|
144
203
|
}, 5, 100);
|
|
204
|
+
// Fetch Polymarket orderbooks in batches
|
|
205
|
+
const polyResults = await batchProcess(polyInfos, async (info) => {
|
|
206
|
+
if (!info.clobTokenIds)
|
|
207
|
+
return { info, depth: null };
|
|
208
|
+
const ids = (0, polymarket_js_1.parseClobTokenIds)(info.clobTokenIds);
|
|
209
|
+
if (!ids)
|
|
210
|
+
return { info, depth: null };
|
|
211
|
+
const depth = await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)(ids[0]);
|
|
212
|
+
return { info, depth };
|
|
213
|
+
}, 5, 100);
|
|
145
214
|
// Build liquidity rows
|
|
146
215
|
const rows = [];
|
|
147
|
-
|
|
216
|
+
// Process Kalshi results
|
|
217
|
+
for (const result of kalshiResults) {
|
|
148
218
|
if (!result || !result.ob)
|
|
149
219
|
continue;
|
|
150
220
|
const { info, ob } = result;
|
|
151
221
|
const yesDollars = ob.yes_dollars.map(([p, q]) => ({
|
|
152
222
|
price: Math.round(parseFloat(p) * 100),
|
|
153
223
|
qty: parseFloat(q),
|
|
154
|
-
})).filter(l => l.price > 0);
|
|
224
|
+
})).filter((l) => l.price > 0);
|
|
155
225
|
const noDollars = ob.no_dollars.map(([p, q]) => ({
|
|
156
226
|
price: Math.round(parseFloat(p) * 100),
|
|
157
227
|
qty: parseFloat(q),
|
|
158
|
-
})).filter(l => l.price > 0);
|
|
159
|
-
// Sort descending
|
|
228
|
+
})).filter((l) => l.price > 0);
|
|
160
229
|
yesDollars.sort((a, b) => b.price - a.price);
|
|
161
230
|
noDollars.sort((a, b) => b.price - a.price);
|
|
162
231
|
const bestBid = yesDollars.length > 0 ? yesDollars[0].price : 0;
|
|
@@ -165,21 +234,40 @@ async function liquidityCommand(opts) {
|
|
|
165
234
|
const bidDepth = yesDollars.reduce((sum, l) => sum + l.qty, 0);
|
|
166
235
|
const askDepth = noDollars.reduce((sum, l) => sum + l.qty, 0);
|
|
167
236
|
const slippage100 = calcSlippage100(ob.no_dollars, 100);
|
|
168
|
-
// Filter by minDepth
|
|
169
237
|
if (opts.minDepth && (bidDepth + askDepth) < opts.minDepth)
|
|
170
238
|
continue;
|
|
171
239
|
rows.push({
|
|
172
240
|
ticker: info.ticker,
|
|
173
|
-
shortTicker: info.ticker,
|
|
241
|
+
shortTicker: info.ticker,
|
|
242
|
+
topic: info.topic.toUpperCase(),
|
|
174
243
|
horizon: info.horizon,
|
|
175
244
|
closeTime: info.closeTime,
|
|
176
|
-
bestBid,
|
|
177
|
-
bestAsk,
|
|
178
|
-
spread,
|
|
179
|
-
bidDepth,
|
|
180
|
-
askDepth,
|
|
181
|
-
slippage100,
|
|
245
|
+
bestBid, bestAsk, spread, bidDepth, askDepth, slippage100,
|
|
182
246
|
held: heldTickers.has(info.ticker),
|
|
247
|
+
venue: 'kalshi',
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// Process Polymarket results
|
|
251
|
+
for (const result of polyResults) {
|
|
252
|
+
if (!result || !result.depth)
|
|
253
|
+
continue;
|
|
254
|
+
const { info, depth } = result;
|
|
255
|
+
if (opts.minDepth && (depth.bidDepthTop3 + depth.askDepthTop3) < opts.minDepth)
|
|
256
|
+
continue;
|
|
257
|
+
rows.push({
|
|
258
|
+
ticker: (info.question || info.ticker).slice(0, 30),
|
|
259
|
+
shortTicker: (info.question || info.ticker).slice(0, 30),
|
|
260
|
+
topic: info.topic.toUpperCase(),
|
|
261
|
+
horizon: info.horizon,
|
|
262
|
+
closeTime: info.closeTime,
|
|
263
|
+
bestBid: depth.bestBid,
|
|
264
|
+
bestAsk: depth.bestAsk,
|
|
265
|
+
spread: depth.spread,
|
|
266
|
+
bidDepth: depth.totalBidDepth,
|
|
267
|
+
askDepth: depth.totalAskDepth,
|
|
268
|
+
slippage100: '-',
|
|
269
|
+
held: false,
|
|
270
|
+
venue: 'polymarket',
|
|
183
271
|
});
|
|
184
272
|
}
|
|
185
273
|
// ── JSON output ────────────────────────────────────────────────────────────
|
|
@@ -192,21 +280,10 @@ async function liquidityCommand(opts) {
|
|
|
192
280
|
console.log();
|
|
193
281
|
console.log(`${utils_js_1.c.bold}Liquidity Scanner${utils_js_1.c.reset} ${utils_js_1.c.dim}(${now} UTC)${utils_js_1.c.reset}`);
|
|
194
282
|
console.log(utils_js_1.c.dim + '─'.repeat(68) + utils_js_1.c.reset);
|
|
195
|
-
// Group rows by topic → horizon
|
|
283
|
+
// Group rows by topic → horizon (topic is pre-set on each row)
|
|
196
284
|
const grouped = {};
|
|
197
285
|
for (const row of rows) {
|
|
198
|
-
|
|
199
|
-
let topic = 'OTHER';
|
|
200
|
-
for (const [t, series] of Object.entries(topics_js_1.TOPIC_SERIES)) {
|
|
201
|
-
for (const s of series) {
|
|
202
|
-
if (row.ticker.toUpperCase().startsWith(s)) {
|
|
203
|
-
topic = t.toUpperCase();
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (topic !== 'OTHER')
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
286
|
+
const topic = row.topic || 'OTHER';
|
|
210
287
|
if (!grouped[topic])
|
|
211
288
|
grouped[topic] = {};
|
|
212
289
|
if (!grouped[topic][row.horizon])
|
|
@@ -222,21 +299,28 @@ async function liquidityCommand(opts) {
|
|
|
222
299
|
const marketRows = horizons[h];
|
|
223
300
|
if (!marketRows || marketRows.length === 0)
|
|
224
301
|
continue;
|
|
225
|
-
//
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
row
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
row.shortTicker = row.ticker;
|
|
302
|
+
// Abbreviate Kalshi tickers (strip common prefix), keep Polymarket titles as-is
|
|
303
|
+
const kalshiRows = marketRows.filter(r => r.venue === 'kalshi');
|
|
304
|
+
if (kalshiRows.length > 1) {
|
|
305
|
+
const commonPrefix = findCommonPrefix(kalshiRows.map(r => r.ticker));
|
|
306
|
+
for (const row of kalshiRows) {
|
|
307
|
+
const short = commonPrefix.length > 0
|
|
308
|
+
? row.ticker.slice(commonPrefix.length).replace(/^-/, '')
|
|
309
|
+
: row.ticker;
|
|
310
|
+
row.shortTicker = short.length > 0 ? short : row.ticker;
|
|
311
|
+
}
|
|
234
312
|
}
|
|
235
|
-
// Sort by ticker
|
|
236
|
-
marketRows.sort((a, b) =>
|
|
313
|
+
// Sort: Kalshi by ticker, Polymarket by spread
|
|
314
|
+
marketRows.sort((a, b) => {
|
|
315
|
+
if (a.venue !== b.venue)
|
|
316
|
+
return a.venue === 'kalshi' ? -1 : 1;
|
|
317
|
+
return a.venue === 'kalshi'
|
|
318
|
+
? a.ticker.localeCompare(b.ticker)
|
|
319
|
+
: a.spread - b.spread;
|
|
320
|
+
});
|
|
237
321
|
console.log();
|
|
238
322
|
console.log(`${utils_js_1.c.bold}${utils_js_1.c.cyan}${topic}${utils_js_1.c.reset} ${utils_js_1.c.dim}— ${horizonLabel(h)}${utils_js_1.c.reset}`);
|
|
239
|
-
console.log(`${utils_js_1.c.dim}${(0, utils_js_1.pad)('
|
|
323
|
+
console.log(`${utils_js_1.c.dim} ${(0, utils_js_1.pad)('Market', 22)} ${(0, utils_js_1.rpad)('Bid¢', 5)} ${(0, utils_js_1.rpad)('Ask¢', 5)} ${(0, utils_js_1.rpad)('Spread', 6)} ${(0, utils_js_1.rpad)('BidDep', 6)} ${(0, utils_js_1.rpad)('AskDep', 6)} ${(0, utils_js_1.rpad)('Slip100', 7)}${utils_js_1.c.reset}`);
|
|
240
324
|
for (const row of marketRows) {
|
|
241
325
|
totalMarkets++;
|
|
242
326
|
if (row.held)
|
|
@@ -264,7 +348,8 @@ async function liquidityCommand(opts) {
|
|
|
264
348
|
: row.spread <= 5
|
|
265
349
|
? `${utils_js_1.c.yellow}${spreadPadded}${utils_js_1.c.reset}`
|
|
266
350
|
: `${utils_js_1.c.red}${spreadPadded}${utils_js_1.c.reset}`;
|
|
267
|
-
|
|
351
|
+
const venueTag = row.venue === 'polymarket' ? `${utils_js_1.c.blue}POLY${utils_js_1.c.reset} ` : `${utils_js_1.c.cyan}KLSH${utils_js_1.c.reset} `;
|
|
352
|
+
console.log(`${venueTag}${(0, utils_js_1.pad)(row.shortTicker, 22)} ${(0, utils_js_1.rpad)(String(row.bestBid), 5)} ${(0, utils_js_1.rpad)(String(row.bestAsk), 5)} ${spreadColored} ${(0, utils_js_1.rpad)(String(Math.round(row.bidDepth)), 6)} ${(0, utils_js_1.rpad)(String(Math.round(row.askDepth)), 6)} ${(0, utils_js_1.rpad)(row.slippage100, 7)}${thinMark}${heldMark}`);
|
|
268
353
|
}
|
|
269
354
|
}
|
|
270
355
|
}
|
|
@@ -56,15 +56,22 @@ async function performanceCommand(opts) {
|
|
|
56
56
|
if (!ticker)
|
|
57
57
|
continue;
|
|
58
58
|
const action = fill.action || 'buy';
|
|
59
|
+
const side = fill.side || 'yes';
|
|
59
60
|
const count = Math.round(parseFloat(fill.count_fp || fill.count || '0'));
|
|
60
61
|
const yesPrice = Math.round(parseFloat(fill.yes_price_dollars || '0') * 100);
|
|
62
|
+
// buy yes = +count, sell yes = -count
|
|
63
|
+
// buy no = -count (short yes), sell no = +count (close short)
|
|
61
64
|
let delta = count;
|
|
62
65
|
if (action === 'sell')
|
|
63
|
-
delta = -
|
|
66
|
+
delta = -delta;
|
|
67
|
+
if (side === 'no')
|
|
68
|
+
delta = -delta;
|
|
64
69
|
const info = tickerMap.get(ticker) || { ticker, netQty: 0, totalCostCents: 0, totalContracts: 0, earliestFillTs: Infinity };
|
|
65
70
|
info.netQty += delta;
|
|
66
71
|
if (delta > 0) {
|
|
67
|
-
|
|
72
|
+
// Entering a position — accumulate cost
|
|
73
|
+
const costPerContract = side === 'no' ? (100 - yesPrice) : yesPrice;
|
|
74
|
+
info.totalCostCents += costPerContract * count;
|
|
68
75
|
info.totalContracts += count;
|
|
69
76
|
}
|
|
70
77
|
const fillTime = fill.created_time || fill.ts || fill.created_at;
|
|
@@ -14,6 +14,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.positionsCommand = positionsCommand;
|
|
15
15
|
const client_js_1 = require("../client.js");
|
|
16
16
|
const kalshi_js_1 = require("../kalshi.js");
|
|
17
|
+
const polymarket_js_1 = require("../polymarket.js");
|
|
18
|
+
const config_js_1 = require("../config.js");
|
|
17
19
|
const utils_js_1 = require("../utils.js");
|
|
18
20
|
async function positionsCommand(opts) {
|
|
19
21
|
const client = new client_js_1.SFClient(opts.apiKey, opts.apiUrl);
|
|
@@ -23,6 +25,18 @@ async function positionsCommand(opts) {
|
|
|
23
25
|
console.log(`${utils_js_1.c.dim}Fetching Kalshi positions...${utils_js_1.c.reset}`);
|
|
24
26
|
positions = await (0, kalshi_js_1.getPositions)();
|
|
25
27
|
}
|
|
28
|
+
// ── Step 1b: Fetch Polymarket positions (if wallet configured) ──
|
|
29
|
+
const config = (0, config_js_1.loadConfig)();
|
|
30
|
+
let polyPositions = [];
|
|
31
|
+
if (config.polymarketWalletAddress) {
|
|
32
|
+
console.log(`${utils_js_1.c.dim}Fetching Polymarket positions...${utils_js_1.c.reset}`);
|
|
33
|
+
try {
|
|
34
|
+
polyPositions = await (0, polymarket_js_1.polymarketGetPositions)(config.polymarketWalletAddress);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// skip
|
|
38
|
+
}
|
|
39
|
+
}
|
|
26
40
|
// ── Step 2: Fetch all theses and their edges (via SF API) ──
|
|
27
41
|
console.log(`${utils_js_1.c.dim}Fetching thesis edges...${utils_js_1.c.reset}`);
|
|
28
42
|
let theses = [];
|
|
@@ -100,7 +114,9 @@ async function positionsCommand(opts) {
|
|
|
100
114
|
if (opts.json) {
|
|
101
115
|
console.log(JSON.stringify({
|
|
102
116
|
kalshiConfigured: (0, kalshi_js_1.isKalshiConfigured)(),
|
|
117
|
+
polymarketConfigured: !!config.polymarketWalletAddress,
|
|
103
118
|
positions: positions || [],
|
|
119
|
+
polymarketPositions: polyPositions,
|
|
104
120
|
edges: allEdges.map(e => ({ ...e.edge, thesisId: e.thesisId })),
|
|
105
121
|
}, null, 2));
|
|
106
122
|
return;
|
|
@@ -169,6 +185,40 @@ async function positionsCommand(opts) {
|
|
|
169
185
|
else {
|
|
170
186
|
console.log(`\n${utils_js_1.c.yellow}Kalshi not configured.${utils_js_1.c.reset} Set KALSHI_API_KEY_ID and KALSHI_PRIVATE_KEY_PATH to see positions.\n`);
|
|
171
187
|
}
|
|
188
|
+
// C) Polymarket positions
|
|
189
|
+
if (polyPositions.length > 0) {
|
|
190
|
+
(0, utils_js_1.header)('Polymarket Positions');
|
|
191
|
+
console.log(' ' + utils_js_1.c.bold +
|
|
192
|
+
(0, utils_js_1.pad)('Market', 35) +
|
|
193
|
+
(0, utils_js_1.rpad)('Side', 5) +
|
|
194
|
+
(0, utils_js_1.rpad)('Size', 8) +
|
|
195
|
+
(0, utils_js_1.rpad)('Avg', 6) +
|
|
196
|
+
(0, utils_js_1.rpad)('Now', 6) +
|
|
197
|
+
(0, utils_js_1.rpad)('P&L', 9) +
|
|
198
|
+
utils_js_1.c.reset);
|
|
199
|
+
console.log(' ' + utils_js_1.c.dim + '─'.repeat(75) + utils_js_1.c.reset);
|
|
200
|
+
for (const pos of polyPositions) {
|
|
201
|
+
const title = (pos.title || pos.slug || pos.asset || '').slice(0, 34);
|
|
202
|
+
const side = pos.outcome || 'YES';
|
|
203
|
+
const size = pos.size || 0;
|
|
204
|
+
const avgPrice = Math.round((pos.avgPrice || 0) * 100);
|
|
205
|
+
const curPrice = Math.round((pos.curPrice || pos.currentPrice || 0) * 100);
|
|
206
|
+
const pnl = pos.cashPnl || ((curPrice - avgPrice) * size / 100);
|
|
207
|
+
const pnlColor = pnl >= 0 ? utils_js_1.c.green : utils_js_1.c.red;
|
|
208
|
+
const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
|
|
209
|
+
console.log(' ' +
|
|
210
|
+
(0, utils_js_1.pad)(title, 35) +
|
|
211
|
+
(0, utils_js_1.rpad)(side.toUpperCase(), 5) +
|
|
212
|
+
(0, utils_js_1.rpad)(String(Math.round(size)), 8) +
|
|
213
|
+
(0, utils_js_1.rpad)(`${avgPrice}¢`, 6) +
|
|
214
|
+
(0, utils_js_1.rpad)(`${curPrice}¢`, 6) +
|
|
215
|
+
`${pnlColor}${(0, utils_js_1.rpad)(pnlStr, 9)}${utils_js_1.c.reset}`);
|
|
216
|
+
}
|
|
217
|
+
console.log('');
|
|
218
|
+
}
|
|
219
|
+
else if (config.polymarketWalletAddress) {
|
|
220
|
+
console.log(`${utils_js_1.c.dim}No open positions on Polymarket.${utils_js_1.c.reset}\n`);
|
|
221
|
+
}
|
|
172
222
|
// B) Unpositioned edges (edges without matching positions)
|
|
173
223
|
const positionedTickers = new Set((positions || []).map(p => p.ticker));
|
|
174
224
|
const unpositionedEdges = allEdges.filter(e => !positionedTickers.has(e.edge.marketId));
|
package/dist/commands/scan.d.ts
CHANGED
package/dist/commands/scan.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.scanCommand = scanCommand;
|
|
4
4
|
const client_js_1 = require("../client.js");
|
|
5
|
+
const polymarket_js_1 = require("../polymarket.js");
|
|
5
6
|
const utils_js_1 = require("../utils.js");
|
|
6
7
|
async function scanCommand(query, opts) {
|
|
7
8
|
// Mode 1: --market TICKER — single market detail
|
|
@@ -14,8 +15,8 @@ async function scanCommand(query, opts) {
|
|
|
14
15
|
await showSeries(opts.series.toUpperCase(), opts.json);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
|
-
// Mode 3: keyword scan across all series
|
|
18
|
-
await keywordScan(query, opts.json);
|
|
18
|
+
// Mode 3: keyword scan across all series + Polymarket
|
|
19
|
+
await keywordScan(query, opts.json, opts.venue || 'all');
|
|
19
20
|
}
|
|
20
21
|
async function showMarket(ticker, json) {
|
|
21
22
|
console.log(`${utils_js_1.c.dim}Fetching market ${ticker}...${utils_js_1.c.reset}`);
|
|
@@ -77,10 +78,19 @@ async function showSeries(seriesTicker, json) {
|
|
|
77
78
|
printMarketsTable(markets);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
|
-
async function keywordScan(query, json) {
|
|
81
|
+
async function keywordScan(query, json, venue = 'all') {
|
|
81
82
|
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
82
|
-
|
|
83
|
-
const
|
|
83
|
+
// ── Polymarket search (runs in parallel with Kalshi) ───────────────────────
|
|
84
|
+
const polyPromise = (venue === 'kalshi')
|
|
85
|
+
? Promise.resolve([])
|
|
86
|
+
: (0, polymarket_js_1.polymarketSearch)(query, 10).catch(() => []);
|
|
87
|
+
if (venue !== 'polymarket') {
|
|
88
|
+
console.log(`${utils_js_1.c.dim}Scanning Kalshi for: "${query}"...${utils_js_1.c.reset}`);
|
|
89
|
+
}
|
|
90
|
+
if (venue !== 'kalshi') {
|
|
91
|
+
console.log(`${utils_js_1.c.dim}Scanning Polymarket for: "${query}"...${utils_js_1.c.reset}`);
|
|
92
|
+
}
|
|
93
|
+
const allSeries = venue === 'polymarket' ? [] : await (0, client_js_1.kalshiFetchAllSeries)();
|
|
84
94
|
// Score each series
|
|
85
95
|
const thesisKeywords = [
|
|
86
96
|
'oil', 'wti', 'gas', 'recession', 'gdp', 'fed', 'inflation',
|
|
@@ -126,13 +136,14 @@ async function keywordScan(query, json) {
|
|
|
126
136
|
for (const m of markets) {
|
|
127
137
|
if (m.status === 'open' || m.status === 'active') {
|
|
128
138
|
allMarkets.push({
|
|
139
|
+
venue: 'kalshi',
|
|
129
140
|
seriesTicker: s.ticker,
|
|
130
141
|
ticker: m.ticker,
|
|
131
142
|
title: m.title || m.subtitle || '',
|
|
132
143
|
yesAsk: parseFloat(m.yes_ask_dollars || '0'),
|
|
133
144
|
lastPrice: parseFloat(m.last_price_dollars || '0'),
|
|
134
145
|
volume24h: parseFloat(m.volume_24h_fp || '0'),
|
|
135
|
-
liquidity: parseFloat(m.liquidity_dollars || '0'),
|
|
146
|
+
liquidity: parseFloat(m.open_interest_fp || m.liquidity_dollars || '0'),
|
|
136
147
|
});
|
|
137
148
|
}
|
|
138
149
|
}
|
|
@@ -144,27 +155,67 @@ async function keywordScan(query, json) {
|
|
|
144
155
|
}
|
|
145
156
|
}
|
|
146
157
|
allMarkets.sort((a, b) => b.liquidity - a.liquidity);
|
|
158
|
+
// ── Polymarket results ──────────────────────────────────────────────────────
|
|
159
|
+
const polyEvents = await polyPromise;
|
|
160
|
+
const polyMarkets = [];
|
|
161
|
+
for (const event of polyEvents) {
|
|
162
|
+
for (const m of (event.markets || [])) {
|
|
163
|
+
if (!m.active || m.closed)
|
|
164
|
+
continue;
|
|
165
|
+
const prices = (0, polymarket_js_1.parseOutcomePrices)(m.outcomePrices);
|
|
166
|
+
const yesPrice = prices[0] || 0;
|
|
167
|
+
polyMarkets.push({
|
|
168
|
+
venue: 'polymarket',
|
|
169
|
+
ticker: m.conditionId?.slice(0, 16) || m.id,
|
|
170
|
+
title: m.groupItemTitle
|
|
171
|
+
? `${event.title}: ${m.groupItemTitle}`
|
|
172
|
+
: m.question || event.title,
|
|
173
|
+
yesAsk: yesPrice,
|
|
174
|
+
lastPrice: m.lastTradePrice || yesPrice,
|
|
175
|
+
volume24h: m.volume24hr || 0,
|
|
176
|
+
liquidity: m.liquidityNum || 0,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// ── Merge & output ─────────────────────────────────────────────────────────
|
|
181
|
+
// Tag Kalshi markets with venue
|
|
182
|
+
for (const m of allMarkets) {
|
|
183
|
+
if (!m.venue)
|
|
184
|
+
m.venue = 'kalshi';
|
|
185
|
+
}
|
|
186
|
+
const combined = [...allMarkets, ...polyMarkets];
|
|
187
|
+
combined.sort((a, b) => b.liquidity - a.liquidity);
|
|
147
188
|
if (json) {
|
|
148
|
-
console.log(JSON.stringify(
|
|
189
|
+
console.log(JSON.stringify(combined, null, 2));
|
|
149
190
|
return;
|
|
150
191
|
}
|
|
151
|
-
(0, utils_js_1.header)(`${
|
|
192
|
+
(0, utils_js_1.header)(`${combined.length} Live Markets (${allMarkets.length} Kalshi + ${polyMarkets.length} Polymarket)`);
|
|
152
193
|
console.log(utils_js_1.c.bold +
|
|
153
|
-
(0, utils_js_1.pad)('
|
|
194
|
+
(0, utils_js_1.pad)('', 5) +
|
|
195
|
+
(0, utils_js_1.pad)('Ticker', 30) +
|
|
154
196
|
(0, utils_js_1.rpad)('Yes', 6) +
|
|
155
197
|
(0, utils_js_1.rpad)('Last', 6) +
|
|
156
198
|
(0, utils_js_1.rpad)('Vol24h', 10) +
|
|
157
199
|
(0, utils_js_1.rpad)('Liq', 10) +
|
|
158
200
|
' Title' +
|
|
159
201
|
utils_js_1.c.reset);
|
|
160
|
-
(0, utils_js_1.hr)(
|
|
161
|
-
for (const m of
|
|
162
|
-
|
|
202
|
+
(0, utils_js_1.hr)(120);
|
|
203
|
+
for (const m of combined.slice(0, 60)) {
|
|
204
|
+
// Venue tag: fixed 5-char column, color applied AFTER padding
|
|
205
|
+
const venuePad = m.venue === 'polymarket' ? 'POLY ' : 'KLSH ';
|
|
206
|
+
const venueColored = m.venue === 'polymarket'
|
|
207
|
+
? utils_js_1.c.blue + venuePad + utils_js_1.c.reset
|
|
208
|
+
: utils_js_1.c.cyan + venuePad + utils_js_1.c.reset;
|
|
209
|
+
const tickerStr = m.venue === 'polymarket'
|
|
210
|
+
? (m.ticker || '').slice(0, 28)
|
|
211
|
+
: (m.ticker || '').slice(0, 28);
|
|
212
|
+
console.log(venueColored +
|
|
213
|
+
(0, utils_js_1.pad)(tickerStr, 30) +
|
|
163
214
|
(0, utils_js_1.rpad)(`${Math.round(m.yesAsk * 100)}¢`, 6) +
|
|
164
215
|
(0, utils_js_1.rpad)(`${Math.round(m.lastPrice * 100)}¢`, 6) +
|
|
165
|
-
(0, utils_js_1.rpad)((0, utils_js_1.vol)(m.volume24h), 10) +
|
|
166
|
-
(0, utils_js_1.rpad)((0, utils_js_1.vol)(m.liquidity), 10) +
|
|
167
|
-
` ${m.title.slice(0, 55)}`);
|
|
216
|
+
(0, utils_js_1.rpad)((0, utils_js_1.vol)(Math.round(m.volume24h)), 10) +
|
|
217
|
+
(0, utils_js_1.rpad)((0, utils_js_1.vol)(Math.round(m.liquidity)), 10) +
|
|
218
|
+
` ${(m.title || '').slice(0, 55)}`);
|
|
168
219
|
}
|
|
169
220
|
console.log('');
|
|
170
221
|
}
|
package/dist/commands/setup.d.ts
CHANGED
package/dist/commands/setup.js
CHANGED
|
@@ -165,6 +165,17 @@ async function setupCommand(opts) {
|
|
|
165
165
|
blank();
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
|
+
// ── sf setup --polymarket (reconfigure Polymarket credentials) ──────────
|
|
169
|
+
if (opts.polymarket) {
|
|
170
|
+
const existing = (0, config_js_1.loadFileConfig)();
|
|
171
|
+
blank();
|
|
172
|
+
console.log(` ${bold('Reconfigure Polymarket Credentials')}`);
|
|
173
|
+
blank();
|
|
174
|
+
await promptForPolymarket(existing);
|
|
175
|
+
(0, config_js_1.saveConfig)(existing);
|
|
176
|
+
blank();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
168
179
|
// ── sf setup --enable-trading / --disable-trading ────────────────────────
|
|
169
180
|
if (opts.enableTrading) {
|
|
170
181
|
const existing = (0, config_js_1.loadFileConfig)();
|
|
@@ -211,6 +222,13 @@ async function showCheck() {
|
|
|
211
222
|
else {
|
|
212
223
|
info(`${dim('○')} KALSHI ${dim('skipped')}`);
|
|
213
224
|
}
|
|
225
|
+
// Polymarket
|
|
226
|
+
if (config.polymarketWalletAddress) {
|
|
227
|
+
ok(`POLYMARKET ${dim(mask(config.polymarketWalletAddress))}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
info(`${dim('○')} POLYMARKET ${dim('skipped')}`);
|
|
231
|
+
}
|
|
214
232
|
// Tavily
|
|
215
233
|
if (config.tavilyKey) {
|
|
216
234
|
ok(`TAVILY ${dim(mask(config.tavilyKey))}`);
|
|
@@ -318,9 +336,27 @@ async function runWizard() {
|
|
|
318
336
|
}
|
|
319
337
|
(0, config_js_1.saveConfig)(config);
|
|
320
338
|
// ════════════════════════════════════════════════════════════════════════════
|
|
321
|
-
// Step 4:
|
|
339
|
+
// Step 4: Polymarket
|
|
340
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
341
|
+
console.log(` ${bold('Step 4: Polymarket (optional)')}`);
|
|
342
|
+
blank();
|
|
343
|
+
const existingPolyWallet = process.env.POLYMARKET_WALLET_ADDRESS || config.polymarketWalletAddress;
|
|
344
|
+
if (existingPolyWallet) {
|
|
345
|
+
ok(`Detected wallet — ${dim(mask(existingPolyWallet))}`);
|
|
346
|
+
info(dim('Skipping.'));
|
|
347
|
+
config.polymarketWalletAddress = existingPolyWallet;
|
|
348
|
+
blank();
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
await promptForPolymarket(config);
|
|
352
|
+
}
|
|
353
|
+
(0, config_js_1.saveConfig)(config);
|
|
354
|
+
if (config.polymarketWalletAddress)
|
|
355
|
+
process.env.POLYMARKET_WALLET_ADDRESS = config.polymarketWalletAddress;
|
|
356
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
357
|
+
// Step 5: Tavily
|
|
322
358
|
// ════════════════════════════════════════════════════════════════════════════
|
|
323
|
-
console.log(` ${bold('Step
|
|
359
|
+
console.log(` ${bold('Step 5: News Search (optional)')}`);
|
|
324
360
|
blank();
|
|
325
361
|
const existingTavily = process.env.TAVILY_API_KEY || config.tavilyKey;
|
|
326
362
|
if (existingTavily) {
|
|
@@ -346,7 +382,7 @@ async function runWizard() {
|
|
|
346
382
|
// Step 5: Trading
|
|
347
383
|
// ════════════════════════════════════════════════════════════════════════════
|
|
348
384
|
if (config.kalshiKeyId) {
|
|
349
|
-
console.log(` ${bold('Step
|
|
385
|
+
console.log(` ${bold('Step 6: Trading (optional)')}`);
|
|
350
386
|
blank();
|
|
351
387
|
info('Warning: enabling this unlocks sf buy / sf sell / sf cancel.');
|
|
352
388
|
info('Your Kalshi API key must have read+write permissions.');
|
|
@@ -493,7 +529,36 @@ async function promptForTavily() {
|
|
|
493
529
|
blank();
|
|
494
530
|
return answer;
|
|
495
531
|
}
|
|
496
|
-
|
|
532
|
+
async function promptForPolymarket(config) {
|
|
533
|
+
info('Connect Polymarket to view positions and scan orderbooks.');
|
|
534
|
+
info('Your Polygon wallet address is needed (starts with 0x...).');
|
|
535
|
+
info(`Find it at ${cyan('https://polymarket.com')} → Settings → Profile.`);
|
|
536
|
+
info('Press Enter to skip:');
|
|
537
|
+
blank();
|
|
538
|
+
const walletAddress = await prompt(' Wallet address > ');
|
|
539
|
+
if (!walletAddress) {
|
|
540
|
+
info(dim('Skipped.'));
|
|
541
|
+
blank();
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (!walletAddress.startsWith('0x') || walletAddress.length < 40) {
|
|
545
|
+
fail('Invalid wallet address (must start with 0x and be 42 characters)');
|
|
546
|
+
info(dim('Saved anyway. You can fix it later with sf setup.'));
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
ok(`Wallet: ${mask(walletAddress)}`);
|
|
550
|
+
}
|
|
551
|
+
config.polymarketWalletAddress = walletAddress;
|
|
552
|
+
// Optionally configure private key for future trading
|
|
553
|
+
info(dim('Private key (for future trading) — press Enter to skip:'));
|
|
554
|
+
const keyPath = await prompt(' Key path > ');
|
|
555
|
+
if (keyPath) {
|
|
556
|
+
config.polymarketPrivateKeyPath = keyPath;
|
|
557
|
+
ok(`Private key path: ${dim(keyPath)}`);
|
|
558
|
+
}
|
|
559
|
+
blank();
|
|
560
|
+
}
|
|
561
|
+
// ─── Step 7: Thesis ──────────────────────────────────────────────────────────
|
|
497
562
|
async function handleThesisStep(config) {
|
|
498
563
|
try {
|
|
499
564
|
const client = new client_js_1.SFClient(config.apiKey, config.apiUrl);
|
|
@@ -501,7 +566,7 @@ async function handleThesisStep(config) {
|
|
|
501
566
|
const theses = data.theses || [];
|
|
502
567
|
const activeTheses = theses.filter((t) => t.status === 'active');
|
|
503
568
|
if (activeTheses.length > 0) {
|
|
504
|
-
console.log(` ${bold('Step
|
|
569
|
+
console.log(` ${bold('Step 7: Theses')}`);
|
|
505
570
|
blank();
|
|
506
571
|
ok(`Found ${activeTheses.length} active thesis(es):`);
|
|
507
572
|
for (const t of activeTheses.slice(0, 5)) {
|
|
@@ -542,7 +607,7 @@ async function handleThesisStep(config) {
|
|
|
542
607
|
return;
|
|
543
608
|
}
|
|
544
609
|
// No theses — offer to create one
|
|
545
|
-
console.log(` ${bold('Step
|
|
610
|
+
console.log(` ${bold('Step 7: Create Your First Thesis')}`);
|
|
546
611
|
blank();
|
|
547
612
|
info('A thesis is your core market conviction. The system builds a causal model');
|
|
548
613
|
info('from it, then continuously scans prediction markets for mispriced contracts.');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sf telegram — Start Telegram bot
|
|
3
|
+
*
|
|
4
|
+
* Token is saved to ~/.sf/config.json on first use.
|
|
5
|
+
* --daemon: fork to background, write PID to ~/.sf/telegram.pid
|
|
6
|
+
* --stop: kill running daemon
|
|
7
|
+
* --status: check if daemon is running
|
|
8
|
+
*/
|
|
9
|
+
export declare function telegramCommand(opts: {
|
|
10
|
+
token?: string;
|
|
11
|
+
chatId?: string;
|
|
12
|
+
daemon?: boolean;
|
|
13
|
+
stop?: boolean;
|
|
14
|
+
status?: boolean;
|
|
15
|
+
}): Promise<void>;
|