pmxt-core 2.0.10 → 2.1.0

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 {
@@ -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 || [];
@@ -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;
@@ -158,11 +158,21 @@ async function fetchMarketsDefault(params) {
158
158
  const limit = params?.limit || 50;
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,6 +8,9 @@ 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: {
@@ -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({
@@ -77,18 +81,14 @@ async function fetchMarketsDefault(marketFetcher, params) {
77
81
  if (params?.sort === 'volume') {
78
82
  sortBy = 'high_value';
79
83
  }
80
- // Calculate page number from offset
81
- const page = Math.floor(offset / limit) + 1;
82
84
  try {
83
- // Use SDK's getActiveMarkets method
84
- const response = await marketFetcher.getActiveMarkets({
85
- limit: limit,
86
- page: page,
87
- sortBy: sortBy,
88
- });
89
- const markets = response.data || [];
85
+ // Use pagination utility to handle limits > 25
86
+ // The utility over-fetches to account for markets that get filtered out
87
+ const totalToFetch = limit + offset;
88
+ const rawMarkets = await (0, utils_1.paginateLimitlessMarkets)(marketFetcher, totalToFetch, sortBy);
89
+ // Map and filter markets
90
90
  const unifiedMarkets = [];
91
- for (const market of markets) {
91
+ for (const market of rawMarkets) {
92
92
  const unifiedMarket = (0, utils_1.mapMarketToUnified)(market);
93
93
  // Only include markets that are valid and have outcomes (compliance requirement)
94
94
  if (unifiedMarket && unifiedMarket.outcomes.length > 0) {
@@ -99,7 +99,9 @@ async function fetchMarketsDefault(marketFetcher, params) {
99
99
  if (params?.sort === 'volume') {
100
100
  unifiedMarkets.sort((a, b) => (b.volume ?? 0) - (a.volume ?? 0));
101
101
  }
102
- return unifiedMarkets;
102
+ // Apply offset and limit to the FILTERED results
103
+ const marketsAfterOffset = offset > 0 ? unifiedMarkets.slice(offset) : unifiedMarkets;
104
+ return marketsAfterOffset.slice(0, limit);
103
105
  }
104
106
  catch (error) {
105
107
  throw errors_1.limitlessErrorMapper.mapError(error);
@@ -2,3 +2,12 @@ import { UnifiedMarket, CandleInterval } from '../../types';
2
2
  export declare const LIMITLESS_API_URL = "https://api.limitless.exchange";
3
3
  export declare function mapMarketToUnified(market: any): UnifiedMarket | null;
4
4
  export declare function mapIntervalToFidelity(interval: CandleInterval): number;
5
+ /**
6
+ * Fetch paginated results from Limitless API.
7
+ * The API has a hard limit of 25 items per request, so this function
8
+ * handles automatic pagination when more items are requested.
9
+ *
10
+ * This function fetches all available markets up to a reasonable limit
11
+ * to ensure the caller can filter and still get the requested number.
12
+ */
13
+ export declare function paginateLimitlessMarkets(fetcher: any, requestedLimit: number, sortBy: 'lp_rewards' | 'ending_soon' | 'newest' | 'high_value'): Promise<any[]>;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LIMITLESS_API_URL = void 0;
4
4
  exports.mapMarketToUnified = mapMarketToUnified;
5
5
  exports.mapIntervalToFidelity = mapIntervalToFidelity;
6
+ exports.paginateLimitlessMarkets = paginateLimitlessMarkets;
6
7
  const market_utils_1 = require("../../utils/market-utils");
7
8
  exports.LIMITLESS_API_URL = 'https://api.limitless.exchange';
8
9
  function mapMarketToUnified(market) {
@@ -60,3 +61,50 @@ function mapIntervalToFidelity(interval) {
60
61
  };
61
62
  return mapping[interval];
62
63
  }
64
+ /**
65
+ * Fetch paginated results from Limitless API.
66
+ * The API has a hard limit of 25 items per request, so this function
67
+ * handles automatic pagination when more items are requested.
68
+ *
69
+ * This function fetches all available markets up to a reasonable limit
70
+ * to ensure the caller can filter and still get the requested number.
71
+ */
72
+ async function paginateLimitlessMarkets(fetcher, requestedLimit, sortBy) {
73
+ const PAGE_SIZE = 25;
74
+ const targetLimit = requestedLimit || PAGE_SIZE;
75
+ const MAX_PAGES = 20; // Safety limit to prevent infinite loops
76
+ if (targetLimit <= PAGE_SIZE) {
77
+ const response = await fetcher.getActiveMarkets({
78
+ limit: targetLimit,
79
+ page: 1,
80
+ sortBy: sortBy,
81
+ });
82
+ return response.data || [];
83
+ }
84
+ // Fetch more pages than theoretically needed to account for filtering
85
+ // ~33% of markets lack tokens and get filtered out, so we over-fetch
86
+ // by 70% to ensure we get enough valid markets after filtering
87
+ const estimatedPages = Math.ceil(targetLimit / PAGE_SIZE);
88
+ const pagesWithBuffer = Math.min(Math.ceil(estimatedPages * 1.7), MAX_PAGES);
89
+ const pageNumbers = [];
90
+ for (let i = 1; i <= pagesWithBuffer; i++) {
91
+ pageNumbers.push(i);
92
+ }
93
+ const pages = await Promise.all(pageNumbers.map(async (page) => {
94
+ try {
95
+ const response = await fetcher.getActiveMarkets({
96
+ limit: PAGE_SIZE,
97
+ page: page,
98
+ sortBy: sortBy,
99
+ });
100
+ return response.data || [];
101
+ }
102
+ catch (e) {
103
+ return [];
104
+ }
105
+ }));
106
+ const allMarkets = pages.flat();
107
+ // Don't slice here - let the caller handle limiting after filtering
108
+ // This ensures we return enough raw markets for the caller to filter
109
+ return allMarkets;
110
+ }
@@ -6,12 +6,23 @@ const errors_1 = require("./errors");
6
6
  async function fetchEvents(params) {
7
7
  const searchLimit = 100000; // Fetch all events for comprehensive search
8
8
  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',
9
+ const status = params?.status || 'active';
10
+ const queryParams = {
13
11
  limit: searchLimit
14
- });
12
+ };
13
+ if (status === 'active') {
14
+ queryParams.active = 'true';
15
+ queryParams.closed = 'false';
16
+ }
17
+ else if (status === 'closed') {
18
+ queryParams.active = 'false';
19
+ queryParams.closed = 'true';
20
+ }
21
+ else {
22
+ // 'all' - no filter, maybe handled by default or API behavior
23
+ }
24
+ // Fetch events from Gamma API using parallel pagination
25
+ const events = await (0, utils_1.paginateParallel)(utils_1.GAMMA_API_URL, queryParams);
15
26
  // Client-side text filtering
16
27
  const lowerQuery = (params?.query || '').toLowerCase();
17
28
  const searchIn = params?.searchIn || 'title';
@@ -72,11 +72,21 @@ async function fetchMarketsDefault(params) {
72
72
  const offset = params?.offset || 0;
73
73
  // Map generic sort params to Polymarket Gamma API params
74
74
  let queryParams = {
75
- active: 'true',
76
- closed: 'false',
77
75
  limit: limit,
78
76
  offset: offset,
79
77
  };
78
+ const status = params?.status || 'active';
79
+ if (status === 'active') {
80
+ queryParams.active = 'true';
81
+ queryParams.closed = 'false';
82
+ }
83
+ else if (status === 'closed') {
84
+ queryParams.active = 'false';
85
+ queryParams.closed = 'true';
86
+ }
87
+ else {
88
+ // 'all' - don't filter by status
89
+ }
80
90
  // Gamma API uses 'order' and 'ascending' for sorting
81
91
  if (params?.sort === 'volume') {
82
92
  queryParams.order = 'volume';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.0.10",
3
+ "version": "2.1.0",
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.10,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.10,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.0,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.0,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"