pmxt-core 0.0.1 → 1.0.0-b3
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/API_REFERENCE.md +515 -0
- package/LICENSE +21 -0
- package/bin/pmxt-ensure-server +149 -0
- package/dist/exchanges/kalshi/fetchMarkets.d.ts +1 -0
- package/dist/exchanges/kalshi/fetchMarkets.js +40 -8
- package/dist/server/app.js +22 -13
- package/package.json +57 -6
- package/.env +0 -5
- package/index.js +0 -1
- package/pmxt-core-0.4.4.tgz +0 -0
package/API_REFERENCE.md
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
# Prediction Market API Reference
|
|
2
|
+
|
|
3
|
+
A unified interface for interacting with multiple prediction market exchanges (Kalshi, Polymarket) identically.
|
|
4
|
+
|
|
5
|
+
## Installation & Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install pmxtjs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Basic Import (CommonJS)
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import pmxt from 'pmxtjs';
|
|
15
|
+
|
|
16
|
+
// Both PascalCase and lowercase work
|
|
17
|
+
const poly = new pmxt.Polymarket();
|
|
18
|
+
const kalshi = new pmxt.Kalshi();
|
|
19
|
+
|
|
20
|
+
// Or lowercase if you prefer
|
|
21
|
+
const poly2 = new pmxt.polymarket();
|
|
22
|
+
const kalshi2 = new pmxt.kalshi();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Note for ESM Users
|
|
26
|
+
|
|
27
|
+
**pmxt is currently CommonJS-only.** If you're using `"type": "module"` in your `package.json`, you have two options:
|
|
28
|
+
|
|
29
|
+
**Option 1: Default import (recommended)**
|
|
30
|
+
```typescript
|
|
31
|
+
import pmxt from 'pmxtjs';
|
|
32
|
+
const poly = new pmxt.polymarket();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Option 2: Dynamic import**
|
|
36
|
+
```typescript
|
|
37
|
+
const pmxt = await import('pmxtjs');
|
|
38
|
+
const poly = new pmxt.default.polymarket();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Note**: Named exports like `import { polymarket } from 'pmxtjs'` will **not work** in ESM projects.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Core Methods
|
|
46
|
+
|
|
47
|
+
### `fetchMarkets(params?)`
|
|
48
|
+
Get active markets from an exchange.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const markets = await polymarket.fetchMarkets({
|
|
52
|
+
limit: 20,
|
|
53
|
+
offset: 0,
|
|
54
|
+
sort: 'volume' // 'volume' | 'liquidity' | 'newest'
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `searchMarkets(query, params?)`
|
|
59
|
+
Search markets by keyword. By default, searches only in titles.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const results = await kalshi.searchMarkets('Fed rates', {
|
|
63
|
+
limit: 10,
|
|
64
|
+
searchIn: 'title' // 'title' (default) | 'description' | 'both'
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `getMarketsBySlug(slug)`
|
|
69
|
+
Fetch markets by URL slug/ticker.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Polymarket: use URL slug
|
|
73
|
+
const polyMarkets = await polymarket.getMarketsBySlug('who-will-trump-nominate-as-fed-chair');
|
|
74
|
+
|
|
75
|
+
// Kalshi: use market ticker (auto-uppercased)
|
|
76
|
+
const kalshiMarkets = await kalshi.getMarketsBySlug('KXFEDCHAIRNOM-29');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Universal Slug/Ticker Reference
|
|
80
|
+
|
|
81
|
+
| Platform | Example Market URL | What to extract (Slug/Ticker) | Logic |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| **Kalshi** | `kalshi.com/markets/kxfedchairnom/.../kxfedchairnom-29` | `KXFEDCHAIRNOM-29` | The **last** path segment of the URL. |
|
|
84
|
+
| **Polymarket** | `polymarket.com/event/who-will-trump-nominate-as-fed-chair` | `who-will-trump-nominate-as-fed-chair` | The slug immediately after `/event/`. |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Deep-Dive Methods
|
|
89
|
+
|
|
90
|
+
### `fetchOHLCV(outcomeId, params)`
|
|
91
|
+
Get historical price candles.
|
|
92
|
+
|
|
93
|
+
**CRITICAL**: Use `outcome.id`, not `market.id`.
|
|
94
|
+
- **Polymarket**: `outcome.id` is the CLOB Token ID
|
|
95
|
+
- **Kalshi**: `outcome.id` is the Market Ticker
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const markets = await polymarket.searchMarkets('Trump');
|
|
99
|
+
const outcomeId = markets[0].outcomes[0].id; // Get the outcome ID
|
|
100
|
+
|
|
101
|
+
const candles = await polymarket.fetchOHLCV(outcomeId, {
|
|
102
|
+
resolution: '1h', // '1m' | '5m' | '15m' | '1h' | '6h' | '1d'
|
|
103
|
+
start: new Date('2024-01-01'),
|
|
104
|
+
end: new Date('2024-01-31'),
|
|
105
|
+
limit: 100
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `fetchOrderBook(outcomeId)`
|
|
110
|
+
Get current bids/asks.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const orderBook = await kalshi.fetchOrderBook('FED-25JAN');
|
|
114
|
+
console.log('Best bid:', orderBook.bids[0].price);
|
|
115
|
+
console.log('Best ask:', orderBook.asks[0].price);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `fetchTrades(outcomeId, params)`
|
|
119
|
+
Get trade history.
|
|
120
|
+
|
|
121
|
+
**Note**: Polymarket requires API key. Use `fetchOHLCV` for public historical data.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const trades = await kalshi.fetchTrades('FED-25JAN', {
|
|
125
|
+
resolution: '1h',
|
|
126
|
+
limit: 100
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Data Models
|
|
133
|
+
|
|
134
|
+
### `UnifiedMarket`
|
|
135
|
+
```typescript
|
|
136
|
+
{
|
|
137
|
+
id: string; // Market ID
|
|
138
|
+
title: string;
|
|
139
|
+
description: string;
|
|
140
|
+
outcomes: MarketOutcome[]; // All tradeable outcomes
|
|
141
|
+
|
|
142
|
+
resolutionDate: Date;
|
|
143
|
+
volume24h: number; // USD
|
|
144
|
+
volume?: number; // Total volume (USD)
|
|
145
|
+
liquidity: number; // USD
|
|
146
|
+
openInterest?: number; // USD
|
|
147
|
+
|
|
148
|
+
url: string;
|
|
149
|
+
image?: string;
|
|
150
|
+
category?: string;
|
|
151
|
+
tags?: string[];
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `MarketOutcome`
|
|
156
|
+
```typescript
|
|
157
|
+
{
|
|
158
|
+
id: string; // Use this for fetchOHLCV/fetchOrderBook/fetchTrades
|
|
159
|
+
// Polymarket: CLOB Token ID
|
|
160
|
+
// Kalshi: Market Ticker
|
|
161
|
+
label: string; // "Trump", "Yes", etc.
|
|
162
|
+
price: number; // 0.0 to 1.0 (probability)
|
|
163
|
+
priceChange24h?: number;
|
|
164
|
+
metadata?: {
|
|
165
|
+
clobTokenId?: string; // Polymarket only
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Other Types
|
|
171
|
+
```typescript
|
|
172
|
+
interface PriceCandle {
|
|
173
|
+
timestamp: number; // Unix ms
|
|
174
|
+
open: number; // 0.0 to 1.0
|
|
175
|
+
high: number;
|
|
176
|
+
low: number;
|
|
177
|
+
close: number;
|
|
178
|
+
volume?: number;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
interface OrderBook {
|
|
182
|
+
bids: OrderLevel[]; // Sorted high to low
|
|
183
|
+
asks: OrderLevel[]; // Sorted low to high
|
|
184
|
+
timestamp?: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface OrderLevel {
|
|
188
|
+
price: number; // 0.0 to 1.0
|
|
189
|
+
size: number; // Contracts
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface Trade {
|
|
193
|
+
id: string;
|
|
194
|
+
timestamp: number; // Unix ms
|
|
195
|
+
price: number; // 0.0 to 1.0
|
|
196
|
+
amount: number;
|
|
197
|
+
side: 'buy' | 'sell' | 'unknown';
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Complete Workflow Example
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// 1. Search for markets
|
|
207
|
+
const markets = await polymarket.searchMarkets('Fed Chair');
|
|
208
|
+
const market = markets[0];
|
|
209
|
+
|
|
210
|
+
// 2. Get outcome details
|
|
211
|
+
const outcome = market.outcomes[0];
|
|
212
|
+
console.log(`${outcome.label}: ${(outcome.price * 100).toFixed(1)}%`);
|
|
213
|
+
|
|
214
|
+
// 3. Fetch historical data (use outcome.id!)
|
|
215
|
+
const candles = await polymarket.fetchOHLCV(outcome.id, {
|
|
216
|
+
resolution: '1d',
|
|
217
|
+
limit: 30
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// 4. Get current order book
|
|
221
|
+
const orderBook = await polymarket.fetchOrderBook(outcome.id);
|
|
222
|
+
const spread = orderBook.asks[0].price - orderBook.bids[0].price;
|
|
223
|
+
console.log(`Spread: ${(spread * 100).toFixed(2)}%`);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Exchange Differences
|
|
229
|
+
|
|
230
|
+
| Feature | Polymarket | Kalshi |
|
|
231
|
+
|---------|-----------|--------|
|
|
232
|
+
| **Sorting** | Server-side | Client-side (slower for large sets) |
|
|
233
|
+
| **Market ID** | UUID | Event Ticker (e.g., "PRES-2024") |
|
|
234
|
+
| **Outcome ID** | CLOB Token ID | Market Ticker (e.g., "FED-25JAN") |
|
|
235
|
+
| **OHLCV Quality** | Synthetic (O=H=L=C) | Native (distinct values) |
|
|
236
|
+
| **Auth Required** | Only for `fetchTrades()` | No (all public) |
|
|
237
|
+
| **Slug Format** | lowercase-with-hyphens | UPPERCASE (auto-normalized) |
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Error Handling
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
try {
|
|
245
|
+
const markets = await kalshi.getMarketsBySlug('INVALID-TICKER');
|
|
246
|
+
} catch (error) {
|
|
247
|
+
// Kalshi: "Event not found: INVALID-TICKER"
|
|
248
|
+
// Polymarket: Returns empty array []
|
|
249
|
+
console.error(error.message);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Common Errors**:
|
|
254
|
+
- `404`: Market/event doesn't exist
|
|
255
|
+
- `401`: Missing API key (Polymarket `fetchTrades`)
|
|
256
|
+
- `429`: Rate limited
|
|
257
|
+
- `500`: Exchange API issue
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Type Exports
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import pmxt, {
|
|
265
|
+
UnifiedMarket,
|
|
266
|
+
MarketOutcome,
|
|
267
|
+
PriceCandle,
|
|
268
|
+
OrderBook,
|
|
269
|
+
Trade,
|
|
270
|
+
CandleInterval,
|
|
271
|
+
MarketFilterParams,
|
|
272
|
+
HistoryFilterParams
|
|
273
|
+
} from 'pmxtjs';
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Authentication & Trading
|
|
279
|
+
|
|
280
|
+
Both Polymarket and Kalshi support authenticated trading operations. You must provide credentials when initializing the exchange.
|
|
281
|
+
|
|
282
|
+
### Polymarket Authentication
|
|
283
|
+
|
|
284
|
+
Requires your **Polygon Private Key**. See [Setup Guide](https://github.com/qoery-com/pmxt/blob/main/docs/SETUP_POLYMARKET.md) for details.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import pmxt from 'pmxtjs';
|
|
288
|
+
|
|
289
|
+
const polymarket = new pmxt.Polymarket({
|
|
290
|
+
privateKey: process.env.POLYMARKET_PRIVATE_KEY
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Kalshi Authentication
|
|
295
|
+
|
|
296
|
+
Requires **API Key** and **Private Key**.
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import pmxt from 'pmxtjs';
|
|
300
|
+
|
|
301
|
+
const kalshi = new pmxt.Kalshi({
|
|
302
|
+
apiKey: process.env.KALSHI_API_KEY,
|
|
303
|
+
privateKey: process.env.KALSHI_PRIVATE_KEY
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Account Methods
|
|
310
|
+
|
|
311
|
+
### `fetchBalance()`
|
|
312
|
+
Get your account balance.
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const balances = await polymarket.fetchBalance();
|
|
316
|
+
console.log(balances);
|
|
317
|
+
// [{ currency: 'USDC', total: 1000, available: 950, locked: 50 }]
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Returns**: `Balance[]`
|
|
321
|
+
```typescript
|
|
322
|
+
interface Balance {
|
|
323
|
+
currency: string; // e.g., 'USDC'
|
|
324
|
+
total: number; // Total balance
|
|
325
|
+
available: number; // Available for trading
|
|
326
|
+
locked: number; // Locked in open orders
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### `fetchPositions()`
|
|
331
|
+
Get your current positions across all markets.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const positions = await kalshi.fetchPositions();
|
|
335
|
+
positions.forEach(pos => {
|
|
336
|
+
console.log(`${pos.outcomeLabel}: ${pos.size} @ $${pos.entryPrice}`);
|
|
337
|
+
console.log(`Unrealized P&L: $${pos.unrealizedPnL}`);
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Returns**: `Position[]`
|
|
342
|
+
```typescript
|
|
343
|
+
interface Position {
|
|
344
|
+
marketId: string;
|
|
345
|
+
outcomeId: string;
|
|
346
|
+
outcomeLabel: string;
|
|
347
|
+
size: number; // Positive for long, negative for short
|
|
348
|
+
entryPrice: number;
|
|
349
|
+
currentPrice: number;
|
|
350
|
+
unrealizedPnL: number;
|
|
351
|
+
realizedPnL?: number;
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Trading Methods
|
|
358
|
+
|
|
359
|
+
### `createOrder(params)`
|
|
360
|
+
Place a new order (market or limit).
|
|
361
|
+
|
|
362
|
+
**Limit Order Example**:
|
|
363
|
+
```typescript
|
|
364
|
+
const order = await polymarket.createOrder({
|
|
365
|
+
marketId: '663583',
|
|
366
|
+
outcomeId: '10991849228756847439673778874175365458450913336396982752046655649803657501964',
|
|
367
|
+
side: 'buy',
|
|
368
|
+
type: 'limit',
|
|
369
|
+
amount: 10, // Number of contracts
|
|
370
|
+
price: 0.55 // Required for limit orders (0.0-1.0)
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
console.log(`Order ${order.id}: ${order.status}`);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Market Order Example**:
|
|
377
|
+
```typescript
|
|
378
|
+
const order = await kalshi.createOrder({
|
|
379
|
+
marketId: 'FED-25JAN',
|
|
380
|
+
outcomeId: 'FED-25JAN-YES',
|
|
381
|
+
side: 'sell',
|
|
382
|
+
type: 'market',
|
|
383
|
+
amount: 5 // Price not needed for market orders
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Parameters**: `CreateOrderParams`
|
|
388
|
+
```typescript
|
|
389
|
+
interface CreateOrderParams {
|
|
390
|
+
marketId: string;
|
|
391
|
+
outcomeId: string; // Use outcome.id from market data
|
|
392
|
+
side: 'buy' | 'sell';
|
|
393
|
+
type: 'market' | 'limit';
|
|
394
|
+
amount: number; // Number of contracts/shares
|
|
395
|
+
price?: number; // Required for limit orders (0.0-1.0)
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Returns**: `Order`
|
|
400
|
+
```typescript
|
|
401
|
+
interface Order {
|
|
402
|
+
id: string;
|
|
403
|
+
marketId: string;
|
|
404
|
+
outcomeId: string;
|
|
405
|
+
side: 'buy' | 'sell';
|
|
406
|
+
type: 'market' | 'limit';
|
|
407
|
+
price?: number;
|
|
408
|
+
amount: number;
|
|
409
|
+
status: 'pending' | 'open' | 'filled' | 'cancelled' | 'rejected';
|
|
410
|
+
filled: number; // Amount filled so far
|
|
411
|
+
remaining: number; // Amount remaining
|
|
412
|
+
timestamp: number;
|
|
413
|
+
fee?: number;
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### `cancelOrder(orderId)`
|
|
418
|
+
Cancel an open order.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
const cancelledOrder = await polymarket.cancelOrder('order-123');
|
|
422
|
+
console.log(cancelledOrder.status); // 'cancelled'
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Returns**: `Order` (with updated status)
|
|
426
|
+
|
|
427
|
+
### `fetchOrder(orderId)`
|
|
428
|
+
Get details of a specific order.
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
const order = await kalshi.fetchOrder('order-456');
|
|
432
|
+
console.log(`Filled: ${order.filled}/${order.amount}`);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Returns**: `Order`
|
|
436
|
+
|
|
437
|
+
### `fetchOpenOrders(marketId?)`
|
|
438
|
+
Get all open orders, optionally filtered by market.
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// All open orders
|
|
442
|
+
const allOrders = await polymarket.fetchOpenOrders();
|
|
443
|
+
|
|
444
|
+
// Open orders for specific market
|
|
445
|
+
const marketOrders = await kalshi.fetchOpenOrders('FED-25JAN');
|
|
446
|
+
|
|
447
|
+
allOrders.forEach(order => {
|
|
448
|
+
console.log(`${order.side} ${order.amount} @ ${order.price}`);
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Returns**: `Order[]`
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Complete Trading Workflow
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import pmxt from 'pmxtjs';
|
|
460
|
+
|
|
461
|
+
const exchange = new pmxt.Polymarket({
|
|
462
|
+
privateKey: process.env.POLYMARKET_PRIVATE_KEY
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// 1. Check balance
|
|
466
|
+
const [balance] = await exchange.fetchBalance();
|
|
467
|
+
console.log(`Available: $${balance.available}`);
|
|
468
|
+
|
|
469
|
+
// 2. Search for a market
|
|
470
|
+
const markets = await exchange.searchMarkets('Trump');
|
|
471
|
+
const market = markets[0];
|
|
472
|
+
const outcome = market.outcomes[0];
|
|
473
|
+
|
|
474
|
+
// 3. Place a limit order
|
|
475
|
+
const order = await exchange.createOrder({
|
|
476
|
+
marketId: market.id,
|
|
477
|
+
outcomeId: outcome.id,
|
|
478
|
+
side: 'buy',
|
|
479
|
+
type: 'limit',
|
|
480
|
+
amount: 10,
|
|
481
|
+
price: 0.50
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
console.log(`Order placed: ${order.id}`);
|
|
485
|
+
|
|
486
|
+
// 4. Check order status
|
|
487
|
+
const updatedOrder = await exchange.fetchOrder(order.id);
|
|
488
|
+
console.log(`Status: ${updatedOrder.status}`);
|
|
489
|
+
console.log(`Filled: ${updatedOrder.filled}/${updatedOrder.amount}`);
|
|
490
|
+
|
|
491
|
+
// 5. Cancel if needed
|
|
492
|
+
if (updatedOrder.status === 'open') {
|
|
493
|
+
await exchange.cancelOrder(order.id);
|
|
494
|
+
console.log('Order cancelled');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 6. Check positions
|
|
498
|
+
const positions = await exchange.fetchPositions();
|
|
499
|
+
positions.forEach(pos => {
|
|
500
|
+
console.log(`${pos.outcomeLabel}: ${pos.unrealizedPnL > 0 ? '+' : ''}$${pos.unrealizedPnL.toFixed(2)}`);
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## Quick Reference
|
|
507
|
+
|
|
508
|
+
- **Prices**: Always 0.0-1.0 (multiply by 100 for %)
|
|
509
|
+
- **Timestamps**: Unix milliseconds
|
|
510
|
+
- **Volumes**: USD
|
|
511
|
+
- **IDs**: Use `outcome.id` for deep-dive methods, not `market.id`
|
|
512
|
+
- **Authentication**: Required for all trading and account methods
|
|
513
|
+
|
|
514
|
+
For more examples, see [`examples/`](examples/).
|
|
515
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 qoery.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PMXT Server Launcher
|
|
5
|
+
*
|
|
6
|
+
* This script ensures the PMXT sidecar server is running.
|
|
7
|
+
* It's designed to be called by SDKs in any language (Python, Java, C#, Go, etc.)
|
|
8
|
+
*
|
|
9
|
+
* Behavior:
|
|
10
|
+
* 1. Check if server is already running (via lock file)
|
|
11
|
+
* 2. If running, exit successfully
|
|
12
|
+
* 3. If not running, spawn the server and wait for health check
|
|
13
|
+
* 4. Exit with code 0 on success, 1 on failure
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const { spawn } = require('child_process');
|
|
20
|
+
const http = require('http');
|
|
21
|
+
|
|
22
|
+
const LOCK_FILE = path.join(os.homedir(), '.pmxt', 'server.lock');
|
|
23
|
+
const DEFAULT_PORT = 3847;
|
|
24
|
+
const HEALTH_CHECK_TIMEOUT = 10000; // 10 seconds
|
|
25
|
+
const HEALTH_CHECK_INTERVAL = 100; // 100ms
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if the server is currently running
|
|
29
|
+
*/
|
|
30
|
+
function isServerRunning() {
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.existsSync(LOCK_FILE)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const lockData = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
37
|
+
const { pid, port } = lockData;
|
|
38
|
+
|
|
39
|
+
// Check if process exists
|
|
40
|
+
try {
|
|
41
|
+
process.kill(pid, 0); // Signal 0 checks existence without killing
|
|
42
|
+
return { running: true, port };
|
|
43
|
+
} catch (err) {
|
|
44
|
+
// Process doesn't exist, remove stale lock file
|
|
45
|
+
fs.unlinkSync(LOCK_FILE);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Wait for server to respond to health check
|
|
55
|
+
*/
|
|
56
|
+
function waitForHealth(port, timeout = HEALTH_CHECK_TIMEOUT) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
|
|
60
|
+
const checkHealth = () => {
|
|
61
|
+
const req = http.get(`http://localhost:${port}/health`, (res) => {
|
|
62
|
+
if (res.statusCode === 200) {
|
|
63
|
+
resolve(true);
|
|
64
|
+
} else {
|
|
65
|
+
scheduleNextCheck();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
req.on('error', () => {
|
|
70
|
+
scheduleNextCheck();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
req.setTimeout(1000);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const scheduleNextCheck = () => {
|
|
77
|
+
if (Date.now() - startTime > timeout) {
|
|
78
|
+
reject(new Error('Server health check timeout'));
|
|
79
|
+
} else {
|
|
80
|
+
setTimeout(checkHealth, HEALTH_CHECK_INTERVAL);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
checkHealth();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Start the PMXT server
|
|
90
|
+
*/
|
|
91
|
+
async function startServer() {
|
|
92
|
+
// 1. Try to find the server binary/script
|
|
93
|
+
let serverCmd = 'pmxt-server';
|
|
94
|
+
let args = [];
|
|
95
|
+
|
|
96
|
+
// Local dev search: Check for ../dist/server/index.js relative to this script
|
|
97
|
+
const localDistServer = path.join(__dirname, '..', 'dist', 'server', 'index.js');
|
|
98
|
+
const localBinServer = path.join(__dirname, 'pmxt-server');
|
|
99
|
+
|
|
100
|
+
if (fs.existsSync(localDistServer)) {
|
|
101
|
+
serverCmd = 'node';
|
|
102
|
+
args = [localDistServer];
|
|
103
|
+
} else if (fs.existsSync(localBinServer)) {
|
|
104
|
+
serverCmd = localBinServer;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Spawn server as detached process
|
|
108
|
+
const serverProcess = spawn(serverCmd, args, {
|
|
109
|
+
detached: true,
|
|
110
|
+
stdio: 'ignore',
|
|
111
|
+
env: process.env
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Detach from parent process
|
|
115
|
+
serverProcess.unref();
|
|
116
|
+
|
|
117
|
+
// Wait for server to be ready
|
|
118
|
+
await waitForHealth(DEFAULT_PORT);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Main entry point
|
|
123
|
+
*/
|
|
124
|
+
async function main() {
|
|
125
|
+
try {
|
|
126
|
+
// Check if server is already running
|
|
127
|
+
const serverStatus = isServerRunning();
|
|
128
|
+
|
|
129
|
+
if (serverStatus && serverStatus.running) {
|
|
130
|
+
// Server is running, verify it's healthy
|
|
131
|
+
try {
|
|
132
|
+
await waitForHealth(serverStatus.port, 2000);
|
|
133
|
+
process.exit(0);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
// Server process exists but not responding, try to start fresh
|
|
136
|
+
console.error('Server process exists but not responding, starting fresh...');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Start the server
|
|
141
|
+
await startServer();
|
|
142
|
+
process.exit(0);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error('Failed to ensure server is running:', err.message);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
main();
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resetCache = resetCache;
|
|
6
7
|
exports.fetchMarkets = fetchMarkets;
|
|
7
8
|
const axios_1 = __importDefault(require("axios"));
|
|
8
9
|
const utils_1 = require("./utils");
|
|
@@ -36,12 +37,17 @@ async function fetchActiveEvents(targetMarketCount) {
|
|
|
36
37
|
totalMarketCount += (event.markets || []).length;
|
|
37
38
|
}
|
|
38
39
|
// Early termination: if we have enough markets, stop fetching
|
|
39
|
-
|
|
40
|
+
// Use 1.5x multiplier to ensure we have enough for sorting/filtering
|
|
41
|
+
if (totalMarketCount >= targetMarketCount * 1.5) {
|
|
40
42
|
break;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
cursor = response.data.cursor;
|
|
44
46
|
page++;
|
|
47
|
+
// Additional safety: if no target specified, limit to reasonable number of pages
|
|
48
|
+
if (!targetMarketCount && page >= 10) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
52
|
catch (e) {
|
|
47
53
|
console.error(`Error fetching Kalshi page ${page}:`, e);
|
|
@@ -67,18 +73,44 @@ async function fetchSeriesMap() {
|
|
|
67
73
|
return new Map();
|
|
68
74
|
}
|
|
69
75
|
}
|
|
76
|
+
// Simple in-memory cache to avoid redundant API calls within a short period
|
|
77
|
+
let cachedEvents = null;
|
|
78
|
+
let cachedSeriesMap = null;
|
|
79
|
+
let lastCacheTime = 0;
|
|
80
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
81
|
+
// Export a function to reset the cache (useful for testing)
|
|
82
|
+
function resetCache() {
|
|
83
|
+
cachedEvents = null;
|
|
84
|
+
cachedSeriesMap = null;
|
|
85
|
+
lastCacheTime = 0;
|
|
86
|
+
}
|
|
70
87
|
async function fetchMarkets(params) {
|
|
71
88
|
const limit = params?.limit || 50;
|
|
89
|
+
const now = Date.now();
|
|
72
90
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
let events;
|
|
92
|
+
let seriesMap;
|
|
93
|
+
if (cachedEvents && cachedSeriesMap && (now - lastCacheTime < CACHE_TTL)) {
|
|
94
|
+
events = cachedEvents;
|
|
95
|
+
seriesMap = cachedSeriesMap;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Fetch active events with nested markets
|
|
99
|
+
// We also fetch Series metadata to get tags (tags are on Series, not Event)
|
|
100
|
+
const [allEvents, fetchedSeriesMap] = await Promise.all([
|
|
101
|
+
fetchActiveEvents(limit),
|
|
102
|
+
fetchSeriesMap()
|
|
103
|
+
]);
|
|
104
|
+
events = allEvents;
|
|
105
|
+
seriesMap = fetchedSeriesMap;
|
|
106
|
+
cachedEvents = allEvents;
|
|
107
|
+
cachedSeriesMap = fetchedSeriesMap;
|
|
108
|
+
lastCacheTime = now;
|
|
109
|
+
}
|
|
79
110
|
// Extract ALL markets from all events
|
|
80
111
|
const allMarkets = [];
|
|
81
|
-
|
|
112
|
+
// ... rest of the logic
|
|
113
|
+
for (const event of events) {
|
|
82
114
|
// Enrich event with tags from Series
|
|
83
115
|
if (event.series_ticker && seriesMap.has(event.series_ticker)) {
|
|
84
116
|
// If event has no tags or empty tags, use series tags
|
package/dist/server/app.js
CHANGED
|
@@ -8,8 +8,8 @@ const express_1 = __importDefault(require("express"));
|
|
|
8
8
|
const cors_1 = __importDefault(require("cors"));
|
|
9
9
|
const polymarket_1 = require("../exchanges/polymarket");
|
|
10
10
|
const kalshi_1 = require("../exchanges/kalshi");
|
|
11
|
-
// Singleton instances for local usage
|
|
12
|
-
const
|
|
11
|
+
// Singleton instances for local usage (when no credentials provided)
|
|
12
|
+
const defaultExchanges = {
|
|
13
13
|
polymarket: null,
|
|
14
14
|
kalshi: null
|
|
15
15
|
};
|
|
@@ -22,17 +22,26 @@ async function startServer(port) {
|
|
|
22
22
|
res.json({ status: 'ok', timestamp: Date.now() });
|
|
23
23
|
});
|
|
24
24
|
// API endpoint: POST /api/:exchange/:method
|
|
25
|
-
// Body: { args: any[] }
|
|
25
|
+
// Body: { args: any[], credentials?: ExchangeCredentials }
|
|
26
26
|
app.post('/api/:exchange/:method', async (req, res, next) => {
|
|
27
27
|
try {
|
|
28
28
|
const exchangeName = req.params.exchange.toLowerCase();
|
|
29
29
|
const methodName = req.params.method;
|
|
30
30
|
const args = Array.isArray(req.body.args) ? req.body.args : [];
|
|
31
|
+
const credentials = req.body.credentials;
|
|
31
32
|
// 1. Get or Initialize Exchange
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
// If credentials are provided, create a new instance for this request
|
|
34
|
+
// Otherwise, use the singleton instance
|
|
35
|
+
let exchange;
|
|
36
|
+
if (credentials && (credentials.privateKey || credentials.apiKey)) {
|
|
37
|
+
exchange = createExchange(exchangeName, credentials);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (!defaultExchanges[exchangeName]) {
|
|
41
|
+
defaultExchanges[exchangeName] = createExchange(exchangeName);
|
|
42
|
+
}
|
|
43
|
+
exchange = defaultExchanges[exchangeName];
|
|
34
44
|
}
|
|
35
|
-
const exchange = exchanges[exchangeName];
|
|
36
45
|
// 2. Validate Method
|
|
37
46
|
if (typeof exchange[methodName] !== 'function') {
|
|
38
47
|
res.status(404).json({ success: false, error: `Method '${methodName}' not found on ${exchangeName}` });
|
|
@@ -59,19 +68,19 @@ async function startServer(port) {
|
|
|
59
68
|
});
|
|
60
69
|
return app.listen(port);
|
|
61
70
|
}
|
|
62
|
-
function createExchange(name) {
|
|
71
|
+
function createExchange(name, credentials) {
|
|
63
72
|
switch (name) {
|
|
64
73
|
case 'polymarket':
|
|
65
74
|
return new polymarket_1.PolymarketExchange({
|
|
66
|
-
privateKey: process.env.POLYMARKET_PK || process.env.POLYMARKET_PRIVATE_KEY,
|
|
67
|
-
apiKey: process.env.POLYMARKET_API_KEY,
|
|
68
|
-
apiSecret: process.env.POLYMARKET_API_SECRET,
|
|
69
|
-
passphrase: process.env.POLYMARKET_PASSPHRASE
|
|
75
|
+
privateKey: credentials?.privateKey || process.env.POLYMARKET_PK || process.env.POLYMARKET_PRIVATE_KEY,
|
|
76
|
+
apiKey: credentials?.apiKey || process.env.POLYMARKET_API_KEY,
|
|
77
|
+
apiSecret: credentials?.apiSecret || process.env.POLYMARKET_API_SECRET,
|
|
78
|
+
passphrase: credentials?.passphrase || process.env.POLYMARKET_PASSPHRASE
|
|
70
79
|
});
|
|
71
80
|
case 'kalshi':
|
|
72
81
|
return new kalshi_1.KalshiExchange({
|
|
73
|
-
apiKey: process.env.KALSHI_API_KEY,
|
|
74
|
-
privateKey: process.env.KALSHI_PRIVATE_KEY
|
|
82
|
+
apiKey: credentials?.apiKey || process.env.KALSHI_API_KEY,
|
|
83
|
+
privateKey: credentials?.privateKey || process.env.KALSHI_PRIVATE_KEY
|
|
75
84
|
});
|
|
76
85
|
default:
|
|
77
86
|
throw new Error(`Unknown exchange: ${name}`);
|
package/package.json
CHANGED
|
@@ -1,11 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxt-core",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.js",
|
|
3
|
+
"version": "1.0.0-b3",
|
|
4
|
+
"description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/pmxt-dev/pmxt.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"pmxt-server": "./dist/server/index.js",
|
|
13
|
+
"pmxt-ensure-server": "./bin/pmxt-ensure-server"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"bin",
|
|
18
|
+
"API_REFERENCE.md"
|
|
19
|
+
],
|
|
20
|
+
"directories": {
|
|
21
|
+
"example": "examples",
|
|
22
|
+
"test": "test"
|
|
23
|
+
},
|
|
6
24
|
"scripts": {
|
|
7
|
-
"
|
|
25
|
+
"clean": "rm -rf dist",
|
|
26
|
+
"prebuild": "npm run clean",
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"test": "jest -c jest.config.js",
|
|
29
|
+
"server": "tsx watch src/server/index.ts",
|
|
30
|
+
"server:prod": "node dist/server/index.js",
|
|
31
|
+
"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.0.0b3,library=urllib3",
|
|
32
|
+
"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.0.0-b3,supportsES6=true,typescriptThreePlus=true",
|
|
33
|
+
"generate:docs": "node ../scripts/generate-api-docs.js",
|
|
34
|
+
"generate:sdk:all": "npm run generate:sdk:python && npm run generate:sdk:typescript && npm run generate:docs"
|
|
8
35
|
},
|
|
36
|
+
"keywords": [],
|
|
9
37
|
"author": "",
|
|
10
|
-
"license": "
|
|
11
|
-
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"type": "commonjs",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@polymarket/clob-client": "^5.2.0",
|
|
42
|
+
"@types/express": "^5.0.6",
|
|
43
|
+
"axios": "^1.7.9",
|
|
44
|
+
"cors": "^2.8.5",
|
|
45
|
+
"dotenv": "^17.2.3",
|
|
46
|
+
"ethers": "^5.8.0",
|
|
47
|
+
"express": "^5.2.1",
|
|
48
|
+
"jest": "^30.2.0",
|
|
49
|
+
"tsx": "^4.21.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@openapitools/openapi-generator-cli": "^2.27.0",
|
|
53
|
+
"@types/cors": "^2.8.19",
|
|
54
|
+
"@types/jest": "^30.0.0",
|
|
55
|
+
"@types/node": "^25.0.3",
|
|
56
|
+
"handlebars": "^4.7.8",
|
|
57
|
+
"identity-obj-proxy": "^3.0.0",
|
|
58
|
+
"js-yaml": "^3.14.2",
|
|
59
|
+
"ts-jest": "^29.4.6",
|
|
60
|
+
"typescript": "^5.9.3"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/.env
DELETED
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log("pmxt-core placeholder");
|
package/pmxt-core-0.4.4.tgz
DELETED
|
Binary file
|