pmxt-core 2.51.1 → 2.51.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/API_REFERENCE.md +17 -28
- package/bin/pmxt-ensure-server +22 -1
- package/bin/pmxt-ensure-server.js +64 -3
- package/dist/BaseExchange.d.ts +34 -0
- package/dist/BaseExchange.js +44 -2
- package/dist/exchanges/hunch/fetcher.d.ts +4 -0
- package/dist/exchanges/hunch/fetcher.js +19 -6
- package/dist/exchanges/hunch/utils.d.ts +8 -0
- package/dist/exchanges/hunch/utils.js +61 -7
- package/dist/exchanges/hyperliquid/normalizer.js +1 -1
- package/dist/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/kalshi/fetcher.js +13 -0
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/limitless/index.js +4 -1
- package/dist/exchanges/metaculus/index.js +2 -1
- package/dist/exchanges/mock/index.d.ts +1 -1
- package/dist/exchanges/mock/index.js +37 -3
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/myriad/index.js +1 -1
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/opinion/index.js +1 -1
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket_us/config.js +4 -2
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/probable/index.js +1 -1
- package/dist/exchanges/rain/fetcher.d.ts +2 -1
- package/dist/exchanges/rain/fetcher.js +5 -6
- package/dist/exchanges/rain/index.js +5 -1
- package/dist/exchanges/smarkets/config.js +2 -1
- package/dist/exchanges/smarkets/fetcher.js +16 -6
- package/dist/server/app.d.ts +9 -0
- package/dist/server/app.js +34 -16
- package/dist/server/exchange-factory.js +9 -3
- package/dist/server/openapi.yaml +14 -2
- package/dist/types.d.ts +2 -0
- package/package.json +3 -3
package/API_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Prediction Market API Reference
|
|
2
2
|
|
|
3
|
-
A unified interface for interacting with multiple prediction market exchanges
|
|
3
|
+
A unified interface for interacting with multiple prediction market exchanges and venues through one interface.
|
|
4
4
|
|
|
5
5
|
## Installation & Usage
|
|
6
6
|
|
|
@@ -8,33 +8,22 @@ A unified interface for interacting with multiple prediction market exchanges (K
|
|
|
8
8
|
npm install pmxtjs
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Import Styles
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import
|
|
14
|
+
import { Polymarket, Kalshi } from 'pmxtjs';
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
const kalshi = new
|
|
16
|
+
const polymarket = new Polymarket();
|
|
17
|
+
const kalshi = new Kalshi();
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
### Note for ESM Users
|
|
21
|
-
|
|
22
|
-
**pmxt is currently CommonJS-only.** If you're using `"type": "module"` in your `package.json`, you have two options:
|
|
23
|
-
|
|
24
|
-
**Option 1: Default import (recommended)**
|
|
25
20
|
```typescript
|
|
26
21
|
import pmxt from 'pmxtjs';
|
|
27
|
-
const poly = new pmxt.Polymarket();
|
|
28
|
-
```
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const pmxt = await import('pmxtjs');
|
|
33
|
-
const poly = new pmxt.default.Polymarket();
|
|
23
|
+
const polymarket = new pmxt.Polymarket();
|
|
24
|
+
const kalshi = new pmxt.Kalshi();
|
|
34
25
|
```
|
|
35
26
|
|
|
36
|
-
**Note**: Named exports like `import { Polymarket } from 'pmxtjs'` will **not work** in ESM projects.
|
|
37
|
-
|
|
38
27
|
---
|
|
39
28
|
|
|
40
29
|
## Core Methods
|
|
@@ -46,8 +35,8 @@ Get active markets from an exchange. This is the unified method for all market f
|
|
|
46
35
|
- `limit`: Number of results (default: 20)
|
|
47
36
|
- `offset`: Pagination offset
|
|
48
37
|
- `sort`: 'volume' | 'liquidity' | 'newest'
|
|
49
|
-
- `query`: Search text
|
|
50
|
-
- `slug`: Market slug or ticker
|
|
38
|
+
- `query`: Search text
|
|
39
|
+
- `slug`: Market slug or ticker
|
|
51
40
|
|
|
52
41
|
```typescript
|
|
53
42
|
// 1. Fetch recent markets
|
|
@@ -56,13 +45,13 @@ const markets = await polymarket.fetchMarkets({
|
|
|
56
45
|
sort: 'volume'
|
|
57
46
|
});
|
|
58
47
|
|
|
59
|
-
// 2. Search by text
|
|
48
|
+
// 2. Search by text
|
|
60
49
|
const results = await kalshi.fetchMarkets({
|
|
61
50
|
query: 'Fed rates',
|
|
62
51
|
limit: 10
|
|
63
52
|
});
|
|
64
53
|
|
|
65
|
-
// 3. Fetch by slug/ticker
|
|
54
|
+
// 3. Fetch by slug/ticker
|
|
66
55
|
const market = await polymarket.fetchMarkets({
|
|
67
56
|
slug: 'who-will-trump-nominate-as-fed-chair'
|
|
68
57
|
});
|
|
@@ -140,7 +129,7 @@ Calculate the volume-weighted average price for a given amount. Returns `0` if t
|
|
|
140
129
|
|
|
141
130
|
```typescript
|
|
142
131
|
const orderBook = await polymarket.fetchOrderBook(outcomeId);
|
|
143
|
-
const price =
|
|
132
|
+
const price = polymarket.getExecutionPrice(orderBook, 'buy', 100);
|
|
144
133
|
console.log(`Average price for 100 shares: ${price}`);
|
|
145
134
|
```
|
|
146
135
|
|
|
@@ -148,7 +137,7 @@ console.log(`Average price for 100 shares: ${price}`);
|
|
|
148
137
|
Calculate detailed execution price information, including partial fills.
|
|
149
138
|
|
|
150
139
|
```typescript
|
|
151
|
-
const detailed =
|
|
140
|
+
const detailed = polymarket.getExecutionPriceDetailed(orderBook, 'buy', 100);
|
|
152
141
|
console.log(`Average Price: ${detailed.price}`);
|
|
153
142
|
console.log(`Filled Amount: ${detailed.filledAmount}`);
|
|
154
143
|
console.log(`Fully Filled: ${detailed.fullyFilled}`);
|
|
@@ -275,10 +264,10 @@ console.log(`Spread: ${(spread * 100).toFixed(2)}%`);
|
|
|
275
264
|
|
|
276
265
|
```typescript
|
|
277
266
|
try {
|
|
278
|
-
const
|
|
267
|
+
const market = await kalshi.fetchMarket({ slug: 'INVALID-TICKER' });
|
|
268
|
+
console.log(market.title);
|
|
279
269
|
} catch (error) {
|
|
280
|
-
//
|
|
281
|
-
// Polymarket: Returns empty array []
|
|
270
|
+
// Throws MarketNotFound when no market matches the lookup
|
|
282
271
|
console.error(error.message);
|
|
283
272
|
}
|
|
284
273
|
```
|
|
@@ -310,7 +299,7 @@ import pmxt, {
|
|
|
310
299
|
|
|
311
300
|
## Authentication & Trading
|
|
312
301
|
|
|
313
|
-
|
|
302
|
+
Authenticated trading is available on venues that expose trading APIs. You must provide the venue-specific credentials when initializing the exchange; the examples below show Polymarket and Kalshi.
|
|
314
303
|
|
|
315
304
|
### Polymarket Authentication
|
|
316
305
|
|
package/bin/pmxt-ensure-server
CHANGED
|
@@ -39,7 +39,7 @@ function isServerRunning() {
|
|
|
39
39
|
// Check if process exists
|
|
40
40
|
try {
|
|
41
41
|
process.kill(pid, 0); // Signal 0 checks existence without killing
|
|
42
|
-
return { running: true, port };
|
|
42
|
+
return { running: true, pid, port };
|
|
43
43
|
} catch (err) {
|
|
44
44
|
// Process doesn't exist, remove stale lock file
|
|
45
45
|
fs.unlinkSync(LOCK_FILE);
|
|
@@ -50,6 +50,26 @@ function isServerRunning() {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Remove the lock we just proved unhealthy, without deleting a newer sidecar's
|
|
55
|
+
* lock if another launcher won the race and replaced it first.
|
|
56
|
+
*/
|
|
57
|
+
function removeLockFileIfMatches(staleLock) {
|
|
58
|
+
try {
|
|
59
|
+
if (!fs.existsSync(LOCK_FILE)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const currentLock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
63
|
+
if (currentLock.pid === staleLock.pid && currentLock.port === staleLock.port) {
|
|
64
|
+
fs.unlinkSync(LOCK_FILE);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
/**
|
|
54
74
|
* Wait for server to respond to health check
|
|
55
75
|
*/
|
|
@@ -196,6 +216,7 @@ async function main() {
|
|
|
196
216
|
} catch (err) {
|
|
197
217
|
// Server process exists but not responding, try to start fresh
|
|
198
218
|
console.error('Server process exists but not responding, starting fresh...');
|
|
219
|
+
removeLockFileIfMatches(serverStatus);
|
|
199
220
|
}
|
|
200
221
|
}
|
|
201
222
|
|
|
@@ -39,7 +39,7 @@ function isServerRunning() {
|
|
|
39
39
|
// Check if process exists
|
|
40
40
|
try {
|
|
41
41
|
process.kill(pid, 0); // Signal 0 checks existence without killing
|
|
42
|
-
return { running: true, port };
|
|
42
|
+
return { running: true, pid, port };
|
|
43
43
|
} catch (err) {
|
|
44
44
|
// Process doesn't exist, remove stale lock file
|
|
45
45
|
fs.unlinkSync(LOCK_FILE);
|
|
@@ -50,6 +50,26 @@ function isServerRunning() {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Remove the lock we just proved unhealthy, without deleting a newer sidecar's
|
|
55
|
+
* lock if another launcher won the race and replaced it first.
|
|
56
|
+
*/
|
|
57
|
+
function removeLockFileIfMatches(staleLock) {
|
|
58
|
+
try {
|
|
59
|
+
if (!fs.existsSync(LOCK_FILE)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const currentLock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
63
|
+
if (currentLock.pid === staleLock.pid && currentLock.port === staleLock.port) {
|
|
64
|
+
fs.unlinkSync(LOCK_FILE);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
/**
|
|
54
74
|
* Wait for server to respond to health check
|
|
55
75
|
*/
|
|
@@ -85,6 +105,44 @@ function waitForHealth(port, timeout = HEALTH_CHECK_TIMEOUT) {
|
|
|
85
105
|
});
|
|
86
106
|
}
|
|
87
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Wait for the lock file to appear, then health-check the port it specifies.
|
|
110
|
+
*
|
|
111
|
+
* The sidecar may bind a non-default port when the default is occupied by an
|
|
112
|
+
* orphan. Polling DEFAULT_PORT would be fooled by the orphan, so we wait for
|
|
113
|
+
* the new sidecar to write its lock file and health-check THAT port.
|
|
114
|
+
*/
|
|
115
|
+
function waitForLockAndHealth(timeout = HEALTH_CHECK_TIMEOUT) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const startTime = Date.now();
|
|
118
|
+
|
|
119
|
+
const poll = () => {
|
|
120
|
+
if (Date.now() - startTime > timeout) {
|
|
121
|
+
return reject(new Error('Server health check timeout (lock file never appeared)'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
if (fs.existsSync(LOCK_FILE)) {
|
|
126
|
+
const lockData = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
|
127
|
+
const port = lockData.port || DEFAULT_PORT;
|
|
128
|
+
// Lock file appeared — now health-check the actual port
|
|
129
|
+
const remaining = timeout - (Date.now() - startTime);
|
|
130
|
+
waitForHealth(port, Math.max(remaining, 1000))
|
|
131
|
+
.then(resolve)
|
|
132
|
+
.catch(reject);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
// Lock file partially written or unreadable — retry
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setTimeout(poll, HEALTH_CHECK_INTERVAL);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
poll();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
88
146
|
/**
|
|
89
147
|
* Start the PMXT server
|
|
90
148
|
*/
|
|
@@ -136,8 +194,10 @@ async function startServer() {
|
|
|
136
194
|
// Detach from parent process
|
|
137
195
|
serverProcess.unref();
|
|
138
196
|
|
|
139
|
-
// Wait for
|
|
140
|
-
|
|
197
|
+
// Wait for the sidecar to write its lock file, then health-check
|
|
198
|
+
// the actual port it bound (which may differ from DEFAULT_PORT if
|
|
199
|
+
// an orphan sidecar is occupying it).
|
|
200
|
+
await waitForLockAndHealth();
|
|
141
201
|
}
|
|
142
202
|
|
|
143
203
|
/**
|
|
@@ -156,6 +216,7 @@ async function main() {
|
|
|
156
216
|
} catch (err) {
|
|
157
217
|
// Server process exists but not responding, try to start fresh
|
|
158
218
|
console.error('Server process exists but not responding, starting fresh...');
|
|
219
|
+
removeLockFileIfMatches(serverStatus);
|
|
159
220
|
}
|
|
160
221
|
}
|
|
161
222
|
|
package/dist/BaseExchange.d.ts
CHANGED
|
@@ -627,8 +627,42 @@ export declare abstract class PredictionMarketExchange {
|
|
|
627
627
|
* @returns Array of open orders
|
|
628
628
|
*/
|
|
629
629
|
fetchOpenOrders(marketId?: string): Promise<Order[]>;
|
|
630
|
+
/**
|
|
631
|
+
* Fetch authenticated user trade history.
|
|
632
|
+
*
|
|
633
|
+
* @param params - Optional trade history filters
|
|
634
|
+
* @param params.outcomeId - Filter by outcome token ID
|
|
635
|
+
* @param params.marketId - Filter by market ID or ticker
|
|
636
|
+
* @param params.since - Start timestamp or ISO date
|
|
637
|
+
* @param params.until - End timestamp or ISO date
|
|
638
|
+
* @param params.limit - Maximum number of trades
|
|
639
|
+
* @param params.cursor - Pagination cursor
|
|
640
|
+
* @returns Array of user trades
|
|
641
|
+
*/
|
|
630
642
|
fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]>;
|
|
643
|
+
/**
|
|
644
|
+
* Fetch authenticated closed orders.
|
|
645
|
+
*
|
|
646
|
+
* @param params - Optional order history filters
|
|
647
|
+
* @param params.marketId - Filter by market ID or ticker
|
|
648
|
+
* @param params.since - Start timestamp or ISO date
|
|
649
|
+
* @param params.until - End timestamp or ISO date
|
|
650
|
+
* @param params.limit - Maximum number of orders
|
|
651
|
+
* @param params.cursor - Pagination cursor
|
|
652
|
+
* @returns Array of closed orders
|
|
653
|
+
*/
|
|
631
654
|
fetchClosedOrders(params?: OrderHistoryParams): Promise<Order[]>;
|
|
655
|
+
/**
|
|
656
|
+
* Fetch authenticated order history across open and closed orders.
|
|
657
|
+
*
|
|
658
|
+
* @param params - Optional order history filters
|
|
659
|
+
* @param params.marketId - Filter by market ID or ticker
|
|
660
|
+
* @param params.since - Start timestamp or ISO date
|
|
661
|
+
* @param params.until - End timestamp or ISO date
|
|
662
|
+
* @param params.limit - Maximum number of orders
|
|
663
|
+
* @param params.cursor - Pagination cursor
|
|
664
|
+
* @returns Array of orders
|
|
665
|
+
*/
|
|
632
666
|
fetchAllOrders(params?: OrderHistoryParams): Promise<Order[]>;
|
|
633
667
|
/**
|
|
634
668
|
* Fetch current user positions across all markets.
|
package/dist/BaseExchange.js
CHANGED
|
@@ -213,7 +213,13 @@ class PredictionMarketExchange {
|
|
|
213
213
|
return limit !== undefined ? filtered.slice(start, start + limit) : filtered.slice(start);
|
|
214
214
|
}
|
|
215
215
|
const { limit, offset, ...venueParams } = fetchParams;
|
|
216
|
-
const
|
|
216
|
+
const hasVenueParams = Object.keys(venueParams).length > 0;
|
|
217
|
+
const shouldForwardSimpleLimit = limit !== undefined && offset === undefined && !hasVenueParams;
|
|
218
|
+
const markets = await this.fetchMarketsImpl(shouldForwardSimpleLimit
|
|
219
|
+
? { limit }
|
|
220
|
+
: hasVenueParams
|
|
221
|
+
? venueParams
|
|
222
|
+
: undefined);
|
|
217
223
|
const start = offset ?? 0;
|
|
218
224
|
return limit !== undefined ? markets.slice(start, start + limit) : markets.slice(start);
|
|
219
225
|
}
|
|
@@ -357,7 +363,9 @@ class PredictionMarketExchange {
|
|
|
357
363
|
return limit !== undefined ? filtered.slice(start, start + limit) : filtered.slice(start);
|
|
358
364
|
}
|
|
359
365
|
const { limit, offset, ...venueParams } = fetchParams;
|
|
360
|
-
const
|
|
366
|
+
const hasVenueParams = Object.keys(venueParams).length > 0;
|
|
367
|
+
const shouldForwardSimpleLimit = limit !== undefined && offset === undefined && !hasVenueParams;
|
|
368
|
+
const events = await this.fetchEventsImpl(shouldForwardSimpleLimit ? { limit } : venueParams);
|
|
361
369
|
const start = offset ?? 0;
|
|
362
370
|
return limit !== undefined ? events.slice(start, start + limit) : events.slice(start);
|
|
363
371
|
}
|
|
@@ -607,12 +615,46 @@ class PredictionMarketExchange {
|
|
|
607
615
|
async fetchOpenOrders(marketId) {
|
|
608
616
|
throw new Error("Method fetchOpenOrders not implemented.");
|
|
609
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* Fetch authenticated user trade history.
|
|
620
|
+
*
|
|
621
|
+
* @param params - Optional trade history filters
|
|
622
|
+
* @param params.outcomeId - Filter by outcome token ID
|
|
623
|
+
* @param params.marketId - Filter by market ID or ticker
|
|
624
|
+
* @param params.since - Start timestamp or ISO date
|
|
625
|
+
* @param params.until - End timestamp or ISO date
|
|
626
|
+
* @param params.limit - Maximum number of trades
|
|
627
|
+
* @param params.cursor - Pagination cursor
|
|
628
|
+
* @returns Array of user trades
|
|
629
|
+
*/
|
|
610
630
|
async fetchMyTrades(params) {
|
|
611
631
|
throw new Error("Method fetchMyTrades not implemented.");
|
|
612
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Fetch authenticated closed orders.
|
|
635
|
+
*
|
|
636
|
+
* @param params - Optional order history filters
|
|
637
|
+
* @param params.marketId - Filter by market ID or ticker
|
|
638
|
+
* @param params.since - Start timestamp or ISO date
|
|
639
|
+
* @param params.until - End timestamp or ISO date
|
|
640
|
+
* @param params.limit - Maximum number of orders
|
|
641
|
+
* @param params.cursor - Pagination cursor
|
|
642
|
+
* @returns Array of closed orders
|
|
643
|
+
*/
|
|
613
644
|
async fetchClosedOrders(params) {
|
|
614
645
|
throw new Error("Method fetchClosedOrders not implemented.");
|
|
615
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Fetch authenticated order history across open and closed orders.
|
|
649
|
+
*
|
|
650
|
+
* @param params - Optional order history filters
|
|
651
|
+
* @param params.marketId - Filter by market ID or ticker
|
|
652
|
+
* @param params.since - Start timestamp or ISO date
|
|
653
|
+
* @param params.until - End timestamp or ISO date
|
|
654
|
+
* @param params.limit - Maximum number of orders
|
|
655
|
+
* @param params.cursor - Pagination cursor
|
|
656
|
+
* @returns Array of orders
|
|
657
|
+
*/
|
|
616
658
|
async fetchAllOrders(params) {
|
|
617
659
|
throw new Error("Method fetchAllOrders not implemented.");
|
|
618
660
|
}
|
|
@@ -34,6 +34,10 @@ export interface HunchRawMarket {
|
|
|
34
34
|
/** Present in the schema; absent on the bare list endpoint — optional. */
|
|
35
35
|
volumeUsd?: number;
|
|
36
36
|
totalBets?: number;
|
|
37
|
+
/** Trailing-24h pool inflow (USD); absent on older API builds — optional. */
|
|
38
|
+
volume24hUsd?: number;
|
|
39
|
+
/** Live binary YES/NO odds on the list item; null/absent for N-way markets. */
|
|
40
|
+
odds?: HunchRawBinaryOdds | null;
|
|
37
41
|
targetMarketCapUsd: number | null;
|
|
38
42
|
outcomes: HunchRawOutcome[] | null;
|
|
39
43
|
headline?: string | null;
|
|
@@ -5,6 +5,8 @@ const utils_1 = require("./utils");
|
|
|
5
5
|
const errors_1 = require("./errors");
|
|
6
6
|
const AGENT_PREFIX = '/api/agent/v1';
|
|
7
7
|
const DEFAULT_LIMIT = 200;
|
|
8
|
+
/** Safety backstop for cursor draining: 50 pages * 200 = 10k markets. */
|
|
9
|
+
const MAX_LIST_PAGES = 50;
|
|
8
10
|
/**
|
|
9
11
|
* HunchFetcher — raw GETs against the live agent API. Hunch reads are keyless,
|
|
10
12
|
* so we hit the HTTP client directly (clearer + more robust than the implicit
|
|
@@ -28,6 +30,7 @@ class HunchFetcher {
|
|
|
28
30
|
const one = await this.fetchRawMarketById(params.slug);
|
|
29
31
|
return one ? [one] : [];
|
|
30
32
|
}
|
|
33
|
+
const explicitLimit = typeof params?.limit === 'number';
|
|
31
34
|
const query = {
|
|
32
35
|
limit: params?.limit ?? DEFAULT_LIMIT,
|
|
33
36
|
};
|
|
@@ -36,12 +39,22 @@ class HunchFetcher {
|
|
|
36
39
|
query.status = status;
|
|
37
40
|
if (params?.query)
|
|
38
41
|
query.token = params.query;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// A catalog crawl (no explicit limit) follows `nextCursor` to drain
|
|
43
|
+
// the whole list; an explicit limit fetches just that one page.
|
|
44
|
+
// MAX_LIST_PAGES is a safety backstop against a runaway cursor loop.
|
|
45
|
+
const all = [];
|
|
46
|
+
let cursor;
|
|
47
|
+
let pages = 0;
|
|
48
|
+
do {
|
|
49
|
+
const res = await this.ctx.http.get(`${this.baseUrl}${AGENT_PREFIX}/markets`, {
|
|
50
|
+
params: cursor ? { ...query, cursor } : query,
|
|
51
|
+
headers: this.ctx.getHeaders(),
|
|
52
|
+
});
|
|
53
|
+
all.push(...(res.data?.markets ?? []));
|
|
54
|
+
cursor = typeof res.data?.nextCursor === 'string' ? res.data.nextCursor : undefined;
|
|
55
|
+
pages += 1;
|
|
56
|
+
} while (!explicitLimit && cursor && pages < MAX_LIST_PAGES);
|
|
57
|
+
return all;
|
|
45
58
|
}
|
|
46
59
|
catch (error) {
|
|
47
60
|
throw errors_1.hunchErrorMapper.mapError(error);
|
|
@@ -45,6 +45,14 @@ export declare function parseHunchSide(outcomeId: string): {
|
|
|
45
45
|
marketId: string;
|
|
46
46
|
side: string;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Map a Hunch native category to a pmxt top-level category. Hunch is
|
|
50
|
+
* crypto-native, so every token/price/on-chain subtype rolls up to "Crypto";
|
|
51
|
+
* only the manual "event" markets (fights/debates) map elsewhere.
|
|
52
|
+
*/
|
|
53
|
+
export declare function mapHunchCategory(rawCategory: string | undefined): string;
|
|
54
|
+
/** Tags = top category + a human subtype label + the token (deduped, non-empty). */
|
|
55
|
+
export declare function hunchMarketTags(raw: HunchRawMarket): string[];
|
|
48
56
|
/**
|
|
49
57
|
* Shared market normalizer used by both the live fetch path and the (rare)
|
|
50
58
|
* direct-mapping helper. Pulls a Hunch market ref into a {@link UnifiedMarket}.
|
|
@@ -5,6 +5,8 @@ exports.mapHunchStatus = mapHunchStatus;
|
|
|
5
5
|
exports.mapStatusToHunch = mapStatusToHunch;
|
|
6
6
|
exports.buildOutcomeId = buildOutcomeId;
|
|
7
7
|
exports.parseHunchSide = parseHunchSide;
|
|
8
|
+
exports.mapHunchCategory = mapHunchCategory;
|
|
9
|
+
exports.hunchMarketTags = hunchMarketTags;
|
|
8
10
|
exports.mapHunchMarketToUnified = mapHunchMarketToUnified;
|
|
9
11
|
const market_utils_1 = require("../../utils/market-utils");
|
|
10
12
|
const metadata_1 = require("../../utils/metadata");
|
|
@@ -102,6 +104,55 @@ function parseHunchSide(outcomeId) {
|
|
|
102
104
|
side: outcomeId.slice(idx + 1),
|
|
103
105
|
};
|
|
104
106
|
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Category + tags — map Hunch's fine-grained native taxonomy onto pmxt's
|
|
109
|
+
// top-level categories (Crypto / Culture / …) + granular tags, so Hunch
|
|
110
|
+
// markets answer `?category=` filters and match with higher confidence.
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
/** Hunch native categories that are NOT crypto (manual-resolution markets). */
|
|
113
|
+
const HUNCH_TOP_CATEGORY = {
|
|
114
|
+
event: 'Culture',
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Map a Hunch native category to a pmxt top-level category. Hunch is
|
|
118
|
+
* crypto-native, so every token/price/on-chain subtype rolls up to "Crypto";
|
|
119
|
+
* only the manual "event" markets (fights/debates) map elsewhere.
|
|
120
|
+
*/
|
|
121
|
+
function mapHunchCategory(rawCategory) {
|
|
122
|
+
return HUNCH_TOP_CATEGORY[rawCategory ?? ''] ?? 'Crypto';
|
|
123
|
+
}
|
|
124
|
+
/** Human-readable granular label per Hunch native category (for tags). */
|
|
125
|
+
const HUNCH_SUBTYPE_LABEL = {
|
|
126
|
+
market_cap: 'Market Cap',
|
|
127
|
+
token_mcap_range: 'Market Cap',
|
|
128
|
+
token_mcap_flip: 'Market Cap',
|
|
129
|
+
token_mcap_close: 'Market Cap',
|
|
130
|
+
token_basket_mcap: 'Market Cap',
|
|
131
|
+
price_direction: 'Price',
|
|
132
|
+
token_price_range: 'Price',
|
|
133
|
+
token_return: 'Returns',
|
|
134
|
+
token_rank_milestone: 'Ranking',
|
|
135
|
+
chain_volume: 'On-chain Volume',
|
|
136
|
+
chain_throughput: 'Throughput',
|
|
137
|
+
chain_stablecoins: 'Stablecoins',
|
|
138
|
+
launchpad_volume: 'Launchpad',
|
|
139
|
+
dune_metric: 'On-chain',
|
|
140
|
+
volume_eta: 'Volume',
|
|
141
|
+
event: 'Event',
|
|
142
|
+
};
|
|
143
|
+
function titleCaseSlug(s) {
|
|
144
|
+
return s
|
|
145
|
+
.split(/[_\s-]+/)
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
148
|
+
.join(' ');
|
|
149
|
+
}
|
|
150
|
+
/** Tags = top category + a human subtype label + the token (deduped, non-empty). */
|
|
151
|
+
function hunchMarketTags(raw) {
|
|
152
|
+
const top = mapHunchCategory(raw.category);
|
|
153
|
+
const label = HUNCH_SUBTYPE_LABEL[raw.category] ?? (raw.category ? titleCaseSlug(raw.category) : '');
|
|
154
|
+
return [...new Set([top, label, raw.tokenSymbol].filter((t) => Boolean(t)))];
|
|
155
|
+
}
|
|
105
156
|
/**
|
|
106
157
|
* Shared market normalizer used by both the live fetch path and the (rare)
|
|
107
158
|
* direct-mapping helper. Pulls a Hunch market ref into a {@link UnifiedMarket}.
|
|
@@ -119,6 +170,9 @@ function mapHunchMarketToUnified(raw, odds, ladder) {
|
|
|
119
170
|
if (!raw || !raw.id)
|
|
120
171
|
return null;
|
|
121
172
|
const marketId = raw.id;
|
|
173
|
+
// Explicit odds (detail/quote read) win; else fall back to the live odds the
|
|
174
|
+
// list item now carries — so a bare list-crawl prices binary markets too.
|
|
175
|
+
const effectiveOdds = odds ?? raw.odds ?? null;
|
|
122
176
|
let outcomes;
|
|
123
177
|
if (Array.isArray(raw.outcomes) && raw.outcomes.length > 0) {
|
|
124
178
|
// N-way parimutuel ladder / date-window market.
|
|
@@ -148,9 +202,9 @@ function mapHunchMarketToUnified(raw, odds, ladder) {
|
|
|
148
202
|
}
|
|
149
203
|
else {
|
|
150
204
|
// Binary YES/NO market.
|
|
151
|
-
const yesPrice =
|
|
152
|
-
const noPrice =
|
|
153
|
-
?
|
|
205
|
+
const yesPrice = effectiveOdds && typeof effectiveOdds.yesPriceCents === 'number' ? effectiveOdds.yesPriceCents / 100 : 0;
|
|
206
|
+
const noPrice = effectiveOdds && typeof effectiveOdds.noPriceCents === 'number'
|
|
207
|
+
? effectiveOdds.noPriceCents / 100
|
|
154
208
|
: yesPrice > 0
|
|
155
209
|
? 1 - yesPrice
|
|
156
210
|
: 0;
|
|
@@ -166,13 +220,13 @@ function mapHunchMarketToUnified(raw, odds, ladder) {
|
|
|
166
220
|
slug: raw.slug,
|
|
167
221
|
outcomes,
|
|
168
222
|
resolutionDate: raw.deadlineAt ? new Date(raw.deadlineAt) : undefined,
|
|
169
|
-
//
|
|
170
|
-
volume24h: 0,
|
|
223
|
+
// Trailing-24h pool inflow off the list item (0 when absent / no trades).
|
|
224
|
+
volume24h: typeof raw.volume24hUsd === 'number' ? raw.volume24hUsd : 0,
|
|
171
225
|
volume: typeof raw.volumeUsd === 'number' ? raw.volumeUsd : undefined,
|
|
172
226
|
liquidity: Number(raw.virtualLiquidityUsd || 0),
|
|
173
227
|
url: raw.links?.app || `${exports.DEFAULT_BASE_URL}/markets/${raw.slug || marketId}`,
|
|
174
|
-
category: raw.category,
|
|
175
|
-
tags: raw
|
|
228
|
+
category: mapHunchCategory(raw.category),
|
|
229
|
+
tags: hunchMarketTags(raw),
|
|
176
230
|
status: mapHunchStatus(raw.status),
|
|
177
231
|
sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, exports.HUNCH_PROMOTED_MARKET_KEYS),
|
|
178
232
|
};
|
|
@@ -175,7 +175,7 @@ class HyperliquidNormalizer {
|
|
|
175
175
|
description: outcome.description,
|
|
176
176
|
slug: `hl-${outcomeId}`,
|
|
177
177
|
outcomes,
|
|
178
|
-
resolutionDate: expiryDate
|
|
178
|
+
resolutionDate: expiryDate,
|
|
179
179
|
volume24h: raw.volume24h ?? 0,
|
|
180
180
|
liquidity: 0,
|
|
181
181
|
url: buildMarketUrl(outcomeId),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
3
|
-
* Generated at: 2026-06-
|
|
3
|
+
* Generated at: 2026-06-24T09:17:49.567Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const kalshiApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.kalshiApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
6
|
-
* Generated at: 2026-06-
|
|
6
|
+
* Generated at: 2026-06-24T09:17:49.567Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.kalshiApiSpec = {
|
|
@@ -64,6 +64,19 @@ class KalshiFetcher {
|
|
|
64
64
|
return this.fetchAllWithSeriesTicker(params.series);
|
|
65
65
|
}
|
|
66
66
|
const status = params?.status || 'active';
|
|
67
|
+
const hasBoundedDefaultRead = status === 'active' &&
|
|
68
|
+
(params.limit !== undefined || params.cursor !== undefined) &&
|
|
69
|
+
!params.query &&
|
|
70
|
+
params.offset === undefined &&
|
|
71
|
+
params.sort === undefined &&
|
|
72
|
+
params.searchIn === undefined &&
|
|
73
|
+
params.category === undefined &&
|
|
74
|
+
params.tags === undefined &&
|
|
75
|
+
params.filter === undefined;
|
|
76
|
+
if (hasBoundedDefaultRead) {
|
|
77
|
+
const page = await this.fetchRawEventPage(params);
|
|
78
|
+
return page.events;
|
|
79
|
+
}
|
|
67
80
|
if (status === 'all') {
|
|
68
81
|
const openEvents = await this.fetchAllWithStatus('open');
|
|
69
82
|
const closedEvents = await this.fetchAllWithStatus('closed');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
3
|
-
* Generated at: 2026-06-
|
|
3
|
+
* Generated at: 2026-06-24T09:17:49.606Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const limitlessApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.limitlessApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
6
|
-
* Generated at: 2026-06-
|
|
6
|
+
* Generated at: 2026-06-24T09:17:49.606Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.limitlessApiSpec = {
|
|
@@ -100,7 +100,10 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
100
100
|
callApi: this.callApi.bind(this),
|
|
101
101
|
getHeaders: () => this.getHeaders(),
|
|
102
102
|
};
|
|
103
|
-
const limitlessBaseUrl = this.auth?.host ||
|
|
103
|
+
const limitlessBaseUrl = this.auth?.host ||
|
|
104
|
+
credentials?.baseUrl ||
|
|
105
|
+
process.env.LIMITLESS_BASE_URL ||
|
|
106
|
+
utils_1.DEFAULT_LIMITLESS_API_URL;
|
|
104
107
|
this.fetcher = new fetcher_1.LimitlessFetcher(ctx, this.http, this.auth?.getApiKey(), limitlessBaseUrl);
|
|
105
108
|
this.normalizer = new normalizer_1.LimitlessNormalizer();
|
|
106
109
|
}
|
|
@@ -58,7 +58,8 @@ class MetaculusExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
58
58
|
this.apiToken = credentials?.apiToken;
|
|
59
59
|
// Rate-limit conservatively; authenticated users get higher Metaculus quotas
|
|
60
60
|
this.rateLimit = 500;
|
|
61
|
-
this.baseUrl =
|
|
61
|
+
this.baseUrl =
|
|
62
|
+
credentials?.baseUrl || process.env.METACULUS_BASE_URL || utils_1.DEFAULT_BASE_URL;
|
|
62
63
|
const descriptor = (0, openapi_1.parseOpenApiSpec)(api_1.metaculusApiSpec, this.baseUrl);
|
|
63
64
|
this.defineImplicitApi(descriptor);
|
|
64
65
|
}
|
|
@@ -43,7 +43,7 @@ export declare class MockExchange extends PredictionMarketExchange {
|
|
|
43
43
|
fillOrder(orderId: string, amount?: number): Promise<Order>;
|
|
44
44
|
cancelOrder(orderId: string): Promise<Order>;
|
|
45
45
|
fetchOrder(orderId: string): Promise<Order>;
|
|
46
|
-
fetchOpenOrders(
|
|
46
|
+
fetchOpenOrders(marketId?: string): Promise<Order[]>;
|
|
47
47
|
fetchMyTrades(_params?: {
|
|
48
48
|
outcomeId?: string;
|
|
49
49
|
marketId?: string;
|