@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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Liquidity view — full-screen market liquidity scanner
|
|
4
|
+
*
|
|
5
|
+
* Shows markets grouped by topic and horizon with bid/ask/spread/depth info.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.renderLiquidity = renderLiquidity;
|
|
9
|
+
const border_js_1 = require("../border.js");
|
|
10
|
+
const topics_js_1 = require("../../topics.js");
|
|
11
|
+
const TAB_BG_ACTIVE = (0, border_js_1.bgRgb)(30, 30, 35);
|
|
12
|
+
const TAB_BG = (0, border_js_1.bgRgb)(16, 16, 18);
|
|
13
|
+
function renderLiquidity(screen, region, state) {
|
|
14
|
+
(0, border_js_1.drawBorder)(screen, region, 'LIQUIDITY SCANNER');
|
|
15
|
+
const x = region.col + 2;
|
|
16
|
+
const w = region.width - 4;
|
|
17
|
+
let line = 1;
|
|
18
|
+
// ── Topic tabs ──
|
|
19
|
+
const topics = Object.keys(topics_js_1.TOPIC_SERIES);
|
|
20
|
+
let tabCol = x;
|
|
21
|
+
for (const topic of topics) {
|
|
22
|
+
const isActive = topic === state.liquidityTopic;
|
|
23
|
+
const label = ` ${topic.toUpperCase()} `;
|
|
24
|
+
const fg = isActive ? border_js_1.CLR.emerald : border_js_1.CLR.dim;
|
|
25
|
+
const bg = isActive ? TAB_BG_ACTIVE : TAB_BG;
|
|
26
|
+
screen.write(region.row + line, tabCol, label, fg, bg);
|
|
27
|
+
tabCol += label.length + 1;
|
|
28
|
+
}
|
|
29
|
+
line += 2;
|
|
30
|
+
// ── Header ──
|
|
31
|
+
const tickerW = Math.min(28, Math.floor(w * 0.35));
|
|
32
|
+
const colW = 8;
|
|
33
|
+
screen.writeStyled(region.row + line, x, [
|
|
34
|
+
{ text: (0, border_js_1.fit)('Market', tickerW), fg: border_js_1.CLR.title },
|
|
35
|
+
{ text: (0, border_js_1.fit)('Bid\u00A2', colW, 'right'), fg: border_js_1.CLR.title },
|
|
36
|
+
{ text: (0, border_js_1.fit)('Ask\u00A2', colW, 'right'), fg: border_js_1.CLR.title },
|
|
37
|
+
{ text: (0, border_js_1.fit)('Spread', colW, 'right'), fg: border_js_1.CLR.title },
|
|
38
|
+
{ text: (0, border_js_1.fit)('Depth', colW, 'right'), fg: border_js_1.CLR.title },
|
|
39
|
+
{ text: ' ', fg: border_js_1.CLR.dim },
|
|
40
|
+
]);
|
|
41
|
+
line++;
|
|
42
|
+
// Thin divider
|
|
43
|
+
for (let c = x; c < x + w; c++) {
|
|
44
|
+
screen.write(region.row + line, c, '─', border_js_1.CLR.borderDim);
|
|
45
|
+
}
|
|
46
|
+
line++;
|
|
47
|
+
// ── Market rows ──
|
|
48
|
+
const markets = state.liquidityData.get(state.liquidityTopic) || [];
|
|
49
|
+
const heldTickers = new Set(state.positions.map(p => p.ticker_symbol || p.ticker));
|
|
50
|
+
const maxRows = region.height - line - 3; // reserve space for detail
|
|
51
|
+
// Group by horizon (extract from ticker patterns)
|
|
52
|
+
const groups = groupByHorizon(markets);
|
|
53
|
+
let itemIndex = 0;
|
|
54
|
+
for (const [horizon, items] of groups) {
|
|
55
|
+
if (line >= region.row + region.height - 4)
|
|
56
|
+
break;
|
|
57
|
+
// Horizon header
|
|
58
|
+
screen.write(region.row + line, x, ` ${horizon}`, border_js_1.CLR.title);
|
|
59
|
+
line++;
|
|
60
|
+
for (const mkt of items) {
|
|
61
|
+
if (line >= region.row + region.height - 4)
|
|
62
|
+
break;
|
|
63
|
+
const selected = state.liquiditySelectedIndex === itemIndex;
|
|
64
|
+
const marker = selected ? '\u25B8' : ' ';
|
|
65
|
+
const held = heldTickers.has(mkt.ticker) ? '\u2190 held' : '';
|
|
66
|
+
const ticker = mkt.ticker || '???';
|
|
67
|
+
const bid = mkt.bestBid ?? mkt.yes_bid ?? 0;
|
|
68
|
+
const ask = mkt.bestAsk ?? mkt.yes_ask ?? 0;
|
|
69
|
+
const spread = mkt.spread ?? (ask - bid);
|
|
70
|
+
const depth = mkt.totalDepth ?? mkt.depth ?? 0;
|
|
71
|
+
const spreadColor = spread <= 2 ? border_js_1.CLR.green : spread <= 5 ? border_js_1.CLR.yellow : border_js_1.CLR.red;
|
|
72
|
+
screen.writeStyled(region.row + line, x, [
|
|
73
|
+
{ text: marker + ' ', fg: selected ? border_js_1.CLR.emerald : border_js_1.CLR.dim },
|
|
74
|
+
{ text: (0, border_js_1.fit)(ticker, tickerW - 2), fg: border_js_1.CLR.text },
|
|
75
|
+
{ text: (0, border_js_1.fit)(String(bid), colW, 'right'), fg: border_js_1.CLR.green },
|
|
76
|
+
{ text: (0, border_js_1.fit)(String(ask), colW, 'right'), fg: border_js_1.CLR.red },
|
|
77
|
+
{ text: (0, border_js_1.fit)(String(spread), colW, 'right'), fg: spreadColor },
|
|
78
|
+
{ text: (0, border_js_1.fit)(String(depth), colW, 'right'), fg: border_js_1.CLR.dim },
|
|
79
|
+
{ text: held ? ` ${held}` : '', fg: border_js_1.CLR.emerald },
|
|
80
|
+
]);
|
|
81
|
+
line++;
|
|
82
|
+
itemIndex++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ── Selected market detail (bottom) ──
|
|
86
|
+
if (markets.length > 0) {
|
|
87
|
+
const selIdx = Math.min(state.liquiditySelectedIndex, markets.length - 1);
|
|
88
|
+
const allItems = Array.from(groups.values()).flat();
|
|
89
|
+
const sel = allItems[selIdx] || allItems[0];
|
|
90
|
+
if (sel) {
|
|
91
|
+
const detailRow = region.row + region.height - 3;
|
|
92
|
+
screen.write(detailRow, x, (0, border_js_1.fit)(`\u25B6 ${sel.ticker || ''}`, w), border_js_1.CLR.emerald);
|
|
93
|
+
// Slippage and edge info
|
|
94
|
+
const slippage = sel.slippage != null ? `Slippage: ~${sel.slippage}\u00A2` : '';
|
|
95
|
+
const edge = sel.edge != null ? `Edge: ${sel.edge >= 0 ? '+' : ''}${sel.edge}\u00A2` : '';
|
|
96
|
+
screen.writeStyled(detailRow + 1, x, [
|
|
97
|
+
{ text: slippage ? (0, border_js_1.fit)(slippage, 20) : '', fg: border_js_1.CLR.dim },
|
|
98
|
+
{ text: edge ? (0, border_js_1.fit)(edge, 20) : '', fg: sel.edge >= 0 ? border_js_1.CLR.green : border_js_1.CLR.red },
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function groupByHorizon(markets) {
|
|
104
|
+
const groups = new Map();
|
|
105
|
+
for (const mkt of markets) {
|
|
106
|
+
const ticker = (mkt.ticker || '').toUpperCase();
|
|
107
|
+
let horizon = 'Other';
|
|
108
|
+
if (ticker.includes('-D') || ticker.includes('DAILY')) {
|
|
109
|
+
horizon = 'Daily';
|
|
110
|
+
}
|
|
111
|
+
else if (ticker.includes('-W') || ticker.includes('WEEKLY')) {
|
|
112
|
+
horizon = 'Weekly';
|
|
113
|
+
}
|
|
114
|
+
else if (ticker.includes('-M') || ticker.match(/-\d{2}(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\d{2}/)) {
|
|
115
|
+
horizon = 'Monthly';
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Try to guess from expiry
|
|
119
|
+
const expiry = mkt.expiration_time || mkt.close_time || '';
|
|
120
|
+
if (expiry) {
|
|
121
|
+
const daysOut = Math.ceil((new Date(expiry).getTime() - Date.now()) / 86400000);
|
|
122
|
+
if (daysOut <= 7)
|
|
123
|
+
horizon = 'Weekly';
|
|
124
|
+
else if (daysOut <= 35)
|
|
125
|
+
horizon = 'Monthly';
|
|
126
|
+
else
|
|
127
|
+
horizon = 'Long-term';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!groups.has(horizon))
|
|
131
|
+
groups.set(horizon, []);
|
|
132
|
+
groups.get(horizon).push(mkt);
|
|
133
|
+
}
|
|
134
|
+
// Sort groups by time horizon
|
|
135
|
+
const order = ['Daily', 'Weekly', 'Monthly', 'Long-term', 'Other'];
|
|
136
|
+
const sorted = new Map();
|
|
137
|
+
for (const h of order) {
|
|
138
|
+
if (groups.has(h))
|
|
139
|
+
sorted.set(h, groups.get(h));
|
|
140
|
+
}
|
|
141
|
+
return sorted;
|
|
142
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderOrders = renderOrders;
|
|
4
|
+
const border_js_1 = require("../border.js");
|
|
5
|
+
function renderOrders(screen, region, state) {
|
|
6
|
+
(0, border_js_1.drawBorder)(screen, region, 'ORDERS');
|
|
7
|
+
const x = region.col + 2;
|
|
8
|
+
const w = region.width - 4;
|
|
9
|
+
const maxRows = region.height - 2;
|
|
10
|
+
if (state.orders.length === 0) {
|
|
11
|
+
screen.write(region.row + 1, x, 'No resting orders', border_js_1.CLR.dim);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (let i = 0; i < Math.min(state.orders.length, maxRows); i++) {
|
|
15
|
+
const order = state.orders[i];
|
|
16
|
+
const y = region.row + 1 + i;
|
|
17
|
+
const ticker = (order.ticker || '???').slice(0, 10);
|
|
18
|
+
const qty = Math.round(parseFloat(order.remaining_count_fp || order.initial_count_fp || '0'));
|
|
19
|
+
const priceDollars = parseFloat(order.yes_price_dollars || '0');
|
|
20
|
+
const priceCents = Math.round(priceDollars * 100);
|
|
21
|
+
const status = order.status || 'resting';
|
|
22
|
+
const qtyPrice = `${qty}@${priceCents}¢`;
|
|
23
|
+
// Age in days
|
|
24
|
+
const created = new Date(order.created_time || Date.now()).getTime();
|
|
25
|
+
const ageDays = Math.floor((Date.now() - created) / 86400000);
|
|
26
|
+
const ageStr = ageDays === 0 ? '<1d' : `${ageDays}d`;
|
|
27
|
+
const stale = ageDays > 3;
|
|
28
|
+
const warn = stale ? '⚠ ' : ' ';
|
|
29
|
+
screen.writeStyled(y, x, [
|
|
30
|
+
{ text: warn, fg: stale ? border_js_1.CLR.yellow : border_js_1.CLR.dim },
|
|
31
|
+
{ text: (0, border_js_1.fit)(ticker, 12), fg: border_js_1.CLR.text },
|
|
32
|
+
{ text: (0, border_js_1.fit)(qtyPrice, 10), fg: border_js_1.CLR.dim },
|
|
33
|
+
{ text: (0, border_js_1.fit)(status, 10), fg: status === 'filled' ? border_js_1.CLR.green : border_js_1.CLR.dim },
|
|
34
|
+
{ text: (0, border_js_1.fit)(ageStr, 4, 'right'), fg: stale ? border_js_1.CLR.yellow : border_js_1.CLR.dim },
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderPortfolio = renderPortfolio;
|
|
4
|
+
const border_js_1 = require("../border.js");
|
|
5
|
+
function renderPortfolio(screen, region, state) {
|
|
6
|
+
(0, border_js_1.drawBorder)(screen, region, 'POSITIONS');
|
|
7
|
+
const x = region.col + 2;
|
|
8
|
+
const w = region.width - 4;
|
|
9
|
+
const maxRows = region.height - 2;
|
|
10
|
+
let line = 0;
|
|
11
|
+
if (state.positions.length === 0) {
|
|
12
|
+
screen.write(region.row + 1, x, 'No positions', border_js_1.CLR.dim);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Sort positions by |P&L| descending
|
|
16
|
+
const sorted = [...state.positions].sort((a, b) => {
|
|
17
|
+
const pnlA = Math.abs(((a.current_value || a.average_price_paid || 0) - (a.average_price_paid || 0)) * (a.quantity || 0));
|
|
18
|
+
const pnlB = Math.abs(((b.current_value || b.average_price_paid || 0) - (b.average_price_paid || 0)) * (b.quantity || 0));
|
|
19
|
+
return pnlB - pnlA;
|
|
20
|
+
});
|
|
21
|
+
for (const pos of sorted) {
|
|
22
|
+
if (line + 1 >= maxRows)
|
|
23
|
+
break;
|
|
24
|
+
const y = region.row + 1 + line;
|
|
25
|
+
const idx = sorted.indexOf(pos);
|
|
26
|
+
const selected = state.focusArea === 'positions' && state.selectedIndex === idx;
|
|
27
|
+
const marker = selected ? '▸' : ' ';
|
|
28
|
+
const ticker = pos.ticker || '???';
|
|
29
|
+
const venueTag = pos.venue === 'polymarket' ? 'P ' : 'K ';
|
|
30
|
+
// Line 1: marker + venue + ticker
|
|
31
|
+
screen.write(y, x, (0, border_js_1.fit)(`${marker}${venueTag}${ticker}`, w), selected ? border_js_1.CLR.white : border_js_1.CLR.text);
|
|
32
|
+
// Line 2: qty @ entry → current P&L sparkline
|
|
33
|
+
if (line + 1 < maxRows) {
|
|
34
|
+
const qty = pos.quantity ?? 0;
|
|
35
|
+
const entry = pos.average_price_paid ?? 0; // cents
|
|
36
|
+
const current = pos.current_value ?? entry; // cents
|
|
37
|
+
const pnlCents = (current - entry) * qty; // always compute fresh
|
|
38
|
+
const pnlDollars = pnlCents / 100;
|
|
39
|
+
const pnlStr = (pnlDollars >= 0 ? '+$' : '-$') + Math.abs(pnlDollars).toFixed(2);
|
|
40
|
+
const pnlColor = pnlDollars >= 0 ? border_js_1.CLR.green : border_js_1.CLR.red;
|
|
41
|
+
const detail = ` ${qty} @ ${entry}¢ → ${current}¢ `;
|
|
42
|
+
const segments = [
|
|
43
|
+
{ text: (0, border_js_1.fit)(detail, Math.min(detail.length + 1, w - 18)), fg: border_js_1.CLR.dim },
|
|
44
|
+
{ text: (0, border_js_1.fit)(pnlStr, 10), fg: pnlColor },
|
|
45
|
+
];
|
|
46
|
+
// Sparkline from candle cache
|
|
47
|
+
const candles = state.candleCache.get(ticker);
|
|
48
|
+
if (candles && candles.length > 1) {
|
|
49
|
+
const pnlVals = candles.slice(-7).map((c) => {
|
|
50
|
+
const close = c.close ?? 0;
|
|
51
|
+
return (close - entry) * qty;
|
|
52
|
+
});
|
|
53
|
+
segments.push(...(0, border_js_1.sparkline)(pnlVals, v => v >= 0 ? border_js_1.CLR.green : border_js_1.CLR.red));
|
|
54
|
+
}
|
|
55
|
+
screen.writeStyled(y + 1, x, segments);
|
|
56
|
+
}
|
|
57
|
+
line += 2;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderSignals = renderSignals;
|
|
4
|
+
const border_js_1 = require("../border.js");
|
|
5
|
+
function renderSignals(screen, region, state) {
|
|
6
|
+
(0, border_js_1.drawBorder)(screen, region, 'SIGNALS');
|
|
7
|
+
const x = region.col + 2;
|
|
8
|
+
const w = region.width - 4;
|
|
9
|
+
const maxRows = region.height - 2;
|
|
10
|
+
// Filter signals with |delta| >= 1%, most recent first
|
|
11
|
+
const filtered = state.signals
|
|
12
|
+
.filter((s) => Math.abs(s.delta ?? s.change ?? 0) >= 0.01)
|
|
13
|
+
.sort((a, b) => {
|
|
14
|
+
const ta = new Date(a.timestamp || a.time || 0).getTime();
|
|
15
|
+
const tb = new Date(b.timestamp || b.time || 0).getTime();
|
|
16
|
+
return tb - ta;
|
|
17
|
+
});
|
|
18
|
+
for (let i = 0; i < Math.min(filtered.length, maxRows); i++) {
|
|
19
|
+
const sig = filtered[i];
|
|
20
|
+
const y = region.row + 1 + i;
|
|
21
|
+
const delta = sig.delta ?? sig.change ?? 0;
|
|
22
|
+
const up = delta >= 0;
|
|
23
|
+
const arrow = up ? '▲' : '▼';
|
|
24
|
+
const color = up ? border_js_1.CLR.green : border_js_1.CLR.red;
|
|
25
|
+
const summary = sig.summary || sig.title || sig.description || '???';
|
|
26
|
+
screen.writeStyled(y, x, [
|
|
27
|
+
{ text: arrow + ' ', fg: color },
|
|
28
|
+
{ text: (0, border_js_1.fit)(summary, w - 2), fg: border_js_1.CLR.text },
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status bar — top row of the dashboard
|
|
3
|
+
*
|
|
4
|
+
* Shows: P&L (colored) | Cash balance | Active thesis + confidence | UTC time
|
|
5
|
+
*/
|
|
6
|
+
import type { ScreenBuffer } from '../screen.js';
|
|
7
|
+
import type { DashboardState } from '../state.js';
|
|
8
|
+
export declare function renderStatusBar(screen: ScreenBuffer, row: number, state: DashboardState): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Status bar — top row of the dashboard
|
|
4
|
+
*
|
|
5
|
+
* Shows: P&L (colored) | Cash balance | Active thesis + confidence | UTC time
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.renderStatusBar = renderStatusBar;
|
|
9
|
+
const border_js_1 = require("../border.js");
|
|
10
|
+
const STATUS_BG = (0, border_js_1.bgRgb)(22, 22, 26);
|
|
11
|
+
function renderStatusBar(screen, row, state) {
|
|
12
|
+
const w = screen.cols;
|
|
13
|
+
// Fill background
|
|
14
|
+
screen.fill(row, 0, w, 1, ' ', '', STATUS_BG);
|
|
15
|
+
// Compute total P&L from positions
|
|
16
|
+
let totalPnl = 0;
|
|
17
|
+
let totalCost = 0;
|
|
18
|
+
for (const pos of state.positions) {
|
|
19
|
+
const entry = pos.average_price_paid ?? pos.cost_basis ?? 0;
|
|
20
|
+
const current = pos.current_value ?? entry;
|
|
21
|
+
const qty = pos.quantity ?? 0;
|
|
22
|
+
totalPnl += (current - entry) * qty;
|
|
23
|
+
totalCost += entry * qty;
|
|
24
|
+
}
|
|
25
|
+
const pnlDollars = totalPnl / 100;
|
|
26
|
+
const pctReturn = totalCost > 0 ? (totalPnl / totalCost) * 100 : 0;
|
|
27
|
+
const pnlSign = pnlDollars >= 0 ? '+' : '';
|
|
28
|
+
const pnlStr = `P&L ${pnlSign}$${Math.abs(pnlDollars).toFixed(2)}`;
|
|
29
|
+
const pctStr = `(${pnlSign}${pctReturn.toFixed(1)}%)`;
|
|
30
|
+
const pnlColor = pnlDollars >= 0 ? border_js_1.CLR.green : border_js_1.CLR.red;
|
|
31
|
+
// Cash balance
|
|
32
|
+
const cashStr = `Cash $${state.balance.toFixed(2)}`;
|
|
33
|
+
// Active thesis
|
|
34
|
+
let thesisStr = '';
|
|
35
|
+
if (state.theses.length > 0) {
|
|
36
|
+
const t = state.theses[0];
|
|
37
|
+
const title = (t.title || t.short_title || 'Thesis').slice(0, 20);
|
|
38
|
+
const conf = t.confidence != null ? `${Math.round((t.confidence ?? 0) * 100)}%` : '?%';
|
|
39
|
+
thesisStr = `${title} ${conf}`;
|
|
40
|
+
}
|
|
41
|
+
// UTC time
|
|
42
|
+
const now = new Date();
|
|
43
|
+
const timeStr = now.toISOString().slice(11, 19) + ' UTC';
|
|
44
|
+
// Layout: left-aligned items separated by │
|
|
45
|
+
let col = 1;
|
|
46
|
+
screen.write(row, col, pnlStr, pnlColor, STATUS_BG);
|
|
47
|
+
col += pnlStr.length + 1;
|
|
48
|
+
screen.write(row, col, pctStr, pnlColor, STATUS_BG);
|
|
49
|
+
col += pctStr.length + 1;
|
|
50
|
+
screen.write(row, col, '│', border_js_1.CLR.dim, STATUS_BG);
|
|
51
|
+
col += 2;
|
|
52
|
+
screen.write(row, col, cashStr, border_js_1.CLR.text, STATUS_BG);
|
|
53
|
+
col += cashStr.length + 1;
|
|
54
|
+
if (thesisStr) {
|
|
55
|
+
screen.write(row, col, '│', border_js_1.CLR.dim, STATUS_BG);
|
|
56
|
+
col += 2;
|
|
57
|
+
screen.write(row, col, thesisStr, border_js_1.CLR.title, STATUS_BG);
|
|
58
|
+
col += thesisStr.length + 1;
|
|
59
|
+
}
|
|
60
|
+
// Exchange status
|
|
61
|
+
if (state.exchangeOpen !== null) {
|
|
62
|
+
screen.write(row, col, '│', border_js_1.CLR.dim, STATUS_BG);
|
|
63
|
+
col += 2;
|
|
64
|
+
const exStr = state.exchangeOpen ? 'OPEN' : 'CLOSED';
|
|
65
|
+
const exColor = state.exchangeOpen ? border_js_1.CLR.green : border_js_1.CLR.red;
|
|
66
|
+
screen.write(row, col, exStr, exColor, STATUS_BG);
|
|
67
|
+
col += exStr.length + 1;
|
|
68
|
+
}
|
|
69
|
+
// Time — right-aligned
|
|
70
|
+
const timeCol = w - timeStr.length - 1;
|
|
71
|
+
screen.write(row, timeCol, timeStr, border_js_1.CLR.dim, STATUS_BG);
|
|
72
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderThesis = renderThesis;
|
|
4
|
+
const border_js_1 = require("../border.js");
|
|
5
|
+
function renderThesis(screen, region, state) {
|
|
6
|
+
(0, border_js_1.drawBorder)(screen, region, 'THESIS');
|
|
7
|
+
const x = region.col + 2;
|
|
8
|
+
const w = region.width - 4;
|
|
9
|
+
const maxRows = region.height - 2;
|
|
10
|
+
let line = 0;
|
|
11
|
+
for (const thesis of state.theses) {
|
|
12
|
+
if (line >= maxRows - 2)
|
|
13
|
+
break; // reserve 2 lines for summary
|
|
14
|
+
const y = region.row + 1 + line;
|
|
15
|
+
const title = thesis.title || thesis.short_title || '???';
|
|
16
|
+
const confidence = thesis.confidence ?? thesis.current_confidence ?? 0;
|
|
17
|
+
const confStr = `${Math.round(confidence * 100)}%`;
|
|
18
|
+
// Trend from lastEvaluation
|
|
19
|
+
const delta = thesis.lastEvaluation?.confidenceDelta ?? thesis.confidence_delta ?? 0;
|
|
20
|
+
let arrow, arrowColor, deltaStr;
|
|
21
|
+
if (delta >= 0.02) {
|
|
22
|
+
arrow = '▲';
|
|
23
|
+
arrowColor = border_js_1.CLR.green;
|
|
24
|
+
deltaStr = `+${Math.round(delta * 100)}`;
|
|
25
|
+
}
|
|
26
|
+
else if (delta <= -0.02) {
|
|
27
|
+
arrow = '▼';
|
|
28
|
+
arrowColor = border_js_1.CLR.red;
|
|
29
|
+
deltaStr = `${Math.round(delta * 100)}`;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
arrow = '─';
|
|
33
|
+
arrowColor = border_js_1.CLR.dim;
|
|
34
|
+
deltaStr = delta >= 0 ? `+${Math.round(delta * 100)}` : `${Math.round(delta * 100)}`;
|
|
35
|
+
}
|
|
36
|
+
const titleW = w - 10;
|
|
37
|
+
screen.writeStyled(y, x, [
|
|
38
|
+
{ text: (0, border_js_1.fit)(title, titleW), fg: border_js_1.CLR.text },
|
|
39
|
+
{ text: (0, border_js_1.fit)(confStr, 5, 'right'), fg: border_js_1.CLR.white },
|
|
40
|
+
{ text: ` ${arrow}`, fg: arrowColor },
|
|
41
|
+
{ text: (0, border_js_1.fit)(deltaStr, 3, 'right'), fg: arrowColor },
|
|
42
|
+
]);
|
|
43
|
+
line++;
|
|
44
|
+
}
|
|
45
|
+
// Bottom: latest evaluation summary
|
|
46
|
+
if (state.theses.length > 0 && line < maxRows) {
|
|
47
|
+
const first = state.theses[0];
|
|
48
|
+
const ctx = state.contexts.get(first.id || first.thesis_id);
|
|
49
|
+
const evalSummary = ctx?.lastEvaluation?.summary
|
|
50
|
+
|| first.lastEvaluation?.summary || '';
|
|
51
|
+
if (evalSummary) {
|
|
52
|
+
const y = region.row + 1 + maxRows - 1;
|
|
53
|
+
const evalTime = ctx?.lastEvaluation?.timestamp || first.lastEvaluation?.timestamp;
|
|
54
|
+
let ago = '';
|
|
55
|
+
if (evalTime) {
|
|
56
|
+
const hours = Math.floor((Date.now() - new Date(evalTime).getTime()) / 3600000);
|
|
57
|
+
ago = hours < 1 ? '<1h ago' : `${hours}h ago`;
|
|
58
|
+
}
|
|
59
|
+
const summaryW = w - ago.length - 1;
|
|
60
|
+
screen.writeStyled(y, x, [
|
|
61
|
+
{ text: (0, border_js_1.fit)(evalSummary, summaryW), fg: border_js_1.CLR.dim },
|
|
62
|
+
{ text: ' ' + ago, fg: border_js_1.CLR.veryDim },
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trade overlay — centered modal for order entry
|
|
3
|
+
*
|
|
4
|
+
* Displays ticker, side, bid/ask, quantity/price fields, preview, and countdown.
|
|
5
|
+
*/
|
|
6
|
+
import type { ScreenBuffer } from '../screen.js';
|
|
7
|
+
import type { Region } from '../layout.js';
|
|
8
|
+
import type { DashboardState } from '../state.js';
|
|
9
|
+
export declare function renderTrade(screen: ScreenBuffer, region: Region, state: DashboardState): void;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Trade overlay — centered modal for order entry
|
|
4
|
+
*
|
|
5
|
+
* Displays ticker, side, bid/ask, quantity/price fields, preview, and countdown.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.renderTrade = renderTrade;
|
|
9
|
+
const border_js_1 = require("../border.js");
|
|
10
|
+
const OVERLAY_BG = (0, border_js_1.bgRgb)(18, 18, 22);
|
|
11
|
+
const FIELD_BG = (0, border_js_1.bgRgb)(30, 30, 35);
|
|
12
|
+
const ACTIVE_BG = (0, border_js_1.bgRgb)(16, 50, 40);
|
|
13
|
+
function renderTrade(screen, region, state) {
|
|
14
|
+
if (!state.tradeParams)
|
|
15
|
+
return;
|
|
16
|
+
const params = state.tradeParams;
|
|
17
|
+
// Centered overlay box
|
|
18
|
+
const boxW = Math.min(50, region.width - 4);
|
|
19
|
+
const boxH = Math.min(18, region.height - 2);
|
|
20
|
+
const boxCol = region.col + Math.floor((region.width - boxW) / 2);
|
|
21
|
+
const boxRow = region.row + Math.floor((region.height - boxH) / 2);
|
|
22
|
+
const boxRegion = { name: 'trade', row: boxRow, col: boxCol, width: boxW, height: boxH };
|
|
23
|
+
// Fill overlay background
|
|
24
|
+
screen.fill(boxRow, boxCol, boxW, boxH, ' ', '', OVERLAY_BG);
|
|
25
|
+
(0, border_js_1.drawBorder)(screen, boxRegion, 'TRADE');
|
|
26
|
+
const x = boxCol + 2;
|
|
27
|
+
const w = boxW - 4;
|
|
28
|
+
let line = 1;
|
|
29
|
+
// Ticker and side
|
|
30
|
+
const sideColor = params.action === 'buy' ? border_js_1.CLR.green : border_js_1.CLR.red;
|
|
31
|
+
const sideLabel = params.action === 'buy' ? 'BUY' : 'SELL';
|
|
32
|
+
screen.writeStyled(boxRow + line, x, [
|
|
33
|
+
{ text: sideLabel + ' ', fg: sideColor },
|
|
34
|
+
{ text: params.side.toUpperCase() + ' ', fg: border_js_1.CLR.emerald },
|
|
35
|
+
{ text: params.ticker, fg: border_js_1.CLR.white },
|
|
36
|
+
]);
|
|
37
|
+
line += 2;
|
|
38
|
+
// Current market
|
|
39
|
+
// Try to find market price from position data or edges
|
|
40
|
+
const pos = state.positions.find(p => (p.ticker_symbol || p.ticker) === params.ticker);
|
|
41
|
+
const bid = pos?.bestBid ?? pos?.current_value ?? 0;
|
|
42
|
+
const ask = pos?.bestAsk ?? (bid + 2);
|
|
43
|
+
screen.writeStyled(boxRow + line, x, [
|
|
44
|
+
{ text: 'Bid: ', fg: border_js_1.CLR.dim },
|
|
45
|
+
{ text: `${bid}\u00A2`, fg: border_js_1.CLR.green },
|
|
46
|
+
{ text: ' Ask: ', fg: border_js_1.CLR.dim },
|
|
47
|
+
{ text: `${ask}\u00A2`, fg: border_js_1.CLR.red },
|
|
48
|
+
]);
|
|
49
|
+
line += 2;
|
|
50
|
+
// Quantity field
|
|
51
|
+
const qtyActive = state.tradeField === 'qty';
|
|
52
|
+
const qtyBg = qtyActive ? ACTIVE_BG : FIELD_BG;
|
|
53
|
+
screen.write(boxRow + line, x, 'Quantity:', border_js_1.CLR.dim, OVERLAY_BG);
|
|
54
|
+
screen.write(boxRow + line, x + 12, (0, border_js_1.fit)(String(params.qty), 10), qtyActive ? border_js_1.CLR.emerald : border_js_1.CLR.text, qtyBg);
|
|
55
|
+
if (qtyActive)
|
|
56
|
+
screen.write(boxRow + line, x + 22, ' \u25C0', border_js_1.CLR.emerald, OVERLAY_BG);
|
|
57
|
+
line++;
|
|
58
|
+
// Price field
|
|
59
|
+
const priceActive = state.tradeField === 'price';
|
|
60
|
+
const priceBg = priceActive ? ACTIVE_BG : FIELD_BG;
|
|
61
|
+
screen.write(boxRow + line, x, 'Price (\u00A2):', border_js_1.CLR.dim, OVERLAY_BG);
|
|
62
|
+
screen.write(boxRow + line, x + 12, (0, border_js_1.fit)(String(params.price), 10), priceActive ? border_js_1.CLR.emerald : border_js_1.CLR.text, priceBg);
|
|
63
|
+
if (priceActive)
|
|
64
|
+
screen.write(boxRow + line, x + 22, ' \u25C0', border_js_1.CLR.emerald, OVERLAY_BG);
|
|
65
|
+
line += 2;
|
|
66
|
+
// Preview
|
|
67
|
+
const maxCost = (params.qty * params.price) / 100;
|
|
68
|
+
const maxPayout = params.qty; // $1 per contract
|
|
69
|
+
const profit = maxPayout - maxCost;
|
|
70
|
+
screen.write(boxRow + line, x, 'Preview:', border_js_1.CLR.title, OVERLAY_BG);
|
|
71
|
+
line++;
|
|
72
|
+
screen.writeStyled(boxRow + line, x, [
|
|
73
|
+
{ text: `Max cost: $${maxCost.toFixed(2)}`, fg: border_js_1.CLR.dim },
|
|
74
|
+
]);
|
|
75
|
+
line++;
|
|
76
|
+
screen.writeStyled(boxRow + line, x, [
|
|
77
|
+
{ text: `Max payout: $${maxPayout.toFixed(2)}`, fg: border_js_1.CLR.dim },
|
|
78
|
+
]);
|
|
79
|
+
line++;
|
|
80
|
+
screen.writeStyled(boxRow + line, x, [
|
|
81
|
+
{ text: 'Profit: ', fg: border_js_1.CLR.dim },
|
|
82
|
+
{ text: `$${profit.toFixed(2)}`, fg: profit >= 0 ? border_js_1.CLR.green : border_js_1.CLR.red },
|
|
83
|
+
]);
|
|
84
|
+
line++;
|
|
85
|
+
// Thesis edge (if available)
|
|
86
|
+
const edgeInfo = state.edges.find(e => (e.ticker || e.marketId) === params.ticker);
|
|
87
|
+
if (edgeInfo) {
|
|
88
|
+
const edge = edgeInfo.edge ?? edgeInfo.value ?? 0;
|
|
89
|
+
screen.writeStyled(boxRow + line, x, [
|
|
90
|
+
{ text: 'Edge: ', fg: border_js_1.CLR.dim },
|
|
91
|
+
{ text: `${edge >= 0 ? '+' : ''}${edge}\u00A2`, fg: edge >= 0 ? border_js_1.CLR.green : border_js_1.CLR.red },
|
|
92
|
+
]);
|
|
93
|
+
line++;
|
|
94
|
+
}
|
|
95
|
+
// Slippage estimate
|
|
96
|
+
const slippage = Math.max(0, params.price - bid);
|
|
97
|
+
if (slippage > 0) {
|
|
98
|
+
screen.writeStyled(boxRow + line, x, [
|
|
99
|
+
{ text: 'Slippage: ', fg: border_js_1.CLR.dim },
|
|
100
|
+
{ text: `~${slippage}\u00A2`, fg: border_js_1.CLR.yellow },
|
|
101
|
+
]);
|
|
102
|
+
line++;
|
|
103
|
+
}
|
|
104
|
+
line++;
|
|
105
|
+
// Countdown or action prompt
|
|
106
|
+
if (state.tradeCountdown > 0) {
|
|
107
|
+
const countStr = ` Executing in ${state.tradeCountdown}... `;
|
|
108
|
+
const countCol = x + Math.floor((w - countStr.length) / 2);
|
|
109
|
+
screen.write(boxRow + line, countCol, countStr, border_js_1.CLR.yellow, OVERLAY_BG);
|
|
110
|
+
}
|
|
111
|
+
else if (state.tradeCountdown === 0) {
|
|
112
|
+
screen.write(boxRow + line, x + Math.floor((w - 12) / 2), ' EXECUTING ', border_js_1.CLR.white, (0, border_js_1.bgRgb)(16, 120, 80));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
screen.write(boxRow + line, x + Math.floor((w - 24) / 2), 'Press Enter to confirm', border_js_1.CLR.dim, OVERLAY_BG);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderUpcoming = renderUpcoming;
|
|
4
|
+
const border_js_1 = require("../border.js");
|
|
5
|
+
function formatTimeDistance(ms) {
|
|
6
|
+
const hours = ms / 3600000;
|
|
7
|
+
if (hours < 1)
|
|
8
|
+
return '<1h';
|
|
9
|
+
if (hours < 24)
|
|
10
|
+
return `${Math.round(hours)}h`;
|
|
11
|
+
const days = Math.round(hours / 24);
|
|
12
|
+
return `${days}d`;
|
|
13
|
+
}
|
|
14
|
+
function renderUpcoming(screen, region, state) {
|
|
15
|
+
(0, border_js_1.drawBorder)(screen, region, 'UPCOMING');
|
|
16
|
+
const x = region.col + 2;
|
|
17
|
+
const w = region.width - 4;
|
|
18
|
+
const maxRows = region.height - 2;
|
|
19
|
+
const heldTickers = new Set(state.positions.map(p => p.ticker_symbol || p.ticker));
|
|
20
|
+
for (let i = 0; i < Math.min(state.events.length, maxRows); i++) {
|
|
21
|
+
const ev = state.events[i];
|
|
22
|
+
const y = region.row + 1 + i;
|
|
23
|
+
const eventTime = new Date(ev.timestamp || ev.date || ev.time).getTime();
|
|
24
|
+
const diff = eventTime - Date.now();
|
|
25
|
+
const dist = formatTimeDistance(Math.abs(diff));
|
|
26
|
+
const title = ev.title || ev.name || ev.summary || '???';
|
|
27
|
+
// Check if event affects a held position
|
|
28
|
+
const affectedTicker = ev.ticker_symbol || ev.ticker || '';
|
|
29
|
+
const affectsHeld = heldTickers.has(affectedTicker);
|
|
30
|
+
const isUrgent = affectsHeld && Math.abs(diff) < 86400000; // 24h
|
|
31
|
+
const warn = isUrgent ? '⚠ ' : ' ';
|
|
32
|
+
const tickerTag = affectsHeld ? ` [${affectedTicker}]` : '';
|
|
33
|
+
const titleW = w - 6 - tickerTag.length - warn.length;
|
|
34
|
+
screen.writeStyled(y, x, [
|
|
35
|
+
{ text: warn, fg: isUrgent ? border_js_1.CLR.yellow : border_js_1.CLR.dim },
|
|
36
|
+
{ text: (0, border_js_1.fit)(dist, 4, 'right'), fg: border_js_1.CLR.dim },
|
|
37
|
+
{ text: ' ' + (0, border_js_1.fit)(title, titleW), fg: border_js_1.CLR.text },
|
|
38
|
+
{ text: tickerTag, fg: border_js_1.CLR.emerald },
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What-if view — scenario analysis with BEFORE/AFTER comparison
|
|
3
|
+
*/
|
|
4
|
+
import type { ScreenBuffer } from '../screen.js';
|
|
5
|
+
import type { Region } from '../layout.js';
|
|
6
|
+
import type { DashboardState } from '../state.js';
|
|
7
|
+
export declare function renderWhatif(screen: ScreenBuffer, region: Region, state: DashboardState): void;
|