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.
- package/dist/BaseExchange.d.ts +2 -0
- package/dist/exchanges/kalshi/fetchEvents.js +5 -1
- package/dist/exchanges/kalshi/fetchMarkets.js +18 -7
- package/dist/exchanges/limitless/fetchEvents.js +3 -0
- package/dist/exchanges/limitless/fetchMarkets.js +13 -11
- package/dist/exchanges/limitless/utils.d.ts +9 -0
- package/dist/exchanges/limitless/utils.js +48 -0
- package/dist/exchanges/polymarket/fetchEvents.js +16 -5
- package/dist/exchanges/polymarket/fetchMarkets.js +12 -2
- package/package.json +3 -3
package/dist/BaseExchange.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
184
|
-
//
|
|
185
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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"
|