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