pmxtjs 0.3.1 → 0.4.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 (48) hide show
  1. package/API_REFERENCE.md +230 -1
  2. package/dist/BaseExchange.d.ts +36 -2
  3. package/dist/BaseExchange.js +43 -1
  4. package/dist/exchanges/kalshi/auth.d.ts +23 -0
  5. package/dist/exchanges/kalshi/auth.js +99 -0
  6. package/dist/exchanges/kalshi/fetchMarkets.d.ts +3 -0
  7. package/dist/exchanges/kalshi/fetchMarkets.js +110 -0
  8. package/dist/exchanges/kalshi/fetchOHLCV.d.ts +3 -0
  9. package/dist/exchanges/kalshi/fetchOHLCV.js +78 -0
  10. package/dist/exchanges/kalshi/fetchOrderBook.d.ts +2 -0
  11. package/dist/exchanges/kalshi/fetchOrderBook.js +32 -0
  12. package/dist/exchanges/kalshi/fetchTrades.d.ts +3 -0
  13. package/dist/exchanges/kalshi/fetchTrades.js +31 -0
  14. package/dist/exchanges/kalshi/getMarketsBySlug.d.ts +7 -0
  15. package/dist/exchanges/kalshi/getMarketsBySlug.js +62 -0
  16. package/dist/exchanges/kalshi/index.d.ts +21 -0
  17. package/dist/exchanges/kalshi/index.js +273 -0
  18. package/dist/exchanges/kalshi/kalshi.test.d.ts +1 -0
  19. package/dist/exchanges/kalshi/kalshi.test.js +309 -0
  20. package/dist/exchanges/kalshi/searchMarkets.d.ts +3 -0
  21. package/dist/exchanges/kalshi/searchMarkets.js +28 -0
  22. package/dist/exchanges/kalshi/utils.d.ts +5 -0
  23. package/dist/exchanges/kalshi/utils.js +85 -0
  24. package/dist/exchanges/polymarket/auth.d.ts +32 -0
  25. package/dist/exchanges/polymarket/auth.js +98 -0
  26. package/dist/exchanges/polymarket/fetchMarkets.d.ts +3 -0
  27. package/dist/exchanges/polymarket/fetchMarkets.js +75 -0
  28. package/dist/exchanges/polymarket/fetchOHLCV.d.ts +7 -0
  29. package/dist/exchanges/polymarket/fetchOHLCV.js +73 -0
  30. package/dist/exchanges/polymarket/fetchOrderBook.d.ts +6 -0
  31. package/dist/exchanges/polymarket/fetchOrderBook.js +38 -0
  32. package/dist/exchanges/polymarket/fetchPositions.d.ts +2 -0
  33. package/dist/exchanges/polymarket/fetchPositions.js +27 -0
  34. package/dist/exchanges/polymarket/fetchTrades.d.ts +11 -0
  35. package/dist/exchanges/polymarket/fetchTrades.js +59 -0
  36. package/dist/exchanges/polymarket/getMarketsBySlug.d.ts +7 -0
  37. package/dist/exchanges/polymarket/getMarketsBySlug.js +39 -0
  38. package/dist/exchanges/polymarket/index.d.ts +23 -0
  39. package/dist/exchanges/polymarket/index.js +216 -0
  40. package/dist/exchanges/polymarket/searchMarkets.d.ts +3 -0
  41. package/dist/exchanges/polymarket/searchMarkets.js +35 -0
  42. package/dist/exchanges/polymarket/utils.d.ts +7 -0
  43. package/dist/exchanges/polymarket/utils.js +95 -0
  44. package/dist/index.d.ts +4 -4
  45. package/dist/index.js +8 -8
  46. package/dist/types.d.ts +38 -0
  47. package/package.json +5 -1
  48. package/readme.md +37 -3
@@ -0,0 +1,32 @@
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.fetchOrderBook = fetchOrderBook;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ async function fetchOrderBook(id) {
9
+ try {
10
+ const ticker = id.replace(/-NO$/, '');
11
+ const url = `https://api.elections.kalshi.com/trade-api/v2/markets/${ticker}/orderbook`;
12
+ const response = await axios_1.default.get(url);
13
+ const data = response.data.orderbook;
14
+ // Structure: { yes: [[price, qty], ...], no: [[price, qty], ...] }
15
+ const bids = (data.yes || []).map((level) => ({
16
+ price: level[0] / 100,
17
+ size: level[1]
18
+ }));
19
+ const asks = (data.no || []).map((level) => ({
20
+ price: (100 - level[0]) / 100,
21
+ size: level[1]
22
+ }));
23
+ // Sort bids desc, asks asc
24
+ bids.sort((a, b) => b.price - a.price);
25
+ asks.sort((a, b) => a.price - b.price);
26
+ return { bids, asks, timestamp: Date.now() };
27
+ }
28
+ catch (error) {
29
+ console.error(`Error fetching Kalshi orderbook for ${id}:`, error);
30
+ return { bids: [], asks: [] };
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ import { HistoryFilterParams } from '../../BaseExchange';
2
+ import { Trade } from '../../types';
3
+ export declare function fetchTrades(id: string, params: HistoryFilterParams): Promise<Trade[]>;
@@ -0,0 +1,31 @@
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.fetchTrades = fetchTrades;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ async function fetchTrades(id, params) {
9
+ try {
10
+ const ticker = id.replace(/-NO$/, '');
11
+ const url = `https://api.elections.kalshi.com/trade-api/v2/markets/trades`;
12
+ const response = await axios_1.default.get(url, {
13
+ params: {
14
+ ticker: ticker,
15
+ limit: params.limit || 100
16
+ }
17
+ });
18
+ const trades = response.data.trades || [];
19
+ return trades.map((t) => ({
20
+ id: t.trade_id,
21
+ timestamp: new Date(t.created_time).getTime(),
22
+ price: t.yes_price / 100,
23
+ amount: t.count,
24
+ side: t.taker_side === 'yes' ? 'buy' : 'sell'
25
+ }));
26
+ }
27
+ catch (error) {
28
+ console.error(`Error fetching Kalshi trades for ${id}:`, error);
29
+ return [];
30
+ }
31
+ }
@@ -0,0 +1,7 @@
1
+ import { UnifiedMarket } from '../../types';
2
+ /**
3
+ * Fetch specific markets by their event ticker.
4
+ * Useful for looking up a specific event from a URL.
5
+ * @param eventTicker - The event ticker (e.g. "FED-25JAN" or "PRES-2024")
6
+ */
7
+ export declare function getMarketsBySlug(eventTicker: string): Promise<UnifiedMarket[]>;
@@ -0,0 +1,62 @@
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.getMarketsBySlug = getMarketsBySlug;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const utils_1 = require("./utils");
9
+ /**
10
+ * Fetch specific markets by their event ticker.
11
+ * Useful for looking up a specific event from a URL.
12
+ * @param eventTicker - The event ticker (e.g. "FED-25JAN" or "PRES-2024")
13
+ */
14
+ async function getMarketsBySlug(eventTicker) {
15
+ try {
16
+ // Kalshi API expects uppercase tickers, but URLs use lowercase
17
+ const normalizedTicker = eventTicker.toUpperCase();
18
+ const url = `https://api.elections.kalshi.com/trade-api/v2/events/${normalizedTicker}`;
19
+ const response = await axios_1.default.get(url, {
20
+ params: { with_nested_markets: true }
21
+ });
22
+ const event = response.data.event;
23
+ if (!event)
24
+ return [];
25
+ // Enrichment: Fetch series tags if they exist
26
+ if (event.series_ticker) {
27
+ try {
28
+ const seriesUrl = `${utils_1.KALSHI_SERIES_URL}/${event.series_ticker}`;
29
+ const seriesResponse = await axios_1.default.get(seriesUrl);
30
+ const series = seriesResponse.data.series;
31
+ if (series && series.tags && series.tags.length > 0) {
32
+ if (!event.tags || event.tags.length === 0) {
33
+ event.tags = series.tags;
34
+ }
35
+ }
36
+ }
37
+ catch (e) {
38
+ // Ignore errors fetching series info - non-critical
39
+ }
40
+ }
41
+ const unifiedMarkets = [];
42
+ const markets = event.markets || [];
43
+ for (const market of markets) {
44
+ const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market);
45
+ if (unifiedMarket) {
46
+ unifiedMarkets.push(unifiedMarket);
47
+ }
48
+ }
49
+ return unifiedMarkets;
50
+ }
51
+ catch (error) {
52
+ if (axios_1.default.isAxiosError(error) && error.response) {
53
+ if (error.response.status === 404) {
54
+ throw new Error(`Kalshi event not found: "${eventTicker}". Check that the event ticker is correct.`);
55
+ }
56
+ const apiError = error.response.data?.error || error.response.data?.message || "Unknown API Error";
57
+ throw new Error(`Kalshi API Error (${error.response.status}): ${apiError}. Event Ticker: ${eventTicker}`);
58
+ }
59
+ console.error(`Unexpected error fetching Kalshi event ${eventTicker}:`, error);
60
+ throw error;
61
+ }
62
+ }
@@ -0,0 +1,21 @@
1
+ import { PredictionMarketExchange, MarketFilterParams, HistoryFilterParams, ExchangeCredentials } from '../../BaseExchange';
2
+ import { UnifiedMarket, PriceCandle, OrderBook, Trade, Balance, Order, Position, CreateOrderParams } from '../../types';
3
+ export declare class KalshiExchange extends PredictionMarketExchange {
4
+ private auth?;
5
+ constructor(credentials?: ExchangeCredentials);
6
+ get name(): string;
7
+ private ensureAuth;
8
+ fetchMarkets(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
9
+ searchMarkets(query: string, params?: MarketFilterParams): Promise<UnifiedMarket[]>;
10
+ getMarketsBySlug(slug: string): Promise<UnifiedMarket[]>;
11
+ fetchOHLCV(id: string, params: HistoryFilterParams): Promise<PriceCandle[]>;
12
+ fetchOrderBook(id: string): Promise<OrderBook>;
13
+ fetchTrades(id: string, params: HistoryFilterParams): Promise<Trade[]>;
14
+ fetchBalance(): Promise<Balance[]>;
15
+ createOrder(params: CreateOrderParams): Promise<Order>;
16
+ cancelOrder(orderId: string): Promise<Order>;
17
+ fetchOrder(orderId: string): Promise<Order>;
18
+ fetchOpenOrders(marketId?: string): Promise<Order[]>;
19
+ fetchPositions(): Promise<Position[]>;
20
+ private mapKalshiOrderStatus;
21
+ }
@@ -0,0 +1,273 @@
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
+ const fetchMarkets_1 = require("./fetchMarkets");
10
+ const searchMarkets_1 = require("./searchMarkets");
11
+ const getMarketsBySlug_1 = require("./getMarketsBySlug");
12
+ const fetchOHLCV_1 = require("./fetchOHLCV");
13
+ const fetchOrderBook_1 = require("./fetchOrderBook");
14
+ const fetchTrades_1 = require("./fetchTrades");
15
+ const auth_1 = require("./auth");
16
+ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
17
+ constructor(credentials) {
18
+ super(credentials);
19
+ if (credentials?.apiKey && credentials?.privateKey) {
20
+ this.auth = new auth_1.KalshiAuth(credentials);
21
+ }
22
+ }
23
+ get name() {
24
+ return "Kalshi";
25
+ }
26
+ // ----------------------------------------------------------------------------
27
+ // Helpers
28
+ // ----------------------------------------------------------------------------
29
+ ensureAuth() {
30
+ if (!this.auth) {
31
+ throw new Error('Trading operations require authentication. ' +
32
+ 'Initialize KalshiExchange with credentials (apiKey and privateKey).');
33
+ }
34
+ return this.auth;
35
+ }
36
+ // ----------------------------------------------------------------------------
37
+ // Market Data Methods
38
+ // ----------------------------------------------------------------------------
39
+ async fetchMarkets(params) {
40
+ return (0, fetchMarkets_1.fetchMarkets)(params);
41
+ }
42
+ async searchMarkets(query, params) {
43
+ return (0, searchMarkets_1.searchMarkets)(query, params);
44
+ }
45
+ async getMarketsBySlug(slug) {
46
+ return (0, getMarketsBySlug_1.getMarketsBySlug)(slug);
47
+ }
48
+ async fetchOHLCV(id, params) {
49
+ return (0, fetchOHLCV_1.fetchOHLCV)(id, params);
50
+ }
51
+ async fetchOrderBook(id) {
52
+ return (0, fetchOrderBook_1.fetchOrderBook)(id);
53
+ }
54
+ async fetchTrades(id, params) {
55
+ return (0, fetchTrades_1.fetchTrades)(id, params);
56
+ }
57
+ // ----------------------------------------------------------------------------
58
+ // User Data Methods
59
+ // ----------------------------------------------------------------------------
60
+ async fetchBalance() {
61
+ const auth = this.ensureAuth();
62
+ const path = '/trade-api/v2/portfolio/balance';
63
+ // Use demo-api if it's a sandbox key (usually indicated by config, but defaulting to prod for now)
64
+ // Or we could detect it. For now, let's assume Production unless specified.
65
+ // TODO: Make base URL configurable in credentials
66
+ const baseUrl = 'https://trading-api.kalshi.com';
67
+ const headers = auth.getHeaders('GET', path);
68
+ try {
69
+ const response = await axios_1.default.get(`${baseUrl}${path}`, { headers });
70
+ // Kalshi response structure:
71
+ // - balance: Available balance in cents (for trading)
72
+ // - portfolio_value: Total portfolio value in cents (includes positions)
73
+ // - updated_ts: Unix timestamp of last update
74
+ const balanceCents = response.data.balance;
75
+ const portfolioValueCents = response.data.portfolio_value;
76
+ const available = balanceCents / 100;
77
+ const total = portfolioValueCents / 100;
78
+ const locked = total - available;
79
+ return [{
80
+ currency: 'USD',
81
+ total: total, // Total portfolio value (cash + positions)
82
+ available: available, // Available for trading
83
+ locked: locked // Value locked in positions
84
+ }];
85
+ }
86
+ catch (error) {
87
+ console.error("Kalshi fetchBalance error:", error?.response?.data || error.message);
88
+ throw error;
89
+ }
90
+ }
91
+ // ----------------------------------------------------------------------------
92
+ // Trading Methods
93
+ // ----------------------------------------------------------------------------
94
+ async createOrder(params) {
95
+ const auth = this.ensureAuth();
96
+ const path = '/trade-api/v2/portfolio/orders';
97
+ const baseUrl = 'https://trading-api.kalshi.com';
98
+ const headers = auth.getHeaders('POST', path);
99
+ // Map unified params to Kalshi format
100
+ // Kalshi uses 'yes'/'no' for side and 'buy'/'sell' for action
101
+ const isYesSide = params.side === 'buy';
102
+ const kalshiOrder = {
103
+ ticker: params.marketId, // Kalshi uses ticker for market identification
104
+ client_order_id: `pmxt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
105
+ side: isYesSide ? 'yes' : 'no',
106
+ action: params.side === 'buy' ? 'buy' : 'sell',
107
+ count: params.amount, // Number of contracts
108
+ type: params.type === 'limit' ? 'limit' : 'market'
109
+ };
110
+ // Add price field based on side (yes_price for yes side, no_price for no side)
111
+ if (params.price) {
112
+ const priceInCents = Math.round(params.price * 100);
113
+ if (isYesSide) {
114
+ kalshiOrder.yes_price = priceInCents;
115
+ }
116
+ else {
117
+ kalshiOrder.no_price = priceInCents;
118
+ }
119
+ }
120
+ try {
121
+ const response = await axios_1.default.post(`${baseUrl}${path}`, kalshiOrder, { headers });
122
+ const order = response.data.order;
123
+ return {
124
+ id: order.order_id,
125
+ marketId: params.marketId,
126
+ outcomeId: params.outcomeId,
127
+ side: params.side,
128
+ type: params.type,
129
+ price: params.price,
130
+ amount: params.amount,
131
+ status: this.mapKalshiOrderStatus(order.status),
132
+ filled: order.queue_position === 0 ? params.amount : 0,
133
+ remaining: order.remaining_count || params.amount,
134
+ timestamp: new Date(order.created_time).getTime()
135
+ };
136
+ }
137
+ catch (error) {
138
+ console.error("Kalshi createOrder error:", error?.response?.data || error.message);
139
+ throw error;
140
+ }
141
+ }
142
+ async cancelOrder(orderId) {
143
+ const auth = this.ensureAuth();
144
+ const path = `/trade-api/v2/portfolio/orders/${orderId}`;
145
+ const baseUrl = 'https://trading-api.kalshi.com';
146
+ const headers = auth.getHeaders('DELETE', path);
147
+ try {
148
+ const response = await axios_1.default.delete(`${baseUrl}${path}`, { headers });
149
+ const order = response.data.order;
150
+ return {
151
+ id: order.order_id,
152
+ marketId: order.ticker,
153
+ outcomeId: order.ticker,
154
+ side: order.side === 'yes' ? 'buy' : 'sell',
155
+ type: 'limit',
156
+ amount: order.count,
157
+ status: 'cancelled',
158
+ filled: order.count - (order.remaining_count || 0),
159
+ remaining: 0,
160
+ timestamp: new Date(order.created_time).getTime()
161
+ };
162
+ }
163
+ catch (error) {
164
+ console.error("Kalshi cancelOrder error:", error?.response?.data || error.message);
165
+ throw error;
166
+ }
167
+ }
168
+ async fetchOrder(orderId) {
169
+ const auth = this.ensureAuth();
170
+ const path = `/trade-api/v2/portfolio/orders/${orderId}`;
171
+ const baseUrl = 'https://trading-api.kalshi.com';
172
+ const headers = auth.getHeaders('GET', path);
173
+ try {
174
+ const response = await axios_1.default.get(`${baseUrl}${path}`, { headers });
175
+ const order = response.data.order;
176
+ return {
177
+ id: order.order_id,
178
+ marketId: order.ticker,
179
+ outcomeId: order.ticker,
180
+ side: order.side === 'yes' ? 'buy' : 'sell',
181
+ type: order.type === 'limit' ? 'limit' : 'market',
182
+ price: order.yes_price ? order.yes_price / 100 : undefined,
183
+ amount: order.count,
184
+ status: this.mapKalshiOrderStatus(order.status),
185
+ filled: order.count - (order.remaining_count || 0),
186
+ remaining: order.remaining_count || 0,
187
+ timestamp: new Date(order.created_time).getTime()
188
+ };
189
+ }
190
+ catch (error) {
191
+ console.error("Kalshi fetchOrder error:", error?.response?.data || error.message);
192
+ throw error;
193
+ }
194
+ }
195
+ async fetchOpenOrders(marketId) {
196
+ const auth = this.ensureAuth();
197
+ // CRITICAL: Query parameters must NOT be included in the signature
198
+ const basePath = '/trade-api/v2/portfolio/orders';
199
+ let queryParams = '?status=resting';
200
+ if (marketId) {
201
+ queryParams += `&ticker=${marketId}`;
202
+ }
203
+ const baseUrl = 'https://trading-api.kalshi.com';
204
+ // Sign only the base path, not the query parameters
205
+ const headers = auth.getHeaders('GET', basePath);
206
+ try {
207
+ const response = await axios_1.default.get(`${baseUrl}${basePath}${queryParams}`, { headers });
208
+ const orders = response.data.orders || [];
209
+ return orders.map((order) => ({
210
+ id: order.order_id,
211
+ marketId: order.ticker,
212
+ outcomeId: order.ticker,
213
+ side: order.side === 'yes' ? 'buy' : 'sell',
214
+ type: order.type === 'limit' ? 'limit' : 'market',
215
+ price: order.yes_price ? order.yes_price / 100 : undefined,
216
+ amount: order.count,
217
+ status: 'open',
218
+ filled: order.count - (order.remaining_count || 0),
219
+ remaining: order.remaining_count || 0,
220
+ timestamp: new Date(order.created_time).getTime()
221
+ }));
222
+ }
223
+ catch (error) {
224
+ console.error("Kalshi fetchOpenOrders error:", error?.response?.data || error.message);
225
+ return [];
226
+ }
227
+ }
228
+ async fetchPositions() {
229
+ const auth = this.ensureAuth();
230
+ const path = '/trade-api/v2/portfolio/positions';
231
+ const baseUrl = 'https://trading-api.kalshi.com';
232
+ const headers = auth.getHeaders('GET', path);
233
+ try {
234
+ const response = await axios_1.default.get(`${baseUrl}${path}`, { headers });
235
+ const positions = response.data.market_positions || [];
236
+ return positions.map((pos) => {
237
+ const absPosition = Math.abs(pos.position);
238
+ // Prevent division by zero
239
+ const entryPrice = absPosition > 0 ? pos.total_cost / absPosition / 100 : 0;
240
+ return {
241
+ marketId: pos.ticker,
242
+ outcomeId: pos.ticker,
243
+ outcomeLabel: pos.ticker, // Kalshi uses ticker as the outcome label
244
+ size: pos.position, // Positive for long, negative for short
245
+ entryPrice: entryPrice,
246
+ currentPrice: pos.market_price ? pos.market_price / 100 : entryPrice,
247
+ unrealizedPnL: pos.market_exposure ? pos.market_exposure / 100 : 0,
248
+ realizedPnL: pos.realized_pnl ? pos.realized_pnl / 100 : 0
249
+ };
250
+ });
251
+ }
252
+ catch (error) {
253
+ console.error("Kalshi fetchPositions error:", error?.response?.data || error.message);
254
+ return [];
255
+ }
256
+ }
257
+ // Helper to map Kalshi order status to unified status
258
+ mapKalshiOrderStatus(status) {
259
+ switch (status.toLowerCase()) {
260
+ case 'resting':
261
+ return 'open';
262
+ case 'canceled':
263
+ case 'cancelled':
264
+ return 'cancelled';
265
+ case 'executed':
266
+ case 'filled':
267
+ return 'filled';
268
+ default:
269
+ return 'open';
270
+ }
271
+ }
272
+ }
273
+ exports.KalshiExchange = KalshiExchange;
@@ -0,0 +1 @@
1
+ export {};