pmxtjs 0.1.0 → 0.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/{src/BaseExchange.ts → dist/BaseExchange.d.ts} +4 -22
- package/dist/BaseExchange.js +30 -0
- package/dist/exchanges/Kalshi.d.ts +20 -0
- package/{src/exchanges/Kalshi.ts → dist/exchanges/Kalshi.js} +75 -114
- package/dist/exchanges/Polymarket.d.ts +38 -0
- package/{src/exchanges/Polymarket.ts → dist/exchanges/Polymarket.js} +105 -146
- package/{src/index.ts → dist/index.d.ts} +0 -1
- package/dist/index.js +20 -0
- package/{src/types.ts → dist/types.d.ts} +3 -17
- package/dist/types.js +5 -0
- package/package.json +9 -1
- package/readme.md +62 -0
- package/coverage/clover.xml +0 -334
- package/coverage/coverage-final.json +0 -4
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/pmxt/BaseExchange.ts.html +0 -256
- package/coverage/lcov-report/pmxt/exchanges/Kalshi.ts.html +0 -1132
- package/coverage/lcov-report/pmxt/exchanges/Polymarket.ts.html +0 -1456
- package/coverage/lcov-report/pmxt/exchanges/index.html +0 -131
- package/coverage/lcov-report/pmxt/index.html +0 -116
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/BaseExchange.ts.html +0 -256
- package/coverage/lcov-report/src/exchanges/Kalshi.ts.html +0 -1132
- package/coverage/lcov-report/src/exchanges/Polymarket.ts.html +0 -1456
- package/coverage/lcov-report/src/exchanges/index.html +0 -131
- package/coverage/lcov-report/src/index.html +0 -116
- package/coverage/lcov.info +0 -766
- package/examples/get_event_prices.ts +0 -37
- package/examples/historical_prices.ts +0 -117
- package/examples/orderbook.ts +0 -102
- package/examples/recent_trades.ts +0 -29
- package/examples/search_events.ts +0 -68
- package/examples/search_market.ts +0 -29
- package/jest.config.js +0 -11
- package/pmxt-0.1.0.tgz +0 -0
- package/test/exchanges/kalshi/ApiErrors.test.ts +0 -132
- package/test/exchanges/kalshi/EmptyResponse.test.ts +0 -44
- package/test/exchanges/kalshi/FetchAndNormalizeMarkets.test.ts +0 -56
- package/test/exchanges/kalshi/LiveApi.integration.test.ts +0 -40
- package/test/exchanges/kalshi/MarketHistory.test.ts +0 -185
- package/test/exchanges/kalshi/OrderBook.test.ts +0 -149
- package/test/exchanges/kalshi/SearchMarkets.test.ts +0 -174
- package/test/exchanges/kalshi/VolumeFallback.test.ts +0 -44
- package/test/exchanges/polymarket/DataValidation.test.ts +0 -271
- package/test/exchanges/polymarket/ErrorHandling.test.ts +0 -34
- package/test/exchanges/polymarket/FetchAndNormalizeMarkets.test.ts +0 -68
- package/test/exchanges/polymarket/GetMarketsBySlug.test.ts +0 -268
- package/test/exchanges/polymarket/LiveApi.integration.test.ts +0 -44
- package/test/exchanges/polymarket/MarketHistory.test.ts +0 -207
- package/test/exchanges/polymarket/OrderBook.test.ts +0 -167
- package/test/exchanges/polymarket/RequestParameters.test.ts +0 -39
- package/test/exchanges/polymarket/SearchMarkets.test.ts +0 -176
- package/test/exchanges/polymarket/TradeHistory.test.ts +0 -248
- package/tsconfig.json +0 -12
|
@@ -1,103 +1,99 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PolymarketExchange = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const BaseExchange_1 = require("../BaseExchange");
|
|
9
|
+
class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
// Utilizing the Gamma API for rich metadata and list view data
|
|
13
|
+
this.baseUrl = 'https://gamma-api.polymarket.com/events';
|
|
14
|
+
// CLOB API for orderbook, trades, and timeseries
|
|
15
|
+
this.clobUrl = 'https://clob.polymarket.com';
|
|
16
|
+
}
|
|
17
|
+
get name() {
|
|
7
18
|
return 'Polymarket';
|
|
8
19
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
private readonly baseUrl = 'https://gamma-api.polymarket.com/events';
|
|
12
|
-
// CLOB API for orderbook, trades, and timeseries
|
|
13
|
-
private readonly clobUrl = 'https://clob.polymarket.com';
|
|
14
|
-
|
|
15
|
-
async fetchMarkets(params?: MarketFilterParams): Promise<UnifiedMarket[]> {
|
|
16
|
-
const limit = params?.limit || 200; // Higher default for better coverage
|
|
20
|
+
async fetchMarkets(params) {
|
|
21
|
+
const limit = params?.limit || 200; // Higher default for better coverage
|
|
17
22
|
const offset = params?.offset || 0;
|
|
18
|
-
|
|
19
23
|
// Map generic sort params to Polymarket Gamma API params
|
|
20
|
-
let queryParams
|
|
24
|
+
let queryParams = {
|
|
21
25
|
active: 'true',
|
|
22
26
|
closed: 'false',
|
|
23
27
|
limit: limit,
|
|
24
28
|
offset: offset,
|
|
25
29
|
};
|
|
26
|
-
|
|
27
30
|
// Gamma API uses 'order' and 'ascending' for sorting
|
|
28
31
|
if (params?.sort === 'volume') {
|
|
29
32
|
queryParams.order = 'volume';
|
|
30
33
|
queryParams.ascending = 'false';
|
|
31
|
-
}
|
|
34
|
+
}
|
|
35
|
+
else if (params?.sort === 'newest') {
|
|
32
36
|
queryParams.order = 'startDate';
|
|
33
37
|
queryParams.ascending = 'false';
|
|
34
|
-
}
|
|
38
|
+
}
|
|
39
|
+
else if (params?.sort === 'liquidity') {
|
|
35
40
|
// queryParams.order = 'liquidity';
|
|
36
|
-
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
37
43
|
// Default: do not send order param to avoid 422
|
|
38
44
|
}
|
|
39
|
-
|
|
40
45
|
try {
|
|
41
46
|
// Fetch active events from Gamma
|
|
42
|
-
const response = await
|
|
47
|
+
const response = await axios_1.default.get(this.baseUrl, {
|
|
43
48
|
params: queryParams
|
|
44
49
|
});
|
|
45
|
-
|
|
46
50
|
const events = response.data;
|
|
47
|
-
const unifiedMarkets
|
|
48
|
-
|
|
51
|
+
const unifiedMarkets = [];
|
|
49
52
|
for (const event of events) {
|
|
50
53
|
// Each event is a container (e.g. "US Election").
|
|
51
54
|
// It contains specific "markets" (e.g. "Winner", "Pop Vote").
|
|
52
|
-
if (!event.markets)
|
|
53
|
-
|
|
55
|
+
if (!event.markets)
|
|
56
|
+
continue;
|
|
54
57
|
for (const market of event.markets) {
|
|
55
|
-
const outcomes
|
|
56
|
-
|
|
58
|
+
const outcomes = [];
|
|
57
59
|
// Polymarket Gamma often returns 'outcomes' and 'outcomePrices' as stringified JSON keys.
|
|
58
|
-
let outcomeLabels
|
|
59
|
-
let outcomePrices
|
|
60
|
-
|
|
60
|
+
let outcomeLabels = [];
|
|
61
|
+
let outcomePrices = [];
|
|
61
62
|
try {
|
|
62
63
|
outcomeLabels = typeof market.outcomes === 'string' ? JSON.parse(market.outcomes) : (market.outcomes || []);
|
|
63
64
|
outcomePrices = typeof market.outcomePrices === 'string' ? JSON.parse(market.outcomePrices) : (market.outcomePrices || []);
|
|
64
|
-
} catch (e) {
|
|
65
|
-
console.warn(`Failed to parse outcomes for market ${market.id}`, e);
|
|
66
65
|
}
|
|
67
|
-
|
|
66
|
+
catch (e) {
|
|
67
|
+
}
|
|
68
68
|
// Extract CLOB token IDs for granular operations
|
|
69
|
-
let clobTokenIds
|
|
69
|
+
let clobTokenIds = [];
|
|
70
70
|
try {
|
|
71
71
|
clobTokenIds = typeof market.clobTokenIds === 'string' ? JSON.parse(market.clobTokenIds) : (market.clobTokenIds || []);
|
|
72
|
-
} catch (e) {
|
|
73
|
-
console.warn(`Failed to parse clobTokenIds for market ${market.id}`, e);
|
|
74
72
|
}
|
|
75
|
-
|
|
73
|
+
catch (e) {
|
|
74
|
+
}
|
|
76
75
|
// Extract candidate/option name from market question for better outcome labels
|
|
77
|
-
let candidateName
|
|
76
|
+
let candidateName = null;
|
|
78
77
|
if (market.question && market.groupItemTitle) {
|
|
79
78
|
candidateName = market.groupItemTitle;
|
|
80
79
|
}
|
|
81
|
-
|
|
82
80
|
if (outcomeLabels.length > 0) {
|
|
83
|
-
outcomeLabels.forEach((label
|
|
81
|
+
outcomeLabels.forEach((label, index) => {
|
|
84
82
|
const rawPrice = outcomePrices[index] || "0";
|
|
85
|
-
|
|
86
83
|
// For Yes/No markets with specific candidates, use the candidate name
|
|
87
84
|
let outcomeLabel = label;
|
|
88
85
|
if (candidateName && label.toLowerCase() === 'yes') {
|
|
89
86
|
outcomeLabel = candidateName;
|
|
90
|
-
}
|
|
87
|
+
}
|
|
88
|
+
else if (candidateName && label.toLowerCase() === 'no') {
|
|
91
89
|
outcomeLabel = `Not ${candidateName}`;
|
|
92
90
|
}
|
|
93
|
-
|
|
94
91
|
// 24h Price Change
|
|
95
92
|
// Polymarket API provides 'oneDayPriceChange' on the market object
|
|
96
93
|
let priceChange = 0;
|
|
97
94
|
if (index === 0 || label.toLowerCase() === 'yes' || (candidateName && label === candidateName)) {
|
|
98
95
|
priceChange = Number(market.oneDayPriceChange || 0);
|
|
99
96
|
}
|
|
100
|
-
|
|
101
97
|
outcomes.push({
|
|
102
98
|
id: String(index),
|
|
103
99
|
label: outcomeLabel,
|
|
@@ -109,7 +105,6 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
109
105
|
});
|
|
110
106
|
});
|
|
111
107
|
}
|
|
112
|
-
|
|
113
108
|
unifiedMarkets.push({
|
|
114
109
|
id: market.id,
|
|
115
110
|
title: market.question ? `${event.title} - ${market.question}` : event.title,
|
|
@@ -123,115 +118,105 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
123
118
|
url: `https://polymarket.com/event/${event.slug}`,
|
|
124
119
|
image: event.image || market.image || `https://polymarket.com/api/og?slug=${event.slug}`,
|
|
125
120
|
category: event.category || event.tags?.[0]?.label,
|
|
126
|
-
tags: event.tags?.map((t
|
|
121
|
+
tags: event.tags?.map((t) => t.label) || []
|
|
127
122
|
});
|
|
128
123
|
}
|
|
129
124
|
}
|
|
130
|
-
|
|
131
125
|
// Client-side Sort capability to ensure contract fulfillment
|
|
132
126
|
// Often API filters are "good effort" or apply to the 'event' but not the 'market'
|
|
133
127
|
if (params?.sort === 'volume') {
|
|
134
128
|
unifiedMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
135
|
-
}
|
|
129
|
+
}
|
|
130
|
+
else if (params?.sort === 'newest') {
|
|
136
131
|
// unifiedMarkets.sort((a, b) => b.resolutionDate.getTime() - a.resolutionDate.getTime()); // Not quite 'newest'
|
|
137
|
-
}
|
|
132
|
+
}
|
|
133
|
+
else if (params?.sort === 'liquidity') {
|
|
138
134
|
unifiedMarkets.sort((a, b) => b.liquidity - a.liquidity);
|
|
139
|
-
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
140
137
|
// Default volume sort
|
|
141
138
|
unifiedMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
142
139
|
}
|
|
143
|
-
|
|
144
140
|
// Respect limit strictly after flattening
|
|
145
141
|
return unifiedMarkets.slice(0, limit);
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
148
144
|
console.error("Error fetching Polymarket data:", error);
|
|
149
145
|
return [];
|
|
150
146
|
}
|
|
151
147
|
}
|
|
152
|
-
|
|
153
|
-
async searchMarkets(query: string, params?: MarketFilterParams): Promise<UnifiedMarket[]> {
|
|
148
|
+
async searchMarkets(query, params) {
|
|
154
149
|
// Polymarket Gamma API doesn't support native search
|
|
155
150
|
// Fetch a larger batch and filter client-side
|
|
156
151
|
const searchLimit = 100; // Fetch more markets to search through
|
|
157
|
-
|
|
158
152
|
try {
|
|
159
153
|
// Fetch markets with a higher limit
|
|
160
154
|
const markets = await this.fetchMarkets({
|
|
161
155
|
...params,
|
|
162
156
|
limit: searchLimit
|
|
163
157
|
});
|
|
164
|
-
|
|
165
158
|
// Client-side text filtering
|
|
166
159
|
const lowerQuery = query.toLowerCase();
|
|
167
|
-
const filtered = markets.filter(market =>
|
|
168
|
-
market.
|
|
169
|
-
market.description.toLowerCase().includes(lowerQuery)
|
|
170
|
-
);
|
|
171
|
-
|
|
160
|
+
const filtered = markets.filter(market => market.title.toLowerCase().includes(lowerQuery) ||
|
|
161
|
+
market.description.toLowerCase().includes(lowerQuery));
|
|
172
162
|
// Apply limit to filtered results
|
|
173
163
|
const limit = params?.limit || 20;
|
|
174
164
|
return filtered.slice(0, limit);
|
|
175
|
-
|
|
176
|
-
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
177
167
|
console.error("Error searching Polymarket data:", error);
|
|
178
168
|
return [];
|
|
179
169
|
}
|
|
180
170
|
}
|
|
181
|
-
|
|
182
171
|
/**
|
|
183
172
|
* Fetch specific markets by their URL slug.
|
|
184
173
|
* Useful for looking up a specific event from a URL.
|
|
185
174
|
* @param slug - The event slug (e.g. "will-fed-cut-rates-in-march")
|
|
186
175
|
*/
|
|
187
|
-
async getMarketsBySlug(slug
|
|
176
|
+
async getMarketsBySlug(slug) {
|
|
188
177
|
try {
|
|
189
|
-
const response = await
|
|
178
|
+
const response = await axios_1.default.get(this.baseUrl, {
|
|
190
179
|
params: { slug: slug }
|
|
191
180
|
});
|
|
192
|
-
|
|
193
181
|
const events = response.data;
|
|
194
|
-
if (!events || events.length === 0)
|
|
195
|
-
|
|
182
|
+
if (!events || events.length === 0)
|
|
183
|
+
return [];
|
|
196
184
|
// We can reuse the logic from fetchMarkets if we extract it,
|
|
197
185
|
// but for now I'll duplicate the extraction logic to keep it self-contained
|
|
198
186
|
// and avoid safe refactoring risks.
|
|
199
187
|
// Actually, fetchMarkets is built to work with the Gamma response structure.
|
|
200
188
|
// So we can manually map the response using the same logic.
|
|
201
|
-
|
|
202
|
-
const unifiedMarkets: UnifiedMarket[] = [];
|
|
203
|
-
|
|
189
|
+
const unifiedMarkets = [];
|
|
204
190
|
for (const event of events) {
|
|
205
|
-
if (!event.markets)
|
|
206
|
-
|
|
191
|
+
if (!event.markets)
|
|
192
|
+
continue;
|
|
207
193
|
for (const market of event.markets) {
|
|
208
|
-
const outcomes
|
|
209
|
-
let outcomeLabels
|
|
210
|
-
let outcomePrices
|
|
211
|
-
let clobTokenIds
|
|
212
|
-
|
|
194
|
+
const outcomes = [];
|
|
195
|
+
let outcomeLabels = [];
|
|
196
|
+
let outcomePrices = [];
|
|
197
|
+
let clobTokenIds = [];
|
|
213
198
|
try {
|
|
214
199
|
outcomeLabels = typeof market.outcomes === 'string' ? JSON.parse(market.outcomes) : (market.outcomes || []);
|
|
215
200
|
outcomePrices = typeof market.outcomePrices === 'string' ? JSON.parse(market.outcomePrices) : (market.outcomePrices || []);
|
|
216
201
|
clobTokenIds = typeof market.clobTokenIds === 'string' ? JSON.parse(market.clobTokenIds) : (market.clobTokenIds || []);
|
|
217
|
-
}
|
|
218
|
-
|
|
202
|
+
}
|
|
203
|
+
catch (e) { /* ignore */ }
|
|
219
204
|
let candidateName = market.groupItemTitle;
|
|
220
|
-
if (!candidateName && market.question)
|
|
221
|
-
|
|
205
|
+
if (!candidateName && market.question)
|
|
206
|
+
candidateName = market.question;
|
|
222
207
|
if (outcomeLabels.length > 0) {
|
|
223
|
-
outcomeLabels.forEach((label
|
|
208
|
+
outcomeLabels.forEach((label, index) => {
|
|
224
209
|
let outcomeLabel = label;
|
|
225
210
|
// Clean up Yes/No labels if candidate name is available
|
|
226
|
-
if (candidateName && label.toLowerCase() === 'yes')
|
|
227
|
-
|
|
228
|
-
|
|
211
|
+
if (candidateName && label.toLowerCase() === 'yes')
|
|
212
|
+
outcomeLabel = candidateName;
|
|
213
|
+
else if (candidateName && label.toLowerCase() === 'no')
|
|
214
|
+
outcomeLabel = `Not ${candidateName}`;
|
|
229
215
|
// 24h Price Change Logic
|
|
230
216
|
let priceChange = 0;
|
|
231
217
|
if (index === 0 || label.toLowerCase() === 'yes' || (candidateName && label === candidateName)) {
|
|
232
218
|
priceChange = Number(market.oneDayPriceChange || 0);
|
|
233
219
|
}
|
|
234
|
-
|
|
235
220
|
outcomes.push({
|
|
236
221
|
id: String(index),
|
|
237
222
|
label: outcomeLabel,
|
|
@@ -243,7 +228,6 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
243
228
|
});
|
|
244
229
|
});
|
|
245
230
|
}
|
|
246
|
-
|
|
247
231
|
unifiedMarkets.push({
|
|
248
232
|
id: market.id,
|
|
249
233
|
title: event.title,
|
|
@@ -257,23 +241,22 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
257
241
|
url: `https://polymarket.com/event/${event.slug}`,
|
|
258
242
|
image: event.image || market.image,
|
|
259
243
|
category: event.category || event.tags?.[0]?.label,
|
|
260
|
-
tags: event.tags?.map((t
|
|
244
|
+
tags: event.tags?.map((t) => t.label) || []
|
|
261
245
|
});
|
|
262
246
|
}
|
|
263
247
|
}
|
|
264
248
|
return unifiedMarkets;
|
|
265
|
-
|
|
266
|
-
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
267
251
|
console.error(`Error fetching Polymarket slug ${slug}:`, error);
|
|
268
252
|
return [];
|
|
269
253
|
}
|
|
270
254
|
}
|
|
271
|
-
|
|
272
255
|
/**
|
|
273
256
|
* Map our generic CandleInterval to Polymarket's fidelity (in minutes)
|
|
274
257
|
*/
|
|
275
|
-
|
|
276
|
-
const mapping
|
|
258
|
+
mapIntervalToFidelity(interval) {
|
|
259
|
+
const mapping = {
|
|
277
260
|
'1m': 1,
|
|
278
261
|
'5m': 5,
|
|
279
262
|
'15m': 15,
|
|
@@ -283,26 +266,22 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
283
266
|
};
|
|
284
267
|
return mapping[interval];
|
|
285
268
|
}
|
|
286
|
-
|
|
287
269
|
/**
|
|
288
270
|
* Fetch historical price data (OHLCV candles) for a specific token.
|
|
289
271
|
* @param id - The CLOB token ID (e.g., outcome token ID)
|
|
290
272
|
*/
|
|
291
|
-
async getMarketHistory(id
|
|
273
|
+
async getMarketHistory(id, params) {
|
|
292
274
|
// ID Validation: Polymarket CLOB requires a Token ID (long numeric string) not a Market ID
|
|
293
275
|
if (id.length < 10 && /^\d+$/.test(id)) {
|
|
294
276
|
throw new Error(`Invalid ID for Polymarket history: "${id}". You provided a Market ID, but Polymarket's CLOB API requires a Token ID. Use outcome.metadata.clobTokenId instead.`);
|
|
295
277
|
}
|
|
296
|
-
|
|
297
278
|
try {
|
|
298
279
|
const fidelity = this.mapIntervalToFidelity(params.resolution);
|
|
299
280
|
const nowTs = Math.floor(Date.now() / 1000);
|
|
300
|
-
|
|
301
281
|
// 1. Smart Lookback Calculation
|
|
302
282
|
// If start/end not provided, calculate window based on limit * resolution
|
|
303
283
|
let startTs = params.start ? Math.floor(params.start.getTime() / 1000) : 0;
|
|
304
284
|
let endTs = params.end ? Math.floor(params.end.getTime() / 1000) : nowTs;
|
|
305
|
-
|
|
306
285
|
if (!params.start) {
|
|
307
286
|
// Default limit is usually 20 in the example, but safety margin is good.
|
|
308
287
|
// If limit is not set, we default to 100 candles.
|
|
@@ -311,29 +290,23 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
311
290
|
const durationSeconds = count * fidelity * 60;
|
|
312
291
|
startTs = endTs - durationSeconds;
|
|
313
292
|
}
|
|
314
|
-
|
|
315
|
-
const queryParams: any = {
|
|
293
|
+
const queryParams = {
|
|
316
294
|
market: id,
|
|
317
295
|
fidelity: fidelity,
|
|
318
296
|
startTs: startTs,
|
|
319
297
|
endTs: endTs
|
|
320
298
|
};
|
|
321
|
-
|
|
322
|
-
const response = await axios.get(`${this.clobUrl}/prices-history`, {
|
|
299
|
+
const response = await axios_1.default.get(`${this.clobUrl}/prices-history`, {
|
|
323
300
|
params: queryParams
|
|
324
301
|
});
|
|
325
|
-
|
|
326
302
|
const history = response.data.history || [];
|
|
327
|
-
|
|
328
303
|
// 2. Align Timestamps (Snap to Grid)
|
|
329
304
|
// Polymarket returns random tick timestamps (e.g. 1:00:21).
|
|
330
305
|
// We want to normalize this to the start of the bucket (1:00:00).
|
|
331
306
|
const resolutionMs = fidelity * 60 * 1000;
|
|
332
|
-
|
|
333
|
-
const candles: PriceCandle[] = history.map((item: any) => {
|
|
307
|
+
const candles = history.map((item) => {
|
|
334
308
|
const rawMs = item.t * 1000;
|
|
335
309
|
const snappedMs = Math.floor(rawMs / resolutionMs) * resolutionMs;
|
|
336
|
-
|
|
337
310
|
return {
|
|
338
311
|
timestamp: snappedMs, // Aligned timestamp
|
|
339
312
|
open: item.p,
|
|
@@ -343,16 +316,14 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
343
316
|
volume: undefined
|
|
344
317
|
};
|
|
345
318
|
});
|
|
346
|
-
|
|
347
319
|
// Apply limit if specified
|
|
348
320
|
if (params.limit && candles.length > params.limit) {
|
|
349
321
|
return candles.slice(-params.limit);
|
|
350
322
|
}
|
|
351
|
-
|
|
352
323
|
return candles;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
if (axios_1.default.isAxiosError(error) && error.response) {
|
|
356
327
|
const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
|
|
357
328
|
throw new Error(`Polymarket History API Error (${error.response.status}): ${apiError}. Used ID: ${id}`);
|
|
358
329
|
}
|
|
@@ -360,61 +331,53 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
360
331
|
throw error;
|
|
361
332
|
}
|
|
362
333
|
}
|
|
363
|
-
|
|
364
334
|
/**
|
|
365
335
|
* Fetch the current order book for a specific token.
|
|
366
336
|
* @param id - The CLOB token ID
|
|
367
337
|
*/
|
|
368
|
-
async getOrderBook(id
|
|
338
|
+
async getOrderBook(id) {
|
|
369
339
|
try {
|
|
370
|
-
const response = await
|
|
340
|
+
const response = await axios_1.default.get(`${this.clobUrl}/book`, {
|
|
371
341
|
params: { token_id: id }
|
|
372
342
|
});
|
|
373
|
-
|
|
374
343
|
const data = response.data;
|
|
375
|
-
|
|
376
344
|
// Response format: { bids: [{price: "0.52", size: "100"}], asks: [...] }
|
|
377
|
-
const bids = (data.bids || []).map((level
|
|
345
|
+
const bids = (data.bids || []).map((level) => ({
|
|
378
346
|
price: parseFloat(level.price),
|
|
379
347
|
size: parseFloat(level.size)
|
|
380
|
-
})).sort((a
|
|
381
|
-
|
|
382
|
-
const asks = (data.asks || []).map((level: any) => ({
|
|
348
|
+
})).sort((a, b) => b.price - a.price); // Sort Bids Descending (Best/Highest first)
|
|
349
|
+
const asks = (data.asks || []).map((level) => ({
|
|
383
350
|
price: parseFloat(level.price),
|
|
384
351
|
size: parseFloat(level.size)
|
|
385
|
-
})).sort((a
|
|
386
|
-
|
|
352
|
+
})).sort((a, b) => a.price - b.price); // Sort Asks Ascending (Best/Lowest first)
|
|
387
353
|
return {
|
|
388
354
|
bids,
|
|
389
355
|
asks,
|
|
390
356
|
timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now()
|
|
391
357
|
};
|
|
392
|
-
|
|
393
|
-
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
394
360
|
console.error(`Error fetching Polymarket orderbook for ${id}:`, error);
|
|
395
361
|
return { bids: [], asks: [] };
|
|
396
362
|
}
|
|
397
363
|
}
|
|
398
|
-
|
|
399
364
|
/**
|
|
400
365
|
* Fetch raw trade history for a specific token.
|
|
401
366
|
* @param id - The CLOB token ID
|
|
402
|
-
*
|
|
367
|
+
*
|
|
403
368
|
* NOTE: Polymarket's /trades endpoint currently requires L2 Authentication (API Key).
|
|
404
369
|
* This method will return an empty array if an API key is not provided in headers.
|
|
405
370
|
* Use getMarketHistory for public historical price data instead.
|
|
406
371
|
*/
|
|
407
|
-
async getTradeHistory(id
|
|
372
|
+
async getTradeHistory(id, params) {
|
|
408
373
|
// ID Validation
|
|
409
374
|
if (id.length < 10 && /^\d+$/.test(id)) {
|
|
410
375
|
throw new Error(`Invalid ID for Polymarket trades: "${id}". You provided a Market ID, but Polymarket's CLOB API requires a Token ID.`);
|
|
411
376
|
}
|
|
412
|
-
|
|
413
377
|
try {
|
|
414
|
-
const queryParams
|
|
378
|
+
const queryParams = {
|
|
415
379
|
market: id
|
|
416
380
|
};
|
|
417
|
-
|
|
418
381
|
// Add time filters if provided
|
|
419
382
|
if (params.start) {
|
|
420
383
|
queryParams.after = Math.floor(params.start.getTime() / 1000);
|
|
@@ -422,31 +385,26 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
422
385
|
if (params.end) {
|
|
423
386
|
queryParams.before = Math.floor(params.end.getTime() / 1000);
|
|
424
387
|
}
|
|
425
|
-
|
|
426
|
-
const response = await axios.get(`${this.clobUrl}/trades`, {
|
|
388
|
+
const response = await axios_1.default.get(`${this.clobUrl}/trades`, {
|
|
427
389
|
params: queryParams
|
|
428
390
|
});
|
|
429
|
-
|
|
430
391
|
// Response is an array of trade objects
|
|
431
392
|
const trades = response.data || [];
|
|
432
|
-
|
|
433
|
-
const mappedTrades: Trade[] = trades.map((trade: any) => ({
|
|
393
|
+
const mappedTrades = trades.map((trade) => ({
|
|
434
394
|
id: trade.id || `${trade.timestamp}-${trade.price}`,
|
|
435
395
|
timestamp: trade.timestamp * 1000, // Convert to milliseconds
|
|
436
396
|
price: parseFloat(trade.price),
|
|
437
397
|
amount: parseFloat(trade.size || trade.amount || 0),
|
|
438
398
|
side: trade.side === 'BUY' ? 'buy' : trade.side === 'SELL' ? 'sell' : 'unknown'
|
|
439
399
|
}));
|
|
440
|
-
|
|
441
400
|
// Apply limit if specified
|
|
442
401
|
if (params.limit && mappedTrades.length > params.limit) {
|
|
443
402
|
return mappedTrades.slice(-params.limit); // Return most recent N trades
|
|
444
403
|
}
|
|
445
|
-
|
|
446
404
|
return mappedTrades;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
if (axios_1.default.isAxiosError(error) && error.response) {
|
|
450
408
|
const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
|
|
451
409
|
throw new Error(`Polymarket Trades API Error (${error.response.status}): ${apiError}. Used ID: ${id}`);
|
|
452
410
|
}
|
|
@@ -455,3 +413,4 @@ export class PolymarketExchange extends PredictionMarketExchange {
|
|
|
455
413
|
}
|
|
456
414
|
}
|
|
457
415
|
}
|
|
416
|
+
exports.PolymarketExchange = PolymarketExchange;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./BaseExchange"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
__exportStar(require("./exchanges/Polymarket"), exports);
|
|
20
|
+
__exportStar(require("./exchanges/Kalshi"), exports);
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
// ----------------------------------------------------------------------------
|
|
3
|
-
// Core Data Models
|
|
4
|
-
// ----------------------------------------------------------------------------
|
|
5
|
-
|
|
6
1
|
export interface MarketOutcome {
|
|
7
2
|
id: string;
|
|
8
3
|
label: string;
|
|
@@ -10,28 +5,22 @@ export interface MarketOutcome {
|
|
|
10
5
|
priceChange24h?: number;
|
|
11
6
|
metadata?: Record<string, any>;
|
|
12
7
|
}
|
|
13
|
-
|
|
14
8
|
export interface UnifiedMarket {
|
|
15
9
|
id: string;
|
|
16
10
|
title: string;
|
|
17
11
|
description: string;
|
|
18
12
|
outcomes: MarketOutcome[];
|
|
19
|
-
|
|
20
13
|
resolutionDate: Date;
|
|
21
14
|
volume24h: number;
|
|
22
|
-
volume?: number;
|
|
15
|
+
volume?: number;
|
|
23
16
|
liquidity: number;
|
|
24
17
|
openInterest?: number;
|
|
25
|
-
|
|
26
18
|
url: string;
|
|
27
19
|
image?: string;
|
|
28
|
-
|
|
29
20
|
category?: string;
|
|
30
21
|
tags?: string[];
|
|
31
22
|
}
|
|
32
|
-
|
|
33
23
|
export type CandleInterval = '1m' | '5m' | '15m' | '1h' | '6h' | '1d';
|
|
34
|
-
|
|
35
24
|
export interface PriceCandle {
|
|
36
25
|
timestamp: number;
|
|
37
26
|
open: number;
|
|
@@ -40,18 +29,15 @@ export interface PriceCandle {
|
|
|
40
29
|
close: number;
|
|
41
30
|
volume?: number;
|
|
42
31
|
}
|
|
43
|
-
|
|
44
32
|
export interface OrderLevel {
|
|
45
|
-
price: number;
|
|
46
|
-
size: number;
|
|
33
|
+
price: number;
|
|
34
|
+
size: number;
|
|
47
35
|
}
|
|
48
|
-
|
|
49
36
|
export interface OrderBook {
|
|
50
37
|
bids: OrderLevel[];
|
|
51
38
|
asks: OrderLevel[];
|
|
52
39
|
timestamp?: number;
|
|
53
40
|
}
|
|
54
|
-
|
|
55
41
|
export interface Trade {
|
|
56
42
|
id: string;
|
|
57
43
|
timestamp: number;
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxtjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.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",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"API_REFERENCE.md"
|
|
10
|
+
],
|
|
7
11
|
"directories": {
|
|
8
12
|
"example": "examples",
|
|
9
13
|
"test": "test"
|
|
@@ -19,5 +23,9 @@
|
|
|
19
23
|
"dependencies": {
|
|
20
24
|
"axios": "^1.7.9",
|
|
21
25
|
"tsx": "^4.21.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
|
+
"@types/node": "^25.0.3"
|
|
22
30
|
}
|
|
23
31
|
}
|