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