pmxt-core 1.5.3 → 1.5.5
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.
|
@@ -289,14 +289,18 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
289
289
|
if (!this.ws) {
|
|
290
290
|
this.ws = new websocket_1.KalshiWebSocket(auth, this.wsConfig);
|
|
291
291
|
}
|
|
292
|
-
|
|
292
|
+
// Normalize ticker (strip -NO suffix if present)
|
|
293
|
+
const marketTicker = id.replace(/-NO$/, '');
|
|
294
|
+
return this.ws.watchOrderBook(marketTicker);
|
|
293
295
|
}
|
|
294
296
|
async watchTrades(id, since, limit) {
|
|
295
297
|
const auth = this.ensureAuth();
|
|
296
298
|
if (!this.ws) {
|
|
297
299
|
this.ws = new websocket_1.KalshiWebSocket(auth, this.wsConfig);
|
|
298
300
|
}
|
|
299
|
-
|
|
301
|
+
// Normalize ticker (strip -NO suffix if present)
|
|
302
|
+
const marketTicker = id.replace(/-NO$/, '');
|
|
303
|
+
return this.ws.watchTrades(marketTicker);
|
|
300
304
|
}
|
|
301
305
|
async close() {
|
|
302
306
|
if (this.ws) {
|
|
@@ -90,7 +90,7 @@ describe('KalshiExchange', () => {
|
|
|
90
90
|
};
|
|
91
91
|
mockedAxios.post.mockResolvedValue(mockResponse);
|
|
92
92
|
const order = await exchange.createOrder(orderParams);
|
|
93
|
-
expect(mockedAxios.post).toHaveBeenCalledWith('https://
|
|
93
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
|
|
94
94
|
ticker: 'TEST-MARKET',
|
|
95
95
|
side: 'yes',
|
|
96
96
|
action: 'buy',
|
|
@@ -125,7 +125,7 @@ describe('KalshiExchange', () => {
|
|
|
125
125
|
};
|
|
126
126
|
mockedAxios.post.mockResolvedValue(mockResponse);
|
|
127
127
|
await exchange.createOrder(orderParams);
|
|
128
|
-
expect(mockedAxios.post).toHaveBeenCalledWith('https://
|
|
128
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
|
|
129
129
|
ticker: 'TEST-MARKET',
|
|
130
130
|
side: 'no',
|
|
131
131
|
action: 'sell',
|
|
@@ -156,7 +156,7 @@ describe('KalshiExchange', () => {
|
|
|
156
156
|
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
157
157
|
await exchange.fetchOpenOrders();
|
|
158
158
|
// Verify the request URL includes query params
|
|
159
|
-
expect(mockedAxios.get).toHaveBeenCalledWith('https://
|
|
159
|
+
expect(mockedAxios.get).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders?status=resting', expect.any(Object));
|
|
160
160
|
// Verify getHeaders was called with base path only (no query params)
|
|
161
161
|
expect(MockedKalshiAuth.prototype.getHeaders).toHaveBeenCalledWith('GET', '/trade-api/v2/portfolio/orders');
|
|
162
162
|
});
|
|
@@ -164,7 +164,7 @@ describe('KalshiExchange', () => {
|
|
|
164
164
|
const mockResponse = { data: { orders: [] } };
|
|
165
165
|
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
166
166
|
await exchange.fetchOpenOrders('TEST-MARKET');
|
|
167
|
-
expect(mockedAxios.get).toHaveBeenCalledWith('https://
|
|
167
|
+
expect(mockedAxios.get).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders?status=resting&ticker=TEST-MARKET', expect.any(Object));
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
describe('fetchPositions', () => {
|
|
@@ -17,11 +17,14 @@ export declare class KalshiWebSocket {
|
|
|
17
17
|
private orderBookResolvers;
|
|
18
18
|
private tradeResolvers;
|
|
19
19
|
private orderBooks;
|
|
20
|
-
private
|
|
20
|
+
private subscribedOrderBookTickers;
|
|
21
|
+
private subscribedTradeTickers;
|
|
21
22
|
private messageIdCounter;
|
|
22
23
|
private isConnecting;
|
|
23
24
|
private isConnected;
|
|
24
25
|
private reconnectTimer?;
|
|
26
|
+
private connectionPromise?;
|
|
27
|
+
private isTerminated;
|
|
25
28
|
constructor(auth: KalshiAuth, config?: KalshiWebSocketConfig);
|
|
26
29
|
private connect;
|
|
27
30
|
private scheduleReconnect;
|
|
@@ -14,10 +14,12 @@ class KalshiWebSocket {
|
|
|
14
14
|
this.orderBookResolvers = new Map();
|
|
15
15
|
this.tradeResolvers = new Map();
|
|
16
16
|
this.orderBooks = new Map();
|
|
17
|
-
this.
|
|
17
|
+
this.subscribedOrderBookTickers = new Set();
|
|
18
|
+
this.subscribedTradeTickers = new Set();
|
|
18
19
|
this.messageIdCounter = 1;
|
|
19
20
|
this.isConnecting = false;
|
|
20
21
|
this.isConnected = false;
|
|
22
|
+
this.isTerminated = false;
|
|
21
23
|
this.auth = auth;
|
|
22
24
|
this.config = {
|
|
23
25
|
wsUrl: config.wsUrl || 'wss://api.elections.kalshi.com/trade-api/ws/v2',
|
|
@@ -25,11 +27,17 @@ class KalshiWebSocket {
|
|
|
25
27
|
};
|
|
26
28
|
}
|
|
27
29
|
async connect() {
|
|
28
|
-
if (this.isConnected
|
|
30
|
+
if (this.isConnected) {
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
33
|
+
if (this.isTerminated) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (this.connectionPromise) {
|
|
37
|
+
return this.connectionPromise;
|
|
38
|
+
}
|
|
31
39
|
this.isConnecting = true;
|
|
32
|
-
|
|
40
|
+
this.connectionPromise = new Promise((resolve, reject) => {
|
|
33
41
|
try {
|
|
34
42
|
// Extract path from URL for signature
|
|
35
43
|
const url = new URL(this.config.wsUrl);
|
|
@@ -41,10 +49,14 @@ class KalshiWebSocket {
|
|
|
41
49
|
this.ws.on('open', () => {
|
|
42
50
|
this.isConnected = true;
|
|
43
51
|
this.isConnecting = false;
|
|
52
|
+
this.connectionPromise = undefined;
|
|
44
53
|
console.log('Kalshi WebSocket connected');
|
|
45
54
|
// Resubscribe to all tickers if reconnecting
|
|
46
|
-
if (this.
|
|
47
|
-
this.subscribeToOrderbook(Array.from(this.
|
|
55
|
+
if (this.subscribedOrderBookTickers.size > 0) {
|
|
56
|
+
this.subscribeToOrderbook(Array.from(this.subscribedOrderBookTickers));
|
|
57
|
+
}
|
|
58
|
+
if (this.subscribedTradeTickers.size > 0) {
|
|
59
|
+
this.subscribeToTrades(Array.from(this.subscribedTradeTickers));
|
|
48
60
|
}
|
|
49
61
|
resolve();
|
|
50
62
|
});
|
|
@@ -60,22 +72,31 @@ class KalshiWebSocket {
|
|
|
60
72
|
this.ws.on('error', (error) => {
|
|
61
73
|
console.error('Kalshi WebSocket error:', error);
|
|
62
74
|
this.isConnecting = false;
|
|
75
|
+
this.connectionPromise = undefined;
|
|
63
76
|
reject(error);
|
|
64
77
|
});
|
|
65
78
|
this.ws.on('close', () => {
|
|
66
|
-
|
|
79
|
+
if (!this.isTerminated) {
|
|
80
|
+
console.log('Kalshi WebSocket closed');
|
|
81
|
+
this.scheduleReconnect();
|
|
82
|
+
}
|
|
67
83
|
this.isConnected = false;
|
|
68
84
|
this.isConnecting = false;
|
|
69
|
-
this.
|
|
85
|
+
this.connectionPromise = undefined;
|
|
70
86
|
});
|
|
71
87
|
}
|
|
72
88
|
catch (error) {
|
|
73
89
|
this.isConnecting = false;
|
|
90
|
+
this.connectionPromise = undefined;
|
|
74
91
|
reject(error);
|
|
75
92
|
}
|
|
76
93
|
});
|
|
94
|
+
return this.connectionPromise;
|
|
77
95
|
}
|
|
78
96
|
scheduleReconnect() {
|
|
97
|
+
if (this.isTerminated) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
79
100
|
if (this.reconnectTimer) {
|
|
80
101
|
clearTimeout(this.reconnectTimer);
|
|
81
102
|
}
|
|
@@ -271,9 +292,9 @@ class KalshiWebSocket {
|
|
|
271
292
|
await this.connect();
|
|
272
293
|
}
|
|
273
294
|
// Subscribe if not already subscribed
|
|
274
|
-
if (!this.
|
|
275
|
-
this.
|
|
276
|
-
this.subscribeToOrderbook(
|
|
295
|
+
if (!this.subscribedOrderBookTickers.has(ticker)) {
|
|
296
|
+
this.subscribedOrderBookTickers.add(ticker);
|
|
297
|
+
this.subscribeToOrderbook(Array.from(this.subscribedOrderBookTickers));
|
|
277
298
|
}
|
|
278
299
|
// Return a promise that resolves on the next orderbook update
|
|
279
300
|
return new Promise((resolve, reject) => {
|
|
@@ -289,9 +310,9 @@ class KalshiWebSocket {
|
|
|
289
310
|
await this.connect();
|
|
290
311
|
}
|
|
291
312
|
// Subscribe if not already subscribed
|
|
292
|
-
if (!this.
|
|
293
|
-
this.
|
|
294
|
-
this.subscribeToTrades(
|
|
313
|
+
if (!this.subscribedTradeTickers.has(ticker)) {
|
|
314
|
+
this.subscribedTradeTickers.add(ticker);
|
|
315
|
+
this.subscribeToTrades(Array.from(this.subscribedTradeTickers));
|
|
295
316
|
}
|
|
296
317
|
// Return a promise that resolves on the next trade
|
|
297
318
|
return new Promise((resolve, reject) => {
|
|
@@ -302,12 +323,33 @@ class KalshiWebSocket {
|
|
|
302
323
|
});
|
|
303
324
|
}
|
|
304
325
|
async close() {
|
|
326
|
+
this.isTerminated = true;
|
|
305
327
|
if (this.reconnectTimer) {
|
|
306
328
|
clearTimeout(this.reconnectTimer);
|
|
329
|
+
this.reconnectTimer = undefined;
|
|
307
330
|
}
|
|
331
|
+
// Reject all pending resolvers
|
|
332
|
+
this.orderBookResolvers.forEach((resolvers, ticker) => {
|
|
333
|
+
resolvers.forEach(r => r.reject(new Error(`WebSocket closed for ${ticker}`)));
|
|
334
|
+
});
|
|
335
|
+
this.orderBookResolvers.clear();
|
|
336
|
+
this.tradeResolvers.forEach((resolvers, ticker) => {
|
|
337
|
+
resolvers.forEach(r => r.reject(new Error(`WebSocket closed for ${ticker}`)));
|
|
338
|
+
});
|
|
339
|
+
this.tradeResolvers.clear();
|
|
308
340
|
if (this.ws) {
|
|
309
|
-
this.ws
|
|
341
|
+
const ws = this.ws;
|
|
310
342
|
this.ws = undefined;
|
|
343
|
+
if (ws.readyState !== ws_1.default.CLOSED && ws.readyState !== ws_1.default.CLOSING) {
|
|
344
|
+
return new Promise((resolve) => {
|
|
345
|
+
ws.once('close', () => {
|
|
346
|
+
this.isConnected = false;
|
|
347
|
+
this.isConnecting = false;
|
|
348
|
+
resolve();
|
|
349
|
+
});
|
|
350
|
+
ws.close();
|
|
351
|
+
});
|
|
352
|
+
}
|
|
311
353
|
}
|
|
312
354
|
this.isConnected = false;
|
|
313
355
|
this.isConnecting = false;
|
|
@@ -191,11 +191,15 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
191
191
|
const client = await auth.getClobClient();
|
|
192
192
|
try {
|
|
193
193
|
const order = await client.getOrder(orderId);
|
|
194
|
+
if (!order || !order.id) {
|
|
195
|
+
const errorMsg = order?.error || 'Order not found (Invalid ID)';
|
|
196
|
+
throw new Error(errorMsg);
|
|
197
|
+
}
|
|
194
198
|
return {
|
|
195
199
|
id: order.id,
|
|
196
200
|
marketId: order.market || 'unknown',
|
|
197
201
|
outcomeId: order.asset_id,
|
|
198
|
-
side: order.side.toLowerCase(),
|
|
202
|
+
side: (order.side || '').toLowerCase(),
|
|
199
203
|
type: order.order_type === 'GTC' ? 'limit' : 'market',
|
|
200
204
|
price: parseFloat(order.price),
|
|
201
205
|
amount: parseFloat(order.original_size),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxt-core",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"clean": "rm -rf dist",
|
|
26
26
|
"prebuild": "npm run clean",
|
|
27
27
|
"build": "tsc",
|
|
28
|
-
"bundle:server": "esbuild dist/server/index.js --bundle --platform=node --target=node18 --outfile=dist/server/bundled.js --external:fsevents",
|
|
28
|
+
"bundle:server": "npm run build && esbuild dist/server/index.js --bundle --platform=node --target=node18 --outfile=dist/server/bundled.js --external:fsevents",
|
|
29
29
|
"test": "jest -c jest.config.js",
|
|
30
30
|
"server": "tsx watch src/server/index.ts",
|
|
31
31
|
"server:prod": "node dist/server/index.js",
|
|
32
|
-
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=1.5.
|
|
33
|
-
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=1.5.
|
|
32
|
+
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=1.5.5,library=urllib3",
|
|
33
|
+
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=1.5.5,supportsES6=true,typescriptThreePlus=true",
|
|
34
34
|
"generate:docs": "node ../scripts/generate-api-docs.js",
|
|
35
35
|
"generate:sdk:all": "npm run generate:sdk:python && npm run generate:sdk:typescript && npm run generate:docs"
|
|
36
36
|
},
|