@spfunctions/cli 1.4.1 → 1.4.3
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
CHANGED
|
@@ -671,6 +671,8 @@ async function agentCommand(thesisId, opts) {
|
|
|
671
671
|
const matched = series
|
|
672
672
|
.filter((s) => keywords.every((kw) => (s.title || '').toLowerCase().includes(kw) ||
|
|
673
673
|
(s.ticker || '').toLowerCase().includes(kw)))
|
|
674
|
+
.filter((s) => parseFloat(s.volume_fp || '0') > 1000)
|
|
675
|
+
.sort((a, b) => parseFloat(b.volume_fp || '0') - parseFloat(a.volume_fp || '0'))
|
|
674
676
|
.slice(0, 15);
|
|
675
677
|
result = matched;
|
|
676
678
|
}
|
|
@@ -2187,7 +2189,7 @@ async function runPlainTextAgent(params) {
|
|
|
2187
2189
|
else if (p.query) {
|
|
2188
2190
|
const series = await (0, client_js_1.kalshiFetchAllSeries)();
|
|
2189
2191
|
const kws = p.query.toLowerCase().split(/\s+/);
|
|
2190
|
-
result = series.filter((s) => kws.every((k) => ((s.title || '') + (s.ticker || '')).toLowerCase().includes(k))).slice(0, 15);
|
|
2192
|
+
result = series.filter((s) => kws.every((k) => ((s.title || '') + (s.ticker || '')).toLowerCase().includes(k))).filter((s) => parseFloat(s.volume_fp || '0') > 1000).sort((a, b) => parseFloat(b.volume_fp || '0') - parseFloat(a.volume_fp || '0')).slice(0, 15);
|
|
2191
2193
|
}
|
|
2192
2194
|
else {
|
|
2193
2195
|
result = { error: 'Provide query, series, or market' };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sf performance — Portfolio P&L over time with thesis event annotations
|
|
3
|
+
*
|
|
4
|
+
* Layout: each row is a position, with inline sparkline and current P&L.
|
|
5
|
+
* Scales from 7 days to months — sparkline adapts to available data.
|
|
3
6
|
*/
|
|
4
7
|
export declare function performanceCommand(opts: {
|
|
5
8
|
ticker?: string;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* sf performance — Portfolio P&L over time with thesis event annotations
|
|
4
|
+
*
|
|
5
|
+
* Layout: each row is a position, with inline sparkline and current P&L.
|
|
6
|
+
* Scales from 7 days to months — sparkline adapts to available data.
|
|
4
7
|
*/
|
|
5
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
9
|
exports.performanceCommand = performanceCommand;
|
|
@@ -8,27 +11,34 @@ const client_js_1 = require("../client.js");
|
|
|
8
11
|
const kalshi_js_1 = require("../kalshi.js");
|
|
9
12
|
const config_js_1 = require("../config.js");
|
|
10
13
|
const utils_js_1 = require("../utils.js");
|
|
11
|
-
/** Abbreviate ticker: KXWTIMAX-26DEC31-T135 -> T135 */
|
|
12
|
-
function abbrevTicker(ticker) {
|
|
13
|
-
const parts = ticker.split('-');
|
|
14
|
-
return parts[parts.length - 1] || ticker;
|
|
15
|
-
}
|
|
16
|
-
/** Format date as "Mar 01" */
|
|
17
14
|
function fmtDate(d) {
|
|
18
15
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
19
16
|
return `${months[d.getMonth()]} ${String(d.getDate()).padStart(2, '0')}`;
|
|
20
17
|
}
|
|
21
|
-
/** Format dollar amount: positive -> +$12.30, negative -> -$12.30 */
|
|
22
18
|
function fmtDollar(cents) {
|
|
23
19
|
const abs = Math.abs(cents / 100);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return `-$${abs.toFixed(abs >= 100 ? 1 : 2)}`;
|
|
20
|
+
const str = abs >= 1000 ? `${(abs / 1000).toFixed(1)}k` : abs >= 100 ? abs.toFixed(0) : abs.toFixed(2);
|
|
21
|
+
return cents >= 0 ? `+$${str}` : `-$${str}`;
|
|
27
22
|
}
|
|
28
|
-
/** Date string key YYYY-MM-DD from a Date */
|
|
29
23
|
function dateKey(d) {
|
|
30
24
|
return d.toISOString().slice(0, 10);
|
|
31
25
|
}
|
|
26
|
+
/** Build a sparkline string from an array of values */
|
|
27
|
+
function sparkline(values, colorFn) {
|
|
28
|
+
if (values.length === 0)
|
|
29
|
+
return '';
|
|
30
|
+
const min = Math.min(...values);
|
|
31
|
+
const max = Math.max(...values);
|
|
32
|
+
const range = max - min || 1;
|
|
33
|
+
const blocks = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
|
34
|
+
return values.map(v => {
|
|
35
|
+
const idx = Math.round(((v - min) / range) * (blocks.length - 1));
|
|
36
|
+
const ch = blocks[idx];
|
|
37
|
+
if (colorFn)
|
|
38
|
+
return colorFn(v) + ch + utils_js_1.c.reset;
|
|
39
|
+
return ch;
|
|
40
|
+
}).join('');
|
|
41
|
+
}
|
|
32
42
|
async function performanceCommand(opts) {
|
|
33
43
|
if (!(0, kalshi_js_1.isKalshiConfigured)()) {
|
|
34
44
|
console.log(`${utils_js_1.c.yellow}Kalshi not configured.${utils_js_1.c.reset} Run ${utils_js_1.c.cyan}sf setup --kalshi${utils_js_1.c.reset} first.`);
|
|
@@ -40,34 +50,23 @@ async function performanceCommand(opts) {
|
|
|
40
50
|
console.log(`${utils_js_1.c.dim}No fills found.${utils_js_1.c.reset}`);
|
|
41
51
|
return;
|
|
42
52
|
}
|
|
43
|
-
const fills = fillsResult.fills;
|
|
44
53
|
const tickerMap = new Map();
|
|
45
|
-
for (const fill of fills) {
|
|
54
|
+
for (const fill of fillsResult.fills) {
|
|
46
55
|
const ticker = fill.ticker || fill.market_ticker || '';
|
|
47
56
|
if (!ticker)
|
|
48
57
|
continue;
|
|
49
|
-
const side = fill.side || 'yes';
|
|
50
58
|
const action = fill.action || 'buy';
|
|
51
59
|
const count = Math.round(parseFloat(fill.count_fp || fill.count || '0'));
|
|
52
|
-
const yesPrice = Math.round(parseFloat(fill.yes_price_dollars || '0') * 100);
|
|
53
|
-
// Determine direction: buy yes = +count, sell yes = -count
|
|
60
|
+
const yesPrice = Math.round(parseFloat(fill.yes_price_dollars || '0') * 100);
|
|
54
61
|
let delta = count;
|
|
55
62
|
if (action === 'sell')
|
|
56
63
|
delta = -count;
|
|
57
|
-
const info = tickerMap.get(ticker) || {
|
|
58
|
-
ticker,
|
|
59
|
-
netQty: 0,
|
|
60
|
-
totalCostCents: 0,
|
|
61
|
-
totalContracts: 0,
|
|
62
|
-
earliestFillTs: Infinity,
|
|
63
|
-
};
|
|
64
|
+
const info = tickerMap.get(ticker) || { ticker, netQty: 0, totalCostCents: 0, totalContracts: 0, earliestFillTs: Infinity };
|
|
64
65
|
info.netQty += delta;
|
|
65
66
|
if (delta > 0) {
|
|
66
|
-
// Buying: accumulate cost
|
|
67
67
|
info.totalCostCents += yesPrice * count;
|
|
68
68
|
info.totalContracts += count;
|
|
69
69
|
}
|
|
70
|
-
// Track earliest fill
|
|
71
70
|
const fillTime = fill.created_time || fill.ts || fill.created_at;
|
|
72
71
|
if (fillTime) {
|
|
73
72
|
const ts = Math.floor(new Date(fillTime).getTime() / 1000);
|
|
@@ -76,9 +75,7 @@ async function performanceCommand(opts) {
|
|
|
76
75
|
}
|
|
77
76
|
tickerMap.set(ticker, info);
|
|
78
77
|
}
|
|
79
|
-
// 3. Filter out fully closed positions (net qty = 0)
|
|
80
78
|
let tickers = [...tickerMap.values()].filter(t => t.netQty !== 0);
|
|
81
|
-
// 4. Apply --ticker fuzzy filter
|
|
82
79
|
if (opts.ticker) {
|
|
83
80
|
const needle = opts.ticker.toLowerCase();
|
|
84
81
|
tickers = tickers.filter(t => t.ticker.toLowerCase().includes(needle));
|
|
@@ -87,179 +84,160 @@ async function performanceCommand(opts) {
|
|
|
87
84
|
console.log(`${utils_js_1.c.dim}No open positions found${opts.ticker ? ` matching "${opts.ticker}"` : ''}.${utils_js_1.c.reset}`);
|
|
88
85
|
return;
|
|
89
86
|
}
|
|
90
|
-
//
|
|
87
|
+
// 3. Fetch candlesticks
|
|
91
88
|
const sinceTs = opts.since
|
|
92
89
|
? Math.floor(new Date(opts.since).getTime() / 1000)
|
|
93
90
|
: Math.min(...tickers.map(t => t.earliestFillTs === Infinity ? Math.floor(Date.now() / 1000) - 30 * 86400 : t.earliestFillTs));
|
|
94
91
|
const nowTs = Math.floor(Date.now() / 1000);
|
|
95
|
-
// 5. Fetch candlesticks
|
|
96
92
|
const candleData = await (0, kalshi_js_1.getBatchCandlesticks)({
|
|
97
93
|
tickers: tickers.map(t => t.ticker),
|
|
98
94
|
startTs: sinceTs,
|
|
99
95
|
endTs: nowTs,
|
|
100
96
|
periodInterval: 1440,
|
|
101
97
|
});
|
|
102
|
-
// Build candlestick lookup: ticker ->
|
|
98
|
+
// Build candlestick lookup: ticker -> sorted [{ date, closeCents }]
|
|
103
99
|
const candleMap = new Map();
|
|
104
100
|
for (const mc of candleData) {
|
|
105
|
-
const
|
|
101
|
+
const entries = [];
|
|
106
102
|
for (const candle of (mc.candlesticks || [])) {
|
|
107
|
-
// close_dollars is a string like "0.4800"
|
|
108
|
-
// price object may be empty; use midpoint of yes_bid.close and yes_ask.close
|
|
109
103
|
const bidClose = parseFloat(candle.yes_bid?.close_dollars || '0');
|
|
110
104
|
const askClose = parseFloat(candle.yes_ask?.close_dollars || '0');
|
|
111
105
|
const mid = bidClose > 0 && askClose > 0 ? (bidClose + askClose) / 2 : bidClose || askClose;
|
|
112
106
|
const closeDollars = parseFloat(candle.price?.close_dollars || '0') || mid;
|
|
113
107
|
const closeCents = Math.round(closeDollars * 100);
|
|
114
108
|
const ts = candle.end_period_ts || candle.period_end_ts || candle.ts;
|
|
115
|
-
if (ts)
|
|
116
|
-
|
|
117
|
-
priceByDate.set(dateKey(d), closeCents);
|
|
118
|
-
}
|
|
109
|
+
if (ts)
|
|
110
|
+
entries.push({ date: dateKey(new Date(ts * 1000)), close: closeCents });
|
|
119
111
|
}
|
|
120
|
-
|
|
112
|
+
entries.sort((a, b) => a.date.localeCompare(b.date));
|
|
113
|
+
candleMap.set(mc.market_ticker, entries);
|
|
121
114
|
}
|
|
122
|
-
//
|
|
123
|
-
// Collect all unique dates across all tickers
|
|
115
|
+
// Collect all dates for total P&L
|
|
124
116
|
const allDates = new Set();
|
|
125
|
-
for (const [,
|
|
126
|
-
for (const
|
|
127
|
-
allDates.add(
|
|
128
|
-
}
|
|
129
|
-
}
|
|
117
|
+
for (const [, entries] of candleMap)
|
|
118
|
+
for (const e of entries)
|
|
119
|
+
allDates.add(e.date);
|
|
130
120
|
const sortedDates = [...allDates].sort();
|
|
131
|
-
//
|
|
121
|
+
// Entry prices
|
|
132
122
|
const entryPrices = new Map();
|
|
133
123
|
for (const t of tickers) {
|
|
134
|
-
|
|
135
|
-
entryPrices.set(t.ticker, avgEntry);
|
|
136
|
-
}
|
|
137
|
-
const dailyRows = [];
|
|
138
|
-
for (const dk of sortedDates) {
|
|
139
|
-
const pnlByTicker = new Map();
|
|
140
|
-
let total = 0;
|
|
141
|
-
for (const t of tickers) {
|
|
142
|
-
const prices = candleMap.get(t.ticker);
|
|
143
|
-
const closePrice = prices?.get(dk);
|
|
144
|
-
if (closePrice !== undefined) {
|
|
145
|
-
const entry = entryPrices.get(t.ticker) || 0;
|
|
146
|
-
const pnl = (closePrice - entry) * t.netQty;
|
|
147
|
-
pnlByTicker.set(t.ticker, pnl);
|
|
148
|
-
total += pnl;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
dailyRows.push({ date: dk, pnlByTicker, total });
|
|
124
|
+
entryPrices.set(t.ticker, t.totalContracts > 0 ? Math.round(t.totalCostCents / t.totalContracts) : 0);
|
|
152
125
|
}
|
|
153
126
|
const events = [];
|
|
154
127
|
try {
|
|
155
128
|
const config = (0, config_js_1.loadConfig)();
|
|
156
129
|
const client = new client_js_1.SFClient(config.apiKey, config.apiUrl);
|
|
157
130
|
const feedData = await client.getFeed(720);
|
|
158
|
-
const feedItems = feedData?.
|
|
131
|
+
const feedItems = feedData?.feed || feedData?.items || feedData || [];
|
|
159
132
|
if (Array.isArray(feedItems)) {
|
|
160
133
|
for (const item of feedItems) {
|
|
161
|
-
const confDelta = item.
|
|
162
|
-
if (Math.abs(confDelta)
|
|
163
|
-
const itemDate = item.
|
|
134
|
+
const confDelta = item.delta ?? item.confidenceDelta ?? 0;
|
|
135
|
+
if (Math.abs(confDelta) >= 0.02) {
|
|
136
|
+
const itemDate = item.evaluatedAt || item.createdAt || item.timestamp || '';
|
|
164
137
|
if (itemDate) {
|
|
165
138
|
events.push({
|
|
166
139
|
date: dateKey(new Date(itemDate)),
|
|
167
140
|
direction: confDelta > 0 ? 'up' : 'down',
|
|
168
141
|
deltaPct: Math.round(confDelta * 100),
|
|
169
|
-
summary: item.summary ||
|
|
142
|
+
summary: item.summary || '',
|
|
170
143
|
});
|
|
171
144
|
}
|
|
172
145
|
}
|
|
173
146
|
}
|
|
174
147
|
}
|
|
175
148
|
}
|
|
176
|
-
catch {
|
|
177
|
-
|
|
149
|
+
catch { /* feed unavailable */ }
|
|
150
|
+
const perfs = [];
|
|
151
|
+
for (const t of tickers) {
|
|
152
|
+
const entry = entryPrices.get(t.ticker) || 0;
|
|
153
|
+
const entries = candleMap.get(t.ticker) || [];
|
|
154
|
+
const current = entries.length > 0 ? entries[entries.length - 1].close : entry;
|
|
155
|
+
const pnlCents = (current - entry) * t.netQty;
|
|
156
|
+
const costBasis = entry * t.netQty;
|
|
157
|
+
const pnlPct = costBasis !== 0 ? (pnlCents / Math.abs(costBasis)) * 100 : 0;
|
|
158
|
+
const dailyPnl = entries.map(e => (e.close - entry) * t.netQty);
|
|
159
|
+
perfs.push({ ticker: t.ticker, qty: t.netQty, entry, current, pnlCents, pnlPct, dailyPnl });
|
|
178
160
|
}
|
|
179
|
-
//
|
|
161
|
+
// Sort by absolute P&L descending
|
|
162
|
+
perfs.sort((a, b) => Math.abs(b.pnlCents) - Math.abs(a.pnlCents));
|
|
163
|
+
// Total daily P&L
|
|
164
|
+
const totalDailyPnl = sortedDates.map(dk => {
|
|
165
|
+
let total = 0;
|
|
166
|
+
for (const t of tickers) {
|
|
167
|
+
const entries = candleMap.get(t.ticker) || [];
|
|
168
|
+
const entry = entryPrices.get(t.ticker) || 0;
|
|
169
|
+
const dayEntry = entries.find(e => e.date === dk);
|
|
170
|
+
if (dayEntry)
|
|
171
|
+
total += (dayEntry.close - entry) * t.netQty;
|
|
172
|
+
}
|
|
173
|
+
return total;
|
|
174
|
+
});
|
|
175
|
+
// Summary
|
|
180
176
|
const totalCostCents = tickers.reduce((sum, t) => sum + t.totalCostCents, 0);
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
const pnlPct = totalCostCents > 0 ? (currentPnlCents / totalCostCents) * 100 : 0;
|
|
185
|
-
// 8. Output
|
|
177
|
+
const totalPnlCents = perfs.reduce((sum, p) => sum + p.pnlCents, 0);
|
|
178
|
+
const totalPnlPct = totalCostCents > 0 ? (totalPnlCents / totalCostCents) * 100 : 0;
|
|
179
|
+
// 6. JSON output
|
|
186
180
|
if (opts.json) {
|
|
187
181
|
console.log(JSON.stringify({
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return { date: row.date, tickers: tickerPnl, total: row.total };
|
|
194
|
-
}),
|
|
182
|
+
positions: perfs.map(p => ({
|
|
183
|
+
ticker: p.ticker, qty: p.qty, entry: p.entry, current: p.current,
|
|
184
|
+
pnl: p.pnlCents, pnlPct: Math.round(p.pnlPct * 10) / 10,
|
|
185
|
+
})),
|
|
186
|
+
totalDailyPnl: sortedDates.map((d, i) => ({ date: d, pnl: totalDailyPnl[i] })),
|
|
195
187
|
events,
|
|
196
|
-
summary: {
|
|
197
|
-
cost: totalCostCents,
|
|
198
|
-
current: currentValueCents,
|
|
199
|
-
pnl: currentPnlCents,
|
|
200
|
-
pnlPct: Math.round(pnlPct * 10) / 10,
|
|
201
|
-
},
|
|
188
|
+
summary: { cost: totalCostCents, pnl: totalPnlCents, pnlPct: Math.round(totalPnlPct * 10) / 10 },
|
|
202
189
|
}, null, 2));
|
|
203
190
|
return;
|
|
204
191
|
}
|
|
205
|
-
// Formatted output
|
|
192
|
+
// 7. Formatted output — rows are positions
|
|
206
193
|
const startDate = sortedDates.length > 0 ? fmtDate(new Date(sortedDates[0])) : '?';
|
|
207
194
|
const endDate = fmtDate(new Date());
|
|
208
195
|
console.log();
|
|
209
|
-
console.log(` ${utils_js_1.c.bold}Portfolio Performance${utils_js_1.c.reset} ${utils_js_1.c.dim}(${startDate}
|
|
210
|
-
console.log(` ${utils_js_1.c.dim}${'─'.repeat(
|
|
196
|
+
console.log(` ${utils_js_1.c.bold}Portfolio Performance${utils_js_1.c.reset} ${utils_js_1.c.dim}(${startDate} → ${endDate})${utils_js_1.c.reset}`);
|
|
197
|
+
console.log(` ${utils_js_1.c.dim}${'─'.repeat(76)}${utils_js_1.c.reset}`);
|
|
211
198
|
console.log();
|
|
212
|
-
//
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
for (const row of rowsToShow) {
|
|
228
|
-
const d = new Date(row.date);
|
|
229
|
-
const dateStr = fmtDate(d).padEnd(12);
|
|
230
|
-
const cols = tickers.map(t => {
|
|
231
|
-
const pnl = row.pnlByTicker.get(t.ticker);
|
|
232
|
-
if (pnl === undefined)
|
|
233
|
-
return (0, utils_js_1.rpad)('--', colWidth);
|
|
234
|
-
const str = fmtDollar(pnl);
|
|
235
|
-
const color = pnl > 0 ? utils_js_1.c.green : pnl < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
|
|
236
|
-
return color + (0, utils_js_1.rpad)(str, colWidth) + utils_js_1.c.reset;
|
|
237
|
-
}).join('');
|
|
238
|
-
const totalStr = fmtDollar(row.total);
|
|
239
|
-
const totalColor = row.total > 0 ? utils_js_1.c.green : row.total < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
|
|
240
|
-
console.log(` ${dateStr}${cols}${totalColor}${(0, utils_js_1.rpad)(totalStr, colWidth)}${utils_js_1.c.reset}`);
|
|
199
|
+
// Header
|
|
200
|
+
const maxTickerLen = Math.max(...perfs.map(p => p.ticker.length), 5) + 2;
|
|
201
|
+
const w = maxTickerLen + 50;
|
|
202
|
+
const pad2 = (s, n) => s.padEnd(n);
|
|
203
|
+
console.log(` ${utils_js_1.c.dim}${pad2('Ticker', maxTickerLen)} Qty Entry Now P&L Trend${utils_js_1.c.reset}`);
|
|
204
|
+
for (const p of perfs) {
|
|
205
|
+
const pnlStr = fmtDollar(p.pnlCents);
|
|
206
|
+
const pnlColor = p.pnlCents > 0 ? utils_js_1.c.green : p.pnlCents < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
|
|
207
|
+
const spark = sparkline(p.dailyPnl, v => v >= 0 ? utils_js_1.c.green : utils_js_1.c.red);
|
|
208
|
+
console.log(` ${pad2(p.ticker, maxTickerLen)} ` +
|
|
209
|
+
`${(0, utils_js_1.rpad)(String(p.qty), 8)}` +
|
|
210
|
+
`${(0, utils_js_1.rpad)(p.entry + '¢', 7)}` +
|
|
211
|
+
`${(0, utils_js_1.rpad)(p.current + '¢', 7)}` +
|
|
212
|
+
`${pnlColor}${(0, utils_js_1.rpad)(pnlStr, 13)}${utils_js_1.c.reset}` +
|
|
213
|
+
spark);
|
|
241
214
|
}
|
|
215
|
+
// Total row
|
|
216
|
+
console.log(` ${utils_js_1.c.dim}${'─'.repeat(w)}${utils_js_1.c.reset}`);
|
|
217
|
+
const totalPnlStr = fmtDollar(totalPnlCents);
|
|
218
|
+
const totalPctStr = `${totalPnlPct >= 0 ? '+' : ''}${totalPnlPct.toFixed(1)}%`;
|
|
219
|
+
const totalColor = totalPnlCents >= 0 ? utils_js_1.c.green : utils_js_1.c.red;
|
|
220
|
+
const totalSpark = sparkline(totalDailyPnl, v => v >= 0 ? utils_js_1.c.green : utils_js_1.c.red);
|
|
221
|
+
console.log(` ${utils_js_1.c.bold}${pad2('TOTAL', maxTickerLen)}${utils_js_1.c.reset} ` +
|
|
222
|
+
`${(0, utils_js_1.rpad)('', 22)}` +
|
|
223
|
+
`${totalColor}${utils_js_1.c.bold}${(0, utils_js_1.rpad)(`${totalPnlStr} (${totalPctStr})`, 13)}${utils_js_1.c.reset}` +
|
|
224
|
+
totalSpark);
|
|
242
225
|
// Events
|
|
243
226
|
if (events.length > 0) {
|
|
244
|
-
console.log();
|
|
245
|
-
// Match events to date range
|
|
246
227
|
const dateSet = new Set(sortedDates);
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
228
|
+
const relevant = events.filter(e => dateSet.has(e.date));
|
|
229
|
+
if (relevant.length > 0) {
|
|
230
|
+
console.log();
|
|
231
|
+
for (const ev of relevant.slice(0, 8)) {
|
|
232
|
+
const arrow = ev.direction === 'up' ? `${utils_js_1.c.green}▲${utils_js_1.c.reset}` : `${utils_js_1.c.red}▼${utils_js_1.c.reset}`;
|
|
233
|
+
const summary = ev.summary.length > 55 ? ev.summary.slice(0, 54) + '…' : ev.summary;
|
|
234
|
+
console.log(` ${arrow} ${utils_js_1.c.dim}${fmtDate(new Date(ev.date))}${utils_js_1.c.reset} ${ev.deltaPct > 0 ? '+' : ''}${ev.deltaPct}% → ${summary}`);
|
|
235
|
+
}
|
|
254
236
|
}
|
|
255
237
|
}
|
|
256
|
-
// Summary
|
|
238
|
+
// Summary
|
|
257
239
|
console.log();
|
|
258
240
|
const costStr = `$${(totalCostCents / 100).toFixed(0)}`;
|
|
259
|
-
|
|
260
|
-
const pnlStr = fmtDollar(currentPnlCents);
|
|
261
|
-
const pnlPctStr = `${pnlPct >= 0 ? '+' : ''}${pnlPct.toFixed(1)}%`;
|
|
262
|
-
const pnlColor = currentPnlCents >= 0 ? utils_js_1.c.green : utils_js_1.c.red;
|
|
263
|
-
console.log(` ${utils_js_1.c.dim}Cost:${utils_js_1.c.reset} ${costStr} ${utils_js_1.c.dim}| Current:${utils_js_1.c.reset} ${currentStr} ${utils_js_1.c.dim}| P&L:${utils_js_1.c.reset} ${pnlColor}${pnlStr} (${pnlPctStr})${utils_js_1.c.reset}`);
|
|
241
|
+
console.log(` ${utils_js_1.c.dim}Cost basis:${utils_js_1.c.reset} ${costStr} ${utils_js_1.c.dim}|${utils_js_1.c.reset} ${totalColor}${utils_js_1.c.bold}${totalPnlStr} (${totalPctStr})${utils_js_1.c.reset}`);
|
|
264
242
|
console.log();
|
|
265
243
|
}
|
package/dist/commands/scan.js
CHANGED
|
@@ -104,14 +104,16 @@ async function keywordScan(query, json) {
|
|
|
104
104
|
score += 3;
|
|
105
105
|
else if (v > 100_000)
|
|
106
106
|
score += 1;
|
|
107
|
-
if (score > 0)
|
|
108
|
-
matches.push({ series: s, score });
|
|
107
|
+
if (score > 0 && v > 1000)
|
|
108
|
+
matches.push({ series: s, score, volume: v });
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
// Sort by score first, then by volume as tiebreaker
|
|
111
|
+
matches.sort((a, b) => b.score - a.score || b.volume - a.volume);
|
|
111
112
|
const topSeries = matches.slice(0, 15);
|
|
112
113
|
console.log(`\n${utils_js_1.c.bold}Found ${matches.length} relevant series. Top ${topSeries.length}:${utils_js_1.c.reset}\n`);
|
|
113
|
-
for (const { series: s,
|
|
114
|
-
|
|
114
|
+
for (const { series: s, volume } of topSeries) {
|
|
115
|
+
const volStr = volume >= 1_000_000 ? `$${(volume / 1_000_000).toFixed(1)}M` : volume >= 1000 ? `$${(volume / 1000).toFixed(0)}k` : `$${volume.toFixed(0)}`;
|
|
116
|
+
console.log(` ${(0, utils_js_1.rpad)(volStr, 10)} ${(0, utils_js_1.pad)(s.ticker, 25)} ${s.title}`);
|
|
115
117
|
}
|
|
116
118
|
// Fetch live markets for top 10
|
|
117
119
|
console.log(`\n${utils_js_1.c.dim}Fetching live markets...${utils_js_1.c.reset}\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfunctions/cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "Prediction market intelligence CLI. Causal thesis model, 24/7 Kalshi/Polymarket scan, live orderbook, edge detection. Interactive agent mode with tool calling.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sf": "./dist/index.js"
|