@spfunctions/cli 1.4.5 → 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/dist/commands/agent.js +349 -39
- package/dist/commands/book.d.ts +17 -0
- package/dist/commands/book.js +220 -0
- package/dist/commands/dashboard.js +30 -1
- package/dist/commands/liquidity.d.ts +2 -0
- package/dist/commands/liquidity.js +128 -43
- 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/config.d.ts +2 -0
- package/dist/config.js +8 -0
- package/dist/index.js +92 -8
- 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/topics.d.ts +3 -0
- package/dist/topics.js +65 -7
- package/dist/topics.test.js +83 -6
- package/dist/tui/dashboard.js +65 -30
- package/dist/tui/widgets/edges.js +5 -4
- package/dist/tui/widgets/portfolio.js +3 -2
- package/package.json +1 -1
package/dist/commands/agent.js
CHANGED
|
@@ -23,6 +23,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
23
23
|
const os_1 = __importDefault(require("os"));
|
|
24
24
|
const client_js_1 = require("../client.js");
|
|
25
25
|
const kalshi_js_1 = require("../kalshi.js");
|
|
26
|
+
const polymarket_js_1 = require("../polymarket.js");
|
|
27
|
+
const topics_js_1 = require("../topics.js");
|
|
26
28
|
const config_js_1 = require("../config.js");
|
|
27
29
|
// ─── Session persistence ─────────────────────────────────────────────────────
|
|
28
30
|
function getSessionDir() {
|
|
@@ -793,34 +795,55 @@ async function agentCommand(thesisId, opts) {
|
|
|
793
795
|
{
|
|
794
796
|
name: 'scan_markets',
|
|
795
797
|
label: 'Scan Markets',
|
|
796
|
-
description: 'Search Kalshi prediction markets. Provide exactly one of: query (keyword search), series (series ticker), or market (specific ticker).
|
|
798
|
+
description: 'Search Kalshi + Polymarket prediction markets. Provide exactly one of: query (keyword search), series (Kalshi series ticker), or market (specific Kalshi ticker). Keyword search returns results from BOTH venues.',
|
|
797
799
|
parameters: scanParams,
|
|
798
800
|
execute: async (_toolCallId, params) => {
|
|
799
|
-
let result;
|
|
800
801
|
if (params.market) {
|
|
801
|
-
result = await (0, client_js_1.kalshiFetchMarket)(params.market);
|
|
802
|
+
const result = await (0, client_js_1.kalshiFetchMarket)(params.market);
|
|
803
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], details: {} };
|
|
802
804
|
}
|
|
803
|
-
|
|
804
|
-
result = await (0, client_js_1.kalshiFetchMarketsBySeries)(params.series);
|
|
805
|
+
if (params.series) {
|
|
806
|
+
const result = await (0, client_js_1.kalshiFetchMarketsBySeries)(params.series);
|
|
807
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], details: {} };
|
|
805
808
|
}
|
|
806
|
-
|
|
809
|
+
if (params.query) {
|
|
810
|
+
// Kalshi: keyword grep on series
|
|
807
811
|
const series = await (0, client_js_1.kalshiFetchAllSeries)();
|
|
808
812
|
const keywords = params.query.toLowerCase().split(/\s+/);
|
|
809
|
-
const
|
|
810
|
-
.filter((s) => keywords.
|
|
813
|
+
const kalshiMatched = series
|
|
814
|
+
.filter((s) => keywords.some((kw) => (s.title || '').toLowerCase().includes(kw) ||
|
|
811
815
|
(s.ticker || '').toLowerCase().includes(kw)))
|
|
812
816
|
.filter((s) => parseFloat(s.volume_24h_fp || s.volume_fp || '0') > 0)
|
|
813
817
|
.sort((a, b) => parseFloat(b.volume_24h_fp || b.volume_fp || '0') - parseFloat(a.volume_24h_fp || a.volume_fp || '0'))
|
|
814
|
-
.slice(0,
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
818
|
+
.slice(0, 10)
|
|
819
|
+
.map((s) => ({ venue: 'kalshi', ticker: s.ticker, title: s.title, volume: s.volume_fp }));
|
|
820
|
+
// Polymarket: Gamma API search
|
|
821
|
+
let polyMatched = [];
|
|
822
|
+
try {
|
|
823
|
+
const events = await (0, polymarket_js_1.polymarketSearch)(params.query, 10);
|
|
824
|
+
for (const event of events) {
|
|
825
|
+
for (const m of (event.markets || []).slice(0, 3)) {
|
|
826
|
+
if (!m.active || m.closed)
|
|
827
|
+
continue;
|
|
828
|
+
const prices = (0, polymarket_js_1.parseOutcomePrices)(m.outcomePrices);
|
|
829
|
+
polyMatched.push({
|
|
830
|
+
venue: 'polymarket',
|
|
831
|
+
id: m.conditionId || m.id,
|
|
832
|
+
title: m.groupItemTitle ? `${event.title}: ${m.groupItemTitle}` : m.question || event.title,
|
|
833
|
+
price: prices[0] ? Math.round(prices[0] * 100) : null,
|
|
834
|
+
volume24h: m.volume24hr,
|
|
835
|
+
liquidity: m.liquidityNum,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
catch { /* Polymarket search optional */ }
|
|
841
|
+
return {
|
|
842
|
+
content: [{ type: 'text', text: JSON.stringify({ kalshi: kalshiMatched, polymarket: polyMatched }, null, 2) }],
|
|
843
|
+
details: {},
|
|
844
|
+
};
|
|
819
845
|
}
|
|
820
|
-
return {
|
|
821
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
822
|
-
details: {},
|
|
823
|
-
};
|
|
846
|
+
return { content: [{ type: 'text', text: '{"error":"Provide query, series, or market parameter"}' }], details: {} };
|
|
824
847
|
},
|
|
825
848
|
},
|
|
826
849
|
{
|
|
@@ -839,36 +862,57 @@ async function agentCommand(thesisId, opts) {
|
|
|
839
862
|
{
|
|
840
863
|
name: 'get_positions',
|
|
841
864
|
label: 'Get Positions',
|
|
842
|
-
description: 'Get Kalshi
|
|
865
|
+
description: 'Get positions across Kalshi + Polymarket with live prices and PnL',
|
|
843
866
|
parameters: emptyParams,
|
|
844
867
|
execute: async () => {
|
|
868
|
+
const result = { kalshi: [], polymarket: [] };
|
|
869
|
+
// Kalshi positions
|
|
845
870
|
const positions = await (0, kalshi_js_1.getPositions)();
|
|
846
|
-
if (
|
|
871
|
+
if (positions) {
|
|
872
|
+
for (const pos of positions) {
|
|
873
|
+
const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
|
|
874
|
+
if (livePrice !== null) {
|
|
875
|
+
pos.current_value = livePrice;
|
|
876
|
+
pos.unrealized_pnl = Math.round((livePrice - pos.average_price_paid) * pos.quantity);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
cachedPositions = positions;
|
|
880
|
+
result.kalshi = positions.map((p) => ({
|
|
881
|
+
venue: 'kalshi',
|
|
882
|
+
ticker: p.ticker,
|
|
883
|
+
side: p.side,
|
|
884
|
+
quantity: p.quantity,
|
|
885
|
+
avg_price: `${p.average_price_paid}¢`,
|
|
886
|
+
current_price: `${p.current_value}¢`,
|
|
887
|
+
unrealized_pnl: `$${(p.unrealized_pnl / 100).toFixed(2)}`,
|
|
888
|
+
total_cost: `$${(p.total_cost / 100).toFixed(2)}`,
|
|
889
|
+
}));
|
|
890
|
+
}
|
|
891
|
+
// Polymarket positions
|
|
892
|
+
const config = (0, config_js_1.loadConfig)();
|
|
893
|
+
if (config.polymarketWalletAddress) {
|
|
894
|
+
try {
|
|
895
|
+
const polyPos = await (0, polymarket_js_1.polymarketGetPositions)(config.polymarketWalletAddress);
|
|
896
|
+
result.polymarket = polyPos.map((p) => ({
|
|
897
|
+
venue: 'polymarket',
|
|
898
|
+
market: p.title || p.slug || p.asset,
|
|
899
|
+
side: p.outcome || 'Yes',
|
|
900
|
+
size: p.size,
|
|
901
|
+
avg_price: `${Math.round((p.avgPrice || 0) * 100)}¢`,
|
|
902
|
+
current_price: `${Math.round((p.curPrice || p.currentPrice || 0) * 100)}¢`,
|
|
903
|
+
pnl: `$${(p.cashPnl || 0).toFixed(2)}`,
|
|
904
|
+
}));
|
|
905
|
+
}
|
|
906
|
+
catch { /* skip */ }
|
|
907
|
+
}
|
|
908
|
+
if (result.kalshi.length === 0 && result.polymarket.length === 0) {
|
|
847
909
|
return {
|
|
848
|
-
content: [{ type: 'text', text: '
|
|
910
|
+
content: [{ type: 'text', text: 'No positions found. Configure Kalshi (KALSHI_API_KEY_ID) or Polymarket (sf setup --polymarket) to see positions.' }],
|
|
849
911
|
details: {},
|
|
850
912
|
};
|
|
851
913
|
}
|
|
852
|
-
for (const pos of positions) {
|
|
853
|
-
const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
|
|
854
|
-
if (livePrice !== null) {
|
|
855
|
-
pos.current_value = livePrice;
|
|
856
|
-
pos.unrealized_pnl = Math.round((livePrice - pos.average_price_paid) * pos.quantity);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
cachedPositions = positions;
|
|
860
|
-
const formatted = positions.map((p) => ({
|
|
861
|
-
ticker: p.ticker,
|
|
862
|
-
side: p.side,
|
|
863
|
-
quantity: p.quantity,
|
|
864
|
-
avg_price: `${p.average_price_paid}¢`,
|
|
865
|
-
current_price: `${p.current_value}¢`,
|
|
866
|
-
unrealized_pnl: `$${(p.unrealized_pnl / 100).toFixed(2)}`,
|
|
867
|
-
total_cost: `$${(p.total_cost / 100).toFixed(2)}`,
|
|
868
|
-
realized_pnl: `$${(p.realized_pnl / 100).toFixed(2)}`,
|
|
869
|
-
}));
|
|
870
914
|
return {
|
|
871
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
915
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
872
916
|
details: {},
|
|
873
917
|
};
|
|
874
918
|
},
|
|
@@ -1129,6 +1173,139 @@ async function agentCommand(thesisId, opts) {
|
|
|
1129
1173
|
return { content: [{ type: 'text', text: JSON.stringify(result.fills, null, 2) }], details: {} };
|
|
1130
1174
|
},
|
|
1131
1175
|
},
|
|
1176
|
+
{
|
|
1177
|
+
name: 'get_liquidity',
|
|
1178
|
+
label: 'Liquidity Scanner',
|
|
1179
|
+
description: 'Scan orderbook liquidity for a topic across Kalshi + Polymarket. Returns spread, depth, liquidity scores. Topics: ' + Object.keys(topics_js_1.TOPIC_SERIES).join(', '),
|
|
1180
|
+
parameters: Type.Object({
|
|
1181
|
+
topic: Type.String({ description: 'Topic to scan (e.g. oil, crypto, fed, geopolitics)' }),
|
|
1182
|
+
}),
|
|
1183
|
+
execute: async (_toolCallId, params) => {
|
|
1184
|
+
const topicKey = params.topic.toLowerCase();
|
|
1185
|
+
const seriesList = topics_js_1.TOPIC_SERIES[topicKey];
|
|
1186
|
+
if (!seriesList) {
|
|
1187
|
+
return { content: [{ type: 'text', text: `Unknown topic "${params.topic}". Available: ${Object.keys(topics_js_1.TOPIC_SERIES).join(', ')}` }], details: {} };
|
|
1188
|
+
}
|
|
1189
|
+
const results = [];
|
|
1190
|
+
for (const series of seriesList) {
|
|
1191
|
+
try {
|
|
1192
|
+
const url = `https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${series}&status=open&limit=200`;
|
|
1193
|
+
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
1194
|
+
if (!res.ok)
|
|
1195
|
+
continue;
|
|
1196
|
+
const markets = (await res.json()).markets || [];
|
|
1197
|
+
const obResults = await Promise.allSettled(markets.slice(0, 20).map((m) => (0, kalshi_js_1.getPublicOrderbook)(m.ticker).then(ob => ({ ticker: m.ticker, title: m.title, ob }))));
|
|
1198
|
+
for (const r of obResults) {
|
|
1199
|
+
if (r.status !== 'fulfilled' || !r.value.ob)
|
|
1200
|
+
continue;
|
|
1201
|
+
const { ticker, title, ob } = r.value;
|
|
1202
|
+
const yes = (ob.yes_dollars || []).map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), qty: parseFloat(q) })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
1203
|
+
const no = (ob.no_dollars || []).map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), qty: parseFloat(q) })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
1204
|
+
const bestBid = yes[0]?.price || 0;
|
|
1205
|
+
const bestAsk = no.length > 0 ? (100 - no[0].price) : 100;
|
|
1206
|
+
const spread = bestAsk - bestBid;
|
|
1207
|
+
const depth = yes.slice(0, 3).reduce((s, l) => s + l.qty, 0) + no.slice(0, 3).reduce((s, l) => s + l.qty, 0);
|
|
1208
|
+
const liq = spread <= 2 && depth >= 500 ? 'high' : spread <= 5 && depth >= 100 ? 'medium' : 'low';
|
|
1209
|
+
results.push({ venue: 'kalshi', ticker, title: (title || '').slice(0, 50), bestBid, bestAsk, spread, depth: Math.round(depth), liquidityScore: liq });
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
catch { /* skip */ }
|
|
1213
|
+
}
|
|
1214
|
+
try {
|
|
1215
|
+
const events = await (0, polymarket_js_1.polymarketSearch)(topicKey, 5);
|
|
1216
|
+
for (const event of events) {
|
|
1217
|
+
for (const m of (event.markets || []).slice(0, 5)) {
|
|
1218
|
+
if (!m.active || m.closed || !m.clobTokenIds)
|
|
1219
|
+
continue;
|
|
1220
|
+
const ids = (0, polymarket_js_1.parseClobTokenIds)(m.clobTokenIds);
|
|
1221
|
+
if (!ids)
|
|
1222
|
+
continue;
|
|
1223
|
+
const d = await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)(ids[0]);
|
|
1224
|
+
if (!d)
|
|
1225
|
+
continue;
|
|
1226
|
+
results.push({ venue: 'polymarket', ticker: (m.question || event.title).slice(0, 50), bestBid: d.bestBid, bestAsk: d.bestAsk, spread: d.spread, depth: d.bidDepthTop3 + d.askDepthTop3, liquidityScore: d.liquidityScore });
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
catch { /* skip */ }
|
|
1231
|
+
results.sort((a, b) => a.spread - b.spread);
|
|
1232
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }], details: {} };
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
name: 'inspect_book',
|
|
1237
|
+
label: 'Orderbook',
|
|
1238
|
+
description: 'Get orderbook depth, spread, and liquidity for a specific market. Works with Kalshi tickers or Polymarket search queries. Returns bid/ask levels, depth, spread, liquidity score.',
|
|
1239
|
+
parameters: Type.Object({
|
|
1240
|
+
ticker: Type.Optional(Type.String({ description: 'Kalshi market ticker (e.g. KXWTIMAX-26DEC31-T135)' })),
|
|
1241
|
+
polyQuery: Type.Optional(Type.String({ description: 'Search Polymarket by keyword (e.g. "oil price above 100")' })),
|
|
1242
|
+
}),
|
|
1243
|
+
execute: async (_toolCallId, params) => {
|
|
1244
|
+
const results = [];
|
|
1245
|
+
if (params.ticker) {
|
|
1246
|
+
try {
|
|
1247
|
+
const market = await (0, client_js_1.kalshiFetchMarket)(params.ticker);
|
|
1248
|
+
const ob = await (0, kalshi_js_1.getPublicOrderbook)(params.ticker);
|
|
1249
|
+
const yesBids = (ob?.yes_dollars || [])
|
|
1250
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
1251
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
1252
|
+
const noAsks = (ob?.no_dollars || [])
|
|
1253
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
1254
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
1255
|
+
const bestBid = yesBids[0]?.price || 0;
|
|
1256
|
+
const bestAsk = noAsks.length > 0 ? (100 - noAsks[0].price) : 100;
|
|
1257
|
+
const spread = bestAsk - bestBid;
|
|
1258
|
+
const top3Depth = yesBids.slice(0, 3).reduce((s, l) => s + l.size, 0) + noAsks.slice(0, 3).reduce((s, l) => s + l.size, 0);
|
|
1259
|
+
const liq = spread <= 2 && top3Depth >= 500 ? 'high' : spread <= 5 && top3Depth >= 100 ? 'medium' : 'low';
|
|
1260
|
+
results.push({
|
|
1261
|
+
venue: 'kalshi', ticker: params.ticker, title: market.title,
|
|
1262
|
+
bestBid, bestAsk, spread, liquidityScore: liq,
|
|
1263
|
+
bidLevels: yesBids.slice(0, 5), askLevels: noAsks.slice(0, 5).map((l) => ({ price: 100 - l.price, size: l.size })),
|
|
1264
|
+
totalBidDepth: yesBids.reduce((s, l) => s + l.size, 0),
|
|
1265
|
+
totalAskDepth: noAsks.reduce((s, l) => s + l.size, 0),
|
|
1266
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
1267
|
+
volume24h: parseFloat(market.volume_24h_fp || '0'),
|
|
1268
|
+
openInterest: parseFloat(market.open_interest_fp || '0'),
|
|
1269
|
+
expiry: market.close_time || null,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
catch (err) {
|
|
1273
|
+
return { content: [{ type: 'text', text: `Failed: ${err.message}` }], details: {} };
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (params.polyQuery) {
|
|
1277
|
+
try {
|
|
1278
|
+
const events = await (0, polymarket_js_1.polymarketSearch)(params.polyQuery, 5);
|
|
1279
|
+
for (const event of events) {
|
|
1280
|
+
for (const m of (event.markets || []).slice(0, 3)) {
|
|
1281
|
+
if (!m.active || m.closed || !m.clobTokenIds)
|
|
1282
|
+
continue;
|
|
1283
|
+
const ids = (0, polymarket_js_1.parseClobTokenIds)(m.clobTokenIds);
|
|
1284
|
+
if (!ids)
|
|
1285
|
+
continue;
|
|
1286
|
+
const depth = await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)(ids[0]);
|
|
1287
|
+
if (!depth)
|
|
1288
|
+
continue;
|
|
1289
|
+
const prices = (0, polymarket_js_1.parseOutcomePrices)(m.outcomePrices);
|
|
1290
|
+
results.push({
|
|
1291
|
+
venue: 'polymarket', title: m.question || event.title,
|
|
1292
|
+
bestBid: depth.bestBid, bestAsk: depth.bestAsk, spread: depth.spread,
|
|
1293
|
+
liquidityScore: depth.liquidityScore,
|
|
1294
|
+
totalBidDepth: depth.totalBidDepth, totalAskDepth: depth.totalAskDepth,
|
|
1295
|
+
lastPrice: prices[0] ? Math.round(prices[0] * 100) : 0,
|
|
1296
|
+
volume24h: m.volume24hr || 0,
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
catch { /* skip */ }
|
|
1302
|
+
}
|
|
1303
|
+
if (results.length === 0) {
|
|
1304
|
+
return { content: [{ type: 'text', text: 'No markets found. Provide ticker (Kalshi) or polyQuery (Polymarket search).' }], details: {} };
|
|
1305
|
+
}
|
|
1306
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }], details: {} };
|
|
1307
|
+
},
|
|
1308
|
+
},
|
|
1132
1309
|
{
|
|
1133
1310
|
name: 'get_schedule',
|
|
1134
1311
|
label: 'Schedule',
|
|
@@ -2529,6 +2706,139 @@ async function runPlainTextAgent(params) {
|
|
|
2529
2706
|
return { content: [{ type: 'text', text: JSON.stringify(result.fills, null, 2) }], details: {} };
|
|
2530
2707
|
},
|
|
2531
2708
|
},
|
|
2709
|
+
{
|
|
2710
|
+
name: 'get_liquidity',
|
|
2711
|
+
label: 'Liquidity Scanner',
|
|
2712
|
+
description: 'Scan orderbook liquidity for a topic across Kalshi + Polymarket. Returns spread, depth, liquidity scores. Topics: ' + Object.keys(topics_js_1.TOPIC_SERIES).join(', '),
|
|
2713
|
+
parameters: Type.Object({
|
|
2714
|
+
topic: Type.String({ description: 'Topic to scan (e.g. oil, crypto, fed, geopolitics)' }),
|
|
2715
|
+
}),
|
|
2716
|
+
execute: async (_toolCallId, params) => {
|
|
2717
|
+
const topicKey = params.topic.toLowerCase();
|
|
2718
|
+
const seriesList = topics_js_1.TOPIC_SERIES[topicKey];
|
|
2719
|
+
if (!seriesList) {
|
|
2720
|
+
return { content: [{ type: 'text', text: `Unknown topic "${params.topic}". Available: ${Object.keys(topics_js_1.TOPIC_SERIES).join(', ')}` }], details: {} };
|
|
2721
|
+
}
|
|
2722
|
+
const results = [];
|
|
2723
|
+
for (const series of seriesList) {
|
|
2724
|
+
try {
|
|
2725
|
+
const url = `https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${series}&status=open&limit=200`;
|
|
2726
|
+
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
2727
|
+
if (!res.ok)
|
|
2728
|
+
continue;
|
|
2729
|
+
const markets = (await res.json()).markets || [];
|
|
2730
|
+
const obResults = await Promise.allSettled(markets.slice(0, 20).map((m) => (0, kalshi_js_1.getPublicOrderbook)(m.ticker).then(ob => ({ ticker: m.ticker, title: m.title, ob }))));
|
|
2731
|
+
for (const r of obResults) {
|
|
2732
|
+
if (r.status !== 'fulfilled' || !r.value.ob)
|
|
2733
|
+
continue;
|
|
2734
|
+
const { ticker, title, ob } = r.value;
|
|
2735
|
+
const yes = (ob.yes_dollars || []).map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), qty: parseFloat(q) })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
2736
|
+
const no = (ob.no_dollars || []).map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), qty: parseFloat(q) })).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
2737
|
+
const bestBid = yes[0]?.price || 0;
|
|
2738
|
+
const bestAsk = no.length > 0 ? (100 - no[0].price) : 100;
|
|
2739
|
+
const spread = bestAsk - bestBid;
|
|
2740
|
+
const depth = yes.slice(0, 3).reduce((s, l) => s + l.qty, 0) + no.slice(0, 3).reduce((s, l) => s + l.qty, 0);
|
|
2741
|
+
const liq = spread <= 2 && depth >= 500 ? 'high' : spread <= 5 && depth >= 100 ? 'medium' : 'low';
|
|
2742
|
+
results.push({ venue: 'kalshi', ticker, title: (title || '').slice(0, 50), bestBid, bestAsk, spread, depth: Math.round(depth), liquidityScore: liq });
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
catch { /* skip */ }
|
|
2746
|
+
}
|
|
2747
|
+
try {
|
|
2748
|
+
const events = await (0, polymarket_js_1.polymarketSearch)(topicKey, 5);
|
|
2749
|
+
for (const event of events) {
|
|
2750
|
+
for (const m of (event.markets || []).slice(0, 5)) {
|
|
2751
|
+
if (!m.active || m.closed || !m.clobTokenIds)
|
|
2752
|
+
continue;
|
|
2753
|
+
const ids = (0, polymarket_js_1.parseClobTokenIds)(m.clobTokenIds);
|
|
2754
|
+
if (!ids)
|
|
2755
|
+
continue;
|
|
2756
|
+
const d = await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)(ids[0]);
|
|
2757
|
+
if (!d)
|
|
2758
|
+
continue;
|
|
2759
|
+
results.push({ venue: 'polymarket', ticker: (m.question || event.title).slice(0, 50), bestBid: d.bestBid, bestAsk: d.bestAsk, spread: d.spread, depth: d.bidDepthTop3 + d.askDepthTop3, liquidityScore: d.liquidityScore });
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
catch { /* skip */ }
|
|
2764
|
+
results.sort((a, b) => a.spread - b.spread);
|
|
2765
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }], details: {} };
|
|
2766
|
+
},
|
|
2767
|
+
},
|
|
2768
|
+
{
|
|
2769
|
+
name: 'inspect_book',
|
|
2770
|
+
label: 'Orderbook',
|
|
2771
|
+
description: 'Get orderbook depth, spread, and liquidity for a specific market. Works with Kalshi tickers or Polymarket search queries. Returns bid/ask levels, depth, spread, liquidity score.',
|
|
2772
|
+
parameters: Type.Object({
|
|
2773
|
+
ticker: Type.Optional(Type.String({ description: 'Kalshi market ticker (e.g. KXWTIMAX-26DEC31-T135)' })),
|
|
2774
|
+
polyQuery: Type.Optional(Type.String({ description: 'Search Polymarket by keyword (e.g. "oil price above 100")' })),
|
|
2775
|
+
}),
|
|
2776
|
+
execute: async (_toolCallId, params) => {
|
|
2777
|
+
const results = [];
|
|
2778
|
+
if (params.ticker) {
|
|
2779
|
+
try {
|
|
2780
|
+
const market = await (0, client_js_1.kalshiFetchMarket)(params.ticker);
|
|
2781
|
+
const ob = await (0, kalshi_js_1.getPublicOrderbook)(params.ticker);
|
|
2782
|
+
const yesBids = (ob?.yes_dollars || [])
|
|
2783
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
2784
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
2785
|
+
const noAsks = (ob?.no_dollars || [])
|
|
2786
|
+
.map(([p, q]) => ({ price: Math.round(parseFloat(p) * 100), size: Math.round(parseFloat(q)) }))
|
|
2787
|
+
.filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
2788
|
+
const bestBid = yesBids[0]?.price || 0;
|
|
2789
|
+
const bestAsk = noAsks.length > 0 ? (100 - noAsks[0].price) : 100;
|
|
2790
|
+
const spread = bestAsk - bestBid;
|
|
2791
|
+
const top3Depth = yesBids.slice(0, 3).reduce((s, l) => s + l.size, 0) + noAsks.slice(0, 3).reduce((s, l) => s + l.size, 0);
|
|
2792
|
+
const liq = spread <= 2 && top3Depth >= 500 ? 'high' : spread <= 5 && top3Depth >= 100 ? 'medium' : 'low';
|
|
2793
|
+
results.push({
|
|
2794
|
+
venue: 'kalshi', ticker: params.ticker, title: market.title,
|
|
2795
|
+
bestBid, bestAsk, spread, liquidityScore: liq,
|
|
2796
|
+
bidLevels: yesBids.slice(0, 5), askLevels: noAsks.slice(0, 5).map((l) => ({ price: 100 - l.price, size: l.size })),
|
|
2797
|
+
totalBidDepth: yesBids.reduce((s, l) => s + l.size, 0),
|
|
2798
|
+
totalAskDepth: noAsks.reduce((s, l) => s + l.size, 0),
|
|
2799
|
+
lastPrice: Math.round(parseFloat(market.last_price_dollars || '0') * 100),
|
|
2800
|
+
volume24h: parseFloat(market.volume_24h_fp || '0'),
|
|
2801
|
+
openInterest: parseFloat(market.open_interest_fp || '0'),
|
|
2802
|
+
expiry: market.close_time || null,
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
catch (err) {
|
|
2806
|
+
return { content: [{ type: 'text', text: `Failed: ${err.message}` }], details: {} };
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
if (params.polyQuery) {
|
|
2810
|
+
try {
|
|
2811
|
+
const events = await (0, polymarket_js_1.polymarketSearch)(params.polyQuery, 5);
|
|
2812
|
+
for (const event of events) {
|
|
2813
|
+
for (const m of (event.markets || []).slice(0, 3)) {
|
|
2814
|
+
if (!m.active || m.closed || !m.clobTokenIds)
|
|
2815
|
+
continue;
|
|
2816
|
+
const ids = (0, polymarket_js_1.parseClobTokenIds)(m.clobTokenIds);
|
|
2817
|
+
if (!ids)
|
|
2818
|
+
continue;
|
|
2819
|
+
const depth = await (0, polymarket_js_1.polymarketGetOrderbookWithDepth)(ids[0]);
|
|
2820
|
+
if (!depth)
|
|
2821
|
+
continue;
|
|
2822
|
+
const prices = (0, polymarket_js_1.parseOutcomePrices)(m.outcomePrices);
|
|
2823
|
+
results.push({
|
|
2824
|
+
venue: 'polymarket', title: m.question || event.title,
|
|
2825
|
+
bestBid: depth.bestBid, bestAsk: depth.bestAsk, spread: depth.spread,
|
|
2826
|
+
liquidityScore: depth.liquidityScore,
|
|
2827
|
+
totalBidDepth: depth.totalBidDepth, totalAskDepth: depth.totalAskDepth,
|
|
2828
|
+
lastPrice: prices[0] ? Math.round(prices[0] * 100) : 0,
|
|
2829
|
+
volume24h: m.volume24hr || 0,
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
catch { /* skip */ }
|
|
2835
|
+
}
|
|
2836
|
+
if (results.length === 0) {
|
|
2837
|
+
return { content: [{ type: 'text', text: 'No markets found. Provide ticker (Kalshi) or polyQuery (Polymarket search).' }], details: {} };
|
|
2838
|
+
}
|
|
2839
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }], details: {} };
|
|
2840
|
+
},
|
|
2841
|
+
},
|
|
2532
2842
|
{
|
|
2533
2843
|
name: 'get_schedule',
|
|
2534
2844
|
label: 'Schedule',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sf book — Orderbook depth, price history, and liquidity for individual markets
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* sf book KXWTIMAX-26DEC31-T135 Single Kalshi market
|
|
6
|
+
* sf book KXWTI-T135 KXCPI-26MAY Multiple markets
|
|
7
|
+
* sf book --poly "oil price" Polymarket search
|
|
8
|
+
* sf book KXWTIMAX-26DEC31-T135 --history With 7d price history
|
|
9
|
+
* sf book KXWTIMAX-26DEC31-T135 --json JSON output
|
|
10
|
+
*/
|
|
11
|
+
interface BookOpts {
|
|
12
|
+
poly?: string;
|
|
13
|
+
history?: boolean;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function bookCommand(tickers: string[], opts: BookOpts): Promise<void>;
|
|
17
|
+
export {};
|