pmxt-core 2.0.11 → 2.1.1

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.
@@ -4,6 +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
8
  searchIn?: 'title' | 'description' | 'both';
8
9
  query?: string;
9
10
  slug?: string;
@@ -16,6 +17,7 @@ export interface EventFetchParams {
16
17
  query?: string;
17
18
  limit?: number;
18
19
  offset?: number;
20
+ status?: 'active' | 'closed' | 'all';
19
21
  searchIn?: 'title' | 'description' | 'both';
20
22
  }
21
23
  export interface HistoryFilterParams {
@@ -113,14 +115,14 @@ export declare abstract class PredictionMarketExchange {
113
115
  * @returns Array of unified markets
114
116
  *
115
117
  * @example-ts Fetch markets
116
- * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 20 });
118
+ * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
117
119
  * console.log(markets[0].title);
118
120
  *
119
121
  * @example-ts Get market by slug
120
122
  * const markets = await exchange.fetchMarkets({ slug: 'will-trump-win' });
121
123
  *
122
124
  * @example-python Fetch markets
123
- * markets = exchange.fetch_markets(query='Trump', limit=20)
125
+ * markets = exchange.fetch_markets(query='Trump', limit=10000)
124
126
  * print(markets[0].title)
125
127
  *
126
128
  * @example-python Get market by slug
@@ -23,14 +23,14 @@ class PredictionMarketExchange {
23
23
  * @returns Array of unified markets
24
24
  *
25
25
  * @example-ts Fetch markets
26
- * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 20 });
26
+ * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
27
27
  * console.log(markets[0].title);
28
28
  *
29
29
  * @example-ts Get market by slug
30
30
  * const markets = await exchange.fetchMarkets({ slug: 'will-trump-win' });
31
31
  *
32
32
  * @example-python Fetch markets
33
- * markets = exchange.fetch_markets(query='Trump', limit=20)
33
+ * markets = exchange.fetch_markets(query='Trump', limit=10000)
34
34
  * print(markets[0].title)
35
35
  *
36
36
  * @example-python Get market by slug
@@ -9,10 +9,14 @@ const utils_1 = require("./utils");
9
9
  const errors_1 = require("./errors");
10
10
  async function fetchEvents(params) {
11
11
  try {
12
+ const status = params?.status || 'active';
13
+ let apiStatus = 'open';
14
+ if (status === 'closed')
15
+ apiStatus = 'closed';
12
16
  const queryParams = {
13
17
  limit: 200, // Reasonable batch for search
14
18
  with_nested_markets: true,
15
- status: 'open'
19
+ status: apiStatus
16
20
  };
17
21
  const response = await axios_1.default.get(utils_1.KALSHI_API_URL, { params: queryParams });
18
22
  const events = response.data.events || [];
@@ -43,7 +47,7 @@ async function fetchEvents(params) {
43
47
  };
44
48
  return unifiedEvent;
45
49
  });
46
- const limit = params?.limit || 20;
50
+ const limit = params?.limit || 10000;
47
51
  return unifiedEvents.slice(0, limit);
48
52
  }
49
53
  catch (error) {
@@ -8,7 +8,7 @@ exports.fetchMarkets = fetchMarkets;
8
8
  const axios_1 = __importDefault(require("axios"));
9
9
  const utils_1 = require("./utils");
10
10
  const errors_1 = require("./errors");
11
- async function fetchActiveEvents(targetMarketCount) {
11
+ async function fetchActiveEvents(targetMarketCount, status = 'open') {
12
12
  let allEvents = [];
13
13
  let totalMarketCount = 0;
14
14
  let cursor = null;
@@ -23,7 +23,7 @@ async function fetchActiveEvents(targetMarketCount) {
23
23
  const queryParams = {
24
24
  limit: BATCH_SIZE,
25
25
  with_nested_markets: true,
26
- status: 'open' // Filter to open markets to improve relevance and speed
26
+ status: status // Filter by status (default 'open')
27
27
  };
28
28
  if (cursor)
29
29
  queryParams.cursor = cursor;
@@ -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 = 5000;
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,28 @@ async function searchMarkets(query, params) {
151
151
  return descMatch;
152
152
  return titleMatch || descMatch; // 'both'
153
153
  });
154
- const limit = params?.limit || 20;
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 || 50;
158
+ const limit = params?.limit || 10000;
159
159
  const offset = params?.offset || 0;
160
160
  const now = Date.now();
161
+ const status = params?.status || 'active'; // Default to 'active'
162
+ // Map 'active' -> 'open', 'closed' -> 'closed'
163
+ // Kalshi statuses: 'open', 'closed', 'settled'
164
+ let apiStatus = 'open';
165
+ if (status === 'closed')
166
+ apiStatus = 'closed';
167
+ else if (status === 'all')
168
+ apiStatus = 'open'; // Fallback for all? Or loop? For now default to open.
161
169
  try {
162
170
  let events;
163
171
  let seriesMap;
164
172
  // Check if we have valid cached data
165
- if (cachedEvents && cachedSeriesMap && (now - lastCacheTime < CACHE_TTL)) {
173
+ // Only use global cache for the default 'active'/'open' case
174
+ const useCache = (status === 'active' || !params?.status);
175
+ if (useCache && cachedEvents && cachedSeriesMap && (now - lastCacheTime < CACHE_TTL)) {
166
176
  events = cachedEvents;
167
177
  seriesMap = cachedSeriesMap;
168
178
  }
@@ -173,16 +183,17 @@ async function fetchMarketsDefault(params) {
173
183
  const isSorted = params?.sort && (params.sort === 'volume' || params.sort === 'liquidity');
174
184
  const fetchLimit = isSorted ? 1000 : limit;
175
185
  const [allEvents, fetchedSeriesMap] = await Promise.all([
176
- fetchActiveEvents(fetchLimit),
186
+ fetchActiveEvents(fetchLimit, apiStatus),
177
187
  fetchSeriesMap()
178
188
  ]);
179
189
  events = allEvents;
180
190
  seriesMap = fetchedSeriesMap;
181
191
  events = allEvents;
182
192
  seriesMap = fetchedSeriesMap;
183
- // Cache the dataset ONLY if we fetched a comprehensive set (intended for global sorting/pagination)
184
- // If we only fetched a partial set (e.g. limit=5), we shouldn't cache it as the "full" logic assumes we have everything.
185
- if (fetchLimit >= 1000) {
193
+ // Cache the dataset ONLY if:
194
+ // 1. We fetched a comprehensive set (>= 1000)
195
+ // 2. It's the standard 'open' status query
196
+ if (fetchLimit >= 1000 && useCache) {
186
197
  cachedEvents = allEvents;
187
198
  cachedSeriesMap = fetchedSeriesMap;
188
199
  lastCacheTime = now;
@@ -8,11 +8,14 @@ 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
+ }
11
14
  try {
12
15
  const response = await axios_1.default.get(`${utils_1.LIMITLESS_API_URL}/markets/search`, {
13
16
  params: {
14
17
  query: params.query,
15
- limit: params?.limit || 20
18
+ limit: params?.limit || 10000
16
19
  }
17
20
  });
18
21
  const markets = response.data?.markets || [];
@@ -9,6 +9,10 @@ const axios_1 = __importDefault(require("axios"));
9
9
  const utils_1 = require("./utils");
10
10
  const errors_1 = require("./errors");
11
11
  async function fetchMarkets(params, apiKey) {
12
+ // Limitless API currently only supports fetching active markets for lists
13
+ if (params?.status === 'closed') {
14
+ return [];
15
+ }
12
16
  try {
13
17
  // Create HTTP client (no auth needed for market data)
14
18
  const httpClient = new sdk_1.HttpClient({
@@ -43,7 +47,7 @@ async function searchMarkets(marketFetcher, query, params) {
43
47
  const response = await axios_1.default.get(`${utils_1.LIMITLESS_API_URL}/markets/search`, {
44
48
  params: {
45
49
  query: query,
46
- limit: params?.limit || 20,
50
+ limit: params?.limit || 10000,
47
51
  page: params?.page || 1,
48
52
  similarityThreshold: params?.similarityThreshold || 0.5
49
53
  }
@@ -67,10 +71,10 @@ async function searchMarkets(marketFetcher, query, params) {
67
71
  }
68
72
  return allMarkets
69
73
  .filter((m) => m !== null && m.outcomes.length > 0)
70
- .slice(0, params?.limit || 20);
74
+ .slice(0, params?.limit || 10000);
71
75
  }
72
76
  async function fetchMarketsDefault(marketFetcher, params) {
73
- const limit = params?.limit || 200;
77
+ const limit = params?.limit || 10000;
74
78
  const offset = params?.offset || 0;
75
79
  // Map sort parameter to SDK's sortBy
76
80
  let sortBy = 'lp_rewards';
@@ -4,30 +4,47 @@ 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
- // Fetch events from Gamma API using parallel pagination
10
- const events = await (0, utils_1.paginateParallel)(utils_1.GAMMA_API_URL, {
11
- active: 'true',
12
- closed: 'false',
13
- limit: searchLimit
14
- });
15
- // Client-side text filtering
16
- const lowerQuery = (params?.query || '').toLowerCase();
17
- const searchIn = params?.searchIn || 'title';
18
- const filtered = events.filter((event) => {
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';
16
+ const queryParams = {
17
+ q: params.query,
18
+ limit_per_type: 50, // Fetch 50 per page for better efficiency
19
+ events_status: status === 'all' ? undefined : status,
20
+ sort: 'volume',
21
+ ascending: false
22
+ };
23
+ // If specific status requested
24
+ if (status === 'active') {
25
+ queryParams.events_status = 'active';
26
+ }
27
+ else if (status === 'closed') {
28
+ queryParams.events_status = 'closed';
29
+ }
30
+ // Use parallel pagination to fetch all matching events
31
+ const events = await (0, utils_1.paginateSearchParallel)(utils_1.GAMMA_SEARCH_URL, queryParams, limit * 10);
32
+ // Client-side filtering to ensure title matches (API does fuzzy search)
33
+ const lowerQuery = params.query.toLowerCase();
34
+ const searchIn = params.searchIn || 'title';
35
+ const filteredEvents = events.filter((event) => {
19
36
  const titleMatch = (event.title || '').toLowerCase().includes(lowerQuery);
20
37
  const descMatch = (event.description || '').toLowerCase().includes(lowerQuery);
21
38
  if (searchIn === 'title')
22
39
  return titleMatch;
23
40
  if (searchIn === 'description')
24
41
  return descMatch;
25
- return titleMatch || descMatch;
42
+ return titleMatch || descMatch; // 'both'
26
43
  });
27
- // Map to UnifiedEvent
28
- const unifiedEvents = filtered.map((event) => {
44
+ // Map events to UnifiedEvent
45
+ const unifiedEvents = filteredEvents.map((event) => {
29
46
  const markets = [];
30
- if (event.markets) {
47
+ if (event.markets && Array.isArray(event.markets)) {
31
48
  for (const market of event.markets) {
32
49
  const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market, { useQuestionAsCandidateFallback: true });
33
50
  if (unifiedMarket) {
@@ -48,9 +65,7 @@ async function fetchEvents(params) {
48
65
  };
49
66
  return unifiedEvent;
50
67
  });
51
- // Apply limit to filtered results
52
- const limit = params?.limit || 20;
53
- return unifiedEvents.slice(0, limit);
68
+ return unifiedEvents;
54
69
  }
55
70
  catch (error) {
56
71
  throw errors_1.polymarketErrorMapper.mapError(error);
@@ -45,38 +45,66 @@ async function fetchMarketsBySlug(slug) {
45
45
  return unifiedMarkets;
46
46
  }
47
47
  async function searchMarkets(query, params) {
48
- const searchLimit = 5000; // Fetch enough markets for a good search pool
49
- // Fetch markets with a higher limit
50
- const markets = await fetchMarketsDefault({
51
- ...params,
52
- limit: searchLimit
53
- });
54
- // Client-side text filtering
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 || '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'; // Default to title-only search
57
- const filtered = markets.filter(market => {
58
- const titleMatch = (market.title || '').toLowerCase().includes(lowerQuery);
59
- const descMatch = (market.description || '').toLowerCase().includes(lowerQuery);
60
- if (searchIn === 'title')
61
- return titleMatch;
62
- if (searchIn === 'description')
63
- return descMatch;
64
- return titleMatch || descMatch; // 'both'
65
- });
66
- // Apply limit to filtered results
67
- const limit = params?.limit || 20;
68
- return filtered.slice(0, limit);
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 || 200; // Higher default for better coverage
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 = {
75
- active: 'true',
76
- closed: 'false',
77
93
  limit: limit,
78
94
  offset: offset,
79
95
  };
96
+ const status = params?.status || 'active';
97
+ if (status === 'active') {
98
+ queryParams.active = 'true';
99
+ queryParams.closed = 'false';
100
+ }
101
+ else if (status === 'closed') {
102
+ queryParams.active = 'false';
103
+ queryParams.closed = 'true';
104
+ }
105
+ else {
106
+ // 'all' - don't filter by status
107
+ }
80
108
  // Gamma API uses 'order' and 'ascending' for sorting
81
109
  if (params?.sort === 'volume') {
82
110
  queryParams.order = 'volume';
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.0.11",
3
+ "version": "2.1.1",
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.0.11,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.0.11,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.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.1,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.1,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"