@spfunctions/cli 1.7.19 → 1.7.22
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/101.index.js +1 -0
- package/dist/12.index.js +1 -0
- package/dist/160.index.js +1 -0
- package/dist/174.index.js +1 -0
- package/dist/278.index.js +6 -0
- package/dist/582.index.js +1 -0
- package/dist/641.index.js +324 -0
- package/dist/669.index.js +1 -0
- package/dist/722.index.js +1 -0
- package/dist/788.index.js +1 -0
- package/dist/816.index.js +12 -0
- package/dist/830.index.js +1 -0
- package/dist/921.index.js +1 -0
- package/dist/index.js +1 -833
- package/package.json +5 -2
- package/dist/cache.d.ts +0 -6
- package/dist/cache.js +0 -31
- package/dist/cache.test.d.ts +0 -1
- package/dist/cache.test.js +0 -73
- package/dist/client.d.ts +0 -56
- package/dist/client.js +0 -205
- package/dist/client.test.d.ts +0 -1
- package/dist/client.test.js +0 -89
- package/dist/commands/agent.d.ts +0 -20
- package/dist/commands/agent.js +0 -4119
- package/dist/commands/announcements.d.ts +0 -3
- package/dist/commands/announcements.js +0 -28
- package/dist/commands/augment.d.ts +0 -12
- package/dist/commands/augment.js +0 -56
- package/dist/commands/balance.d.ts +0 -3
- package/dist/commands/balance.js +0 -17
- package/dist/commands/book.d.ts +0 -17
- package/dist/commands/book.js +0 -220
- package/dist/commands/cancel.d.ts +0 -5
- package/dist/commands/cancel.js +0 -41
- package/dist/commands/context.d.ts +0 -6
- package/dist/commands/context.js +0 -208
- package/dist/commands/create.d.ts +0 -7
- package/dist/commands/create.js +0 -42
- package/dist/commands/dashboard.d.ts +0 -14
- package/dist/commands/dashboard.js +0 -215
- package/dist/commands/delta.d.ts +0 -16
- package/dist/commands/delta.js +0 -115
- package/dist/commands/edges.d.ts +0 -26
- package/dist/commands/edges.js +0 -246
- package/dist/commands/evaluate.d.ts +0 -4
- package/dist/commands/evaluate.js +0 -30
- package/dist/commands/explore.d.ts +0 -14
- package/dist/commands/explore.js +0 -116
- package/dist/commands/feed.d.ts +0 -13
- package/dist/commands/feed.js +0 -73
- package/dist/commands/fills.d.ts +0 -4
- package/dist/commands/fills.js +0 -29
- package/dist/commands/forecast.d.ts +0 -4
- package/dist/commands/forecast.js +0 -53
- package/dist/commands/get.d.ts +0 -5
- package/dist/commands/get.js +0 -98
- package/dist/commands/heartbeat.d.ts +0 -20
- package/dist/commands/heartbeat.js +0 -73
- package/dist/commands/history.d.ts +0 -3
- package/dist/commands/history.js +0 -38
- package/dist/commands/liquidity.d.ts +0 -14
- package/dist/commands/liquidity.js +0 -378
- package/dist/commands/list.d.ts +0 -5
- package/dist/commands/list.js +0 -38
- package/dist/commands/login.d.ts +0 -10
- package/dist/commands/login.js +0 -98
- package/dist/commands/markets.d.ts +0 -10
- package/dist/commands/markets.js +0 -39
- package/dist/commands/milestones.d.ts +0 -8
- package/dist/commands/milestones.js +0 -56
- package/dist/commands/orders.d.ts +0 -4
- package/dist/commands/orders.js +0 -28
- package/dist/commands/performance.d.ts +0 -11
- package/dist/commands/performance.js +0 -250
- package/dist/commands/positions.d.ts +0 -19
- package/dist/commands/positions.js +0 -294
- package/dist/commands/prompt.d.ts +0 -13
- package/dist/commands/prompt.js +0 -35
- package/dist/commands/publish.d.ts +0 -15
- package/dist/commands/publish.js +0 -39
- package/dist/commands/query.d.ts +0 -15
- package/dist/commands/query.js +0 -132
- package/dist/commands/rfq.d.ts +0 -5
- package/dist/commands/rfq.js +0 -35
- package/dist/commands/scan.d.ts +0 -11
- package/dist/commands/scan.js +0 -230
- package/dist/commands/schedule.d.ts +0 -3
- package/dist/commands/schedule.js +0 -38
- package/dist/commands/settlements.d.ts +0 -6
- package/dist/commands/settlements.js +0 -50
- package/dist/commands/setup.d.ts +0 -24
- package/dist/commands/setup.js +0 -700
- package/dist/commands/signal.d.ts +0 -6
- package/dist/commands/signal.js +0 -32
- package/dist/commands/strategies.d.ts +0 -11
- package/dist/commands/strategies.js +0 -130
- package/dist/commands/telegram.d.ts +0 -15
- package/dist/commands/telegram.js +0 -125
- package/dist/commands/trade.d.ts +0 -12
- package/dist/commands/trade.js +0 -112
- package/dist/commands/watch.d.ts +0 -19
- package/dist/commands/watch.js +0 -157
- package/dist/commands/whatif.d.ts +0 -17
- package/dist/commands/whatif.js +0 -209
- package/dist/commands/x.d.ts +0 -28
- package/dist/commands/x.js +0 -167
- package/dist/config.d.ts +0 -55
- package/dist/config.js +0 -139
- package/dist/config.test.d.ts +0 -1
- package/dist/config.test.js +0 -138
- package/dist/index.d.ts +0 -20
- package/dist/kalshi.d.ts +0 -144
- package/dist/kalshi.js +0 -498
- package/dist/polymarket.d.ts +0 -237
- package/dist/polymarket.js +0 -353
- package/dist/polymarket.test.d.ts +0 -1
- package/dist/polymarket.test.js +0 -424
- package/dist/share.d.ts +0 -4
- package/dist/share.js +0 -27
- package/dist/skills/loader.d.ts +0 -19
- package/dist/skills/loader.js +0 -86
- package/dist/telegram/agent-bridge.d.ts +0 -15
- package/dist/telegram/agent-bridge.js +0 -573
- package/dist/telegram/bot.d.ts +0 -10
- package/dist/telegram/bot.js +0 -297
- package/dist/telegram/commands.d.ts +0 -11
- package/dist/telegram/commands.js +0 -120
- package/dist/telegram/format.d.ts +0 -11
- package/dist/telegram/format.js +0 -51
- package/dist/telegram/format.test.d.ts +0 -1
- package/dist/telegram/format.test.js +0 -73
- package/dist/telegram/poller.d.ts +0 -6
- package/dist/telegram/poller.js +0 -32
- package/dist/topics.d.ts +0 -17
- package/dist/topics.js +0 -102
- package/dist/topics.test.d.ts +0 -1
- package/dist/topics.test.js +0 -131
- package/dist/tui/border.d.ts +0 -33
- package/dist/tui/border.js +0 -87
- package/dist/tui/chart.d.ts +0 -19
- package/dist/tui/chart.js +0 -117
- package/dist/tui/dashboard.d.ts +0 -9
- package/dist/tui/dashboard.js +0 -814
- package/dist/tui/layout.d.ts +0 -16
- package/dist/tui/layout.js +0 -41
- package/dist/tui/screen.d.ts +0 -33
- package/dist/tui/screen.js +0 -102
- package/dist/tui/state.d.ts +0 -40
- package/dist/tui/state.js +0 -36
- package/dist/tui/widgets/commandbar.d.ts +0 -8
- package/dist/tui/widgets/commandbar.js +0 -82
- package/dist/tui/widgets/detail.d.ts +0 -9
- package/dist/tui/widgets/detail.js +0 -151
- package/dist/tui/widgets/edges.d.ts +0 -4
- package/dist/tui/widgets/edges.js +0 -34
- package/dist/tui/widgets/liquidity.d.ts +0 -9
- package/dist/tui/widgets/liquidity.js +0 -142
- package/dist/tui/widgets/orders.d.ts +0 -4
- package/dist/tui/widgets/orders.js +0 -37
- package/dist/tui/widgets/portfolio.d.ts +0 -4
- package/dist/tui/widgets/portfolio.js +0 -59
- package/dist/tui/widgets/signals.d.ts +0 -4
- package/dist/tui/widgets/signals.js +0 -31
- package/dist/tui/widgets/statusbar.d.ts +0 -8
- package/dist/tui/widgets/statusbar.js +0 -72
- package/dist/tui/widgets/thesis.d.ts +0 -4
- package/dist/tui/widgets/thesis.js +0 -66
- package/dist/tui/widgets/trade.d.ts +0 -9
- package/dist/tui/widgets/trade.js +0 -117
- package/dist/tui/widgets/upcoming.d.ts +0 -4
- package/dist/tui/widgets/upcoming.js +0 -41
- package/dist/tui/widgets/whatif.d.ts +0 -7
- package/dist/tui/widgets/whatif.js +0 -113
- package/dist/types/output.d.ts +0 -412
- package/dist/types/output.js +0 -9
- package/dist/utils.d.ts +0 -52
- package/dist/utils.js +0 -146
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -111
package/dist/tui/dashboard.js
DELETED
|
@@ -1,814 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* TUI Dashboard Engine — main loop
|
|
4
|
-
*
|
|
5
|
-
* Manages the render loop, data loading, keypress handling, and mode transitions.
|
|
6
|
-
* This is NOT the commander entry point — see commands/dashboard.ts for that.
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.loadAllData = loadAllData;
|
|
10
|
-
exports.startDashboard = startDashboard;
|
|
11
|
-
const screen_js_1 = require("./screen.js");
|
|
12
|
-
const layout_js_1 = require("./layout.js");
|
|
13
|
-
const state_js_1 = require("./state.js");
|
|
14
|
-
const border_js_1 = require("./border.js");
|
|
15
|
-
const cache_js_1 = require("../cache.js");
|
|
16
|
-
const config_js_1 = require("../config.js");
|
|
17
|
-
const client_js_1 = require("../client.js");
|
|
18
|
-
const kalshi_js_1 = require("../kalshi.js");
|
|
19
|
-
const topics_js_1 = require("../topics.js");
|
|
20
|
-
const polymarket_js_1 = require("../polymarket.js");
|
|
21
|
-
// Widget renderers
|
|
22
|
-
const portfolio_js_1 = require("./widgets/portfolio.js");
|
|
23
|
-
const thesis_js_1 = require("./widgets/thesis.js");
|
|
24
|
-
const edges_js_1 = require("./widgets/edges.js");
|
|
25
|
-
const upcoming_js_1 = require("./widgets/upcoming.js");
|
|
26
|
-
const orders_js_1 = require("./widgets/orders.js");
|
|
27
|
-
const signals_js_1 = require("./widgets/signals.js");
|
|
28
|
-
const statusbar_js_1 = require("./widgets/statusbar.js");
|
|
29
|
-
const commandbar_js_1 = require("./widgets/commandbar.js");
|
|
30
|
-
const detail_js_1 = require("./widgets/detail.js");
|
|
31
|
-
const liquidity_js_1 = require("./widgets/liquidity.js");
|
|
32
|
-
const whatif_js_1 = require("./widgets/whatif.js");
|
|
33
|
-
const trade_js_1 = require("./widgets/trade.js");
|
|
34
|
-
// Refresh intervals (ms)
|
|
35
|
-
const REFRESH_POSITIONS = 15_000;
|
|
36
|
-
const REFRESH_THESES = 30_000;
|
|
37
|
-
const REFRESH_ORDERS = 10_000;
|
|
38
|
-
const REFRESH_BALANCE = 30_000;
|
|
39
|
-
const REFRESH_FEED = 60_000;
|
|
40
|
-
const REFRESH_CANDLES = 60_000;
|
|
41
|
-
let sfClient = null;
|
|
42
|
-
function getClient() {
|
|
43
|
-
if (sfClient)
|
|
44
|
-
return sfClient;
|
|
45
|
-
try {
|
|
46
|
-
const config = (0, config_js_1.loadConfig)();
|
|
47
|
-
if (!config.apiKey)
|
|
48
|
-
return null;
|
|
49
|
-
sfClient = new client_js_1.SFClient(config.apiKey, config.apiUrl);
|
|
50
|
-
return sfClient;
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
// ============================================================================
|
|
57
|
-
// DATA LOADING
|
|
58
|
-
// ============================================================================
|
|
59
|
-
async function loadAllData(state) {
|
|
60
|
-
const client = getClient();
|
|
61
|
-
// Parallel fetch of primary data
|
|
62
|
-
const [positionsResult, thesesResult, ordersResult, balanceResult] = await Promise.allSettled([
|
|
63
|
-
(0, cache_js_1.cached)('positions', REFRESH_POSITIONS, () => (0, kalshi_js_1.getPositions)()),
|
|
64
|
-
client ? (0, cache_js_1.cached)('theses', REFRESH_THESES, () => client.listTheses()) : Promise.resolve(null),
|
|
65
|
-
(0, cache_js_1.cached)('orders', REFRESH_ORDERS, () => (0, kalshi_js_1.getOrders)({ status: 'resting' })),
|
|
66
|
-
(0, cache_js_1.cached)('balance', REFRESH_BALANCE, () => (0, kalshi_js_1.getBalance)()),
|
|
67
|
-
]);
|
|
68
|
-
// Positions
|
|
69
|
-
if (positionsResult.status === 'fulfilled' && positionsResult.value) {
|
|
70
|
-
const positions = positionsResult.value;
|
|
71
|
-
// Enrich with live prices
|
|
72
|
-
const priceResults = await Promise.allSettled(positions.map(p => (0, cache_js_1.cached)(`price:${p.ticker}`, 10_000, () => (0, kalshi_js_1.getMarketPrice)(p.ticker))));
|
|
73
|
-
for (let i = 0; i < positions.length; i++) {
|
|
74
|
-
const pr = priceResults[i];
|
|
75
|
-
if (pr.status === 'fulfilled' && pr.value != null) {
|
|
76
|
-
positions[i].current_value = pr.value;
|
|
77
|
-
positions[i].unrealized_pnl = Math.round((pr.value - positions[i].average_price_paid) * positions[i].quantity);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
state.positions = positions;
|
|
81
|
-
}
|
|
82
|
-
// Polymarket positions (merge into state.positions with venue tag)
|
|
83
|
-
const config = (0, config_js_1.loadConfig)();
|
|
84
|
-
if (config.polymarketWalletAddress) {
|
|
85
|
-
try {
|
|
86
|
-
const polyPos = await (0, cache_js_1.cached)('poly-positions', REFRESH_POSITIONS, () => (0, polymarket_js_1.polymarketGetPositions)(config.polymarketWalletAddress));
|
|
87
|
-
if (polyPos && Array.isArray(polyPos) && polyPos.length > 0) {
|
|
88
|
-
for (const p of polyPos) {
|
|
89
|
-
state.positions.push({
|
|
90
|
-
ticker: (p.title || p.slug || p.asset || '').slice(0, 25),
|
|
91
|
-
quantity: p.size || 0,
|
|
92
|
-
average_price_paid: Math.round((p.avgPrice || 0) * 100),
|
|
93
|
-
current_value: Math.round((p.curPrice || p.currentPrice || 0) * 100),
|
|
94
|
-
unrealized_pnl: Math.round((p.cashPnl || 0) * 100),
|
|
95
|
-
side: (p.outcome || 'yes').toLowerCase(),
|
|
96
|
-
venue: 'polymarket',
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
// skip
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// Theses
|
|
106
|
-
if (thesesResult.status === 'fulfilled' && thesesResult.value) {
|
|
107
|
-
const raw = thesesResult.value;
|
|
108
|
-
state.theses = raw.theses || raw || [];
|
|
109
|
-
}
|
|
110
|
-
// Orders
|
|
111
|
-
if (ordersResult.status === 'fulfilled' && ordersResult.value) {
|
|
112
|
-
state.orders = ordersResult.value.orders || [];
|
|
113
|
-
}
|
|
114
|
-
// Balance
|
|
115
|
-
if (balanceResult.status === 'fulfilled' && balanceResult.value) {
|
|
116
|
-
state.balance = balanceResult.value.balance ?? 0;
|
|
117
|
-
}
|
|
118
|
-
// Fetch contexts for each thesis (parallel)
|
|
119
|
-
if (client && state.theses.length > 0) {
|
|
120
|
-
const ctxResults = await Promise.allSettled(state.theses.map(t => (0, cache_js_1.cached)(`ctx:${t.id}`, REFRESH_THESES, () => client.getContext(t.id))));
|
|
121
|
-
for (let i = 0; i < state.theses.length; i++) {
|
|
122
|
-
const cr = ctxResults[i];
|
|
123
|
-
if (cr.status === 'fulfilled' && cr.value) {
|
|
124
|
-
state.contexts.set(state.theses[i].id, cr.value);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Compute edges from all contexts
|
|
129
|
-
computeEdges(state);
|
|
130
|
-
// Fetch candlesticks for position tickers
|
|
131
|
-
await loadCandles(state);
|
|
132
|
-
// Fetch feed for signals
|
|
133
|
-
if (client) {
|
|
134
|
-
try {
|
|
135
|
-
const feed = await (0, cache_js_1.cached)('feed', REFRESH_FEED, () => client.getFeed(48));
|
|
136
|
-
const entries = feed.entries || feed.feed || feed;
|
|
137
|
-
if (Array.isArray(entries)) {
|
|
138
|
-
state.signals = entries;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
catch { /* ignore */ }
|
|
142
|
-
}
|
|
143
|
-
// Collect upcoming events from contexts
|
|
144
|
-
collectEvents(state);
|
|
145
|
-
// Check exchange status
|
|
146
|
-
checkExchangeStatus(state);
|
|
147
|
-
state.lastRefresh.all = Date.now();
|
|
148
|
-
}
|
|
149
|
-
function computeEdges(state) {
|
|
150
|
-
const allEdges = [];
|
|
151
|
-
for (const [, ctx] of state.contexts) {
|
|
152
|
-
if (!ctx?.edges)
|
|
153
|
-
continue;
|
|
154
|
-
for (const e of ctx.edges) {
|
|
155
|
-
allEdges.push(e);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Dedup by marketId, keep highest |edge|
|
|
159
|
-
const edgeMap = new Map();
|
|
160
|
-
for (const e of allEdges) {
|
|
161
|
-
const key = e.marketId || e.ticker || e.market;
|
|
162
|
-
const existing = edgeMap.get(key);
|
|
163
|
-
if (!existing || Math.abs(e.edge ?? 0) > Math.abs(existing.edge ?? 0)) {
|
|
164
|
-
edgeMap.set(key, e);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Filter out held positions, sort by |edge|
|
|
168
|
-
const heldTickers = new Set(state.positions.map(p => p.ticker_symbol || p.ticker));
|
|
169
|
-
state.edges = [...edgeMap.values()]
|
|
170
|
-
.filter(e => !heldTickers.has(e.marketId || e.ticker))
|
|
171
|
-
.sort((a, b) => Math.abs(b.edge ?? 0) - Math.abs(a.edge ?? 0));
|
|
172
|
-
}
|
|
173
|
-
async function loadCandles(state) {
|
|
174
|
-
const tickers = state.positions.map(p => p.ticker).filter(Boolean);
|
|
175
|
-
if (tickers.length === 0)
|
|
176
|
-
return;
|
|
177
|
-
try {
|
|
178
|
-
const now = Math.floor(Date.now() / 1000);
|
|
179
|
-
const sevenDaysAgo = now - 7 * 86400;
|
|
180
|
-
const result = await (0, cache_js_1.cached)('candles', REFRESH_CANDLES, () => (0, kalshi_js_1.getBatchCandlesticks)({ tickers, startTs: sevenDaysAgo, endTs: now }));
|
|
181
|
-
for (const item of result) {
|
|
182
|
-
if (item.market_ticker && item.candlesticks) {
|
|
183
|
-
// Parse candles to extract close price in cents
|
|
184
|
-
const parsed = item.candlesticks.map((c) => {
|
|
185
|
-
const bidClose = parseFloat(c.yes_bid?.close_dollars || '0');
|
|
186
|
-
const askClose = parseFloat(c.yes_ask?.close_dollars || '0');
|
|
187
|
-
const mid = bidClose > 0 && askClose > 0 ? (bidClose + askClose) / 2 : bidClose || askClose;
|
|
188
|
-
const closeDollars = parseFloat(c.price?.close_dollars || '0') || mid;
|
|
189
|
-
return {
|
|
190
|
-
close: Math.round(closeDollars * 100), // cents
|
|
191
|
-
date: c.end_period_ts ? new Date(c.end_period_ts * 1000).toISOString().slice(0, 10) : '',
|
|
192
|
-
end_period_ts: c.end_period_ts,
|
|
193
|
-
};
|
|
194
|
-
}).filter((c) => c.close > 0);
|
|
195
|
-
state.candleCache.set(item.market_ticker, parsed);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
catch { /* ignore */ }
|
|
200
|
-
}
|
|
201
|
-
function collectEvents(state) {
|
|
202
|
-
const events = [];
|
|
203
|
-
for (const [, ctx] of state.contexts) {
|
|
204
|
-
if (ctx?.milestones) {
|
|
205
|
-
for (const m of ctx.milestones) {
|
|
206
|
-
events.push(m);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (ctx?.upcomingEvents) {
|
|
210
|
-
for (const e of ctx.upcomingEvents) {
|
|
211
|
-
events.push(e);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
// Sort by timestamp ascending (soonest first)
|
|
216
|
-
events.sort((a, b) => {
|
|
217
|
-
const ta = new Date(a.timestamp || a.date || a.time || 0).getTime();
|
|
218
|
-
const tb = new Date(b.timestamp || b.date || b.time || 0).getTime();
|
|
219
|
-
return ta - tb;
|
|
220
|
-
});
|
|
221
|
-
state.events = events.filter(e => {
|
|
222
|
-
const t = new Date(e.timestamp || e.date || e.time || 0).getTime();
|
|
223
|
-
return t > Date.now();
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
function checkExchangeStatus(state) {
|
|
227
|
-
// Kalshi exchange hours: Mon-Fri, no federal holidays
|
|
228
|
-
// Simplified heuristic
|
|
229
|
-
const now = new Date();
|
|
230
|
-
const day = now.getUTCDay();
|
|
231
|
-
state.exchangeOpen = day >= 1 && day <= 5;
|
|
232
|
-
}
|
|
233
|
-
// ============================================================================
|
|
234
|
-
// RENDERING
|
|
235
|
-
// ============================================================================
|
|
236
|
-
function renderFrame(screen, state) {
|
|
237
|
-
// Clear back buffer (it's already cleared by ScreenBuffer)
|
|
238
|
-
// Status bar (row 0)
|
|
239
|
-
(0, statusbar_js_1.renderStatusBar)(screen, 0, state);
|
|
240
|
-
// Command bar (last row)
|
|
241
|
-
(0, commandbar_js_1.renderCommandBar)(screen, screen.rows - 1, state);
|
|
242
|
-
// Mode-specific content
|
|
243
|
-
switch (state.mode) {
|
|
244
|
-
case 'overview':
|
|
245
|
-
renderOverview(screen, state);
|
|
246
|
-
break;
|
|
247
|
-
case 'detail':
|
|
248
|
-
(0, detail_js_1.renderDetail)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
|
|
249
|
-
break;
|
|
250
|
-
case 'liquidity':
|
|
251
|
-
(0, liquidity_js_1.renderLiquidity)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
|
|
252
|
-
break;
|
|
253
|
-
case 'whatif':
|
|
254
|
-
(0, whatif_js_1.renderWhatif)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
|
|
255
|
-
break;
|
|
256
|
-
case 'trade':
|
|
257
|
-
// Render overview or detail behind the trade overlay
|
|
258
|
-
if (state.prevMode === 'detail') {
|
|
259
|
-
(0, detail_js_1.renderDetail)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
renderOverview(screen, state);
|
|
263
|
-
}
|
|
264
|
-
(0, trade_js_1.renderTrade)(screen, (0, layout_js_1.fullLayout)(screen.cols, screen.rows), state);
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
screen.flush();
|
|
268
|
-
}
|
|
269
|
-
function renderOverview(screen, state) {
|
|
270
|
-
const isNarrow = screen.cols < 80;
|
|
271
|
-
const regions = isNarrow
|
|
272
|
-
? (0, layout_js_1.narrowLayout)(screen.cols, screen.rows)
|
|
273
|
-
: (0, layout_js_1.overviewLayout)(screen.cols, screen.rows);
|
|
274
|
-
// Render vertical divider for wide layout
|
|
275
|
-
if (!isNarrow) {
|
|
276
|
-
const leftWidth = Math.floor(screen.cols * 0.55);
|
|
277
|
-
for (let r = 1; r < screen.rows - 1; r++) {
|
|
278
|
-
screen.write(r, leftWidth, '│', border_js_1.CLR.borderDim);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
for (const region of regions) {
|
|
282
|
-
switch (region.name) {
|
|
283
|
-
case 'positions':
|
|
284
|
-
(0, portfolio_js_1.renderPortfolio)(screen, region, state);
|
|
285
|
-
break;
|
|
286
|
-
case 'thesis':
|
|
287
|
-
(0, thesis_js_1.renderThesis)(screen, region, state);
|
|
288
|
-
break;
|
|
289
|
-
case 'edges':
|
|
290
|
-
(0, edges_js_1.renderEdges)(screen, region, state);
|
|
291
|
-
break;
|
|
292
|
-
case 'upcoming':
|
|
293
|
-
(0, upcoming_js_1.renderUpcoming)(screen, region, state);
|
|
294
|
-
break;
|
|
295
|
-
case 'orders':
|
|
296
|
-
(0, orders_js_1.renderOrders)(screen, region, state);
|
|
297
|
-
break;
|
|
298
|
-
case 'signals':
|
|
299
|
-
(0, signals_js_1.renderSignals)(screen, region, state);
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
// ============================================================================
|
|
305
|
-
// KEYPRESS HANDLING
|
|
306
|
-
// ============================================================================
|
|
307
|
-
function handleKeypress(state, key, screen, scheduleRender, cleanup) {
|
|
308
|
-
const ch = key.toString('utf-8');
|
|
309
|
-
const isEsc = key[0] === 0x1b && key.length === 1;
|
|
310
|
-
const isUp = key[0] === 0x1b && key[1] === 0x5b && key[2] === 0x41;
|
|
311
|
-
const isDown = key[0] === 0x1b && key[1] === 0x5b && key[2] === 0x42;
|
|
312
|
-
const isEnter = ch === '\r' || ch === '\n';
|
|
313
|
-
const isCtrlC = key[0] === 0x03;
|
|
314
|
-
if (isCtrlC || (ch === 'q' && state.mode !== 'trade')) {
|
|
315
|
-
cleanup();
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
state.error = null;
|
|
319
|
-
switch (state.mode) {
|
|
320
|
-
case 'overview':
|
|
321
|
-
handleOverviewKey(ch, isUp, isDown, isEnter, state, screen, scheduleRender, cleanup);
|
|
322
|
-
break;
|
|
323
|
-
case 'detail':
|
|
324
|
-
handleDetailKey(ch, isEsc, state, screen, scheduleRender);
|
|
325
|
-
break;
|
|
326
|
-
case 'liquidity':
|
|
327
|
-
handleLiquidityKey(ch, isUp, isDown, isEsc, state, screen, scheduleRender);
|
|
328
|
-
break;
|
|
329
|
-
case 'whatif':
|
|
330
|
-
handleWhatifKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender);
|
|
331
|
-
break;
|
|
332
|
-
case 'trade':
|
|
333
|
-
handleTradeKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender);
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
function handleOverviewKey(ch, isUp, isDown, isEnter, state, screen, scheduleRender, cleanup) {
|
|
338
|
-
const list = state.focusArea === 'positions' ? state.positions : state.edges;
|
|
339
|
-
const maxIdx = Math.max(0, list.length - 1);
|
|
340
|
-
if (ch === 'j' || isDown) {
|
|
341
|
-
state.selectedIndex = Math.min(state.selectedIndex + 1, maxIdx);
|
|
342
|
-
scheduleRender();
|
|
343
|
-
}
|
|
344
|
-
else if (ch === 'k' || isUp) {
|
|
345
|
-
state.selectedIndex = Math.max(state.selectedIndex - 1, 0);
|
|
346
|
-
scheduleRender();
|
|
347
|
-
}
|
|
348
|
-
else if (ch === '\t') {
|
|
349
|
-
state.focusArea = state.focusArea === 'positions' ? 'edges' : 'positions';
|
|
350
|
-
state.selectedIndex = 0;
|
|
351
|
-
scheduleRender();
|
|
352
|
-
}
|
|
353
|
-
else if (isEnter) {
|
|
354
|
-
if (state.focusArea === 'positions' && state.positions.length > 0) {
|
|
355
|
-
const pos = state.positions[state.selectedIndex];
|
|
356
|
-
state.detailTicker = pos?.ticker || null;
|
|
357
|
-
state.prevMode = 'overview';
|
|
358
|
-
state.mode = 'detail';
|
|
359
|
-
scheduleRender();
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
else if (ch === 'l') {
|
|
363
|
-
state.prevMode = 'overview';
|
|
364
|
-
state.mode = 'liquidity';
|
|
365
|
-
state.liquiditySelectedIndex = 0;
|
|
366
|
-
loadLiquidityData(state).then(scheduleRender);
|
|
367
|
-
}
|
|
368
|
-
else if (ch === 'w') {
|
|
369
|
-
if (state.theses.length > 0) {
|
|
370
|
-
state.prevMode = 'overview';
|
|
371
|
-
state.mode = 'whatif';
|
|
372
|
-
state.whatifThesisId = state.theses[0].id;
|
|
373
|
-
loadWhatifData(state).then(scheduleRender);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else if (ch === 'b') {
|
|
377
|
-
openTrade(state, 'buy');
|
|
378
|
-
scheduleRender();
|
|
379
|
-
}
|
|
380
|
-
else if (ch === 's') {
|
|
381
|
-
openTrade(state, 'sell');
|
|
382
|
-
scheduleRender();
|
|
383
|
-
}
|
|
384
|
-
else if (ch === 'e') {
|
|
385
|
-
triggerEvaluate(state).then(scheduleRender);
|
|
386
|
-
}
|
|
387
|
-
else if (ch === 'r') {
|
|
388
|
-
(0, cache_js_1.invalidateAll)();
|
|
389
|
-
loadAllData(state).then(scheduleRender);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
function handleDetailKey(ch, isEsc, state, screen, scheduleRender) {
|
|
393
|
-
if (isEsc) {
|
|
394
|
-
state.mode = 'overview';
|
|
395
|
-
scheduleRender();
|
|
396
|
-
}
|
|
397
|
-
else if (ch === 'b') {
|
|
398
|
-
openTrade(state, 'buy');
|
|
399
|
-
scheduleRender();
|
|
400
|
-
}
|
|
401
|
-
else if (ch === 's') {
|
|
402
|
-
openTrade(state, 'sell');
|
|
403
|
-
scheduleRender();
|
|
404
|
-
}
|
|
405
|
-
else if (ch === 'w') {
|
|
406
|
-
if (state.theses.length > 0) {
|
|
407
|
-
state.prevMode = 'detail';
|
|
408
|
-
state.mode = 'whatif';
|
|
409
|
-
state.whatifThesisId = state.theses[0].id;
|
|
410
|
-
loadWhatifData(state).then(scheduleRender);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
function handleLiquidityKey(ch, isUp, isDown, isEsc, state, screen, scheduleRender) {
|
|
415
|
-
if (isEsc) {
|
|
416
|
-
state.mode = state.prevMode;
|
|
417
|
-
scheduleRender();
|
|
418
|
-
}
|
|
419
|
-
else if (ch === 'j' || isDown) {
|
|
420
|
-
state.liquiditySelectedIndex++;
|
|
421
|
-
scheduleRender();
|
|
422
|
-
}
|
|
423
|
-
else if (ch === 'k' || isUp) {
|
|
424
|
-
state.liquiditySelectedIndex = Math.max(0, state.liquiditySelectedIndex - 1);
|
|
425
|
-
scheduleRender();
|
|
426
|
-
}
|
|
427
|
-
else if (ch === '\t') {
|
|
428
|
-
// Cycle to next topic
|
|
429
|
-
const topics = Object.keys(topics_js_1.TOPIC_SERIES);
|
|
430
|
-
const idx = topics.indexOf(state.liquidityTopic);
|
|
431
|
-
state.liquidityTopic = topics[(idx + 1) % topics.length];
|
|
432
|
-
state.liquiditySelectedIndex = 0;
|
|
433
|
-
loadLiquidityData(state).then(scheduleRender);
|
|
434
|
-
}
|
|
435
|
-
else if (ch === 'b') {
|
|
436
|
-
openTradeFromLiquidity(state);
|
|
437
|
-
scheduleRender();
|
|
438
|
-
}
|
|
439
|
-
else if (ch === 'r') {
|
|
440
|
-
loadLiquidityData(state).then(scheduleRender);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function handleWhatifKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender) {
|
|
444
|
-
if (isEsc) {
|
|
445
|
-
state.mode = state.prevMode;
|
|
446
|
-
scheduleRender();
|
|
447
|
-
}
|
|
448
|
-
else if (ch === 'j' || isDown) {
|
|
449
|
-
const max = (state.whatifResult?.scenarios?.length || 1) - 1;
|
|
450
|
-
state.whatifScenarioIndex = Math.min(state.whatifScenarioIndex + 1, max);
|
|
451
|
-
scheduleRender();
|
|
452
|
-
}
|
|
453
|
-
else if (ch === 'k' || isUp) {
|
|
454
|
-
state.whatifScenarioIndex = Math.max(0, state.whatifScenarioIndex - 1);
|
|
455
|
-
scheduleRender();
|
|
456
|
-
}
|
|
457
|
-
else if (isEnter) {
|
|
458
|
-
// Apply scenario — could trigger re-evaluation
|
|
459
|
-
scheduleRender();
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
function handleTradeKey(ch, isUp, isDown, isEnter, isEsc, state, screen, scheduleRender) {
|
|
463
|
-
if (isEsc) {
|
|
464
|
-
state.mode = state.prevMode;
|
|
465
|
-
state.tradeParams = null;
|
|
466
|
-
state.tradeCountdown = -1;
|
|
467
|
-
scheduleRender();
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (!state.tradeParams)
|
|
471
|
-
return;
|
|
472
|
-
if (ch === '\t') {
|
|
473
|
-
state.tradeField = state.tradeField === 'qty' ? 'price' : 'qty';
|
|
474
|
-
scheduleRender();
|
|
475
|
-
}
|
|
476
|
-
else if (isUp) {
|
|
477
|
-
if (state.tradeField === 'qty') {
|
|
478
|
-
state.tradeParams.qty = Math.min(state.tradeParams.qty + 1, 9999);
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
state.tradeParams.price = Math.min(state.tradeParams.price + 1, 99);
|
|
482
|
-
}
|
|
483
|
-
scheduleRender();
|
|
484
|
-
}
|
|
485
|
-
else if (isDown) {
|
|
486
|
-
if (state.tradeField === 'qty') {
|
|
487
|
-
state.tradeParams.qty = Math.max(state.tradeParams.qty - 1, 1);
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
state.tradeParams.price = Math.max(state.tradeParams.price - 1, 1);
|
|
491
|
-
}
|
|
492
|
-
scheduleRender();
|
|
493
|
-
}
|
|
494
|
-
else if (isEnter) {
|
|
495
|
-
if (state.tradeCountdown < 0) {
|
|
496
|
-
// Start countdown
|
|
497
|
-
state.tradeCountdown = 3;
|
|
498
|
-
scheduleRender();
|
|
499
|
-
startCountdown(state, screen, scheduleRender);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
function startCountdown(state, screen, scheduleRender) {
|
|
504
|
-
const tick = () => {
|
|
505
|
-
if (state.tradeCountdown <= 0 || state.mode !== 'trade')
|
|
506
|
-
return;
|
|
507
|
-
state.tradeCountdown--;
|
|
508
|
-
scheduleRender();
|
|
509
|
-
if (state.tradeCountdown === 0) {
|
|
510
|
-
// Execute trade
|
|
511
|
-
executeTrade(state, scheduleRender);
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
setTimeout(tick, 1000);
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
setTimeout(tick, 1000);
|
|
518
|
-
}
|
|
519
|
-
// ============================================================================
|
|
520
|
-
// TRADE HELPERS
|
|
521
|
-
// ============================================================================
|
|
522
|
-
function openTrade(state, action) {
|
|
523
|
-
let ticker = '';
|
|
524
|
-
if (state.focusArea === 'positions' && state.positions.length > 0) {
|
|
525
|
-
const pos = state.positions[state.selectedIndex];
|
|
526
|
-
ticker = pos?.ticker_symbol || pos?.ticker || '';
|
|
527
|
-
}
|
|
528
|
-
else if (state.focusArea === 'edges' && state.edges.length > 0) {
|
|
529
|
-
const edge = state.edges[state.selectedIndex];
|
|
530
|
-
ticker = edge?.ticker || edge?.marketId || '';
|
|
531
|
-
}
|
|
532
|
-
else if (state.detailTicker) {
|
|
533
|
-
ticker = state.detailTicker;
|
|
534
|
-
}
|
|
535
|
-
if (!ticker) {
|
|
536
|
-
state.error = 'No market selected';
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
state.prevMode = state.mode;
|
|
540
|
-
state.mode = 'trade';
|
|
541
|
-
state.tradeParams = {
|
|
542
|
-
ticker,
|
|
543
|
-
side: 'yes',
|
|
544
|
-
action,
|
|
545
|
-
qty: 10,
|
|
546
|
-
price: 50,
|
|
547
|
-
};
|
|
548
|
-
state.tradeCountdown = -1;
|
|
549
|
-
state.tradeField = 'qty';
|
|
550
|
-
}
|
|
551
|
-
function openTradeFromLiquidity(state) {
|
|
552
|
-
const markets = state.liquidityData.get(state.liquidityTopic) || [];
|
|
553
|
-
if (markets.length === 0)
|
|
554
|
-
return;
|
|
555
|
-
const sel = markets[Math.min(state.liquiditySelectedIndex, markets.length - 1)];
|
|
556
|
-
if (!sel)
|
|
557
|
-
return;
|
|
558
|
-
state.prevMode = state.mode;
|
|
559
|
-
state.mode = 'trade';
|
|
560
|
-
state.tradeParams = {
|
|
561
|
-
ticker: sel.ticker || '',
|
|
562
|
-
side: 'yes',
|
|
563
|
-
action: 'buy',
|
|
564
|
-
qty: 10,
|
|
565
|
-
price: sel.bestAsk ?? sel.yes_ask ?? 50,
|
|
566
|
-
};
|
|
567
|
-
state.tradeCountdown = -1;
|
|
568
|
-
state.tradeField = 'qty';
|
|
569
|
-
}
|
|
570
|
-
async function executeTrade(state, scheduleRender) {
|
|
571
|
-
if (!state.tradeParams)
|
|
572
|
-
return;
|
|
573
|
-
try {
|
|
574
|
-
const config = (0, config_js_1.loadConfig)();
|
|
575
|
-
if (!config.tradingEnabled) {
|
|
576
|
-
state.error = 'Trading disabled. Run: sf setup --enable-trading';
|
|
577
|
-
state.tradeCountdown = -1;
|
|
578
|
-
scheduleRender();
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
await (0, kalshi_js_1.createOrder)({
|
|
582
|
-
ticker: state.tradeParams.ticker,
|
|
583
|
-
side: state.tradeParams.side,
|
|
584
|
-
action: state.tradeParams.action,
|
|
585
|
-
type: 'limit',
|
|
586
|
-
count: state.tradeParams.qty,
|
|
587
|
-
yes_price: state.tradeParams.price,
|
|
588
|
-
});
|
|
589
|
-
state.error = null;
|
|
590
|
-
state.mode = state.prevMode;
|
|
591
|
-
state.tradeParams = null;
|
|
592
|
-
state.tradeCountdown = -1;
|
|
593
|
-
// Refresh orders
|
|
594
|
-
(0, cache_js_1.invalidateAll)();
|
|
595
|
-
await loadAllData(state);
|
|
596
|
-
}
|
|
597
|
-
catch (err) {
|
|
598
|
-
state.error = err instanceof Error ? err.message.slice(0, 40) : 'Trade failed';
|
|
599
|
-
state.tradeCountdown = -1;
|
|
600
|
-
}
|
|
601
|
-
scheduleRender();
|
|
602
|
-
}
|
|
603
|
-
// ============================================================================
|
|
604
|
-
// SECONDARY DATA LOADING
|
|
605
|
-
// ============================================================================
|
|
606
|
-
async function loadLiquidityData(state) {
|
|
607
|
-
const topic = state.liquidityTopic;
|
|
608
|
-
const seriesList = topics_js_1.TOPIC_SERIES[topic];
|
|
609
|
-
if (!seriesList)
|
|
610
|
-
return;
|
|
611
|
-
try {
|
|
612
|
-
// Phase 1: Fetch market lists in parallel (fast — just metadata)
|
|
613
|
-
const seriesResults = await Promise.allSettled(seriesList.map(series => (0, cache_js_1.cached)(`liq:${series}`, 30_000, async () => {
|
|
614
|
-
const url = `https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${series}&status=open&limit=200`;
|
|
615
|
-
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
616
|
-
if (!res.ok)
|
|
617
|
-
return [];
|
|
618
|
-
const data = await res.json();
|
|
619
|
-
return data.markets || [];
|
|
620
|
-
})));
|
|
621
|
-
const allMarkets = [];
|
|
622
|
-
for (const r of seriesResults) {
|
|
623
|
-
if (r.status === 'fulfilled' && Array.isArray(r.value)) {
|
|
624
|
-
allMarkets.push(...r.value);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
// Show markets immediately (before orderbooks load)
|
|
628
|
-
state.liquidityData.set(topic, [...allMarkets]);
|
|
629
|
-
// Phase 2: Fetch orderbooks in parallel batches of 8
|
|
630
|
-
const BATCH = 8;
|
|
631
|
-
for (let i = 0; i < allMarkets.length; i += BATCH) {
|
|
632
|
-
const batch = allMarkets.slice(i, i + BATCH);
|
|
633
|
-
const obResults = await Promise.allSettled(batch.map(mkt => (0, cache_js_1.cached)(`ob:${mkt.ticker}`, 30_000, () => (0, kalshi_js_1.getPublicOrderbook)(mkt.ticker))
|
|
634
|
-
.then(ob => ({ mkt, ob }))));
|
|
635
|
-
for (const r of obResults) {
|
|
636
|
-
if (r.status !== 'fulfilled' || !r.value.ob)
|
|
637
|
-
continue;
|
|
638
|
-
const { mkt, ob } = r.value;
|
|
639
|
-
const yes = (ob.yes_dollars || []).map((l) => ({
|
|
640
|
-
price: Math.round(parseFloat(l[0]) * 100),
|
|
641
|
-
qty: parseFloat(l[1]),
|
|
642
|
-
})).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
643
|
-
const no = (ob.no_dollars || []).map((l) => ({
|
|
644
|
-
price: Math.round(parseFloat(l[0]) * 100),
|
|
645
|
-
qty: parseFloat(l[1]),
|
|
646
|
-
})).filter((l) => l.price > 0).sort((a, b) => b.price - a.price);
|
|
647
|
-
mkt.bestBid = yes.length > 0 ? yes[0].price : 0;
|
|
648
|
-
mkt.bestAsk = no.length > 0 ? (100 - no[0].price) : 100;
|
|
649
|
-
mkt.spread = mkt.bestAsk - mkt.bestBid;
|
|
650
|
-
mkt.totalDepth = yes.slice(0, 3).reduce((s, l) => s + l.qty, 0)
|
|
651
|
-
+ no.slice(0, 3).reduce((s, l) => s + l.qty, 0);
|
|
652
|
-
}
|
|
653
|
-
// Progressive update: refresh display after each batch
|
|
654
|
-
state.liquidityData.set(topic, [...allMarkets]);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
catch (err) {
|
|
658
|
-
state.error = 'Failed to load liquidity data';
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
async function loadWhatifData(state) {
|
|
662
|
-
const client = getClient();
|
|
663
|
-
if (!client || !state.whatifThesisId)
|
|
664
|
-
return;
|
|
665
|
-
try {
|
|
666
|
-
const ctx = state.contexts.get(state.whatifThesisId);
|
|
667
|
-
if (!ctx)
|
|
668
|
-
return;
|
|
669
|
-
// Build scenario list from causal tree nodes
|
|
670
|
-
const nodes = ctx.causalTree?.nodes || ctx.nodes || [];
|
|
671
|
-
const scenarios = nodes.slice(0, 10).map((n) => ({
|
|
672
|
-
name: n.name || n.title || n.id,
|
|
673
|
-
nodeId: n.id,
|
|
674
|
-
description: `Set ${n.name || n.id} to low probability`,
|
|
675
|
-
}));
|
|
676
|
-
state.whatifResult = {
|
|
677
|
-
scenarios,
|
|
678
|
-
before: {
|
|
679
|
-
confidence: state.theses[0]?.confidence,
|
|
680
|
-
edges: state.edges.slice(0, 5),
|
|
681
|
-
},
|
|
682
|
-
after: null,
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
catch {
|
|
686
|
-
state.error = 'Failed to load what-if data';
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
async function triggerEvaluate(state) {
|
|
690
|
-
const client = getClient();
|
|
691
|
-
if (!client || state.theses.length === 0)
|
|
692
|
-
return;
|
|
693
|
-
try {
|
|
694
|
-
state.error = null;
|
|
695
|
-
const thesis = state.theses[0];
|
|
696
|
-
await client.evaluate(thesis.id);
|
|
697
|
-
// Refresh after evaluation
|
|
698
|
-
(0, cache_js_1.invalidateAll)();
|
|
699
|
-
await loadAllData(state);
|
|
700
|
-
}
|
|
701
|
-
catch (err) {
|
|
702
|
-
state.error = err instanceof Error ? err.message.slice(0, 40) : 'Evaluation failed';
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
// ============================================================================
|
|
706
|
-
// MAIN LOOP
|
|
707
|
-
// ============================================================================
|
|
708
|
-
async function startDashboard() {
|
|
709
|
-
const screen = new screen_js_1.ScreenBuffer();
|
|
710
|
-
const state = (0, state_js_1.initialState)();
|
|
711
|
-
// Enter alternate screen, hide cursor
|
|
712
|
-
process.stdout.write('\x1b[?1049h');
|
|
713
|
-
process.stdout.write('\x1b[?25l');
|
|
714
|
-
// Enable raw mode
|
|
715
|
-
if (process.stdin.isTTY) {
|
|
716
|
-
process.stdin.setRawMode(true);
|
|
717
|
-
}
|
|
718
|
-
process.stdin.resume();
|
|
719
|
-
let renderQueued = false;
|
|
720
|
-
const scheduleRender = () => {
|
|
721
|
-
if (renderQueued)
|
|
722
|
-
return;
|
|
723
|
-
renderQueued = true;
|
|
724
|
-
setImmediate(() => {
|
|
725
|
-
renderQueued = false;
|
|
726
|
-
renderFrame(screen, state);
|
|
727
|
-
});
|
|
728
|
-
};
|
|
729
|
-
// Cleanup function
|
|
730
|
-
const intervals = [];
|
|
731
|
-
let cleaned = false;
|
|
732
|
-
const cleanup = () => {
|
|
733
|
-
if (cleaned)
|
|
734
|
-
return;
|
|
735
|
-
cleaned = true;
|
|
736
|
-
for (const iv of intervals)
|
|
737
|
-
clearInterval(iv);
|
|
738
|
-
// Restore terminal
|
|
739
|
-
process.stdout.write('\x1b[?1049l'); // Exit alternate screen
|
|
740
|
-
process.stdout.write('\x1b[?25h'); // Show cursor
|
|
741
|
-
if (process.stdin.isTTY) {
|
|
742
|
-
process.stdin.setRawMode(false);
|
|
743
|
-
}
|
|
744
|
-
process.stdin.pause();
|
|
745
|
-
process.exit(0);
|
|
746
|
-
};
|
|
747
|
-
// Handle signals
|
|
748
|
-
process.on('SIGINT', cleanup);
|
|
749
|
-
process.on('SIGTERM', cleanup);
|
|
750
|
-
// Load initial data
|
|
751
|
-
try {
|
|
752
|
-
await loadAllData(state);
|
|
753
|
-
}
|
|
754
|
-
catch (err) {
|
|
755
|
-
state.error = err instanceof Error ? err.message.slice(0, 50) : 'Failed to load data';
|
|
756
|
-
}
|
|
757
|
-
// First render
|
|
758
|
-
renderFrame(screen, state);
|
|
759
|
-
// Set up refresh intervals
|
|
760
|
-
intervals.push(setInterval(async () => {
|
|
761
|
-
try {
|
|
762
|
-
const positions = await (0, cache_js_1.cached)('positions', REFRESH_POSITIONS, () => (0, kalshi_js_1.getPositions)());
|
|
763
|
-
if (positions) {
|
|
764
|
-
const priceResults = await Promise.allSettled(positions.map(p => (0, cache_js_1.cached)(`price:${p.ticker}`, 10_000, () => (0, kalshi_js_1.getMarketPrice)(p.ticker))));
|
|
765
|
-
for (let i = 0; i < positions.length; i++) {
|
|
766
|
-
const pr = priceResults[i];
|
|
767
|
-
if (pr.status === 'fulfilled' && pr.value != null) {
|
|
768
|
-
positions[i].current_value = pr.value;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
state.positions = positions;
|
|
772
|
-
computeEdges(state);
|
|
773
|
-
}
|
|
774
|
-
scheduleRender();
|
|
775
|
-
}
|
|
776
|
-
catch { /* ignore */ }
|
|
777
|
-
}, REFRESH_POSITIONS));
|
|
778
|
-
intervals.push(setInterval(async () => {
|
|
779
|
-
try {
|
|
780
|
-
const ordersResult = await (0, cache_js_1.cached)('orders', REFRESH_ORDERS, () => (0, kalshi_js_1.getOrders)({ status: 'resting' }));
|
|
781
|
-
if (ordersResult) {
|
|
782
|
-
state.orders = ordersResult.orders || [];
|
|
783
|
-
}
|
|
784
|
-
scheduleRender();
|
|
785
|
-
}
|
|
786
|
-
catch { /* ignore */ }
|
|
787
|
-
}, REFRESH_ORDERS));
|
|
788
|
-
intervals.push(setInterval(async () => {
|
|
789
|
-
try {
|
|
790
|
-
const balanceResult = await (0, cache_js_1.cached)('balance', REFRESH_BALANCE, () => (0, kalshi_js_1.getBalance)());
|
|
791
|
-
if (balanceResult) {
|
|
792
|
-
state.balance = balanceResult.balance ?? 0;
|
|
793
|
-
}
|
|
794
|
-
scheduleRender();
|
|
795
|
-
}
|
|
796
|
-
catch { /* ignore */ }
|
|
797
|
-
}, REFRESH_BALANCE));
|
|
798
|
-
intervals.push(setInterval(async () => {
|
|
799
|
-
try {
|
|
800
|
-
await loadCandles(state);
|
|
801
|
-
scheduleRender();
|
|
802
|
-
}
|
|
803
|
-
catch { /* ignore */ }
|
|
804
|
-
}, REFRESH_CANDLES));
|
|
805
|
-
// Keypress handler
|
|
806
|
-
process.stdin.on('data', (key) => {
|
|
807
|
-
handleKeypress(state, key, screen, scheduleRender, cleanup);
|
|
808
|
-
});
|
|
809
|
-
// Resize handler
|
|
810
|
-
process.stdout.on('resize', () => {
|
|
811
|
-
screen.resize();
|
|
812
|
-
scheduleRender();
|
|
813
|
-
});
|
|
814
|
-
}
|