pmxtjs 0.4.1 → 0.4.3
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/exchanges/kalshi/utils.js +1 -1
- package/package.json +3 -1
- package/readme.md +3 -1
- package/dist/exchanges/Kalshi.d.ts +0 -20
- package/dist/exchanges/Kalshi.js +0 -339
- package/dist/exchanges/Polymarket.d.ts +0 -38
- package/dist/exchanges/Polymarket.js +0 -428
|
@@ -60,7 +60,7 @@ function mapMarketToUnified(event, market) {
|
|
|
60
60
|
return {
|
|
61
61
|
id: market.ticker,
|
|
62
62
|
title: event.title,
|
|
63
|
-
description:
|
|
63
|
+
description: market.rules_primary || market.rules_secondary || "",
|
|
64
64
|
outcomes: outcomes,
|
|
65
65
|
resolutionDate: new Date(market.expiration_time),
|
|
66
66
|
volume24h: Number(market.volume_24h || market.volume || 0),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxtjs",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"test": "test"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
+
"clean": "rm -rf dist",
|
|
17
|
+
"prebuild": "npm run clean",
|
|
16
18
|
"build": "tsc",
|
|
17
19
|
"test": "jest -c jest.config.js"
|
|
18
20
|
},
|
package/readme.md
CHANGED
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
<a href="https://github.com/qoery-com/pmxt/stargazers"><img src="https://img.shields.io/github/stars/qoery-com/pmxt?refresh=1" alt="GitHub stars"></a>
|
|
31
31
|
</td>
|
|
32
32
|
<td>
|
|
33
|
-
|
|
33
|
+
<a href="https://www.npmjs.com/package/pmxtjs">
|
|
34
|
+
<img src="https://img.shields.io/npm/v/pmxtjs.svg" alt="npm version">
|
|
35
|
+
</a>
|
|
34
36
|
</td>
|
|
35
37
|
</tr>
|
|
36
38
|
</table>
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { PredictionMarketExchange, MarketFilterParams, HistoryFilterParams } from '../BaseExchange';
|
|
2
|
-
import { UnifiedMarket, PriceCandle, OrderBook, Trade } from '../types';
|
|
3
|
-
export declare class KalshiExchange extends PredictionMarketExchange {
|
|
4
|
-
get name(): string;
|
|
5
|
-
private baseUrl;
|
|
6
|
-
fetchMarkets(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
7
|
-
private fetchActiveEvents;
|
|
8
|
-
private mapMarketToUnified;
|
|
9
|
-
searchMarkets(query: string, params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
10
|
-
/**
|
|
11
|
-
* Fetch specific markets by their event ticker.
|
|
12
|
-
* Useful for looking up a specific event from a URL.
|
|
13
|
-
* @param eventTicker - The event ticker (e.g. "FED-25JAN" or "PRES-2024")
|
|
14
|
-
*/
|
|
15
|
-
getMarketsBySlug(eventTicker: string): Promise<UnifiedMarket[]>;
|
|
16
|
-
private mapIntervalToKalshi;
|
|
17
|
-
fetchOHLCV(id: string, params: HistoryFilterParams): Promise<PriceCandle[]>;
|
|
18
|
-
fetchOrderBook(id: string): Promise<OrderBook>;
|
|
19
|
-
fetchTrades(id: string, params: HistoryFilterParams): Promise<Trade[]>;
|
|
20
|
-
}
|
package/dist/exchanges/Kalshi.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
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.KalshiExchange = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
const BaseExchange_1 = require("../BaseExchange");
|
|
9
|
-
class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
10
|
-
constructor() {
|
|
11
|
-
super(...arguments);
|
|
12
|
-
this.baseUrl = "https://api.elections.kalshi.com/trade-api/v2/events";
|
|
13
|
-
}
|
|
14
|
-
get name() {
|
|
15
|
-
return "Kalshi";
|
|
16
|
-
}
|
|
17
|
-
async fetchMarkets(params) {
|
|
18
|
-
const limit = params?.limit || 50;
|
|
19
|
-
try {
|
|
20
|
-
// Fetch active events with nested markets
|
|
21
|
-
// For small limits, we can optimize by fetching fewer pages
|
|
22
|
-
const allEvents = await this.fetchActiveEvents(limit);
|
|
23
|
-
// Extract ALL markets from all events
|
|
24
|
-
const allMarkets = [];
|
|
25
|
-
for (const event of allEvents) {
|
|
26
|
-
const markets = event.markets || [];
|
|
27
|
-
for (const market of markets) {
|
|
28
|
-
const unifiedMarket = this.mapMarketToUnified(event, market);
|
|
29
|
-
if (unifiedMarket) {
|
|
30
|
-
allMarkets.push(unifiedMarket);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Sort by 24h volume
|
|
35
|
-
if (params?.sort === 'volume') {
|
|
36
|
-
allMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
37
|
-
}
|
|
38
|
-
else if (params?.sort === 'liquidity') {
|
|
39
|
-
allMarkets.sort((a, b) => b.liquidity - a.liquidity);
|
|
40
|
-
}
|
|
41
|
-
return allMarkets.slice(0, limit);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
console.error("Error fetching Kalshi data:", error);
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async fetchActiveEvents(targetMarketCount) {
|
|
49
|
-
let allEvents = [];
|
|
50
|
-
let totalMarketCount = 0;
|
|
51
|
-
let cursor = null;
|
|
52
|
-
let page = 0;
|
|
53
|
-
// Note: Kalshi API uses cursor-based pagination which requires sequential fetching.
|
|
54
|
-
// We cannot parallelize requests for a single list because we need the cursor from page N to fetch page N+1.
|
|
55
|
-
// To optimize, we use the maximum allowed limit (200) and fetch until exhaustion.
|
|
56
|
-
const MAX_PAGES = 1000; // Safety cap against infinite loops
|
|
57
|
-
const BATCH_SIZE = 200; // Max limit per Kalshi API docs
|
|
58
|
-
do {
|
|
59
|
-
try {
|
|
60
|
-
// console.log(`Fetching Kalshi page ${page + 1}...`);
|
|
61
|
-
const queryParams = {
|
|
62
|
-
limit: BATCH_SIZE,
|
|
63
|
-
with_nested_markets: true,
|
|
64
|
-
status: 'open' // Filter to open markets to improve relevance and speed
|
|
65
|
-
};
|
|
66
|
-
if (cursor)
|
|
67
|
-
queryParams.cursor = cursor;
|
|
68
|
-
const response = await axios_1.default.get(this.baseUrl, { params: queryParams });
|
|
69
|
-
const events = response.data.events || [];
|
|
70
|
-
if (events.length === 0)
|
|
71
|
-
break;
|
|
72
|
-
allEvents = allEvents.concat(events);
|
|
73
|
-
// Count markets in this batch for early termination
|
|
74
|
-
if (targetMarketCount) {
|
|
75
|
-
for (const event of events) {
|
|
76
|
-
totalMarketCount += (event.markets || []).length;
|
|
77
|
-
}
|
|
78
|
-
// Early termination: if we have enough markets, stop fetching
|
|
79
|
-
if (totalMarketCount >= targetMarketCount * 2) {
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
cursor = response.data.cursor;
|
|
84
|
-
page++;
|
|
85
|
-
}
|
|
86
|
-
catch (e) {
|
|
87
|
-
console.error(`Error fetching Kalshi page ${page}:`, e);
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
} while (cursor && page < MAX_PAGES);
|
|
91
|
-
return allEvents;
|
|
92
|
-
}
|
|
93
|
-
mapMarketToUnified(event, market) {
|
|
94
|
-
if (!market)
|
|
95
|
-
return null;
|
|
96
|
-
// Calculate price
|
|
97
|
-
let price = 0.5;
|
|
98
|
-
if (market.last_price) {
|
|
99
|
-
price = market.last_price / 100;
|
|
100
|
-
}
|
|
101
|
-
else if (market.yes_ask && market.yes_bid) {
|
|
102
|
-
price = (market.yes_ask + market.yes_bid) / 200;
|
|
103
|
-
}
|
|
104
|
-
else if (market.yes_ask) {
|
|
105
|
-
price = market.yes_ask / 100;
|
|
106
|
-
}
|
|
107
|
-
// Extract candidate name
|
|
108
|
-
let candidateName = null;
|
|
109
|
-
if (market.subtitle || market.yes_sub_title) {
|
|
110
|
-
candidateName = market.subtitle || market.yes_sub_title;
|
|
111
|
-
}
|
|
112
|
-
// Calculate 24h change
|
|
113
|
-
let priceChange = 0;
|
|
114
|
-
if (market.previous_price_dollars !== undefined && market.last_price_dollars !== undefined) {
|
|
115
|
-
priceChange = market.last_price_dollars - market.previous_price_dollars;
|
|
116
|
-
}
|
|
117
|
-
const outcomes = [
|
|
118
|
-
{
|
|
119
|
-
id: market.ticker, // The actual market ticker (primary tradeable)
|
|
120
|
-
label: candidateName || 'Yes',
|
|
121
|
-
price: price,
|
|
122
|
-
priceChange24h: priceChange
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
id: `${market.ticker}-NO`, // Virtual ID for the No outcome
|
|
126
|
-
label: candidateName ? `Not ${candidateName}` : 'No',
|
|
127
|
-
price: 1 - price,
|
|
128
|
-
priceChange24h: -priceChange // Inverse change for No? simplified assumption
|
|
129
|
-
}
|
|
130
|
-
];
|
|
131
|
-
return {
|
|
132
|
-
id: market.ticker,
|
|
133
|
-
title: event.title,
|
|
134
|
-
description: event.sub_title || market.subtitle || "",
|
|
135
|
-
outcomes: outcomes,
|
|
136
|
-
resolutionDate: new Date(market.expiration_time),
|
|
137
|
-
volume24h: Number(market.volume_24h || market.volume || 0),
|
|
138
|
-
volume: Number(market.volume || 0),
|
|
139
|
-
liquidity: Number(market.liquidity || 0), // Kalshi 'liquidity' might need specific mapping if available, otherwise 0 to avoid conflation
|
|
140
|
-
openInterest: Number(market.open_interest || 0),
|
|
141
|
-
url: `https://kalshi.com/events/${event.event_ticker}`,
|
|
142
|
-
category: event.category,
|
|
143
|
-
tags: event.tags || []
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
async searchMarkets(query, params) {
|
|
147
|
-
// We must fetch ALL markets to search them locally since we don't have server-side search
|
|
148
|
-
const fetchLimit = 100000;
|
|
149
|
-
try {
|
|
150
|
-
const markets = await this.fetchMarkets({ ...params, limit: fetchLimit });
|
|
151
|
-
const lowerQuery = query.toLowerCase();
|
|
152
|
-
const searchIn = params?.searchIn || 'title'; // Default to title-only search
|
|
153
|
-
const filtered = markets.filter(market => {
|
|
154
|
-
const titleMatch = (market.title || '').toLowerCase().includes(lowerQuery);
|
|
155
|
-
const descMatch = (market.description || '').toLowerCase().includes(lowerQuery);
|
|
156
|
-
if (searchIn === 'title')
|
|
157
|
-
return titleMatch;
|
|
158
|
-
if (searchIn === 'description')
|
|
159
|
-
return descMatch;
|
|
160
|
-
return titleMatch || descMatch; // 'both'
|
|
161
|
-
});
|
|
162
|
-
const limit = params?.limit || 20;
|
|
163
|
-
return filtered.slice(0, limit);
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
console.error("Error searching Kalshi data:", error);
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Fetch specific markets by their event ticker.
|
|
172
|
-
* Useful for looking up a specific event from a URL.
|
|
173
|
-
* @param eventTicker - The event ticker (e.g. "FED-25JAN" or "PRES-2024")
|
|
174
|
-
*/
|
|
175
|
-
async getMarketsBySlug(eventTicker) {
|
|
176
|
-
try {
|
|
177
|
-
// Kalshi API expects uppercase tickers, but URLs use lowercase
|
|
178
|
-
const normalizedTicker = eventTicker.toUpperCase();
|
|
179
|
-
const url = `https://api.elections.kalshi.com/trade-api/v2/events/${normalizedTicker}`;
|
|
180
|
-
const response = await axios_1.default.get(url, {
|
|
181
|
-
params: { with_nested_markets: true }
|
|
182
|
-
});
|
|
183
|
-
const event = response.data.event;
|
|
184
|
-
if (!event)
|
|
185
|
-
return [];
|
|
186
|
-
const unifiedMarkets = [];
|
|
187
|
-
const markets = event.markets || [];
|
|
188
|
-
for (const market of markets) {
|
|
189
|
-
const unifiedMarket = this.mapMarketToUnified(event, market);
|
|
190
|
-
if (unifiedMarket) {
|
|
191
|
-
unifiedMarkets.push(unifiedMarket);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return unifiedMarkets;
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
if (axios_1.default.isAxiosError(error) && error.response) {
|
|
198
|
-
if (error.response.status === 404) {
|
|
199
|
-
throw new Error(`Kalshi event not found: "${eventTicker}". Check that the event ticker is correct.`);
|
|
200
|
-
}
|
|
201
|
-
const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
|
|
202
|
-
throw new Error(`Kalshi API Error (${error.response.status}): ${apiError}. Event Ticker: ${eventTicker}`);
|
|
203
|
-
}
|
|
204
|
-
console.error(`Unexpected error fetching Kalshi event ${eventTicker}:`, error);
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
mapIntervalToKalshi(interval) {
|
|
209
|
-
const mapping = {
|
|
210
|
-
'1m': 1,
|
|
211
|
-
'5m': 1,
|
|
212
|
-
'15m': 1,
|
|
213
|
-
'1h': 60,
|
|
214
|
-
'6h': 60,
|
|
215
|
-
'1d': 1440
|
|
216
|
-
};
|
|
217
|
-
return mapping[interval];
|
|
218
|
-
}
|
|
219
|
-
async fetchOHLCV(id, params) {
|
|
220
|
-
try {
|
|
221
|
-
// Kalshi API expects uppercase tickers
|
|
222
|
-
// Handle virtual "-NO" suffix by stripping it (fetching the underlying market history)
|
|
223
|
-
const cleanedId = id.replace(/-NO$/, '');
|
|
224
|
-
const normalizedId = cleanedId.toUpperCase();
|
|
225
|
-
const interval = this.mapIntervalToKalshi(params.resolution);
|
|
226
|
-
// Heuristic for series_ticker
|
|
227
|
-
const parts = normalizedId.split('-');
|
|
228
|
-
if (parts.length < 2) {
|
|
229
|
-
throw new Error(`Invalid Kalshi Ticker format: "${id}". Expected format like "FED-25JAN29-B4.75".`);
|
|
230
|
-
}
|
|
231
|
-
const seriesTicker = parts.slice(0, -1).join('-');
|
|
232
|
-
const url = `https://api.elections.kalshi.com/trade-api/v2/series/${seriesTicker}/markets/${normalizedId}/candlesticks`;
|
|
233
|
-
const queryParams = { period_interval: interval };
|
|
234
|
-
const now = Math.floor(Date.now() / 1000);
|
|
235
|
-
let startTs = now - (24 * 60 * 60);
|
|
236
|
-
let endTs = now;
|
|
237
|
-
if (params.start) {
|
|
238
|
-
startTs = Math.floor(params.start.getTime() / 1000);
|
|
239
|
-
}
|
|
240
|
-
if (params.end) {
|
|
241
|
-
endTs = Math.floor(params.end.getTime() / 1000);
|
|
242
|
-
if (!params.start) {
|
|
243
|
-
startTs = endTs - (24 * 60 * 60);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
queryParams.start_ts = startTs;
|
|
247
|
-
queryParams.end_ts = endTs;
|
|
248
|
-
const response = await axios_1.default.get(url, { params: queryParams });
|
|
249
|
-
const candles = response.data.candlesticks || [];
|
|
250
|
-
const mappedCandles = candles.map((c) => {
|
|
251
|
-
// Priority:
|
|
252
|
-
// 1. Transaction price (close)
|
|
253
|
-
// 2. Mid price (average of yes_ask and yes_bid close)
|
|
254
|
-
// 3. Fallback to 0 if everything is missing
|
|
255
|
-
const p = c.price || {};
|
|
256
|
-
const ask = c.yes_ask || {};
|
|
257
|
-
const bid = c.yes_bid || {};
|
|
258
|
-
const getVal = (field) => {
|
|
259
|
-
if (p[field] !== null && p[field] !== undefined)
|
|
260
|
-
return p[field];
|
|
261
|
-
if (ask[field] !== null && bid[field] !== null && ask[field] !== undefined && bid[field] !== undefined) {
|
|
262
|
-
return (ask[field] + bid[field]) / 2;
|
|
263
|
-
}
|
|
264
|
-
return p.previous || 0;
|
|
265
|
-
};
|
|
266
|
-
return {
|
|
267
|
-
timestamp: c.end_period_ts * 1000,
|
|
268
|
-
open: getVal('open') / 100,
|
|
269
|
-
high: getVal('high') / 100,
|
|
270
|
-
low: getVal('low') / 100,
|
|
271
|
-
close: getVal('close') / 100,
|
|
272
|
-
volume: c.volume || 0
|
|
273
|
-
};
|
|
274
|
-
});
|
|
275
|
-
if (params.limit && mappedCandles.length > params.limit) {
|
|
276
|
-
return mappedCandles.slice(-params.limit);
|
|
277
|
-
}
|
|
278
|
-
return mappedCandles;
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
if (axios_1.default.isAxiosError(error) && error.response) {
|
|
282
|
-
const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
|
|
283
|
-
throw new Error(`Kalshi History API Error (${error.response.status}): ${apiError}. Used Ticker: ${id}`);
|
|
284
|
-
}
|
|
285
|
-
console.error(`Unexpected error fetching Kalshi history for ${id}:`, error);
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
async fetchOrderBook(id) {
|
|
290
|
-
try {
|
|
291
|
-
const ticker = id.replace(/-NO$/, '');
|
|
292
|
-
const url = `https://api.elections.kalshi.com/trade-api/v2/markets/${ticker}/orderbook`;
|
|
293
|
-
const response = await axios_1.default.get(url);
|
|
294
|
-
const data = response.data.orderbook;
|
|
295
|
-
// Structure: { yes: [[price, qty], ...], no: [[price, qty], ...] }
|
|
296
|
-
const bids = (data.yes || []).map((level) => ({
|
|
297
|
-
price: level[0] / 100,
|
|
298
|
-
size: level[1]
|
|
299
|
-
}));
|
|
300
|
-
const asks = (data.no || []).map((level) => ({
|
|
301
|
-
price: (100 - level[0]) / 100,
|
|
302
|
-
size: level[1]
|
|
303
|
-
}));
|
|
304
|
-
// Sort bids desc, asks asc
|
|
305
|
-
bids.sort((a, b) => b.price - a.price);
|
|
306
|
-
asks.sort((a, b) => a.price - b.price);
|
|
307
|
-
return { bids, asks, timestamp: Date.now() };
|
|
308
|
-
}
|
|
309
|
-
catch (error) {
|
|
310
|
-
console.error(`Error fetching Kalshi orderbook for ${id}:`, error);
|
|
311
|
-
return { bids: [], asks: [] };
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
async fetchTrades(id, params) {
|
|
315
|
-
try {
|
|
316
|
-
const ticker = id.replace(/-NO$/, '');
|
|
317
|
-
const url = `https://api.elections.kalshi.com/trade-api/v2/markets/trades`;
|
|
318
|
-
const response = await axios_1.default.get(url, {
|
|
319
|
-
params: {
|
|
320
|
-
ticker: ticker,
|
|
321
|
-
limit: params.limit || 100
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
const trades = response.data.trades || [];
|
|
325
|
-
return trades.map((t) => ({
|
|
326
|
-
id: t.trade_id,
|
|
327
|
-
timestamp: new Date(t.created_time).getTime(),
|
|
328
|
-
price: t.yes_price / 100,
|
|
329
|
-
amount: t.count,
|
|
330
|
-
side: t.taker_side === 'yes' ? 'buy' : 'sell'
|
|
331
|
-
}));
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
console.error(`Error fetching Kalshi trades for ${id}:`, error);
|
|
335
|
-
return [];
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
exports.KalshiExchange = KalshiExchange;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { PredictionMarketExchange, MarketFilterParams, HistoryFilterParams } from '../BaseExchange';
|
|
2
|
-
import { UnifiedMarket, PriceCandle, OrderBook, Trade } from '../types';
|
|
3
|
-
export declare class PolymarketExchange extends PredictionMarketExchange {
|
|
4
|
-
get name(): string;
|
|
5
|
-
private readonly baseUrl;
|
|
6
|
-
private readonly clobUrl;
|
|
7
|
-
fetchMarkets(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
8
|
-
searchMarkets(query: string, params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
9
|
-
/**
|
|
10
|
-
* Fetch specific markets by their URL slug.
|
|
11
|
-
* Useful for looking up a specific event from a URL.
|
|
12
|
-
* @param slug - The event slug (e.g. "will-fed-cut-rates-in-march")
|
|
13
|
-
*/
|
|
14
|
-
getMarketsBySlug(slug: string): Promise<UnifiedMarket[]>;
|
|
15
|
-
/**
|
|
16
|
-
* Map our generic CandleInterval to Polymarket's fidelity (in minutes)
|
|
17
|
-
*/
|
|
18
|
-
private mapIntervalToFidelity;
|
|
19
|
-
/**
|
|
20
|
-
* Fetch historical price data (OHLCV candles) for a specific token.
|
|
21
|
-
* @param id - The CLOB token ID (e.g., outcome token ID)
|
|
22
|
-
*/
|
|
23
|
-
fetchOHLCV(id: string, params: HistoryFilterParams): Promise<PriceCandle[]>;
|
|
24
|
-
/**
|
|
25
|
-
* Fetch the current order book for a specific token.
|
|
26
|
-
* @param id - The CLOB token ID
|
|
27
|
-
*/
|
|
28
|
-
fetchOrderBook(id: string): Promise<OrderBook>;
|
|
29
|
-
/**
|
|
30
|
-
* Fetch raw trade history for a specific token.
|
|
31
|
-
* @param id - The CLOB token ID
|
|
32
|
-
*
|
|
33
|
-
* NOTE: Polymarket's /trades endpoint currently requires L2 Authentication (API Key).
|
|
34
|
-
* This method will return an empty array if an API key is not provided in headers.
|
|
35
|
-
* Use fetchOHLCV for public historical price data instead.
|
|
36
|
-
*/
|
|
37
|
-
fetchTrades(id: string, params: HistoryFilterParams): Promise<Trade[]>;
|
|
38
|
-
}
|
|
@@ -1,428 +0,0 @@
|
|
|
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() {
|
|
18
|
-
return 'Polymarket';
|
|
19
|
-
}
|
|
20
|
-
async fetchMarkets(params) {
|
|
21
|
-
const limit = params?.limit || 200; // Higher default for better coverage
|
|
22
|
-
const offset = params?.offset || 0;
|
|
23
|
-
// Map generic sort params to Polymarket Gamma API params
|
|
24
|
-
let queryParams = {
|
|
25
|
-
active: 'true',
|
|
26
|
-
closed: 'false',
|
|
27
|
-
limit: limit,
|
|
28
|
-
offset: offset,
|
|
29
|
-
};
|
|
30
|
-
// Gamma API uses 'order' and 'ascending' for sorting
|
|
31
|
-
if (params?.sort === 'volume') {
|
|
32
|
-
queryParams.order = 'volume';
|
|
33
|
-
queryParams.ascending = 'false';
|
|
34
|
-
}
|
|
35
|
-
else if (params?.sort === 'newest') {
|
|
36
|
-
queryParams.order = 'startDate';
|
|
37
|
-
queryParams.ascending = 'false';
|
|
38
|
-
}
|
|
39
|
-
else if (params?.sort === 'liquidity') {
|
|
40
|
-
// queryParams.order = 'liquidity';
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
// Default: do not send order param to avoid 422
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
// Fetch active events from Gamma
|
|
47
|
-
const response = await axios_1.default.get(this.baseUrl, {
|
|
48
|
-
params: queryParams
|
|
49
|
-
});
|
|
50
|
-
const events = response.data;
|
|
51
|
-
const unifiedMarkets = [];
|
|
52
|
-
for (const event of events) {
|
|
53
|
-
// Each event is a container (e.g. "US Election").
|
|
54
|
-
// It contains specific "markets" (e.g. "Winner", "Pop Vote").
|
|
55
|
-
if (!event.markets)
|
|
56
|
-
continue;
|
|
57
|
-
for (const market of event.markets) {
|
|
58
|
-
const outcomes = [];
|
|
59
|
-
// Polymarket Gamma often returns 'outcomes' and 'outcomePrices' as stringified JSON keys.
|
|
60
|
-
let outcomeLabels = [];
|
|
61
|
-
let outcomePrices = [];
|
|
62
|
-
try {
|
|
63
|
-
outcomeLabels = typeof market.outcomes === 'string' ? JSON.parse(market.outcomes) : (market.outcomes || []);
|
|
64
|
-
outcomePrices = typeof market.outcomePrices === 'string' ? JSON.parse(market.outcomePrices) : (market.outcomePrices || []);
|
|
65
|
-
}
|
|
66
|
-
catch (e) {
|
|
67
|
-
console.warn(`Error parsing outcomes for market ${market.id}:`, e);
|
|
68
|
-
}
|
|
69
|
-
// Extract CLOB token IDs for granular operations
|
|
70
|
-
let clobTokenIds = [];
|
|
71
|
-
try {
|
|
72
|
-
clobTokenIds = typeof market.clobTokenIds === 'string' ? JSON.parse(market.clobTokenIds) : (market.clobTokenIds || []);
|
|
73
|
-
}
|
|
74
|
-
catch (e) {
|
|
75
|
-
}
|
|
76
|
-
// Extract candidate/option name from market question for better outcome labels
|
|
77
|
-
let candidateName = null;
|
|
78
|
-
if (market.question && market.groupItemTitle) {
|
|
79
|
-
candidateName = market.groupItemTitle;
|
|
80
|
-
}
|
|
81
|
-
if (outcomeLabels.length > 0) {
|
|
82
|
-
outcomeLabels.forEach((label, index) => {
|
|
83
|
-
const rawPrice = outcomePrices[index] || "0";
|
|
84
|
-
// For Yes/No markets with specific candidates, use the candidate name
|
|
85
|
-
let outcomeLabel = label;
|
|
86
|
-
if (candidateName && label.toLowerCase() === 'yes') {
|
|
87
|
-
outcomeLabel = candidateName;
|
|
88
|
-
}
|
|
89
|
-
else if (candidateName && label.toLowerCase() === 'no') {
|
|
90
|
-
outcomeLabel = `Not ${candidateName}`;
|
|
91
|
-
}
|
|
92
|
-
// 24h Price Change
|
|
93
|
-
// Polymarket API provides 'oneDayPriceChange' on the market object
|
|
94
|
-
let priceChange = 0;
|
|
95
|
-
if (index === 0 || label.toLowerCase() === 'yes' || (candidateName && label === candidateName)) {
|
|
96
|
-
priceChange = Number(market.oneDayPriceChange || 0);
|
|
97
|
-
}
|
|
98
|
-
outcomes.push({
|
|
99
|
-
id: clobTokenIds[index] || String(index), // Use CLOB Token ID as the primary ID
|
|
100
|
-
label: outcomeLabel,
|
|
101
|
-
price: parseFloat(rawPrice) || 0,
|
|
102
|
-
priceChange24h: priceChange,
|
|
103
|
-
metadata: {
|
|
104
|
-
// clobTokenId is now the main ID, but keeping it in metadata for backward compat if needed
|
|
105
|
-
clobTokenId: clobTokenIds[index]
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
unifiedMarkets.push({
|
|
111
|
-
id: market.id,
|
|
112
|
-
title: market.question ? `${event.title} - ${market.question}` : event.title,
|
|
113
|
-
description: market.description || event.description,
|
|
114
|
-
outcomes: outcomes,
|
|
115
|
-
resolutionDate: market.endDate ? new Date(market.endDate) : (market.end_date_iso ? new Date(market.end_date_iso) : new Date()),
|
|
116
|
-
volume24h: Number(market.volume24hr || market.volume_24h || 0),
|
|
117
|
-
volume: Number(market.volume || 0),
|
|
118
|
-
liquidity: Number(market.liquidity || market.rewards?.liquidity || 0),
|
|
119
|
-
openInterest: Number(market.openInterest || market.open_interest || 0),
|
|
120
|
-
url: `https://polymarket.com/event/${event.slug}`,
|
|
121
|
-
image: event.image || market.image || `https://polymarket.com/api/og?slug=${event.slug}`,
|
|
122
|
-
category: event.category || event.tags?.[0]?.label,
|
|
123
|
-
tags: event.tags?.map((t) => t.label) || []
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Client-side Sort capability to ensure contract fulfillment
|
|
128
|
-
// Often API filters are "good effort" or apply to the 'event' but not the 'market'
|
|
129
|
-
if (params?.sort === 'volume') {
|
|
130
|
-
unifiedMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
131
|
-
}
|
|
132
|
-
else if (params?.sort === 'newest') {
|
|
133
|
-
// unifiedMarkets.sort((a, b) => b.resolutionDate.getTime() - a.resolutionDate.getTime()); // Not quite 'newest'
|
|
134
|
-
}
|
|
135
|
-
else if (params?.sort === 'liquidity') {
|
|
136
|
-
unifiedMarkets.sort((a, b) => b.liquidity - a.liquidity);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
// Default volume sort
|
|
140
|
-
unifiedMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
141
|
-
}
|
|
142
|
-
// Respect limit strictly after flattening
|
|
143
|
-
return unifiedMarkets.slice(0, limit);
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
console.error("Error fetching Polymarket data:", error);
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
async searchMarkets(query, params) {
|
|
151
|
-
// Polymarket Gamma API doesn't support native search
|
|
152
|
-
// Fetch all active markets and filter client-side
|
|
153
|
-
const searchLimit = 100000; // Fetch all markets for comprehensive search
|
|
154
|
-
try {
|
|
155
|
-
// Fetch markets with a higher limit
|
|
156
|
-
const markets = await this.fetchMarkets({
|
|
157
|
-
...params,
|
|
158
|
-
limit: searchLimit
|
|
159
|
-
});
|
|
160
|
-
// Client-side text filtering
|
|
161
|
-
const lowerQuery = query.toLowerCase();
|
|
162
|
-
const searchIn = params?.searchIn || 'title'; // Default to title-only search
|
|
163
|
-
const filtered = markets.filter(market => {
|
|
164
|
-
const titleMatch = (market.title || '').toLowerCase().includes(lowerQuery);
|
|
165
|
-
const descMatch = (market.description || '').toLowerCase().includes(lowerQuery);
|
|
166
|
-
if (searchIn === 'title')
|
|
167
|
-
return titleMatch;
|
|
168
|
-
if (searchIn === 'description')
|
|
169
|
-
return descMatch;
|
|
170
|
-
return titleMatch || descMatch; // 'both'
|
|
171
|
-
});
|
|
172
|
-
// Apply limit to filtered results
|
|
173
|
-
const limit = params?.limit || 20;
|
|
174
|
-
return filtered.slice(0, limit);
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
console.error("Error searching Polymarket data:", error);
|
|
178
|
-
return [];
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Fetch specific markets by their URL slug.
|
|
183
|
-
* Useful for looking up a specific event from a URL.
|
|
184
|
-
* @param slug - The event slug (e.g. "will-fed-cut-rates-in-march")
|
|
185
|
-
*/
|
|
186
|
-
async getMarketsBySlug(slug) {
|
|
187
|
-
try {
|
|
188
|
-
const response = await axios_1.default.get(this.baseUrl, {
|
|
189
|
-
params: { slug: slug }
|
|
190
|
-
});
|
|
191
|
-
const events = response.data;
|
|
192
|
-
if (!events || events.length === 0)
|
|
193
|
-
return [];
|
|
194
|
-
// We can reuse the logic from fetchMarkets if we extract it,
|
|
195
|
-
// but for now I'll duplicate the extraction logic to keep it self-contained
|
|
196
|
-
// and avoid safe refactoring risks.
|
|
197
|
-
// Actually, fetchMarkets is built to work with the Gamma response structure.
|
|
198
|
-
// So we can manually map the response using the same logic.
|
|
199
|
-
const unifiedMarkets = [];
|
|
200
|
-
for (const event of events) {
|
|
201
|
-
if (!event.markets)
|
|
202
|
-
continue;
|
|
203
|
-
for (const market of event.markets) {
|
|
204
|
-
const outcomes = [];
|
|
205
|
-
let outcomeLabels = [];
|
|
206
|
-
let outcomePrices = [];
|
|
207
|
-
let clobTokenIds = [];
|
|
208
|
-
try {
|
|
209
|
-
outcomeLabels = typeof market.outcomes === 'string' ? JSON.parse(market.outcomes) : (market.outcomes || []);
|
|
210
|
-
outcomePrices = typeof market.outcomePrices === 'string' ? JSON.parse(market.outcomePrices) : (market.outcomePrices || []);
|
|
211
|
-
clobTokenIds = typeof market.clobTokenIds === 'string' ? JSON.parse(market.clobTokenIds) : (market.clobTokenIds || []);
|
|
212
|
-
}
|
|
213
|
-
catch (e) {
|
|
214
|
-
console.warn(`Error parsing outcomes for market ${market.id}:`, e);
|
|
215
|
-
}
|
|
216
|
-
let candidateName = market.groupItemTitle;
|
|
217
|
-
if (!candidateName && market.question)
|
|
218
|
-
candidateName = market.question;
|
|
219
|
-
if (outcomeLabels.length > 0) {
|
|
220
|
-
outcomeLabels.forEach((label, index) => {
|
|
221
|
-
let outcomeLabel = label;
|
|
222
|
-
// Clean up Yes/No labels if candidate name is available
|
|
223
|
-
if (candidateName && label.toLowerCase() === 'yes')
|
|
224
|
-
outcomeLabel = candidateName;
|
|
225
|
-
else if (candidateName && label.toLowerCase() === 'no')
|
|
226
|
-
outcomeLabel = `Not ${candidateName}`;
|
|
227
|
-
// 24h Price Change Logic
|
|
228
|
-
let priceChange = 0;
|
|
229
|
-
if (index === 0 || label.toLowerCase() === 'yes' || (candidateName && label === candidateName)) {
|
|
230
|
-
priceChange = Number(market.oneDayPriceChange || 0);
|
|
231
|
-
}
|
|
232
|
-
outcomes.push({
|
|
233
|
-
id: clobTokenIds[index] || String(index),
|
|
234
|
-
label: outcomeLabel,
|
|
235
|
-
price: parseFloat(outcomePrices[index] || "0") || 0,
|
|
236
|
-
priceChange24h: priceChange,
|
|
237
|
-
metadata: {
|
|
238
|
-
clobTokenId: clobTokenIds[index]
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
unifiedMarkets.push({
|
|
244
|
-
id: market.id,
|
|
245
|
-
title: event.title,
|
|
246
|
-
description: market.description || event.description,
|
|
247
|
-
outcomes: outcomes,
|
|
248
|
-
resolutionDate: market.endDate ? new Date(market.endDate) : new Date(),
|
|
249
|
-
volume24h: Number(market.volume24hr || market.volume_24h || 0),
|
|
250
|
-
volume: Number(market.volume || 0),
|
|
251
|
-
liquidity: Number(market.liquidity || market.rewards?.liquidity || 0),
|
|
252
|
-
openInterest: Number(market.openInterest || market.open_interest || 0),
|
|
253
|
-
url: `https://polymarket.com/event/${event.slug}`,
|
|
254
|
-
image: event.image || market.image,
|
|
255
|
-
category: event.category || event.tags?.[0]?.label,
|
|
256
|
-
tags: event.tags?.map((t) => t.label) || []
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return unifiedMarkets;
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
console.error(`Error fetching Polymarket slug ${slug}:`, error);
|
|
264
|
-
return [];
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Map our generic CandleInterval to Polymarket's fidelity (in minutes)
|
|
269
|
-
*/
|
|
270
|
-
mapIntervalToFidelity(interval) {
|
|
271
|
-
const mapping = {
|
|
272
|
-
'1m': 1,
|
|
273
|
-
'5m': 5,
|
|
274
|
-
'15m': 15,
|
|
275
|
-
'1h': 60,
|
|
276
|
-
'6h': 360,
|
|
277
|
-
'1d': 1440
|
|
278
|
-
};
|
|
279
|
-
return mapping[interval];
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Fetch historical price data (OHLCV candles) for a specific token.
|
|
283
|
-
* @param id - The CLOB token ID (e.g., outcome token ID)
|
|
284
|
-
*/
|
|
285
|
-
async fetchOHLCV(id, params) {
|
|
286
|
-
// ID Validation: Polymarket CLOB requires a Token ID (long numeric string) not a Market ID
|
|
287
|
-
if (id.length < 10 && /^\d+$/.test(id)) {
|
|
288
|
-
throw new Error(`Invalid ID for Polymarket history: "${id}". You provided a Market ID, but Polymarket's CLOB API requires a Token ID. Ensure you are using 'outcome.id'.`);
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const fidelity = this.mapIntervalToFidelity(params.resolution);
|
|
292
|
-
const nowTs = Math.floor(Date.now() / 1000);
|
|
293
|
-
// 1. Smart Lookback Calculation
|
|
294
|
-
// If start/end not provided, calculate window based on limit * resolution
|
|
295
|
-
let startTs = params.start ? Math.floor(params.start.getTime() / 1000) : 0;
|
|
296
|
-
let endTs = params.end ? Math.floor(params.end.getTime() / 1000) : nowTs;
|
|
297
|
-
if (!params.start) {
|
|
298
|
-
// Default limit is usually 20 in the example, but safety margin is good.
|
|
299
|
-
// If limit is not set, we default to 100 candles.
|
|
300
|
-
const count = params.limit || 100;
|
|
301
|
-
// fidelity is in minutes.
|
|
302
|
-
const durationSeconds = count * fidelity * 60;
|
|
303
|
-
startTs = endTs - durationSeconds;
|
|
304
|
-
}
|
|
305
|
-
const queryParams = {
|
|
306
|
-
market: id,
|
|
307
|
-
fidelity: fidelity,
|
|
308
|
-
startTs: startTs,
|
|
309
|
-
endTs: endTs
|
|
310
|
-
};
|
|
311
|
-
const response = await axios_1.default.get(`${this.clobUrl}/prices-history`, {
|
|
312
|
-
params: queryParams
|
|
313
|
-
});
|
|
314
|
-
const history = response.data.history || [];
|
|
315
|
-
// 2. Align Timestamps (Snap to Grid)
|
|
316
|
-
// Polymarket returns random tick timestamps (e.g. 1:00:21).
|
|
317
|
-
// We want to normalize this to the start of the bucket (1:00:00).
|
|
318
|
-
const resolutionMs = fidelity * 60 * 1000;
|
|
319
|
-
const candles = history.map((item) => {
|
|
320
|
-
const rawMs = item.t * 1000;
|
|
321
|
-
const snappedMs = Math.floor(rawMs / resolutionMs) * resolutionMs;
|
|
322
|
-
return {
|
|
323
|
-
timestamp: snappedMs, // Aligned timestamp
|
|
324
|
-
open: item.p,
|
|
325
|
-
high: item.p,
|
|
326
|
-
low: item.p,
|
|
327
|
-
close: item.p,
|
|
328
|
-
volume: undefined
|
|
329
|
-
};
|
|
330
|
-
});
|
|
331
|
-
// Apply limit if specified
|
|
332
|
-
if (params.limit && candles.length > params.limit) {
|
|
333
|
-
return candles.slice(-params.limit);
|
|
334
|
-
}
|
|
335
|
-
return candles;
|
|
336
|
-
}
|
|
337
|
-
catch (error) {
|
|
338
|
-
if (axios_1.default.isAxiosError(error) && error.response) {
|
|
339
|
-
const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
|
|
340
|
-
throw new Error(`Polymarket History API Error (${error.response.status}): ${apiError}. Used ID: ${id}`);
|
|
341
|
-
}
|
|
342
|
-
console.error(`Unexpected error fetching Polymarket history for ${id}:`, error);
|
|
343
|
-
throw error;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Fetch the current order book for a specific token.
|
|
348
|
-
* @param id - The CLOB token ID
|
|
349
|
-
*/
|
|
350
|
-
async fetchOrderBook(id) {
|
|
351
|
-
try {
|
|
352
|
-
const response = await axios_1.default.get(`${this.clobUrl}/book`, {
|
|
353
|
-
params: { token_id: id }
|
|
354
|
-
});
|
|
355
|
-
const data = response.data;
|
|
356
|
-
// Response format: { bids: [{price: "0.52", size: "100"}], asks: [...] }
|
|
357
|
-
const bids = (data.bids || []).map((level) => ({
|
|
358
|
-
price: parseFloat(level.price),
|
|
359
|
-
size: parseFloat(level.size)
|
|
360
|
-
})).sort((a, b) => b.price - a.price); // Sort Bids Descending (Best/Highest first)
|
|
361
|
-
const asks = (data.asks || []).map((level) => ({
|
|
362
|
-
price: parseFloat(level.price),
|
|
363
|
-
size: parseFloat(level.size)
|
|
364
|
-
})).sort((a, b) => a.price - b.price); // Sort Asks Ascending (Best/Lowest first)
|
|
365
|
-
return {
|
|
366
|
-
bids,
|
|
367
|
-
asks,
|
|
368
|
-
timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now()
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
catch (error) {
|
|
372
|
-
console.error(`Error fetching Polymarket orderbook for ${id}:`, error);
|
|
373
|
-
return { bids: [], asks: [] };
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Fetch raw trade history for a specific token.
|
|
378
|
-
* @param id - The CLOB token ID
|
|
379
|
-
*
|
|
380
|
-
* NOTE: Polymarket's /trades endpoint currently requires L2 Authentication (API Key).
|
|
381
|
-
* This method will return an empty array if an API key is not provided in headers.
|
|
382
|
-
* Use fetchOHLCV for public historical price data instead.
|
|
383
|
-
*/
|
|
384
|
-
async fetchTrades(id, params) {
|
|
385
|
-
// ID Validation
|
|
386
|
-
if (id.length < 10 && /^\d+$/.test(id)) {
|
|
387
|
-
throw new Error(`Invalid ID for Polymarket trades: "${id}". You provided a Market ID, but Polymarket's CLOB API requires a Token ID.`);
|
|
388
|
-
}
|
|
389
|
-
try {
|
|
390
|
-
const queryParams = {
|
|
391
|
-
market: id
|
|
392
|
-
};
|
|
393
|
-
// Add time filters if provided
|
|
394
|
-
if (params.start) {
|
|
395
|
-
queryParams.after = Math.floor(params.start.getTime() / 1000);
|
|
396
|
-
}
|
|
397
|
-
if (params.end) {
|
|
398
|
-
queryParams.before = Math.floor(params.end.getTime() / 1000);
|
|
399
|
-
}
|
|
400
|
-
const response = await axios_1.default.get(`${this.clobUrl}/trades`, {
|
|
401
|
-
params: queryParams
|
|
402
|
-
});
|
|
403
|
-
// Response is an array of trade objects
|
|
404
|
-
const trades = response.data || [];
|
|
405
|
-
const mappedTrades = trades.map((trade) => ({
|
|
406
|
-
id: trade.id || `${trade.timestamp}-${trade.price}`,
|
|
407
|
-
timestamp: trade.timestamp * 1000, // Convert to milliseconds
|
|
408
|
-
price: parseFloat(trade.price),
|
|
409
|
-
amount: parseFloat(trade.size || trade.amount || 0),
|
|
410
|
-
side: trade.side === 'BUY' ? 'buy' : trade.side === 'SELL' ? 'sell' : 'unknown'
|
|
411
|
-
}));
|
|
412
|
-
// Apply limit if specified
|
|
413
|
-
if (params.limit && mappedTrades.length > params.limit) {
|
|
414
|
-
return mappedTrades.slice(-params.limit); // Return most recent N trades
|
|
415
|
-
}
|
|
416
|
-
return mappedTrades;
|
|
417
|
-
}
|
|
418
|
-
catch (error) {
|
|
419
|
-
if (axios_1.default.isAxiosError(error) && error.response) {
|
|
420
|
-
const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
|
|
421
|
-
throw new Error(`Polymarket Trades API Error (${error.response.status}): ${apiError}. Used ID: ${id}`);
|
|
422
|
-
}
|
|
423
|
-
console.error(`Unexpected error fetching Polymarket trades for ${id}:`, error);
|
|
424
|
-
throw error;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
exports.PolymarketExchange = PolymarketExchange;
|