@spfunctions/cli 0.1.3 → 0.1.4

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.
@@ -69,6 +69,25 @@ async function positionsCommand(opts) {
69
69
  }
70
70
  }
71
71
  }
72
+ // ── Step 2.5: Enrich positions with live prices ──
73
+ if (positions && positions.length > 0) {
74
+ console.log(`${utils_js_1.c.dim}Fetching live prices for ${positions.length} positions...${utils_js_1.c.reset}`);
75
+ for (const pos of positions) {
76
+ try {
77
+ const livePrice = await (0, kalshi_js_1.getMarketPrice)(pos.ticker);
78
+ if (livePrice !== null) {
79
+ pos.current_value = livePrice;
80
+ // P&L in cents: (currentPrice - avgEntry) * quantity
81
+ pos.unrealized_pnl = (livePrice - pos.average_price_paid) * pos.quantity;
82
+ }
83
+ // Small delay to avoid rate limits
84
+ await new Promise(r => setTimeout(r, 100));
85
+ }
86
+ catch {
87
+ // skip — live price optional
88
+ }
89
+ }
90
+ }
72
91
  // ── Step 3: Merge ──
73
92
  // Build a lookup from ticker → edge
74
93
  const edgeByTicker = new Map();
@@ -156,6 +175,23 @@ async function positionsCommand(opts) {
156
175
  if (unpositionedEdges.length > 0) {
157
176
  // Sort by absolute edge size descending
158
177
  unpositionedEdges.sort((a, b) => Math.abs(b.edge.edge ?? b.edge.edgeSize ?? 0) - Math.abs(a.edge.edge ?? a.edge.edgeSize ?? 0));
178
+ // Pre-fetch orderbooks locally for top Kalshi edges that don't already have server OB data
179
+ const topEdgesForOB = unpositionedEdges.slice(0, 10).filter(item => item.edge.venue === 'kalshi' && !item.edge.orderbook && Math.abs(item.edge.edge ?? item.edge.edgeSize ?? 0) > 5);
180
+ const localObMap = new Map();
181
+ if (topEdgesForOB.length > 0 && (0, kalshi_js_1.isKalshiConfigured)()) {
182
+ console.log(`${utils_js_1.c.dim}Fetching orderbooks for ${topEdgesForOB.length} edges...${utils_js_1.c.reset}`);
183
+ for (const item of topEdgesForOB) {
184
+ try {
185
+ const ob = await (0, kalshi_js_1.getOrderbook)(item.edge.marketId);
186
+ if (ob)
187
+ localObMap.set(item.edge.marketId, ob);
188
+ await new Promise(r => setTimeout(r, 150));
189
+ }
190
+ catch {
191
+ // skip
192
+ }
193
+ }
194
+ }
159
195
  const thesisLabel = opts.thesis ? ` (thesis ${(0, utils_js_1.shortId)(opts.thesis)})` : '';
160
196
  (0, utils_js_1.header)(`Unpositioned Edges${thesisLabel}`);
161
197
  console.log(' ' + utils_js_1.c.bold +
@@ -175,8 +211,10 @@ async function positionsCommand(opts) {
175
211
  const mktPrice = e.marketPrice ?? 0;
176
212
  const thesisPrice = e.thesisPrice ?? e.thesisImpliedPrice ?? 0;
177
213
  const title = (e.market || e.marketTitle || e.marketId || '?').slice(0, 29);
178
- // Orderbook info (if present from rescan)
179
- const ob = e.orderbook;
214
+ // Orderbook: prefer server data, fallback to local fetch
215
+ const serverOb = e.orderbook;
216
+ const localOb = localObMap.get(e.marketId);
217
+ const ob = serverOb || localOb;
180
218
  const spreadStr = ob ? `${ob.spread}¢` : '-';
181
219
  const liqStr = ob ? ob.liquidityScore : '-';
182
220
  const liqColor = ob?.liquidityScore === 'high' ? utils_js_1.c.green : ob?.liquidityScore === 'medium' ? utils_js_1.c.yellow : utils_js_1.c.dim;
package/dist/kalshi.d.ts CHANGED
@@ -37,5 +37,19 @@ export interface KalshiPortfolio {
37
37
  export declare function getPositions(): Promise<KalshiPosition[] | null>;
38
38
  /**
39
39
  * Get the current market price for a given ticker (public, no auth).
40
+ * Returns price in cents (0-100) or null.
40
41
  */
41
42
  export declare function getMarketPrice(ticker: string): Promise<number | null>;
43
+ /**
44
+ * Fetch orderbook for a ticker using local Kalshi auth credentials.
45
+ * Returns simplified spread/depth info or null.
46
+ */
47
+ export interface LocalOrderbook {
48
+ bestBid: number;
49
+ bestAsk: number;
50
+ spread: number;
51
+ bidDepth: number;
52
+ askDepth: number;
53
+ liquidityScore: 'high' | 'medium' | 'low';
54
+ }
55
+ export declare function getOrderbook(ticker: string): Promise<LocalOrderbook | null>;
package/dist/kalshi.js CHANGED
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.isKalshiConfigured = isKalshiConfigured;
18
18
  exports.getPositions = getPositions;
19
19
  exports.getMarketPrice = getMarketPrice;
20
+ exports.getOrderbook = getOrderbook;
20
21
  const fs_1 = __importDefault(require("fs"));
21
22
  const path_1 = __importDefault(require("path"));
22
23
  const crypto_1 = __importDefault(require("crypto"));
@@ -103,20 +104,37 @@ async function getPositions() {
103
104
  return null;
104
105
  try {
105
106
  const data = await kalshiAuthGet('/portfolio/positions');
107
+ // DEBUG: dump raw Kalshi response to see actual field names
108
+ console.log('[Kalshi DEBUG] Raw /portfolio/positions response:');
109
+ console.log(JSON.stringify(data, null, 2));
106
110
  // Kalshi returns { market_positions: [...] } or { positions: [...] }
107
111
  const raw = data.market_positions || data.positions || [];
108
- return raw.map((p) => ({
109
- ticker: p.ticker || p.market_ticker || '',
110
- event_ticker: p.event_ticker || '',
111
- market_title: p.market_title || p.title || '',
112
- side: (p.position_side || p.side || 'yes').toLowerCase(),
113
- quantity: p.total_traded || p.quantity || p.position || 0,
114
- average_price_paid: p.average_price_paid || p.avg_price || 0,
115
- current_value: p.market_value || p.current_value || 0,
116
- realized_pnl: p.realized_pnl || 0,
117
- unrealized_pnl: p.unrealized_pnl || 0,
118
- total_cost: p.total_cost || 0,
119
- }));
112
+ return raw.map((p) => {
113
+ // Kalshi actual fields:
114
+ // position_fp: "795.00" (string, contract count — positive=YES, negative=NO)
115
+ // total_traded_dollars: "453.1500" (string, total cost in dollars)
116
+ // ticker: "KXWTIMAX-26DEC31-T135"
117
+ // event_ticker: "KXWTIMAX-26DEC31"
118
+ // market_result: "", resting_orders_count: 0, etc.
119
+ const positionFp = parseFloat(p.position_fp || '0');
120
+ const totalTradedDollars = parseFloat(p.total_traded_dollars || '0');
121
+ const quantity = Math.abs(positionFp);
122
+ const side = positionFp >= 0 ? 'yes' : 'no';
123
+ // avg price in cents = (total_traded_dollars / quantity) * 100
124
+ const avgPriceCents = quantity > 0 ? Math.round((totalTradedDollars / quantity) * 100) : 0;
125
+ return {
126
+ ticker: p.ticker || p.market_ticker || '',
127
+ event_ticker: p.event_ticker || '',
128
+ market_title: p.market_title || p.title || '',
129
+ side,
130
+ quantity,
131
+ average_price_paid: avgPriceCents,
132
+ current_value: 0, // will be enriched by live price lookup if needed
133
+ realized_pnl: Math.round(parseFloat(p.realized_pnl || '0') * 100),
134
+ unrealized_pnl: 0, // Kalshi doesn't give this directly, needs live price
135
+ total_cost: Math.round(totalTradedDollars * 100), // dollars → cents
136
+ };
137
+ });
120
138
  }
121
139
  catch (err) {
122
140
  console.warn(`[Kalshi] Failed to fetch positions:`, err);
@@ -125,6 +143,7 @@ async function getPositions() {
125
143
  }
126
144
  /**
127
145
  * Get the current market price for a given ticker (public, no auth).
146
+ * Returns price in cents (0-100) or null.
128
147
  */
129
148
  async function getMarketPrice(ticker) {
130
149
  try {
@@ -134,7 +153,64 @@ async function getMarketPrice(ticker) {
134
153
  return null;
135
154
  const data = await res.json();
136
155
  const m = data.market || data;
137
- return m.last_price || m.yes_bid || null;
156
+ // Try cents fields first, then dollar string fields
157
+ if (m.last_price && m.last_price > 0)
158
+ return m.last_price;
159
+ if (m.yes_bid && m.yes_bid > 0)
160
+ return m.yes_bid;
161
+ if (m.last_price_dollars) {
162
+ const parsed = parseFloat(m.last_price_dollars);
163
+ if (!isNaN(parsed) && parsed > 0)
164
+ return Math.round(parsed * 100);
165
+ }
166
+ if (m.yes_bid_dollars) {
167
+ const parsed = parseFloat(m.yes_bid_dollars);
168
+ if (!isNaN(parsed) && parsed > 0)
169
+ return Math.round(parsed * 100);
170
+ }
171
+ return null;
172
+ }
173
+ catch {
174
+ return null;
175
+ }
176
+ }
177
+ async function getOrderbook(ticker) {
178
+ if (!isKalshiConfigured())
179
+ return null;
180
+ try {
181
+ const data = await kalshiAuthGet(`/markets/${ticker}/orderbook`);
182
+ const ob = data.orderbook_fp || data.orderbook;
183
+ if (!ob)
184
+ return null;
185
+ // Kalshi orderbook_fp uses yes_dollars/no_dollars: [["0.57", "100"], ...]
186
+ // Fallback to yes/no (cents format)
187
+ const rawYes = ob.yes_dollars || ob.yes || [];
188
+ const rawNo = ob.no_dollars || ob.no || [];
189
+ const isDollar = !!(ob.yes_dollars || ob.no_dollars);
190
+ // Parse to cents
191
+ const parsedYes = rawYes.map(l => ({
192
+ price: isDollar ? Math.round(parseFloat(l[0]) * 100) : Number(l[0]),
193
+ qty: parseFloat(l[1]),
194
+ })).filter(l => l.price > 0);
195
+ const parsedNo = rawNo.map(l => ({
196
+ price: isDollar ? Math.round(parseFloat(l[0]) * 100) : Number(l[0]),
197
+ qty: parseFloat(l[1]),
198
+ })).filter(l => l.price > 0);
199
+ // Sort descending by price
200
+ parsedYes.sort((a, b) => b.price - a.price);
201
+ parsedNo.sort((a, b) => b.price - a.price);
202
+ const bestBid = parsedYes.length > 0 ? parsedYes[0].price : 0;
203
+ const bestAsk = parsedNo.length > 0 ? (100 - parsedNo[0].price) : 100;
204
+ const spread = bestAsk - bestBid;
205
+ const bidDepth = parsedYes.slice(0, 3).reduce((sum, l) => sum + l.qty, 0);
206
+ const askDepth = parsedNo.slice(0, 3).reduce((sum, l) => sum + l.qty, 0);
207
+ const minDepth = Math.min(bidDepth, askDepth);
208
+ let liquidityScore = 'low';
209
+ if (spread <= 2 && minDepth >= 500)
210
+ liquidityScore = 'high';
211
+ else if (spread <= 5 && minDepth >= 100)
212
+ liquidityScore = 'medium';
213
+ return { bestBid, bestAsk, spread, bidDepth, askDepth, liquidityScore };
138
214
  }
139
215
  catch {
140
216
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfunctions/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI for SimpleFunctions prediction market thesis agent",
5
5
  "bin": {
6
6
  "sf": "./dist/index.js"