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.
Files changed (58) hide show
  1. package/dist/BaseExchange.js +30 -0
  2. package/{src/exchanges/Kalshi.ts → dist/exchanges/Kalshi.js} +75 -106
  3. package/{src/exchanges/Polymarket.ts → dist/exchanges/Polymarket.js} +107 -144
  4. package/dist/index.js +20 -0
  5. package/dist/types.js +5 -0
  6. package/package.json +4 -1
  7. package/API_REFERENCE.md +0 -88
  8. package/coverage/clover.xml +0 -334
  9. package/coverage/coverage-final.json +0 -4
  10. package/coverage/lcov-report/base.css +0 -224
  11. package/coverage/lcov-report/block-navigation.js +0 -87
  12. package/coverage/lcov-report/favicon.png +0 -0
  13. package/coverage/lcov-report/index.html +0 -131
  14. package/coverage/lcov-report/pmxt/BaseExchange.ts.html +0 -256
  15. package/coverage/lcov-report/pmxt/exchanges/Kalshi.ts.html +0 -1132
  16. package/coverage/lcov-report/pmxt/exchanges/Polymarket.ts.html +0 -1456
  17. package/coverage/lcov-report/pmxt/exchanges/index.html +0 -131
  18. package/coverage/lcov-report/pmxt/index.html +0 -116
  19. package/coverage/lcov-report/prettify.css +0 -1
  20. package/coverage/lcov-report/prettify.js +0 -2
  21. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  22. package/coverage/lcov-report/sorter.js +0 -210
  23. package/coverage/lcov-report/src/BaseExchange.ts.html +0 -256
  24. package/coverage/lcov-report/src/exchanges/Kalshi.ts.html +0 -1132
  25. package/coverage/lcov-report/src/exchanges/Polymarket.ts.html +0 -1456
  26. package/coverage/lcov-report/src/exchanges/index.html +0 -131
  27. package/coverage/lcov-report/src/index.html +0 -116
  28. package/coverage/lcov.info +0 -766
  29. package/examples/get_event_prices.ts +0 -37
  30. package/examples/historical_prices.ts +0 -117
  31. package/examples/orderbook.ts +0 -102
  32. package/examples/recent_trades.ts +0 -29
  33. package/examples/search_events.ts +0 -68
  34. package/examples/search_market.ts +0 -29
  35. package/jest.config.js +0 -11
  36. package/pmxt-0.1.0.tgz +0 -0
  37. package/src/BaseExchange.ts +0 -57
  38. package/src/index.ts +0 -5
  39. package/src/types.ts +0 -61
  40. package/test/exchanges/kalshi/ApiErrors.test.ts +0 -132
  41. package/test/exchanges/kalshi/EmptyResponse.test.ts +0 -44
  42. package/test/exchanges/kalshi/FetchAndNormalizeMarkets.test.ts +0 -56
  43. package/test/exchanges/kalshi/LiveApi.integration.test.ts +0 -40
  44. package/test/exchanges/kalshi/MarketHistory.test.ts +0 -185
  45. package/test/exchanges/kalshi/OrderBook.test.ts +0 -149
  46. package/test/exchanges/kalshi/SearchMarkets.test.ts +0 -174
  47. package/test/exchanges/kalshi/VolumeFallback.test.ts +0 -44
  48. package/test/exchanges/polymarket/DataValidation.test.ts +0 -271
  49. package/test/exchanges/polymarket/ErrorHandling.test.ts +0 -34
  50. package/test/exchanges/polymarket/FetchAndNormalizeMarkets.test.ts +0 -68
  51. package/test/exchanges/polymarket/GetMarketsBySlug.test.ts +0 -268
  52. package/test/exchanges/polymarket/LiveApi.integration.test.ts +0 -44
  53. package/test/exchanges/polymarket/MarketHistory.test.ts +0 -207
  54. package/test/exchanges/polymarket/OrderBook.test.ts +0 -167
  55. package/test/exchanges/polymarket/RequestParameters.test.ts +0 -39
  56. package/test/exchanges/polymarket/SearchMarkets.test.ts +0 -176
  57. package/test/exchanges/polymarket/TradeHistory.test.ts +0 -248
  58. package/tsconfig.json +0 -12
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PredictionMarketExchange = void 0;
4
+ // ----------------------------------------------------------------------------
5
+ // Base Exchange Class
6
+ // ----------------------------------------------------------------------------
7
+ class PredictionMarketExchange {
8
+ /**
9
+ * Fetch historical price data for a specific market or outcome.
10
+ * @param id - The market ID or specific outcome ID/Token ID depending on the exchange
11
+ */
12
+ async getMarketHistory(id, params) {
13
+ throw new Error("Method getMarketHistory not implemented.");
14
+ }
15
+ /**
16
+ * Fetch the current order book (bids/asks) for a specific outcome.
17
+ * Essential for calculating localized spread and depth.
18
+ */
19
+ async getOrderBook(id) {
20
+ throw new Error("Method getOrderBook not implemented.");
21
+ }
22
+ /**
23
+ * Fetch raw trade history.
24
+ * Useful for generating synthetic OHLCV candles if the exchange doesn't provide them natively.
25
+ */
26
+ async getTradeHistory(id, params) {
27
+ throw new Error("Method getTradeHistory not implemented.");
28
+ }
29
+ }
30
+ exports.PredictionMarketExchange = PredictionMarketExchange;
@@ -1,24 +1,27 @@
1
- import axios from 'axios';
2
- import { PredictionMarketExchange, MarketFilterParams, HistoryFilterParams } from '../BaseExchange';
3
- import { UnifiedMarket, MarketOutcome, PriceCandle, CandleInterval, OrderBook, Trade } from '../types';
4
-
5
- export class KalshiExchange extends PredictionMarketExchange {
6
- get name(): string {
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() {
7
15
  return "Kalshi";
8
16
  }
9
- private baseUrl = "https://api.elections.kalshi.com/trade-api/v2/events";
10
-
11
- async fetchMarkets(params?: MarketFilterParams): Promise<UnifiedMarket[]> {
17
+ async fetchMarkets(params) {
12
18
  const limit = params?.limit || 50;
13
-
14
19
  try {
15
20
  // Fetch active events with nested markets
16
21
  // For small limits, we can optimize by fetching fewer pages
17
22
  const allEvents = await this.fetchActiveEvents(limit);
18
-
19
23
  // Extract ALL markets from all events
20
- const allMarkets: UnifiedMarket[] = [];
21
-
24
+ const allMarkets = [];
22
25
  for (const event of allEvents) {
23
26
  const markets = event.markets || [];
24
27
  for (const market of markets) {
@@ -28,60 +31,51 @@ export class KalshiExchange extends PredictionMarketExchange {
28
31
  }
29
32
  }
30
33
  }
31
-
32
34
  console.log(`Extracted ${allMarkets.length} markets from ${allEvents.length} events.`);
33
-
34
35
  // Sort by 24h volume
35
36
  if (params?.sort === 'volume') {
36
37
  allMarkets.sort((a, b) => b.volume24h - a.volume24h);
37
- } else if (params?.sort === 'liquidity') {
38
+ }
39
+ else if (params?.sort === 'liquidity') {
38
40
  allMarkets.sort((a, b) => b.liquidity - a.liquidity);
39
41
  }
40
-
41
42
  return allMarkets.slice(0, limit);
42
-
43
- } catch (error) {
43
+ }
44
+ catch (error) {
44
45
  console.error("Error fetching Kalshi data:", error);
45
46
  return [];
46
47
  }
47
48
  }
48
-
49
- private async fetchActiveEvents(targetMarketCount?: number): Promise<any[]> {
50
- let allEvents: any[] = [];
49
+ async fetchActiveEvents(targetMarketCount) {
50
+ let allEvents = [];
51
51
  let totalMarketCount = 0;
52
52
  let cursor = null;
53
53
  let page = 0;
54
-
55
54
  // Note: Kalshi API uses cursor-based pagination which requires sequential fetching.
56
55
  // We cannot parallelize requests for a single list because we need the cursor from page N to fetch page N+1.
57
56
  // To optimize, we use the maximum allowed limit (200) and fetch until exhaustion.
58
-
59
57
  const MAX_PAGES = 1000; // Safety cap against infinite loops
60
58
  const BATCH_SIZE = 200; // Max limit per Kalshi API docs
61
-
62
59
  do {
63
60
  try {
64
61
  // console.log(`Fetching Kalshi page ${page + 1}...`);
65
- const queryParams: any = {
62
+ const queryParams = {
66
63
  limit: BATCH_SIZE,
67
64
  with_nested_markets: true,
68
65
  status: 'open' // Filter to open markets to improve relevance and speed
69
66
  };
70
- if (cursor) queryParams.cursor = cursor;
71
-
72
- const response = await axios.get(this.baseUrl, { params: queryParams });
67
+ if (cursor)
68
+ queryParams.cursor = cursor;
69
+ const response = await axios_1.default.get(this.baseUrl, { params: queryParams });
73
70
  const events = response.data.events || [];
74
-
75
- if (events.length === 0) break;
76
-
71
+ if (events.length === 0)
72
+ break;
77
73
  allEvents = allEvents.concat(events);
78
-
79
74
  // Count markets in this batch for early termination
80
75
  if (targetMarketCount) {
81
76
  for (const event of events) {
82
77
  totalMarketCount += (event.markets || []).length;
83
78
  }
84
-
85
79
  // Early termination: if we have enough markets, stop fetching
86
80
  // Add a buffer (2x) to ensure we have enough after filtering/sorting
87
81
  if (totalMarketCount >= targetMarketCount * 2) {
@@ -89,51 +83,46 @@ export class KalshiExchange extends PredictionMarketExchange {
89
83
  break;
90
84
  }
91
85
  }
92
-
93
86
  cursor = response.data.cursor;
94
87
  page++;
95
-
96
88
  // Log progress every few pages to avoid spam
97
89
  if (page % 5 === 0) {
98
90
  console.log(`Fetched ${page} pages (${allEvents.length} events) from Kalshi...`);
99
91
  }
100
-
101
- } catch (e) {
92
+ }
93
+ catch (e) {
102
94
  console.error(`Error fetching Kalshi page ${page}:`, e);
103
95
  break;
104
96
  }
105
97
  } while (cursor && page < MAX_PAGES);
106
-
107
98
  console.log(`Finished fetching Kalshi: ${allEvents.length} total events across ${page} pages.`);
108
99
  return allEvents;
109
100
  }
110
-
111
- private mapMarketToUnified(event: any, market: any): UnifiedMarket | null {
112
- if (!market) return null;
113
-
101
+ mapMarketToUnified(event, market) {
102
+ if (!market)
103
+ return null;
114
104
  // Calculate price
115
105
  let price = 0.5;
116
106
  if (market.last_price) {
117
107
  price = market.last_price / 100;
118
- } else if (market.yes_ask && market.yes_bid) {
108
+ }
109
+ else if (market.yes_ask && market.yes_bid) {
119
110
  price = (market.yes_ask + market.yes_bid) / 200;
120
- } else if (market.yes_ask) {
111
+ }
112
+ else if (market.yes_ask) {
121
113
  price = market.yes_ask / 100;
122
114
  }
123
-
124
115
  // Extract candidate name
125
- let candidateName: string | null = null;
116
+ let candidateName = null;
126
117
  if (market.subtitle || market.yes_sub_title) {
127
118
  candidateName = market.subtitle || market.yes_sub_title;
128
119
  }
129
-
130
120
  // Calculate 24h change
131
121
  let priceChange = 0;
132
122
  if (market.previous_price_dollars !== undefined && market.last_price_dollars !== undefined) {
133
123
  priceChange = market.last_price_dollars - market.previous_price_dollars;
134
124
  }
135
-
136
- const outcomes: MarketOutcome[] = [
125
+ const outcomes = [
137
126
  {
138
127
  id: 'yes',
139
128
  label: candidateName || 'Yes',
@@ -147,7 +136,6 @@ export class KalshiExchange extends PredictionMarketExchange {
147
136
  priceChange24h: -priceChange // Inverse change for No? simplified assumption
148
137
  }
149
138
  ];
150
-
151
139
  return {
152
140
  id: market.ticker,
153
141
  title: event.title,
@@ -163,56 +151,50 @@ export class KalshiExchange extends PredictionMarketExchange {
163
151
  tags: event.tags || []
164
152
  };
165
153
  }
166
-
167
- async searchMarkets(query: string, params?: MarketFilterParams): Promise<UnifiedMarket[]> {
154
+ async searchMarkets(query, params) {
168
155
  // We must fetch ALL markets to search them locally since we don't have server-side search
169
156
  const fetchLimit = 100000;
170
157
  try {
171
158
  const markets = await this.fetchMarkets({ ...params, limit: fetchLimit });
172
159
  const lowerQuery = query.toLowerCase();
173
- const filtered = markets.filter(market =>
174
- market.title.toLowerCase().includes(lowerQuery) ||
175
- market.description.toLowerCase().includes(lowerQuery)
176
- );
160
+ const filtered = markets.filter(market => market.title.toLowerCase().includes(lowerQuery) ||
161
+ market.description.toLowerCase().includes(lowerQuery));
177
162
  const limit = params?.limit || 20;
178
163
  return filtered.slice(0, limit);
179
- } catch (error) {
164
+ }
165
+ catch (error) {
180
166
  console.error("Error searching Kalshi data:", error);
181
167
  return [];
182
168
  }
183
169
  }
184
-
185
170
  /**
186
171
  * Fetch specific markets by their event ticker.
187
172
  * Useful for looking up a specific event from a URL.
188
173
  * @param eventTicker - The event ticker (e.g. "FED-25JAN" or "PRES-2024")
189
174
  */
190
- async getMarketsBySlug(eventTicker: string): Promise<UnifiedMarket[]> {
175
+ async getMarketsBySlug(eventTicker) {
191
176
  try {
192
177
  // Kalshi API expects uppercase tickers, but URLs use lowercase
193
178
  const normalizedTicker = eventTicker.toUpperCase();
194
179
  const url = `https://api.elections.kalshi.com/trade-api/v2/events/${normalizedTicker}`;
195
- const response = await axios.get(url, {
180
+ const response = await axios_1.default.get(url, {
196
181
  params: { with_nested_markets: true }
197
182
  });
198
-
199
183
  const event = response.data.event;
200
- if (!event) return [];
201
-
202
- const unifiedMarkets: UnifiedMarket[] = [];
184
+ if (!event)
185
+ return [];
186
+ const unifiedMarkets = [];
203
187
  const markets = event.markets || [];
204
-
205
188
  for (const market of markets) {
206
189
  const unifiedMarket = this.mapMarketToUnified(event, market);
207
190
  if (unifiedMarket) {
208
191
  unifiedMarkets.push(unifiedMarket);
209
192
  }
210
193
  }
211
-
212
194
  return unifiedMarkets;
213
-
214
- } catch (error: any) {
215
- if (axios.isAxiosError(error) && error.response) {
195
+ }
196
+ catch (error) {
197
+ if (axios_1.default.isAxiosError(error) && error.response) {
216
198
  if (error.response.status === 404) {
217
199
  throw new Error(`Kalshi event not found: "${eventTicker}". Check that the event ticker is correct.`);
218
200
  }
@@ -223,9 +205,8 @@ export class KalshiExchange extends PredictionMarketExchange {
223
205
  throw error;
224
206
  }
225
207
  }
226
-
227
- private mapIntervalToKalshi(interval: CandleInterval): number {
228
- const mapping: Record<CandleInterval, number> = {
208
+ mapIntervalToKalshi(interval) {
209
+ const mapping = {
229
210
  '1m': 1,
230
211
  '5m': 1,
231
212
  '15m': 1,
@@ -235,13 +216,11 @@ export class KalshiExchange extends PredictionMarketExchange {
235
216
  };
236
217
  return mapping[interval];
237
218
  }
238
-
239
- async getMarketHistory(id: string, params: HistoryFilterParams): Promise<PriceCandle[]> {
219
+ async getMarketHistory(id, params) {
240
220
  try {
241
221
  // Kalshi API expects uppercase tickers
242
222
  const normalizedId = id.toUpperCase();
243
223
  const interval = this.mapIntervalToKalshi(params.resolution);
244
-
245
224
  // Heuristic for series_ticker
246
225
  const parts = normalizedId.split('-');
247
226
  if (parts.length < 2) {
@@ -249,13 +228,10 @@ export class KalshiExchange extends PredictionMarketExchange {
249
228
  }
250
229
  const seriesTicker = parts.slice(0, -1).join('-');
251
230
  const url = `https://api.elections.kalshi.com/trade-api/v2/series/${seriesTicker}/markets/${normalizedId}/candlesticks`;
252
-
253
- const queryParams: any = { period_interval: interval };
254
-
231
+ const queryParams = { period_interval: interval };
255
232
  const now = Math.floor(Date.now() / 1000);
256
233
  let startTs = now - (24 * 60 * 60);
257
234
  let endTs = now;
258
-
259
235
  if (params.start) {
260
236
  startTs = Math.floor(params.start.getTime() / 1000);
261
237
  }
@@ -265,14 +241,11 @@ export class KalshiExchange extends PredictionMarketExchange {
265
241
  startTs = endTs - (24 * 60 * 60);
266
242
  }
267
243
  }
268
-
269
244
  queryParams.start_ts = startTs;
270
245
  queryParams.end_ts = endTs;
271
-
272
- const response = await axios.get(url, { params: queryParams });
246
+ const response = await axios_1.default.get(url, { params: queryParams });
273
247
  const candles = response.data.candlesticks || [];
274
-
275
- const mappedCandles: PriceCandle[] = candles.map((c: any) => ({
248
+ const mappedCandles = candles.map((c) => ({
276
249
  timestamp: c.end_period_ts * 1000,
277
250
  open: (c.price.open || 0) / 100,
278
251
  high: (c.price.high || 0) / 100,
@@ -280,13 +253,13 @@ export class KalshiExchange extends PredictionMarketExchange {
280
253
  close: (c.price.close || 0) / 100,
281
254
  volume: c.volume
282
255
  }));
283
-
284
256
  if (params.limit && mappedCandles.length > params.limit) {
285
257
  return mappedCandles.slice(-params.limit);
286
258
  }
287
259
  return mappedCandles;
288
- } catch (error: any) {
289
- if (axios.isAxiosError(error) && error.response) {
260
+ }
261
+ catch (error) {
262
+ if (axios_1.default.isAxiosError(error) && error.response) {
290
263
  const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
291
264
  throw new Error(`Kalshi History API Error (${error.response.status}): ${apiError}. Used Ticker: ${id}`);
292
265
  }
@@ -294,56 +267,52 @@ export class KalshiExchange extends PredictionMarketExchange {
294
267
  throw error;
295
268
  }
296
269
  }
297
-
298
- async getOrderBook(id: string): Promise<OrderBook> {
270
+ async getOrderBook(id) {
299
271
  try {
300
272
  const url = `https://api.elections.kalshi.com/trade-api/v2/markets/${id}/orderbook`;
301
- const response = await axios.get(url);
273
+ const response = await axios_1.default.get(url);
302
274
  const data = response.data.orderbook;
303
-
304
275
  // Structure: { yes: [[price, qty], ...], no: [[price, qty], ...] }
305
- const bids = (data.yes || []).map((level: number[]) => ({
276
+ const bids = (data.yes || []).map((level) => ({
306
277
  price: level[0] / 100,
307
278
  size: level[1]
308
279
  }));
309
-
310
- const asks = (data.no || []).map((level: number[]) => ({
280
+ const asks = (data.no || []).map((level) => ({
311
281
  price: (100 - level[0]) / 100,
312
282
  size: level[1]
313
283
  }));
314
-
315
284
  // Sort bids desc, asks asc
316
- bids.sort((a: any, b: any) => b.price - a.price);
317
- asks.sort((a: any, b: any) => a.price - b.price);
318
-
285
+ bids.sort((a, b) => b.price - a.price);
286
+ asks.sort((a, b) => a.price - b.price);
319
287
  return { bids, asks, timestamp: Date.now() };
320
- } catch (error) {
288
+ }
289
+ catch (error) {
321
290
  console.error(`Error fetching Kalshi orderbook for ${id}:`, error);
322
291
  return { bids: [], asks: [] };
323
292
  }
324
293
  }
325
-
326
- async getTradeHistory(id: string, params: HistoryFilterParams): Promise<Trade[]> {
294
+ async getTradeHistory(id, params) {
327
295
  try {
328
296
  const url = `https://api.elections.kalshi.com/trade-api/v2/markets/trades`;
329
- const response = await axios.get(url, {
297
+ const response = await axios_1.default.get(url, {
330
298
  params: {
331
299
  ticker: id,
332
300
  limit: params.limit || 100
333
301
  }
334
302
  });
335
303
  const trades = response.data.trades || [];
336
-
337
- return trades.map((t: any) => ({
304
+ return trades.map((t) => ({
338
305
  id: t.trade_id,
339
306
  timestamp: new Date(t.created_time).getTime(),
340
307
  price: t.yes_price / 100,
341
308
  amount: t.count,
342
309
  side: t.taker_side === 'yes' ? 'buy' : 'sell'
343
310
  }));
344
- } catch (error) {
311
+ }
312
+ catch (error) {
345
313
  console.error(`Error fetching Kalshi trades for ${id}:`, error);
346
314
  return [];
347
315
  }
348
316
  }
349
317
  }
318
+ exports.KalshiExchange = KalshiExchange;