pmxtjs 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_REFERENCE.md +88 -0
- package/LICENSE +21 -0
- package/dist/BaseExchange.d.ts +39 -0
- package/dist/BaseExchange.js +2 -2
- package/dist/exchanges/Kalshi.d.ts +20 -0
- package/dist/exchanges/Kalshi.js +9 -13
- package/dist/exchanges/Polymarket.d.ts +38 -0
- package/dist/exchanges/Polymarket.js +5 -8
- package/dist/index.d.ts +4 -0
- package/dist/types.d.ts +47 -0
- package/package.json +10 -4
- package/readme.md +111 -0
package/API_REFERENCE.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 qoery.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { UnifiedMarket, PriceCandle, CandleInterval, OrderBook, Trade } from './types';
|
|
2
|
+
export interface MarketFilterParams {
|
|
3
|
+
limit?: number;
|
|
4
|
+
offset?: number;
|
|
5
|
+
sort?: 'volume' | 'liquidity' | 'newest';
|
|
6
|
+
}
|
|
7
|
+
export interface HistoryFilterParams {
|
|
8
|
+
resolution: CandleInterval;
|
|
9
|
+
start?: Date;
|
|
10
|
+
end?: Date;
|
|
11
|
+
limit?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare abstract class PredictionMarketExchange {
|
|
14
|
+
abstract get name(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch all relevant markets from the source.
|
|
17
|
+
*/
|
|
18
|
+
abstract fetchMarkets(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Search for markets matching a keyword query.
|
|
21
|
+
* Searches across title and description fields.
|
|
22
|
+
*/
|
|
23
|
+
abstract searchMarkets(query: string, params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Fetch historical price data for a specific market outcome.
|
|
26
|
+
* @param id - The Outcome ID (MarketOutcome.id). This should be the ID of the specific tradeable asset.
|
|
27
|
+
*/
|
|
28
|
+
getMarketHistory(id: string, params: HistoryFilterParams): Promise<PriceCandle[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Fetch the current order book (bids/asks) for a specific outcome.
|
|
31
|
+
* Essential for calculating localized spread and depth.
|
|
32
|
+
*/
|
|
33
|
+
getOrderBook(id: string): Promise<OrderBook>;
|
|
34
|
+
/**
|
|
35
|
+
* Fetch raw trade history.
|
|
36
|
+
* Useful for generating synthetic OHLCV candles if the exchange doesn't provide them natively.
|
|
37
|
+
*/
|
|
38
|
+
getTradeHistory(id: string, params: HistoryFilterParams): Promise<Trade[]>;
|
|
39
|
+
}
|
package/dist/BaseExchange.js
CHANGED
|
@@ -6,8 +6,8 @@ exports.PredictionMarketExchange = void 0;
|
|
|
6
6
|
// ----------------------------------------------------------------------------
|
|
7
7
|
class PredictionMarketExchange {
|
|
8
8
|
/**
|
|
9
|
-
* Fetch historical price data for a specific market
|
|
10
|
-
* @param id - The
|
|
9
|
+
* Fetch historical price data for a specific market outcome.
|
|
10
|
+
* @param id - The Outcome ID (MarketOutcome.id). This should be the ID of the specific tradeable asset.
|
|
11
11
|
*/
|
|
12
12
|
async getMarketHistory(id, params) {
|
|
13
13
|
throw new Error("Method getMarketHistory not implemented.");
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
getMarketHistory(id: string, params: HistoryFilterParams): Promise<PriceCandle[]>;
|
|
18
|
+
getOrderBook(id: string): Promise<OrderBook>;
|
|
19
|
+
getTradeHistory(id: string, params: HistoryFilterParams): Promise<Trade[]>;
|
|
20
|
+
}
|
package/dist/exchanges/Kalshi.js
CHANGED
|
@@ -31,7 +31,6 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
console.log(`Extracted ${allMarkets.length} markets from ${allEvents.length} events.`);
|
|
35
34
|
// Sort by 24h volume
|
|
36
35
|
if (params?.sort === 'volume') {
|
|
37
36
|
allMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
@@ -77,25 +76,18 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
77
76
|
totalMarketCount += (event.markets || []).length;
|
|
78
77
|
}
|
|
79
78
|
// Early termination: if we have enough markets, stop fetching
|
|
80
|
-
// Add a buffer (2x) to ensure we have enough after filtering/sorting
|
|
81
79
|
if (totalMarketCount >= targetMarketCount * 2) {
|
|
82
|
-
console.log(`Early termination: collected ${totalMarketCount} markets (target: ${targetMarketCount})`);
|
|
83
80
|
break;
|
|
84
81
|
}
|
|
85
82
|
}
|
|
86
83
|
cursor = response.data.cursor;
|
|
87
84
|
page++;
|
|
88
|
-
// Log progress every few pages to avoid spam
|
|
89
|
-
if (page % 5 === 0) {
|
|
90
|
-
console.log(`Fetched ${page} pages (${allEvents.length} events) from Kalshi...`);
|
|
91
|
-
}
|
|
92
85
|
}
|
|
93
86
|
catch (e) {
|
|
94
87
|
console.error(`Error fetching Kalshi page ${page}:`, e);
|
|
95
88
|
break;
|
|
96
89
|
}
|
|
97
90
|
} while (cursor && page < MAX_PAGES);
|
|
98
|
-
console.log(`Finished fetching Kalshi: ${allEvents.length} total events across ${page} pages.`);
|
|
99
91
|
return allEvents;
|
|
100
92
|
}
|
|
101
93
|
mapMarketToUnified(event, market) {
|
|
@@ -124,13 +116,13 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
124
116
|
}
|
|
125
117
|
const outcomes = [
|
|
126
118
|
{
|
|
127
|
-
id:
|
|
119
|
+
id: market.ticker, // The actual market ticker (primary tradeable)
|
|
128
120
|
label: candidateName || 'Yes',
|
|
129
121
|
price: price,
|
|
130
122
|
priceChange24h: priceChange
|
|
131
123
|
},
|
|
132
124
|
{
|
|
133
|
-
id:
|
|
125
|
+
id: `${market.ticker}-NO`, // Virtual ID for the No outcome
|
|
134
126
|
label: candidateName ? `Not ${candidateName}` : 'No',
|
|
135
127
|
price: 1 - price,
|
|
136
128
|
priceChange24h: -priceChange // Inverse change for No? simplified assumption
|
|
@@ -219,7 +211,9 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
219
211
|
async getMarketHistory(id, params) {
|
|
220
212
|
try {
|
|
221
213
|
// Kalshi API expects uppercase tickers
|
|
222
|
-
|
|
214
|
+
// Handle virtual "-NO" suffix by stripping it (fetching the underlying market history)
|
|
215
|
+
const cleanedId = id.replace(/-NO$/, '');
|
|
216
|
+
const normalizedId = cleanedId.toUpperCase();
|
|
223
217
|
const interval = this.mapIntervalToKalshi(params.resolution);
|
|
224
218
|
// Heuristic for series_ticker
|
|
225
219
|
const parts = normalizedId.split('-');
|
|
@@ -269,7 +263,8 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
269
263
|
}
|
|
270
264
|
async getOrderBook(id) {
|
|
271
265
|
try {
|
|
272
|
-
const
|
|
266
|
+
const ticker = id.replace(/-NO$/, '');
|
|
267
|
+
const url = `https://api.elections.kalshi.com/trade-api/v2/markets/${ticker}/orderbook`;
|
|
273
268
|
const response = await axios_1.default.get(url);
|
|
274
269
|
const data = response.data.orderbook;
|
|
275
270
|
// Structure: { yes: [[price, qty], ...], no: [[price, qty], ...] }
|
|
@@ -293,10 +288,11 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
293
288
|
}
|
|
294
289
|
async getTradeHistory(id, params) {
|
|
295
290
|
try {
|
|
291
|
+
const ticker = id.replace(/-NO$/, '');
|
|
296
292
|
const url = `https://api.elections.kalshi.com/trade-api/v2/markets/trades`;
|
|
297
293
|
const response = await axios_1.default.get(url, {
|
|
298
294
|
params: {
|
|
299
|
-
ticker:
|
|
295
|
+
ticker: ticker,
|
|
300
296
|
limit: params.limit || 100
|
|
301
297
|
}
|
|
302
298
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
getMarketHistory(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
|
+
getOrderBook(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 getMarketHistory for public historical price data instead.
|
|
36
|
+
*/
|
|
37
|
+
getTradeHistory(id: string, params: HistoryFilterParams): Promise<Trade[]>;
|
|
38
|
+
}
|
|
@@ -64,7 +64,6 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
64
64
|
outcomePrices = typeof market.outcomePrices === 'string' ? JSON.parse(market.outcomePrices) : (market.outcomePrices || []);
|
|
65
65
|
}
|
|
66
66
|
catch (e) {
|
|
67
|
-
console.warn(`Failed to parse outcomes for market ${market.id}`, e);
|
|
68
67
|
}
|
|
69
68
|
// Extract CLOB token IDs for granular operations
|
|
70
69
|
let clobTokenIds = [];
|
|
@@ -72,7 +71,6 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
72
71
|
clobTokenIds = typeof market.clobTokenIds === 'string' ? JSON.parse(market.clobTokenIds) : (market.clobTokenIds || []);
|
|
73
72
|
}
|
|
74
73
|
catch (e) {
|
|
75
|
-
console.warn(`Failed to parse clobTokenIds for market ${market.id}`, e);
|
|
76
74
|
}
|
|
77
75
|
// Extract candidate/option name from market question for better outcome labels
|
|
78
76
|
let candidateName = null;
|
|
@@ -97,11 +95,12 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
97
95
|
priceChange = Number(market.oneDayPriceChange || 0);
|
|
98
96
|
}
|
|
99
97
|
outcomes.push({
|
|
100
|
-
id: String(index),
|
|
98
|
+
id: clobTokenIds[index] || String(index), // Use CLOB Token ID as the primary ID
|
|
101
99
|
label: outcomeLabel,
|
|
102
100
|
price: parseFloat(rawPrice) || 0,
|
|
103
101
|
priceChange24h: priceChange,
|
|
104
102
|
metadata: {
|
|
103
|
+
// clobTokenId is now the main ID, but keeping it in metadata for backward compat if needed
|
|
105
104
|
clobTokenId: clobTokenIds[index]
|
|
106
105
|
}
|
|
107
106
|
});
|
|
@@ -202,9 +201,7 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
202
201
|
outcomePrices = typeof market.outcomePrices === 'string' ? JSON.parse(market.outcomePrices) : (market.outcomePrices || []);
|
|
203
202
|
clobTokenIds = typeof market.clobTokenIds === 'string' ? JSON.parse(market.clobTokenIds) : (market.clobTokenIds || []);
|
|
204
203
|
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
console.warn(`Parse error for market ${market.id}`, e);
|
|
207
|
-
}
|
|
204
|
+
catch (e) { /* ignore */ }
|
|
208
205
|
let candidateName = market.groupItemTitle;
|
|
209
206
|
if (!candidateName && market.question)
|
|
210
207
|
candidateName = market.question;
|
|
@@ -222,7 +219,7 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
222
219
|
priceChange = Number(market.oneDayPriceChange || 0);
|
|
223
220
|
}
|
|
224
221
|
outcomes.push({
|
|
225
|
-
id: String(index),
|
|
222
|
+
id: clobTokenIds[index] || String(index),
|
|
226
223
|
label: outcomeLabel,
|
|
227
224
|
price: parseFloat(outcomePrices[index] || "0") || 0,
|
|
228
225
|
priceChange24h: priceChange,
|
|
@@ -277,7 +274,7 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
277
274
|
async getMarketHistory(id, params) {
|
|
278
275
|
// ID Validation: Polymarket CLOB requires a Token ID (long numeric string) not a Market ID
|
|
279
276
|
if (id.length < 10 && /^\d+$/.test(id)) {
|
|
280
|
-
throw new Error(`Invalid ID for Polymarket history: "${id}". You provided a Market ID, but Polymarket's CLOB API requires a Token ID.
|
|
277
|
+
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'.`);
|
|
281
278
|
}
|
|
282
279
|
try {
|
|
283
280
|
const fidelity = this.mapIntervalToFidelity(params.resolution);
|
package/dist/index.d.ts
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface MarketOutcome {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
price: number;
|
|
5
|
+
priceChange24h?: number;
|
|
6
|
+
metadata?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
export interface UnifiedMarket {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
outcomes: MarketOutcome[];
|
|
13
|
+
resolutionDate: Date;
|
|
14
|
+
volume24h: number;
|
|
15
|
+
volume?: number;
|
|
16
|
+
liquidity: number;
|
|
17
|
+
openInterest?: number;
|
|
18
|
+
url: string;
|
|
19
|
+
image?: string;
|
|
20
|
+
category?: string;
|
|
21
|
+
tags?: string[];
|
|
22
|
+
}
|
|
23
|
+
export type CandleInterval = '1m' | '5m' | '15m' | '1h' | '6h' | '1d';
|
|
24
|
+
export interface PriceCandle {
|
|
25
|
+
timestamp: number;
|
|
26
|
+
open: number;
|
|
27
|
+
high: number;
|
|
28
|
+
low: number;
|
|
29
|
+
close: number;
|
|
30
|
+
volume?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface OrderLevel {
|
|
33
|
+
price: number;
|
|
34
|
+
size: number;
|
|
35
|
+
}
|
|
36
|
+
export interface OrderBook {
|
|
37
|
+
bids: OrderLevel[];
|
|
38
|
+
asks: OrderLevel[];
|
|
39
|
+
timestamp?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface Trade {
|
|
42
|
+
id: string;
|
|
43
|
+
timestamp: number;
|
|
44
|
+
price: number;
|
|
45
|
+
amount: number;
|
|
46
|
+
side: 'buy' | 'sell' | 'unknown';
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxtjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"dist"
|
|
8
|
+
"dist",
|
|
9
|
+
"API_REFERENCE.md"
|
|
9
10
|
],
|
|
10
11
|
"directories": {
|
|
11
12
|
"example": "examples",
|
|
@@ -17,10 +18,15 @@
|
|
|
17
18
|
},
|
|
18
19
|
"keywords": [],
|
|
19
20
|
"author": "",
|
|
20
|
-
"license": "
|
|
21
|
+
"license": "MIT",
|
|
21
22
|
"type": "commonjs",
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"axios": "^1.7.9",
|
|
25
|
+
"jest": "^30.2.0",
|
|
24
26
|
"tsx": "^4.21.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.0.3",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
25
31
|
}
|
|
26
|
-
}
|
|
32
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# pmxt [](https://twitter.com/intent/tweet?text=The%20ccxt%20for%20prediction%20markets.&url=https://github.com/qoery-com/pmxt&hashtags=predictionmarkets,trading)
|
|
2
|
+
|
|
3
|
+
**The ccxt for prediction markets.** A unified API for accessing prediction market data across multiple exchanges.
|
|
4
|
+
|
|
5
|
+
<img width="3840" height="2160" alt="plot" src="https://github.com/user-attachments/assets/ed77d244-c95f-4fe0-a7a7-89af713c053f" />
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<table>
|
|
9
|
+
<tr>
|
|
10
|
+
<td rowspan="3">
|
|
11
|
+
<a href="https://www.producthunt.com/products/qoery-python-sdk?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-qoery-python-sdk" target="_blank" rel="noopener noreferrer"><img alt="Qoery Python SDK - 50% cheaper crypto data. Now in Python. | Product Hunt" width="250" height="54" src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1056631&theme=light&t=1767263265752"></a>
|
|
12
|
+
</td>
|
|
13
|
+
<td>
|
|
14
|
+
<img src="https://img.shields.io/github/watchers/qoery-com/pmxt?style=social" alt="GitHub watchers">
|
|
15
|
+
</td>
|
|
16
|
+
<td>
|
|
17
|
+
<a href="https://www.npmjs.com/package/pmxtjs"><img src="https://img.shields.io/npm/dt/pmxtjs" alt="Downloads"></a>
|
|
18
|
+
</td>
|
|
19
|
+
</tr>
|
|
20
|
+
<tr>
|
|
21
|
+
<td>
|
|
22
|
+
<img src="https://img.shields.io/github/forks/qoery-com/pmxt?style=social" alt="GitHub forks">
|
|
23
|
+
</td>
|
|
24
|
+
<td>
|
|
25
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
<tr>
|
|
29
|
+
<td>
|
|
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
|
+
</td>
|
|
32
|
+
<td>
|
|
33
|
+
<!-- Space for future badge -->
|
|
34
|
+
</td>
|
|
35
|
+
</tr>
|
|
36
|
+
</table>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="https://polymarket.com/favicon.ico" alt="Polymarket" width="40" height="40">
|
|
41
|
+
|
|
42
|
+
<img src="https://kalshi.com/favicon.ico" alt="Kalshi" width="40" height="40">
|
|
43
|
+
|
|
44
|
+
<img src="https://manifold.markets/logo.svg" alt="Manifold Markets" width="40" height="40">
|
|
45
|
+
|
|
46
|
+
<img src="https://metaculus.com/favicon.ico" alt="Metaculus" width="40" height="40">
|
|
47
|
+
|
|
48
|
+
<img src="https://predictit.org/favicon.ico" alt="PredictIt" width="40" height="40">
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
## Why pmxt?
|
|
52
|
+
|
|
53
|
+
Different prediction market platforms have different APIs, data formats, and conventions. pmxt provides a single, consistent interface to work with all of them.
|
|
54
|
+
|
|
55
|
+
## Quick Example
|
|
56
|
+
|
|
57
|
+
Search for markets across Polymarket and Kalshi using the same API:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { PolymarketExchange, KalshiExchange } from 'pmxtjs';
|
|
61
|
+
|
|
62
|
+
const query = process.argv[2] || 'Who will Trump nominate as Fed Chair?';
|
|
63
|
+
console.log(`Searching for "${query}"...\n`);
|
|
64
|
+
|
|
65
|
+
// Polymarket
|
|
66
|
+
const polymarket = new PolymarketExchange();
|
|
67
|
+
const polyResults = await polymarket.searchMarkets(query, { sort: 'volume' });
|
|
68
|
+
|
|
69
|
+
console.log(`--- Polymarket Found ${polyResults.length} ---`);
|
|
70
|
+
polyResults.slice(0, 10).forEach(m => {
|
|
71
|
+
const label = m.outcomes[0]?.label || 'Unknown';
|
|
72
|
+
console.log(`[${m.id}] ${m.title} - ${label} (Vol24h: $${m.volume24h.toLocaleString()})`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Kalshi
|
|
76
|
+
const kalshi = new KalshiExchange();
|
|
77
|
+
const kalshiResults = await kalshi.searchMarkets(query);
|
|
78
|
+
|
|
79
|
+
console.log(`\n--- Kalshi Found ${kalshiResults.length} ---`);
|
|
80
|
+
kalshiResults.slice(0, 10).forEach(m => {
|
|
81
|
+
const label = m.outcomes[0]?.label || 'Unknown';
|
|
82
|
+
console.log(`[${m.id}] ${m.title} - ${label} (Vol24h: $${m.volume24h.toLocaleString()})`);
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install pmxtjs
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Supported Exchanges
|
|
93
|
+
|
|
94
|
+
- Polymarket
|
|
95
|
+
- Kalshi
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
See the [API Reference](pmxt/API_REFERENCE.md) for detailed documentation and more examples.
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
Check out the [examples](pmxt/examples/) directory for more use cases:
|
|
104
|
+
- Market search
|
|
105
|
+
- Order book data
|
|
106
|
+
- Historical prices
|
|
107
|
+
- Event price tracking
|
|
108
|
+
- Recent trades
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
[](https://github.com/qoery-com/pmxt/stargazers)
|