pmxt-core 2.1.0 → 2.1.2
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/dist/BaseExchange.d.ts +8 -4
- package/dist/BaseExchange.js +6 -2
- package/dist/exchanges/kalshi/fetchEvents.js +49 -12
- package/dist/exchanges/kalshi/fetchMarkets.js +4 -4
- package/dist/exchanges/limitless/errors.js +4 -4
- package/dist/exchanges/limitless/fetchEvents.js +17 -5
- package/dist/exchanges/limitless/fetchMarkets.js +44 -7
- package/dist/exchanges/polymarket/fetchEvents.js +52 -23
- package/dist/exchanges/polymarket/fetchMarkets.js +40 -22
- package/dist/exchanges/polymarket/utils.d.ts +6 -0
- package/dist/exchanges/polymarket/utils.js +42 -1
- package/dist/utils/error-mapper.js +8 -0
- package/package.json +3 -3
package/dist/BaseExchange.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface MarketFilterParams {
|
|
|
4
4
|
limit?: number;
|
|
5
5
|
offset?: number;
|
|
6
6
|
sort?: 'volume' | 'liquidity' | 'newest';
|
|
7
|
-
status?: 'active' | 'closed' | 'all';
|
|
7
|
+
status?: 'active' | 'inactive' | 'closed' | 'all';
|
|
8
8
|
searchIn?: 'title' | 'description' | 'both';
|
|
9
9
|
query?: string;
|
|
10
10
|
slug?: string;
|
|
@@ -17,7 +17,7 @@ export interface EventFetchParams {
|
|
|
17
17
|
query?: string;
|
|
18
18
|
limit?: number;
|
|
19
19
|
offset?: number;
|
|
20
|
-
status?: 'active' | 'closed' | 'all';
|
|
20
|
+
status?: 'active' | 'inactive' | 'closed' | 'all';
|
|
21
21
|
searchIn?: 'title' | 'description' | 'both';
|
|
22
22
|
}
|
|
23
23
|
export interface HistoryFilterParams {
|
|
@@ -114,15 +114,17 @@ export declare abstract class PredictionMarketExchange {
|
|
|
114
114
|
* @param params.searchIn - Where to search ('title' | 'description' | 'both')
|
|
115
115
|
* @returns Array of unified markets
|
|
116
116
|
*
|
|
117
|
+
* @note Some exchanges (like Limitless) may only support status 'active' for search results.
|
|
118
|
+
*
|
|
117
119
|
* @example-ts Fetch markets
|
|
118
|
-
* const markets = await exchange.fetchMarkets({ query: 'Trump', limit:
|
|
120
|
+
* const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
|
|
119
121
|
* console.log(markets[0].title);
|
|
120
122
|
*
|
|
121
123
|
* @example-ts Get market by slug
|
|
122
124
|
* const markets = await exchange.fetchMarkets({ slug: 'will-trump-win' });
|
|
123
125
|
*
|
|
124
126
|
* @example-python Fetch markets
|
|
125
|
-
* markets = exchange.fetch_markets(query='Trump', limit=
|
|
127
|
+
* markets = exchange.fetch_markets(query='Trump', limit=10000)
|
|
126
128
|
* print(markets[0].title)
|
|
127
129
|
*
|
|
128
130
|
* @example-python Get market by slug
|
|
@@ -140,6 +142,8 @@ export declare abstract class PredictionMarketExchange {
|
|
|
140
142
|
* @param params.searchIn - Where to search ('title' | 'description' | 'both')
|
|
141
143
|
* @returns Array of unified events
|
|
142
144
|
*
|
|
145
|
+
* @note Some exchanges (like Limitless) may only support status 'active' for search results.
|
|
146
|
+
*
|
|
143
147
|
* @example-ts Search events
|
|
144
148
|
* const events = await exchange.fetchEvents({ query: 'Fed Chair' });
|
|
145
149
|
* const fedEvent = events[0];
|
package/dist/BaseExchange.js
CHANGED
|
@@ -22,15 +22,17 @@ class PredictionMarketExchange {
|
|
|
22
22
|
* @param params.searchIn - Where to search ('title' | 'description' | 'both')
|
|
23
23
|
* @returns Array of unified markets
|
|
24
24
|
*
|
|
25
|
+
* @note Some exchanges (like Limitless) may only support status 'active' for search results.
|
|
26
|
+
*
|
|
25
27
|
* @example-ts Fetch markets
|
|
26
|
-
* const markets = await exchange.fetchMarkets({ query: 'Trump', limit:
|
|
28
|
+
* const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
|
|
27
29
|
* console.log(markets[0].title);
|
|
28
30
|
*
|
|
29
31
|
* @example-ts Get market by slug
|
|
30
32
|
* const markets = await exchange.fetchMarkets({ slug: 'will-trump-win' });
|
|
31
33
|
*
|
|
32
34
|
* @example-python Fetch markets
|
|
33
|
-
* markets = exchange.fetch_markets(query='Trump', limit=
|
|
35
|
+
* markets = exchange.fetch_markets(query='Trump', limit=10000)
|
|
34
36
|
* print(markets[0].title)
|
|
35
37
|
*
|
|
36
38
|
* @example-python Get market by slug
|
|
@@ -50,6 +52,8 @@ class PredictionMarketExchange {
|
|
|
50
52
|
* @param params.searchIn - Where to search ('title' | 'description' | 'both')
|
|
51
53
|
* @returns Array of unified events
|
|
52
54
|
*
|
|
55
|
+
* @note Some exchanges (like Limitless) may only support status 'active' for search results.
|
|
56
|
+
*
|
|
53
57
|
* @example-ts Search events
|
|
54
58
|
* const events = await exchange.fetchEvents({ query: 'Fed Chair' });
|
|
55
59
|
* const fedEvent = events[0];
|
|
@@ -10,19 +10,57 @@ const errors_1 = require("./errors");
|
|
|
10
10
|
async function fetchEvents(params) {
|
|
11
11
|
try {
|
|
12
12
|
const status = params?.status || 'active';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const limit = params?.limit || 10000;
|
|
14
|
+
const query = (params?.query || '').toLowerCase();
|
|
15
|
+
const fetchAllWithStatus = async (apiStatus) => {
|
|
16
|
+
let allEvents = [];
|
|
17
|
+
let cursor = null;
|
|
18
|
+
let page = 0;
|
|
19
|
+
const MAX_PAGES = 1000; // Safety cap against infinite loops
|
|
20
|
+
const BATCH_SIZE = 200; // Max limit per Kalshi API docs
|
|
21
|
+
do {
|
|
22
|
+
const queryParams = {
|
|
23
|
+
limit: BATCH_SIZE,
|
|
24
|
+
with_nested_markets: true,
|
|
25
|
+
status: apiStatus
|
|
26
|
+
};
|
|
27
|
+
if (cursor)
|
|
28
|
+
queryParams.cursor = cursor;
|
|
29
|
+
const response = await axios_1.default.get(utils_1.KALSHI_API_URL, { params: queryParams });
|
|
30
|
+
const events = response.data.events || [];
|
|
31
|
+
if (events.length === 0)
|
|
32
|
+
break;
|
|
33
|
+
allEvents = allEvents.concat(events);
|
|
34
|
+
cursor = response.data.cursor;
|
|
35
|
+
page++;
|
|
36
|
+
// If we have no search query and have fetched enough events, we can stop early
|
|
37
|
+
if (!query && allEvents.length >= limit * 1.5) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
} while (cursor && page < MAX_PAGES);
|
|
41
|
+
return allEvents;
|
|
20
42
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
let events = [];
|
|
44
|
+
if (status === 'all') {
|
|
45
|
+
const [openEvents, closedEvents, settledEvents] = await Promise.all([
|
|
46
|
+
fetchAllWithStatus('open'),
|
|
47
|
+
fetchAllWithStatus('closed'),
|
|
48
|
+
fetchAllWithStatus('settled')
|
|
49
|
+
]);
|
|
50
|
+
events = [...openEvents, ...closedEvents, ...settledEvents];
|
|
51
|
+
}
|
|
52
|
+
else if (status === 'closed' || status === 'inactive') {
|
|
53
|
+
const [closedEvents, settledEvents] = await Promise.all([
|
|
54
|
+
fetchAllWithStatus('closed'),
|
|
55
|
+
fetchAllWithStatus('settled')
|
|
56
|
+
]);
|
|
57
|
+
events = [...closedEvents, ...settledEvents];
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
events = await fetchAllWithStatus('open');
|
|
61
|
+
}
|
|
24
62
|
const filtered = events.filter((event) => {
|
|
25
|
-
return (event.title || '').toLowerCase().includes(
|
|
63
|
+
return (event.title || '').toLowerCase().includes(query);
|
|
26
64
|
});
|
|
27
65
|
const unifiedEvents = filtered.map((event) => {
|
|
28
66
|
const markets = [];
|
|
@@ -47,7 +85,6 @@ async function fetchEvents(params) {
|
|
|
47
85
|
};
|
|
48
86
|
return unifiedEvent;
|
|
49
87
|
});
|
|
50
|
-
const limit = params?.limit || 20;
|
|
51
88
|
return unifiedEvents.slice(0, limit);
|
|
52
89
|
}
|
|
53
90
|
catch (error) {
|
|
@@ -138,7 +138,7 @@ async function fetchMarketsBySlug(eventTicker) {
|
|
|
138
138
|
}
|
|
139
139
|
async function searchMarkets(query, params) {
|
|
140
140
|
// We must fetch ALL markets to search them locally since we don't have server-side search
|
|
141
|
-
const searchLimit =
|
|
141
|
+
const searchLimit = 10000;
|
|
142
142
|
const markets = await fetchMarketsDefault({ ...params, limit: searchLimit });
|
|
143
143
|
const lowerQuery = query.toLowerCase();
|
|
144
144
|
const searchIn = params?.searchIn || 'title'; // Default to title-only search
|
|
@@ -151,18 +151,18 @@ async function searchMarkets(query, params) {
|
|
|
151
151
|
return descMatch;
|
|
152
152
|
return titleMatch || descMatch; // 'both'
|
|
153
153
|
});
|
|
154
|
-
const limit = params?.limit ||
|
|
154
|
+
const limit = params?.limit || 10000;
|
|
155
155
|
return filtered.slice(0, limit);
|
|
156
156
|
}
|
|
157
157
|
async function fetchMarketsDefault(params) {
|
|
158
|
-
const limit = params?.limit ||
|
|
158
|
+
const limit = params?.limit || 10000;
|
|
159
159
|
const offset = params?.offset || 0;
|
|
160
160
|
const now = Date.now();
|
|
161
161
|
const status = params?.status || 'active'; // Default to 'active'
|
|
162
162
|
// Map 'active' -> 'open', 'closed' -> 'closed'
|
|
163
163
|
// Kalshi statuses: 'open', 'closed', 'settled'
|
|
164
164
|
let apiStatus = 'open';
|
|
165
|
-
if (status === 'closed')
|
|
165
|
+
if (status === 'closed' || status === 'inactive')
|
|
166
166
|
apiStatus = 'closed';
|
|
167
167
|
else if (status === 'all')
|
|
168
168
|
apiStatus = 'open'; // Fallback for all? Or loop? For now default to open.
|
|
@@ -25,14 +25,14 @@ class LimitlessErrorMapper extends error_mapper_1.ErrorMapper {
|
|
|
25
25
|
const data = error.response.data;
|
|
26
26
|
// Limitless uses errorMsg field (CLOB client)
|
|
27
27
|
if (data.errorMsg) {
|
|
28
|
-
return data.errorMsg;
|
|
28
|
+
return typeof data.errorMsg === 'string' ? data.errorMsg : JSON.stringify(data.errorMsg);
|
|
29
29
|
}
|
|
30
30
|
// Also check standard error paths
|
|
31
31
|
if (data.error?.message) {
|
|
32
|
-
return data.error.message;
|
|
32
|
+
return String(data.error.message);
|
|
33
33
|
}
|
|
34
34
|
if (data.message) {
|
|
35
|
-
return data.message;
|
|
35
|
+
return String(data.message);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
return super.extractErrorMessage(error);
|
|
@@ -41,7 +41,7 @@ class LimitlessErrorMapper extends error_mapper_1.ErrorMapper {
|
|
|
41
41
|
* Override to detect Limitless-specific error patterns
|
|
42
42
|
*/
|
|
43
43
|
mapBadRequestError(message, data) {
|
|
44
|
-
const lowerMessage = message.toLowerCase();
|
|
44
|
+
const lowerMessage = (message || '').toString().toLowerCase();
|
|
45
45
|
// Limitless-specific authentication errors (400 status)
|
|
46
46
|
if (lowerMessage.includes('api key') ||
|
|
47
47
|
lowerMessage.includes('proxy') ||
|
|
@@ -8,17 +8,29 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
8
8
|
const utils_1 = require("./utils");
|
|
9
9
|
const errors_1 = require("./errors");
|
|
10
10
|
async function fetchEvents(params) {
|
|
11
|
-
if (params?.status === 'closed') {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
11
|
try {
|
|
12
|
+
// NOTE: The Limitless /markets/search endpoint currently only returns active/funded markets.
|
|
13
|
+
// It does not include expired or resolved markets in search results.
|
|
14
|
+
// Consequently, status 'inactive' will likely return 0 results and 'all' will only show active markets.
|
|
15
15
|
const response = await axios_1.default.get(`${utils_1.LIMITLESS_API_URL}/markets/search`, {
|
|
16
16
|
params: {
|
|
17
17
|
query: params.query,
|
|
18
|
-
limit: params?.limit ||
|
|
18
|
+
limit: params?.limit || 10000,
|
|
19
|
+
similarityThreshold: 0.5
|
|
19
20
|
}
|
|
20
21
|
});
|
|
21
|
-
|
|
22
|
+
let markets = response.data?.markets || [];
|
|
23
|
+
// Filter by status based on expired/resolved state
|
|
24
|
+
// Active: not expired and not resolved
|
|
25
|
+
// Inactive: expired OR resolved (has winningOutcomeIndex)
|
|
26
|
+
const status = params?.status || 'active';
|
|
27
|
+
if (status === 'active') {
|
|
28
|
+
markets = markets.filter((m) => !m.expired && m.winningOutcomeIndex === null);
|
|
29
|
+
}
|
|
30
|
+
else if (status === 'inactive' || status === 'closed') {
|
|
31
|
+
markets = markets.filter((m) => m.expired === true || m.winningOutcomeIndex !== null);
|
|
32
|
+
}
|
|
33
|
+
// If status === 'all', don't filter
|
|
22
34
|
return markets.map((market) => {
|
|
23
35
|
let marketsList = [];
|
|
24
36
|
if (market.markets && Array.isArray(market.markets)) {
|
|
@@ -1,25 +1,60 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.fetchMarkets = fetchMarkets;
|
|
7
|
-
const sdk_1 = require("@limitless-exchange/sdk");
|
|
8
40
|
const axios_1 = __importDefault(require("axios"));
|
|
9
41
|
const utils_1 = require("./utils");
|
|
10
42
|
const errors_1 = require("./errors");
|
|
11
43
|
async function fetchMarkets(params, apiKey) {
|
|
12
44
|
// Limitless API currently only supports fetching active markets for lists
|
|
13
|
-
|
|
45
|
+
// Early return to avoid SDK initialization in tests
|
|
46
|
+
if (params?.status === 'inactive' || params?.status === 'closed') {
|
|
14
47
|
return [];
|
|
15
48
|
}
|
|
49
|
+
// Lazy import SDK to avoid initialization when not needed
|
|
50
|
+
const { HttpClient, MarketFetcher } = await Promise.resolve().then(() => __importStar(require('@limitless-exchange/sdk')));
|
|
16
51
|
try {
|
|
17
52
|
// Create HTTP client (no auth needed for market data)
|
|
18
|
-
const httpClient = new
|
|
53
|
+
const httpClient = new HttpClient({
|
|
19
54
|
baseURL: utils_1.LIMITLESS_API_URL,
|
|
20
55
|
apiKey: apiKey, // Optional - not required for public market data
|
|
21
56
|
});
|
|
22
|
-
const marketFetcher = new
|
|
57
|
+
const marketFetcher = new MarketFetcher(httpClient);
|
|
23
58
|
// Handle slug-based lookup
|
|
24
59
|
if (params?.slug) {
|
|
25
60
|
return await fetchMarketsBySlug(marketFetcher, params.slug);
|
|
@@ -44,10 +79,12 @@ async function fetchMarketsBySlug(marketFetcher, slug) {
|
|
|
44
79
|
}
|
|
45
80
|
async function searchMarkets(marketFetcher, query, params) {
|
|
46
81
|
// SDK doesn't have a search method yet, use axios directly
|
|
82
|
+
// NOTE: The Limitless /markets/search endpoint currently only returns active/funded markets.
|
|
83
|
+
// It does not include expired or resolved markets in search results.
|
|
47
84
|
const response = await axios_1.default.get(`${utils_1.LIMITLESS_API_URL}/markets/search`, {
|
|
48
85
|
params: {
|
|
49
86
|
query: query,
|
|
50
|
-
limit: params?.limit ||
|
|
87
|
+
limit: params?.limit || 10000,
|
|
51
88
|
page: params?.page || 1,
|
|
52
89
|
similarityThreshold: params?.similarityThreshold || 0.5
|
|
53
90
|
}
|
|
@@ -71,10 +108,10 @@ async function searchMarkets(marketFetcher, query, params) {
|
|
|
71
108
|
}
|
|
72
109
|
return allMarkets
|
|
73
110
|
.filter((m) => m !== null && m.outcomes.length > 0)
|
|
74
|
-
.slice(0, params?.limit ||
|
|
111
|
+
.slice(0, params?.limit || 10000);
|
|
75
112
|
}
|
|
76
113
|
async function fetchMarketsDefault(marketFetcher, params) {
|
|
77
|
-
const limit = params?.limit ||
|
|
114
|
+
const limit = params?.limit || 10000;
|
|
78
115
|
const offset = params?.offset || 0;
|
|
79
116
|
// Map sort parameter to SDK's sortBy
|
|
80
117
|
let sortBy = 'lp_rewards';
|
|
@@ -4,41 +4,72 @@ exports.fetchEvents = fetchEvents;
|
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
5
|
const errors_1 = require("./errors");
|
|
6
6
|
async function fetchEvents(params) {
|
|
7
|
-
const searchLimit = 100000; // Fetch all events for comprehensive search
|
|
8
7
|
try {
|
|
9
|
-
|
|
8
|
+
if (!params.query) {
|
|
9
|
+
// If no query is provided, we can't use the search endpoint effectively.
|
|
10
|
+
// However, the BaseExchange interface enforces query presence for fetchEvents.
|
|
11
|
+
// Just in case, we return empty or throw.
|
|
12
|
+
throw new Error("Query is required for Polymarket event search");
|
|
13
|
+
}
|
|
14
|
+
const limit = params.limit || 10000;
|
|
15
|
+
const status = params.status || 'active';
|
|
10
16
|
const queryParams = {
|
|
11
|
-
|
|
17
|
+
q: params.query,
|
|
18
|
+
limit_per_type: 50,
|
|
19
|
+
sort: 'volume',
|
|
20
|
+
ascending: false
|
|
21
|
+
};
|
|
22
|
+
const fetchWithStatus = async (eventStatus) => {
|
|
23
|
+
const currentParams = { ...queryParams, events_status: eventStatus };
|
|
24
|
+
return (0, utils_1.paginateSearchParallel)(utils_1.GAMMA_SEARCH_URL, currentParams, limit * 10);
|
|
12
25
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
// Client-side filtering logic
|
|
27
|
+
// The API returns active events when querying for 'closed' status sometimes.
|
|
28
|
+
// We must strictly filter based on the event's `active` and `closed` properties.
|
|
29
|
+
const filterActive = (e) => e.active === true;
|
|
30
|
+
const filterClosed = (e) => e.closed === true;
|
|
31
|
+
let events = [];
|
|
32
|
+
if (status === 'all') {
|
|
33
|
+
const [activeEvents, closedEvents] = await Promise.all([
|
|
34
|
+
fetchWithStatus('active'),
|
|
35
|
+
fetchWithStatus('closed')
|
|
36
|
+
]);
|
|
37
|
+
// Merge and de-duplicate by ID
|
|
38
|
+
const seenIds = new Set();
|
|
39
|
+
events = [...activeEvents, ...closedEvents].filter(event => {
|
|
40
|
+
const id = event.id || event.slug;
|
|
41
|
+
if (seenIds.has(id))
|
|
42
|
+
return false;
|
|
43
|
+
seenIds.add(id);
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
16
46
|
}
|
|
17
|
-
else if (status === '
|
|
18
|
-
|
|
19
|
-
|
|
47
|
+
else if (status === 'active') {
|
|
48
|
+
const rawEvents = await fetchWithStatus('active');
|
|
49
|
+
events = rawEvents.filter(filterActive);
|
|
20
50
|
}
|
|
21
|
-
else {
|
|
22
|
-
//
|
|
51
|
+
else if (status === 'inactive' || status === 'closed') {
|
|
52
|
+
// Polymarket sometimes returns active events when querying for closed
|
|
53
|
+
// So we fetch 'closed' but strictly filter
|
|
54
|
+
const rawEvents = await fetchWithStatus('closed');
|
|
55
|
+
events = rawEvents.filter(filterClosed);
|
|
23
56
|
}
|
|
24
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const searchIn = params?.searchIn || 'title';
|
|
29
|
-
const filtered = events.filter((event) => {
|
|
57
|
+
// Client-side filtering to ensure title matches (API does fuzzy search)
|
|
58
|
+
const lowerQuery = params.query.toLowerCase();
|
|
59
|
+
const searchIn = params.searchIn || 'title';
|
|
60
|
+
const filteredEvents = events.filter((event) => {
|
|
30
61
|
const titleMatch = (event.title || '').toLowerCase().includes(lowerQuery);
|
|
31
62
|
const descMatch = (event.description || '').toLowerCase().includes(lowerQuery);
|
|
32
63
|
if (searchIn === 'title')
|
|
33
64
|
return titleMatch;
|
|
34
65
|
if (searchIn === 'description')
|
|
35
66
|
return descMatch;
|
|
36
|
-
return titleMatch || descMatch;
|
|
67
|
+
return titleMatch || descMatch; // 'both'
|
|
37
68
|
});
|
|
38
|
-
// Map to UnifiedEvent
|
|
39
|
-
const unifiedEvents =
|
|
69
|
+
// Map events to UnifiedEvent
|
|
70
|
+
const unifiedEvents = filteredEvents.map((event) => {
|
|
40
71
|
const markets = [];
|
|
41
|
-
if (event.markets) {
|
|
72
|
+
if (event.markets && Array.isArray(event.markets)) {
|
|
42
73
|
for (const market of event.markets) {
|
|
43
74
|
const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market, { useQuestionAsCandidateFallback: true });
|
|
44
75
|
if (unifiedMarket) {
|
|
@@ -59,8 +90,6 @@ async function fetchEvents(params) {
|
|
|
59
90
|
};
|
|
60
91
|
return unifiedEvent;
|
|
61
92
|
});
|
|
62
|
-
// Apply limit to filtered results
|
|
63
|
-
const limit = params?.limit || 20;
|
|
64
93
|
return unifiedEvents.slice(0, limit);
|
|
65
94
|
}
|
|
66
95
|
catch (error) {
|
|
@@ -45,30 +45,48 @@ async function fetchMarketsBySlug(slug) {
|
|
|
45
45
|
return unifiedMarkets;
|
|
46
46
|
}
|
|
47
47
|
async function searchMarkets(query, params) {
|
|
48
|
-
const
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const limit = params?.limit || 10000;
|
|
49
|
+
// Use parallel pagination to fetch all matching events
|
|
50
|
+
// Each event can contain multiple markets, so we need a larger pool
|
|
51
|
+
const queryParams = {
|
|
52
|
+
q: query,
|
|
53
|
+
limit_per_type: 50, // Fetch 50 events per page
|
|
54
|
+
events_status: params?.status === 'all' ? undefined : (params?.status === 'inactive' || params?.status === 'closed' ? 'closed' : 'active'),
|
|
55
|
+
sort: 'volume',
|
|
56
|
+
ascending: false
|
|
57
|
+
};
|
|
58
|
+
// Fetch events with parallel pagination
|
|
59
|
+
const events = await (0, utils_1.paginateSearchParallel)(utils_1.GAMMA_SEARCH_URL, queryParams, limit * 5);
|
|
60
|
+
const unifiedMarkets = [];
|
|
55
61
|
const lowerQuery = query.toLowerCase();
|
|
56
|
-
const searchIn = params?.searchIn || 'title';
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
const searchIn = params?.searchIn || 'title';
|
|
63
|
+
// Flatten events into markets
|
|
64
|
+
for (const event of events) {
|
|
65
|
+
if (!event.markets)
|
|
66
|
+
continue;
|
|
67
|
+
for (const market of event.markets) {
|
|
68
|
+
const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market, { useQuestionAsCandidateFallback: true });
|
|
69
|
+
if (!unifiedMarket)
|
|
70
|
+
continue;
|
|
71
|
+
// Apply client-side filtering on market title
|
|
72
|
+
const titleMatch = (unifiedMarket.title || '').toLowerCase().includes(lowerQuery);
|
|
73
|
+
const descMatch = (unifiedMarket.description || '').toLowerCase().includes(lowerQuery);
|
|
74
|
+
let matches = false;
|
|
75
|
+
if (searchIn === 'title')
|
|
76
|
+
matches = titleMatch;
|
|
77
|
+
else if (searchIn === 'description')
|
|
78
|
+
matches = descMatch;
|
|
79
|
+
else
|
|
80
|
+
matches = titleMatch || descMatch;
|
|
81
|
+
if (matches) {
|
|
82
|
+
unifiedMarkets.push(unifiedMarket);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return unifiedMarkets.slice(0, limit);
|
|
69
87
|
}
|
|
70
88
|
async function fetchMarketsDefault(params) {
|
|
71
|
-
const limit = params?.limit ||
|
|
89
|
+
const limit = params?.limit || 10000; // Higher default for better coverage
|
|
72
90
|
const offset = params?.offset || 0;
|
|
73
91
|
// Map generic sort params to Polymarket Gamma API params
|
|
74
92
|
let queryParams = {
|
|
@@ -80,7 +98,7 @@ async function fetchMarketsDefault(params) {
|
|
|
80
98
|
queryParams.active = 'true';
|
|
81
99
|
queryParams.closed = 'false';
|
|
82
100
|
}
|
|
83
|
-
else if (status === 'closed') {
|
|
101
|
+
else if (status === 'closed' || status === 'inactive') {
|
|
84
102
|
queryParams.active = 'false';
|
|
85
103
|
queryParams.closed = 'true';
|
|
86
104
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UnifiedMarket, CandleInterval } from '../../types';
|
|
2
2
|
export declare const GAMMA_API_URL = "https://gamma-api.polymarket.com/events";
|
|
3
|
+
export declare const GAMMA_SEARCH_URL = "https://gamma-api.polymarket.com/public-search";
|
|
3
4
|
export declare const CLOB_API_URL = "https://clob.polymarket.com";
|
|
4
5
|
export declare const DATA_API_URL = "https://data-api.polymarket.com";
|
|
5
6
|
export declare function mapMarketToUnified(event: any, market: any, options?: {
|
|
@@ -11,3 +12,8 @@ export declare function mapIntervalToFidelity(interval: CandleInterval): number;
|
|
|
11
12
|
* Polymarket Gamma API has a hard limit of 500 results per request.
|
|
12
13
|
*/
|
|
13
14
|
export declare function paginateParallel(url: string, params: any, maxResults?: number): Promise<any[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch all results from Gamma public-search API using parallel pagination.
|
|
17
|
+
* Uses 'page' parameter instead of 'offset'.
|
|
18
|
+
*/
|
|
19
|
+
export declare function paginateSearchParallel(url: string, params: any, maxResults?: number): Promise<any[]>;
|
|
@@ -33,12 +33,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.DATA_API_URL = exports.CLOB_API_URL = exports.GAMMA_API_URL = void 0;
|
|
36
|
+
exports.DATA_API_URL = exports.CLOB_API_URL = exports.GAMMA_SEARCH_URL = exports.GAMMA_API_URL = void 0;
|
|
37
37
|
exports.mapMarketToUnified = mapMarketToUnified;
|
|
38
38
|
exports.mapIntervalToFidelity = mapIntervalToFidelity;
|
|
39
39
|
exports.paginateParallel = paginateParallel;
|
|
40
|
+
exports.paginateSearchParallel = paginateSearchParallel;
|
|
40
41
|
const market_utils_1 = require("../../utils/market-utils");
|
|
41
42
|
exports.GAMMA_API_URL = 'https://gamma-api.polymarket.com/events';
|
|
43
|
+
exports.GAMMA_SEARCH_URL = 'https://gamma-api.polymarket.com/public-search';
|
|
42
44
|
exports.CLOB_API_URL = 'https://clob.polymarket.com';
|
|
43
45
|
exports.DATA_API_URL = 'https://data-api.polymarket.com';
|
|
44
46
|
function mapMarketToUnified(event, market, options = {}) {
|
|
@@ -171,3 +173,42 @@ async function paginateParallel(url, params, maxResults = 10000) {
|
|
|
171
173
|
}));
|
|
172
174
|
return [firstPage, ...remainingPages].flat();
|
|
173
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Fetch all results from Gamma public-search API using parallel pagination.
|
|
178
|
+
* Uses 'page' parameter instead of 'offset'.
|
|
179
|
+
*/
|
|
180
|
+
async function paginateSearchParallel(url, params, maxResults = 10000) {
|
|
181
|
+
const axios = (await Promise.resolve().then(() => __importStar(require('axios')))).default;
|
|
182
|
+
// 1. Fetch the first page to check pagination info
|
|
183
|
+
const firstPageResponse = await axios.get(url, {
|
|
184
|
+
params: { ...params, page: 1 }
|
|
185
|
+
});
|
|
186
|
+
const data = firstPageResponse.data;
|
|
187
|
+
const firstPageEvents = data.events || [];
|
|
188
|
+
const pagination = data.pagination;
|
|
189
|
+
// If no more pages, return what we have
|
|
190
|
+
if (!pagination?.hasMore || firstPageEvents.length === 0) {
|
|
191
|
+
return firstPageEvents;
|
|
192
|
+
}
|
|
193
|
+
// 2. Calculate how many pages to fetch based on totalResults and limit_per_type
|
|
194
|
+
const limitPerType = params.limit_per_type || 20;
|
|
195
|
+
const totalResults = Math.min(pagination.totalResults || 0, maxResults);
|
|
196
|
+
const totalPages = Math.ceil(totalResults / limitPerType);
|
|
197
|
+
// Fetch remaining pages in parallel
|
|
198
|
+
const pageNumbers = [];
|
|
199
|
+
for (let i = 2; i <= totalPages; i++) {
|
|
200
|
+
pageNumbers.push(i);
|
|
201
|
+
}
|
|
202
|
+
const remainingPages = await Promise.all(pageNumbers.map(async (pageNum) => {
|
|
203
|
+
try {
|
|
204
|
+
const res = await axios.get(url, {
|
|
205
|
+
params: { ...params, page: pageNum }
|
|
206
|
+
});
|
|
207
|
+
return res.data?.events || [];
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
return []; // Swallow individual page errors to be robust
|
|
211
|
+
}
|
|
212
|
+
}));
|
|
213
|
+
return [firstPageEvents, ...remainingPages].flat();
|
|
214
|
+
}
|
|
@@ -231,6 +231,14 @@ class ErrorMapper {
|
|
|
231
231
|
return error;
|
|
232
232
|
}
|
|
233
233
|
// Unknown error format
|
|
234
|
+
if (typeof error === 'object' && error !== null) {
|
|
235
|
+
try {
|
|
236
|
+
return JSON.stringify(error, Object.getOwnPropertyNames(error));
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
return String(error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
234
242
|
return String(error);
|
|
235
243
|
}
|
|
236
244
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxt-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
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",
|
|
@@ -29,8 +29,8 @@
|
|
|
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=2.1.
|
|
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=2.1.
|
|
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=2.1.2,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=2.1.2,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
|
|
34
34
|
"extract:jsdoc": "node ../scripts/extract-jsdoc.js",
|
|
35
35
|
"generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",
|
|
36
36
|
"generate:sdk:all": "npm run generate:sdk:python && npm run generate:sdk:typescript && npm run generate:docs"
|