@spfunctions/cli 1.4.2 → 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.
@@ -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,53 +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 to something readable:
12
- * KXWTIMAX-26DEC31-T135 → Oil 135
13
- * KXRECSSNBER-26 → Recsn
14
- * KXAAAGASM-26MAR31-4.40 → Gas 4.40
15
- * KXINXY-26DEC31H1600-T4000 → S&P 4000
16
- */
17
- function abbrevTicker(ticker) {
18
- // Short topic names for performance table
19
- const SHORT_TOPICS = {
20
- KXWTIMAX: 'Oil', KXWTI: 'Oil', KXRECSSNBER: 'Recsn',
21
- KXAAAGASM: 'Gas', KXCPI: 'CPI', KXINXY: 'S&P',
22
- KXFEDDECISION: 'Fed', KXUNEMPLOYMENT: 'Unemp', KXCLOSEHORMUZ: 'Hormuz',
23
- };
24
- const sorted = Object.keys(SHORT_TOPICS).sort((a, b) => b.length - a.length);
25
- let topic = '';
26
- for (const prefix of sorted) {
27
- if (ticker.startsWith(prefix)) {
28
- topic = SHORT_TOPICS[prefix];
29
- break;
30
- }
31
- }
32
- // Extract the meaningful suffix (strike/level)
33
- const parts = ticker.split('-');
34
- const last = parts[parts.length - 1] || '';
35
- const suffix = last.startsWith('T') ? last.slice(1) : '';
36
- if (topic && suffix)
37
- return `${topic} ${suffix}`;
38
- if (topic)
39
- return topic;
40
- return last || ticker.slice(0, 8);
41
- }
42
- /** Format date as "Mar 01" */
43
14
  function fmtDate(d) {
44
15
  const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
45
16
  return `${months[d.getMonth()]} ${String(d.getDate()).padStart(2, '0')}`;
46
17
  }
47
- /** Format dollar amount: positive -> +$12.30, negative -> -$12.30 */
48
18
  function fmtDollar(cents) {
49
19
  const abs = Math.abs(cents / 100);
50
- if (cents >= 0)
51
- return `+$${abs.toFixed(cents === 0 ? 2 : abs >= 100 ? 1 : 2)}`;
52
- 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}`;
53
22
  }
54
- /** Date string key YYYY-MM-DD from a Date */
55
23
  function dateKey(d) {
56
24
  return d.toISOString().slice(0, 10);
57
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
+ }
58
42
  async function performanceCommand(opts) {
59
43
  if (!(0, kalshi_js_1.isKalshiConfigured)()) {
60
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.`);
@@ -66,34 +50,23 @@ async function performanceCommand(opts) {
66
50
  console.log(`${utils_js_1.c.dim}No fills found.${utils_js_1.c.reset}`);
67
51
  return;
68
52
  }
69
- const fills = fillsResult.fills;
70
53
  const tickerMap = new Map();
71
- for (const fill of fills) {
54
+ for (const fill of fillsResult.fills) {
72
55
  const ticker = fill.ticker || fill.market_ticker || '';
73
56
  if (!ticker)
74
57
  continue;
75
- const side = fill.side || 'yes';
76
58
  const action = fill.action || 'buy';
77
59
  const count = Math.round(parseFloat(fill.count_fp || fill.count || '0'));
78
- const yesPrice = Math.round(parseFloat(fill.yes_price_dollars || '0') * 100); // dollars string → cents int
79
- // Determine direction: buy yes = +count, sell yes = -count
60
+ const yesPrice = Math.round(parseFloat(fill.yes_price_dollars || '0') * 100);
80
61
  let delta = count;
81
62
  if (action === 'sell')
82
63
  delta = -count;
83
- const info = tickerMap.get(ticker) || {
84
- ticker,
85
- netQty: 0,
86
- totalCostCents: 0,
87
- totalContracts: 0,
88
- earliestFillTs: Infinity,
89
- };
64
+ const info = tickerMap.get(ticker) || { ticker, netQty: 0, totalCostCents: 0, totalContracts: 0, earliestFillTs: Infinity };
90
65
  info.netQty += delta;
91
66
  if (delta > 0) {
92
- // Buying: accumulate cost
93
67
  info.totalCostCents += yesPrice * count;
94
68
  info.totalContracts += count;
95
69
  }
96
- // Track earliest fill
97
70
  const fillTime = fill.created_time || fill.ts || fill.created_at;
98
71
  if (fillTime) {
99
72
  const ts = Math.floor(new Date(fillTime).getTime() / 1000);
@@ -102,9 +75,7 @@ async function performanceCommand(opts) {
102
75
  }
103
76
  tickerMap.set(ticker, info);
104
77
  }
105
- // 3. Filter out fully closed positions (net qty = 0)
106
78
  let tickers = [...tickerMap.values()].filter(t => t.netQty !== 0);
107
- // 4. Apply --ticker fuzzy filter
108
79
  if (opts.ticker) {
109
80
  const needle = opts.ticker.toLowerCase();
110
81
  tickers = tickers.filter(t => t.ticker.toLowerCase().includes(needle));
@@ -113,194 +84,160 @@ async function performanceCommand(opts) {
113
84
  console.log(`${utils_js_1.c.dim}No open positions found${opts.ticker ? ` matching "${opts.ticker}"` : ''}.${utils_js_1.c.reset}`);
114
85
  return;
115
86
  }
116
- // Determine date range
87
+ // 3. Fetch candlesticks
117
88
  const sinceTs = opts.since
118
89
  ? Math.floor(new Date(opts.since).getTime() / 1000)
119
90
  : Math.min(...tickers.map(t => t.earliestFillTs === Infinity ? Math.floor(Date.now() / 1000) - 30 * 86400 : t.earliestFillTs));
120
91
  const nowTs = Math.floor(Date.now() / 1000);
121
- // 5. Fetch candlesticks
122
92
  const candleData = await (0, kalshi_js_1.getBatchCandlesticks)({
123
93
  tickers: tickers.map(t => t.ticker),
124
94
  startTs: sinceTs,
125
95
  endTs: nowTs,
126
96
  periodInterval: 1440,
127
97
  });
128
- // Build candlestick lookup: ticker -> dateKey -> close price (cents)
98
+ // Build candlestick lookup: ticker -> sorted [{ date, closeCents }]
129
99
  const candleMap = new Map();
130
100
  for (const mc of candleData) {
131
- const priceByDate = new Map();
101
+ const entries = [];
132
102
  for (const candle of (mc.candlesticks || [])) {
133
- // close_dollars is a string like "0.4800"
134
- // price object may be empty; use midpoint of yes_bid.close and yes_ask.close
135
103
  const bidClose = parseFloat(candle.yes_bid?.close_dollars || '0');
136
104
  const askClose = parseFloat(candle.yes_ask?.close_dollars || '0');
137
105
  const mid = bidClose > 0 && askClose > 0 ? (bidClose + askClose) / 2 : bidClose || askClose;
138
106
  const closeDollars = parseFloat(candle.price?.close_dollars || '0') || mid;
139
107
  const closeCents = Math.round(closeDollars * 100);
140
108
  const ts = candle.end_period_ts || candle.period_end_ts || candle.ts;
141
- if (ts) {
142
- const d = new Date(ts * 1000);
143
- priceByDate.set(dateKey(d), closeCents);
144
- }
109
+ if (ts)
110
+ entries.push({ date: dateKey(new Date(ts * 1000)), close: closeCents });
145
111
  }
146
- candleMap.set(mc.market_ticker, priceByDate);
112
+ entries.sort((a, b) => a.date.localeCompare(b.date));
113
+ candleMap.set(mc.market_ticker, entries);
147
114
  }
148
- // 6. Build daily P&L matrix
149
- // Collect all unique dates across all tickers
115
+ // Collect all dates for total P&L
150
116
  const allDates = new Set();
151
- for (const [, priceByDate] of candleMap) {
152
- for (const dk of priceByDate.keys()) {
153
- allDates.add(dk);
154
- }
155
- }
117
+ for (const [, entries] of candleMap)
118
+ for (const e of entries)
119
+ allDates.add(e.date);
156
120
  const sortedDates = [...allDates].sort();
157
- // For each ticker compute entry price
121
+ // Entry prices
158
122
  const entryPrices = new Map();
159
123
  for (const t of tickers) {
160
- const avgEntry = t.totalContracts > 0 ? Math.round(t.totalCostCents / t.totalContracts) : 0;
161
- entryPrices.set(t.ticker, avgEntry);
162
- }
163
- const dailyRows = [];
164
- for (const dk of sortedDates) {
165
- const pnlByTicker = new Map();
166
- let total = 0;
167
- for (const t of tickers) {
168
- const prices = candleMap.get(t.ticker);
169
- const closePrice = prices?.get(dk);
170
- if (closePrice !== undefined) {
171
- const entry = entryPrices.get(t.ticker) || 0;
172
- const pnl = (closePrice - entry) * t.netQty;
173
- pnlByTicker.set(t.ticker, pnl);
174
- total += pnl;
175
- }
176
- }
177
- dailyRows.push({ date: dk, pnlByTicker, total });
124
+ entryPrices.set(t.ticker, t.totalContracts > 0 ? Math.round(t.totalCostCents / t.totalContracts) : 0);
178
125
  }
179
126
  const events = [];
180
127
  try {
181
128
  const config = (0, config_js_1.loadConfig)();
182
129
  const client = new client_js_1.SFClient(config.apiKey, config.apiUrl);
183
130
  const feedData = await client.getFeed(720);
184
- const feedItems = feedData?.feed || feedData?.items || feedData?.events || feedData || [];
131
+ const feedItems = feedData?.feed || feedData?.items || feedData || [];
185
132
  if (Array.isArray(feedItems)) {
186
133
  for (const item of feedItems) {
187
- const confDelta = item.delta ?? item.confidenceDelta ?? item.confidence_delta ?? 0;
134
+ const confDelta = item.delta ?? item.confidenceDelta ?? 0;
188
135
  if (Math.abs(confDelta) >= 0.02) {
189
- const itemDate = item.evaluatedAt || item.createdAt || item.created_at || item.timestamp || '';
136
+ const itemDate = item.evaluatedAt || item.createdAt || item.timestamp || '';
190
137
  if (itemDate) {
191
138
  events.push({
192
139
  date: dateKey(new Date(itemDate)),
193
140
  direction: confDelta > 0 ? 'up' : 'down',
194
141
  deltaPct: Math.round(confDelta * 100),
195
- summary: item.summary || item.title || item.description || '',
142
+ summary: item.summary || '',
196
143
  });
197
144
  }
198
145
  }
199
146
  }
200
147
  }
201
148
  }
202
- catch {
203
- // Feed unavailable — continue without events
204
- }
205
- // Compute summary
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 });
160
+ }
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
206
176
  const totalCostCents = tickers.reduce((sum, t) => sum + t.totalCostCents, 0);
207
- const lastRow = dailyRows.length > 0 ? dailyRows[dailyRows.length - 1] : null;
208
- const currentPnlCents = lastRow?.total ?? 0;
209
- const currentValueCents = totalCostCents + currentPnlCents;
210
- const pnlPct = totalCostCents > 0 ? (currentPnlCents / totalCostCents) * 100 : 0;
211
- // 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
212
180
  if (opts.json) {
213
181
  console.log(JSON.stringify({
214
- daily: dailyRows.map(row => {
215
- const tickerPnl = {};
216
- for (const [tk, pnl] of row.pnlByTicker) {
217
- tickerPnl[tk] = pnl;
218
- }
219
- return { date: row.date, tickers: tickerPnl, total: row.total };
220
- }),
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] })),
221
187
  events,
222
- summary: {
223
- cost: totalCostCents,
224
- current: currentValueCents,
225
- pnl: currentPnlCents,
226
- pnlPct: Math.round(pnlPct * 10) / 10,
227
- },
188
+ summary: { cost: totalCostCents, pnl: totalPnlCents, pnlPct: Math.round(totalPnlPct * 10) / 10 },
228
189
  }, null, 2));
229
190
  return;
230
191
  }
231
- // Formatted output
192
+ // 7. Formatted output — rows are positions
232
193
  const startDate = sortedDates.length > 0 ? fmtDate(new Date(sortedDates[0])) : '?';
233
194
  const endDate = fmtDate(new Date());
234
195
  console.log();
235
- 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}`);
236
- console.log(` ${utils_js_1.c.dim}${'─'.repeat(50)}${utils_js_1.c.reset}`);
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}`);
237
198
  console.log();
238
- // Column headers
239
- const abbrevs = tickers.map(t => abbrevTicker(t.ticker));
240
- const colWidth = 11;
241
- const dateCol = 'Date'.padEnd(12);
242
- const headerCols = abbrevs.map(a => (0, utils_js_1.rpad)(a, colWidth)).join('');
243
- const totalCol = (0, utils_js_1.rpad)('Total', colWidth);
244
- console.log(` ${utils_js_1.c.dim}${dateCol}${headerCols}${totalCol}${utils_js_1.c.reset}`);
245
- // Rows show at most ~30 rows, sample if more
246
- const maxRows = 30;
247
- let rowsToShow = dailyRows;
248
- if (dailyRows.length > maxRows) {
249
- // Sample evenly + always include first and last
250
- const step = Math.ceil(dailyRows.length / maxRows);
251
- rowsToShow = dailyRows.filter((_, i) => i === 0 || i === dailyRows.length - 1 || i % step === 0);
252
- }
253
- for (const row of rowsToShow) {
254
- const d = new Date(row.date);
255
- const dateStr = fmtDate(d).padEnd(12);
256
- const cols = tickers.map(t => {
257
- const pnl = row.pnlByTicker.get(t.ticker);
258
- if (pnl === undefined)
259
- return (0, utils_js_1.rpad)('--', colWidth);
260
- const str = fmtDollar(pnl);
261
- const color = pnl > 0 ? utils_js_1.c.green : pnl < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
262
- return color + (0, utils_js_1.rpad)(str, colWidth) + utils_js_1.c.reset;
263
- }).join('');
264
- const totalStr = fmtDollar(row.total);
265
- const totalColor = row.total > 0 ? utils_js_1.c.green : row.total < 0 ? utils_js_1.c.red : utils_js_1.c.dim;
266
- console.log(` ${dateStr}${cols}${totalColor}${(0, utils_js_1.rpad)(totalStr, colWidth)}${utils_js_1.c.reset}`);
267
- }
268
- // Sparkline of total P&L
269
- if (dailyRows.length >= 2) {
270
- const totals = dailyRows.map(r => r.total);
271
- const min = Math.min(...totals);
272
- const max = Math.max(...totals);
273
- const range = max - min || 1;
274
- const blocks = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
275
- const spark = totals.map(v => {
276
- const idx = Math.round(((v - min) / range) * (blocks.length - 1));
277
- const ch = blocks[idx];
278
- return v >= 0 ? `${utils_js_1.c.green}${ch}${utils_js_1.c.reset}` : `${utils_js_1.c.red}${ch}${utils_js_1.c.reset}`;
279
- }).join('');
280
- console.log();
281
- console.log(` ${utils_js_1.c.dim}P&L trend:${utils_js_1.c.reset} ${spark}`);
282
- }
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);
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);
283
225
  // Events
284
226
  if (events.length > 0) {
285
- console.log();
286
- // Match events to date range
287
227
  const dateSet = new Set(sortedDates);
288
- const relevantEvents = events.filter(e => dateSet.has(e.date));
289
- for (const ev of relevantEvents.slice(0, 10)) {
290
- 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}`;
291
- const dateFmt = fmtDate(new Date(ev.date));
292
- const sign = ev.deltaPct > 0 ? '+' : '';
293
- const summary = ev.summary.length > 60 ? ev.summary.slice(0, 59) + '...' : ev.summary;
294
- console.log(` ${arrow} ${utils_js_1.c.dim}${dateFmt}${utils_js_1.c.reset} ${sign}${ev.deltaPct}% -> ${summary}`);
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
+ }
295
236
  }
296
237
  }
297
- // Summary line
238
+ // Summary
298
239
  console.log();
299
240
  const costStr = `$${(totalCostCents / 100).toFixed(0)}`;
300
- const currentStr = `$${(currentValueCents / 100).toFixed(0)}`;
301
- const pnlStr = fmtDollar(currentPnlCents);
302
- const pnlPctStr = `${pnlPct >= 0 ? '+' : ''}${pnlPct.toFixed(1)}%`;
303
- const pnlColor = currentPnlCents >= 0 ? utils_js_1.c.green : utils_js_1.c.red;
304
- 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}`);
305
242
  console.log();
306
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "1.4.2",
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"