pmxt-core 2.9.2 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/BaseExchange.d.ts +118 -4
  2. package/dist/BaseExchange.js +160 -7
  3. package/dist/exchanges/baozi/fetchEvents.js +16 -11
  4. package/dist/exchanges/baozi/index.d.ts +5 -0
  5. package/dist/exchanges/baozi/index.js +6 -0
  6. package/dist/exchanges/kalshi/api.d.ts +7 -1
  7. package/dist/exchanges/kalshi/api.js +11 -2
  8. package/dist/exchanges/kalshi/config.d.ts +103 -0
  9. package/dist/exchanges/kalshi/config.js +144 -0
  10. package/dist/exchanges/kalshi/fetchEvents.d.ts +2 -2
  11. package/dist/exchanges/kalshi/fetchEvents.js +138 -67
  12. package/dist/exchanges/kalshi/fetchMarkets.d.ts +2 -2
  13. package/dist/exchanges/kalshi/fetchMarkets.js +36 -25
  14. package/dist/exchanges/kalshi/fetchOHLCV.d.ts +3 -3
  15. package/dist/exchanges/kalshi/fetchOHLCV.js +20 -17
  16. package/dist/exchanges/kalshi/fetchOrderBook.d.ts +2 -0
  17. package/dist/exchanges/kalshi/fetchOrderBook.js +60 -0
  18. package/dist/exchanges/kalshi/fetchTrades.d.ts +3 -0
  19. package/dist/exchanges/kalshi/fetchTrades.js +32 -0
  20. package/dist/exchanges/kalshi/index.d.ts +20 -4
  21. package/dist/exchanges/kalshi/index.js +171 -90
  22. package/dist/exchanges/kalshi/kalshi.test.js +440 -157
  23. package/dist/exchanges/kalshi/utils.d.ts +1 -3
  24. package/dist/exchanges/kalshi/utils.js +15 -16
  25. package/dist/exchanges/kalshi/websocket.d.ts +4 -3
  26. package/dist/exchanges/kalshi/websocket.js +87 -61
  27. package/dist/exchanges/kalshi-demo/index.d.ts +10 -0
  28. package/dist/exchanges/kalshi-demo/index.js +23 -0
  29. package/dist/exchanges/limitless/api.d.ts +1 -1
  30. package/dist/exchanges/limitless/api.js +1 -1
  31. package/dist/exchanges/limitless/fetchEvents.d.ts +2 -1
  32. package/dist/exchanges/limitless/fetchEvents.js +95 -49
  33. package/dist/exchanges/limitless/fetchOHLCV.d.ts +2 -2
  34. package/dist/exchanges/limitless/index.d.ts +11 -3
  35. package/dist/exchanges/limitless/index.js +69 -1
  36. package/dist/exchanges/limitless/utils.js +1 -0
  37. package/dist/exchanges/myriad/api.d.ts +1 -1
  38. package/dist/exchanges/myriad/api.js +1 -1
  39. package/dist/exchanges/myriad/fetchOHLCV.d.ts +2 -2
  40. package/dist/exchanges/myriad/index.d.ts +9 -3
  41. package/dist/exchanges/myriad/index.js +34 -0
  42. package/dist/exchanges/myriad/utils.js +5 -1
  43. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  44. package/dist/exchanges/polymarket/api-clob.js +1 -1
  45. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  46. package/dist/exchanges/polymarket/api-data.js +1 -1
  47. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  48. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  49. package/dist/exchanges/polymarket/auth.js +3 -1
  50. package/dist/exchanges/polymarket/fetchEvents.js +116 -80
  51. package/dist/exchanges/polymarket/fetchOHLCV.d.ts +2 -2
  52. package/dist/exchanges/polymarket/index.d.ts +30 -6
  53. package/dist/exchanges/polymarket/index.js +101 -31
  54. package/dist/exchanges/polymarket/utils.js +1 -0
  55. package/dist/exchanges/probable/api.d.ts +1 -1
  56. package/dist/exchanges/probable/api.js +1 -1
  57. package/dist/exchanges/probable/index.d.ts +45 -3
  58. package/dist/exchanges/probable/index.js +61 -0
  59. package/dist/exchanges/probable/utils.js +5 -1
  60. package/dist/index.d.ts +4 -0
  61. package/dist/index.js +5 -1
  62. package/dist/server/app.js +56 -48
  63. package/dist/server/utils/port-manager.js +1 -1
  64. package/dist/types.d.ts +29 -0
  65. package/dist/utils/throttler.d.ts +17 -0
  66. package/dist/utils/throttler.js +50 -0
  67. package/package.json +7 -4
@@ -6,10 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const kalshi_1 = require("../kalshi");
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const auth_1 = require("./auth");
9
+ const config_1 = require("./config");
9
10
  // Jest hoisting means we can't use outer variables in jest.mock factory
10
11
  // unless they start with 'mock'. However, let's just define it inline to be safe and simple.
11
12
  // To access the inner methods, we'll grab the instance returned by axios.create().
12
- jest.mock('axios', () => {
13
+ jest.mock("axios", () => {
13
14
  const mockInstance = {
14
15
  interceptors: {
15
16
  request: { use: jest.fn() },
@@ -21,7 +22,7 @@ jest.mock('axios', () => {
21
22
  request: jest.fn(),
22
23
  defaults: { headers: { common: {} } },
23
24
  };
24
- const actualAxios = jest.requireActual('axios');
25
+ const actualAxios = jest.requireActual("axios");
25
26
  const mockAxios = {
26
27
  create: jest.fn(() => mockInstance),
27
28
  isAxiosError: actualAxios.isAxiosError,
@@ -38,13 +39,13 @@ jest.mock('axios', () => {
38
39
  const mockAxiosInstance = axios_1.default.create();
39
40
  const mockedAxios = axios_1.default;
40
41
  // Mock KalshiAuth
41
- jest.mock('./auth');
42
+ jest.mock("./auth");
42
43
  const MockedKalshiAuth = auth_1.KalshiAuth;
43
- describe('KalshiExchange', () => {
44
+ describe("KalshiExchange", () => {
44
45
  let exchange;
45
46
  const mockCredentials = {
46
- apiKey: 'test-api-key',
47
- privateKey: 'mock-private-key'
47
+ apiKey: "test-api-key",
48
+ privateKey: "mock-private-key",
48
49
  };
49
50
  beforeEach(() => {
50
51
  jest.clearAllMocks();
@@ -55,181 +56,184 @@ describe('KalshiExchange', () => {
55
56
  mockAxiosInstance.request.mockReset();
56
57
  // Mock the getHeaders method
57
58
  MockedKalshiAuth.prototype.getHeaders = jest.fn().mockReturnValue({
58
- 'KALSHI-ACCESS-KEY': 'test-api-key',
59
- 'KALSHI-ACCESS-TIMESTAMP': '1234567890',
60
- 'KALSHI-ACCESS-SIGNATURE': 'mock-signature',
61
- 'Content-Type': 'application/json'
59
+ "KALSHI-ACCESS-KEY": "test-api-key",
60
+ "KALSHI-ACCESS-TIMESTAMP": "1234567890",
61
+ "KALSHI-ACCESS-SIGNATURE": "mock-signature",
62
+ "Content-Type": "application/json",
62
63
  });
63
64
  });
64
- describe('Authentication', () => {
65
- it('should throw error when trading without credentials', async () => {
65
+ describe("Authentication", () => {
66
+ it("should throw error when trading without credentials", async () => {
66
67
  exchange = new kalshi_1.KalshiExchange();
67
- await expect(exchange.fetchBalance()).rejects.toThrow('Trading operations require authentication');
68
+ await expect(exchange.fetchBalance()).rejects.toThrow("Trading operations require authentication");
68
69
  });
69
- it('should initialize with credentials', () => {
70
+ it("should initialize with credentials", () => {
70
71
  exchange = new kalshi_1.KalshiExchange(mockCredentials);
71
72
  expect(exchange).toBeDefined();
72
73
  });
73
74
  });
74
- describe('Market Data Methods', () => {
75
+ describe("Market Data Methods", () => {
75
76
  beforeEach(() => {
76
77
  exchange = new kalshi_1.KalshiExchange();
77
78
  });
78
- it('should fetch markets', async () => {
79
+ it("should fetch markets", async () => {
79
80
  const mockResponse = {
80
81
  data: {
81
82
  markets: [
82
83
  {
83
- ticker: 'TEST-MARKET',
84
- title: 'Test Market',
84
+ ticker: "TEST-MARKET",
85
+ title: "Test Market",
85
86
  yes_bid: 50,
86
87
  yes_ask: 52,
87
- volume: 1000
88
- }
89
- ]
90
- }
88
+ volume: 1000,
89
+ },
90
+ ],
91
+ },
91
92
  };
92
93
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
93
94
  const markets = await exchange.fetchMarkets();
94
95
  expect(markets).toBeDefined();
95
96
  });
96
97
  });
97
- describe('Trading Methods', () => {
98
+ describe("Trading Methods", () => {
98
99
  beforeEach(() => {
99
100
  exchange = new kalshi_1.KalshiExchange(mockCredentials);
100
101
  });
101
- describe('createOrder', () => {
102
- it('should create buy order with yes_price for buy side', async () => {
102
+ describe("createOrder", () => {
103
+ it("should create buy order with yes_price for buy side", async () => {
103
104
  const orderParams = {
104
- marketId: 'TEST-MARKET',
105
- outcomeId: 'yes',
106
- side: 'buy',
107
- type: 'limit',
105
+ marketId: "TEST-MARKET",
106
+ outcomeId: "yes",
107
+ side: "buy",
108
+ type: "limit",
108
109
  amount: 10,
109
- price: 0.55
110
+ price: 0.55,
110
111
  };
111
112
  const mockResponse = {
112
113
  data: {
113
114
  order: {
114
- order_id: 'order-123',
115
- ticker: 'TEST-MARKET',
116
- status: 'resting',
115
+ order_id: "order-123",
116
+ ticker: "TEST-MARKET",
117
+ status: "resting",
117
118
  count: 10,
118
119
  remaining_count: 10,
119
- created_time: '2026-01-13T12:00:00Z',
120
- queue_position: 1
121
- }
122
- }
120
+ created_time: "2026-01-13T12:00:00Z",
121
+ queue_position: 1,
122
+ },
123
+ },
123
124
  };
124
125
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
125
126
  const order = await exchange.createOrder(orderParams);
126
127
  expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
127
- method: 'POST',
128
- url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
128
+ method: "POST",
129
+ url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
129
130
  data: expect.objectContaining({
130
- ticker: 'TEST-MARKET',
131
- side: 'yes',
132
- action: 'buy',
131
+ ticker: "TEST-MARKET",
132
+ side: "yes",
133
+ action: "buy",
133
134
  count: 10,
134
- type: 'limit',
135
- yes_price: 55 // 0.55 * 100
135
+ type: "limit",
136
+ yes_price: 55, // 0.55 * 100
136
137
  }),
137
138
  }));
138
- expect(order.id).toBe('order-123');
139
- expect(order.status).toBe('open');
139
+ expect(order.id).toBe("order-123");
140
+ expect(order.status).toBe("open");
140
141
  });
141
- it('should create sell order with no_price for sell side', async () => {
142
+ it("should create sell order with no_price for sell side", async () => {
142
143
  const orderParams = {
143
- marketId: 'TEST-MARKET',
144
- outcomeId: 'no',
145
- side: 'sell',
146
- type: 'limit',
144
+ marketId: "TEST-MARKET",
145
+ outcomeId: "no",
146
+ side: "sell",
147
+ type: "limit",
147
148
  amount: 5,
148
- price: 0.45
149
+ price: 0.45,
149
150
  };
150
151
  const mockResponse = {
151
152
  data: {
152
153
  order: {
153
- order_id: 'order-456',
154
- ticker: 'TEST-MARKET',
155
- status: 'resting',
154
+ order_id: "order-456",
155
+ ticker: "TEST-MARKET",
156
+ status: "resting",
156
157
  count: 5,
157
158
  remaining_count: 5,
158
- created_time: '2026-01-13T12:00:00Z',
159
- queue_position: 1
160
- }
161
- }
159
+ created_time: "2026-01-13T12:00:00Z",
160
+ queue_position: 1,
161
+ },
162
+ },
162
163
  };
163
164
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
164
165
  await exchange.createOrder(orderParams);
165
166
  expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
166
- method: 'POST',
167
- url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
167
+ method: "POST",
168
+ url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
168
169
  data: expect.objectContaining({
169
- ticker: 'TEST-MARKET',
170
- side: 'no',
171
- action: 'sell',
170
+ ticker: "TEST-MARKET",
171
+ side: "no",
172
+ action: "sell",
172
173
  count: 5,
173
- type: 'limit',
174
- no_price: 45 // 0.45 * 100
174
+ type: "limit",
175
+ no_price: 45, // 0.45 * 100
175
176
  }),
176
177
  }));
177
178
  });
178
179
  });
179
- describe('fetchOpenOrders', () => {
180
- it('should sign request without query parameters', async () => {
180
+ describe("fetchOpenOrders", () => {
181
+ it("should sign request without query parameters", async () => {
181
182
  const mockResponse = {
182
183
  data: {
183
184
  orders: [
184
185
  {
185
- order_id: 'order-123',
186
- ticker: 'TEST-MARKET',
187
- side: 'yes',
188
- type: 'limit',
186
+ order_id: "order-123",
187
+ ticker: "TEST-MARKET",
188
+ side: "yes",
189
+ type: "limit",
189
190
  yes_price: 55,
190
191
  count: 10,
191
192
  remaining_count: 10,
192
- created_time: '2026-01-13T12:00:00Z'
193
- }
194
- ]
195
- }
193
+ created_time: "2026-01-13T12:00:00Z",
194
+ },
195
+ ],
196
+ },
196
197
  };
197
198
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
198
199
  await exchange.fetchOpenOrders();
199
200
  // Verify the request includes the correct params
200
201
  expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
201
- method: 'GET',
202
- url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
203
- params: expect.objectContaining({ status: 'resting' }),
202
+ method: "GET",
203
+ url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
204
+ params: expect.objectContaining({ status: "resting" }),
204
205
  }));
205
206
  // Verify getHeaders was called with base path only (no query params)
206
- expect(MockedKalshiAuth.prototype.getHeaders).toHaveBeenCalledWith('GET', '/trade-api/v2/portfolio/orders');
207
+ expect(MockedKalshiAuth.prototype.getHeaders).toHaveBeenCalledWith("GET", (0, config_1.getApiPath)("/portfolio/orders"));
207
208
  });
208
- it('should include ticker in query params when marketId provided', async () => {
209
+ it("should include ticker in query params when marketId provided", async () => {
209
210
  const mockResponse = { data: { orders: [] } };
210
211
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
211
- await exchange.fetchOpenOrders('TEST-MARKET');
212
+ await exchange.fetchOpenOrders("TEST-MARKET");
212
213
  expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
213
- method: 'GET',
214
- url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
215
- params: expect.objectContaining({ status: 'resting', ticker: 'TEST-MARKET' }),
214
+ method: "GET",
215
+ url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
216
+ params: expect.objectContaining({
217
+ status: "resting",
218
+ ticker: "TEST-MARKET",
219
+ }),
216
220
  }));
217
221
  });
218
222
  });
219
- describe('fetchPositions', () => {
220
- it('should handle positions with zero contracts', async () => {
223
+ describe("fetchPositions", () => {
224
+ it("should handle positions with zero contracts", async () => {
221
225
  const mockResponse = {
222
226
  data: {
223
227
  market_positions: [
224
228
  {
225
- ticker: 'TEST-MARKET',
229
+ ticker: "TEST-MARKET",
226
230
  position: 0,
227
231
  total_cost: 0,
228
232
  market_exposure: 0,
229
- realized_pnl: 0
230
- }
231
- ]
232
- }
233
+ realized_pnl: 0,
234
+ },
235
+ ],
236
+ },
233
237
  };
234
238
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
235
239
  const positions = await exchange.fetchPositions();
@@ -237,41 +241,41 @@ describe('KalshiExchange', () => {
237
241
  expect(positions[0].size).toBe(0);
238
242
  expect(positions[0].entryPrice).toBe(0); // Should not throw division by zero
239
243
  });
240
- it('should correctly calculate average price and PnL', async () => {
244
+ it("should correctly calculate average price and PnL", async () => {
241
245
  const mockResponse = {
242
246
  data: {
243
247
  market_positions: [
244
248
  {
245
- ticker: 'TEST-MARKET',
249
+ ticker: "TEST-MARKET",
246
250
  position: 10,
247
251
  total_cost: 550, // 10 contracts at $0.55 each = $5.50 = 550 cents
248
252
  market_exposure: 100, // $1.00 unrealized PnL
249
- realized_pnl: 50 // $0.50 realized PnL
250
- }
251
- ]
252
- }
253
+ realized_pnl: 50, // $0.50 realized PnL
254
+ },
255
+ ],
256
+ },
253
257
  };
254
258
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
255
259
  const positions = await exchange.fetchPositions();
256
260
  expect(positions).toHaveLength(1);
257
261
  expect(positions[0].size).toBe(10);
258
262
  expect(positions[0].entryPrice).toBe(0.55); // 550 / 10 / 100
259
- expect(positions[0].unrealizedPnL).toBe(1.00); // 100 / 100
260
- expect(positions[0].realizedPnL).toBe(0.50); // 50 / 100
263
+ expect(positions[0].unrealizedPnL).toBe(1.0); // 100 / 100
264
+ expect(positions[0].realizedPnL).toBe(0.5); // 50 / 100
261
265
  });
262
- it('should handle short positions', async () => {
266
+ it("should handle short positions", async () => {
263
267
  const mockResponse = {
264
268
  data: {
265
269
  market_positions: [
266
270
  {
267
- ticker: 'TEST-MARKET',
271
+ ticker: "TEST-MARKET",
268
272
  position: -5, // Short position
269
273
  total_cost: 250,
270
274
  market_exposure: -50,
271
- realized_pnl: 25
272
- }
273
- ]
274
- }
275
+ realized_pnl: 25,
276
+ },
277
+ ],
278
+ },
275
279
  };
276
280
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
277
281
  const positions = await exchange.fetchPositions();
@@ -279,80 +283,359 @@ describe('KalshiExchange', () => {
279
283
  expect(Math.abs(positions[0].size)).toBe(5); // Absolute value
280
284
  });
281
285
  });
282
- describe('fetchBalance', () => {
283
- it('should correctly convert cents to dollars', async () => {
286
+ describe("fetchBalance", () => {
287
+ it("should correctly convert cents to dollars", async () => {
284
288
  const mockResponse = {
285
289
  data: {
286
290
  balance: 10000, // $100.00 available
287
- portfolio_value: 15000 // $150.00 total
288
- }
291
+ portfolio_value: 15000, // $150.00 total
292
+ },
289
293
  };
290
294
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
291
295
  const balances = await exchange.fetchBalance();
292
296
  expect(balances).toHaveLength(1);
293
- expect(balances[0].currency).toBe('USD');
294
- expect(balances[0].available).toBe(100.00);
295
- expect(balances[0].total).toBe(150.00);
296
- expect(balances[0].locked).toBe(50.00); // 150 - 100
297
+ expect(balances[0].currency).toBe("USD");
298
+ expect(balances[0].available).toBe(100.0);
299
+ expect(balances[0].total).toBe(150.0);
300
+ expect(balances[0].locked).toBe(50.0); // 150 - 100
297
301
  });
298
302
  });
299
- describe('cancelOrder', () => {
300
- it('should cancel order successfully', async () => {
303
+ describe("cancelOrder", () => {
304
+ it("should cancel order successfully", async () => {
301
305
  const mockResponse = {
302
306
  data: {
303
307
  order: {
304
- order_id: 'order-123',
305
- ticker: 'TEST-MARKET',
306
- side: 'yes',
308
+ order_id: "order-123",
309
+ ticker: "TEST-MARKET",
310
+ side: "yes",
307
311
  count: 10,
308
312
  remaining_count: 5,
309
- created_time: '2026-01-13T12:00:00Z'
310
- }
311
- }
313
+ created_time: "2026-01-13T12:00:00Z",
314
+ },
315
+ },
312
316
  };
313
317
  mockAxiosInstance.request.mockResolvedValue(mockResponse);
314
- const order = await exchange.cancelOrder('order-123');
315
- expect(order.status).toBe('cancelled');
318
+ const order = await exchange.cancelOrder("order-123");
319
+ expect(order.status).toBe("cancelled");
316
320
  expect(order.filled).toBe(5); // count - remaining_count
317
321
  expect(order.remaining).toBe(0);
318
322
  });
319
323
  });
320
- });
321
- describe('Order Status Mapping', () => {
322
- beforeEach(() => {
323
- exchange = new kalshi_1.KalshiExchange(mockCredentials);
324
+ describe("Trading History Methods", () => {
325
+ it("should map GetFills response to UserTrade array", async () => {
326
+ const mockResponse = {
327
+ data: {
328
+ fills: [
329
+ {
330
+ fill_id: "fill-abc",
331
+ order_id: "order-123",
332
+ created_time: "2026-01-13T12:00:00Z",
333
+ yes_price: 55,
334
+ count: 10,
335
+ side: "yes",
336
+ },
337
+ {
338
+ fill_id: "fill-def",
339
+ order_id: "order-456",
340
+ created_time: "2026-01-13T13:00:00Z",
341
+ yes_price: 45,
342
+ count: 5,
343
+ side: "no",
344
+ },
345
+ ],
346
+ },
347
+ };
348
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
349
+ const trades = await exchange.fetchMyTrades();
350
+ expect(Array.isArray(trades)).toBe(true);
351
+ expect(trades).toHaveLength(2);
352
+ expect(trades[0].id).toBe("fill-abc");
353
+ expect(trades[0].orderId).toBe("order-123");
354
+ expect(trades[0].price).toBe(0.55); // 55 / 100
355
+ expect(trades[0].amount).toBe(10);
356
+ expect(trades[0].side).toBe("buy"); // 'yes' => 'buy'
357
+ expect(trades[1].id).toBe("fill-def");
358
+ expect(trades[1].side).toBe("sell"); // 'no' => 'sell'
359
+ expect(trades[1].price).toBe(0.45); // 45 / 100
360
+ });
361
+ it("should pass outcomeId as ticker (stripping -NO suffix) and date params", async () => {
362
+ const mockResponse = { data: { fills: [] } };
363
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
364
+ await exchange.fetchMyTrades({
365
+ outcomeId: "TEST-MARKET-NO",
366
+ since: new Date("2026-01-01T00:00:00Z"),
367
+ until: new Date("2026-01-31T00:00:00Z"),
368
+ limit: 50,
369
+ });
370
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
371
+ params: expect.objectContaining({
372
+ ticker: "TEST-MARKET", // -NO stripped
373
+ min_ts: Math.floor(new Date("2026-01-01T00:00:00Z").getTime() / 1000),
374
+ max_ts: Math.floor(new Date("2026-01-31T00:00:00Z").getTime() / 1000),
375
+ limit: 50,
376
+ }),
377
+ }));
378
+ });
379
+ it("should return empty array when fills is missing", async () => {
380
+ mockAxiosInstance.request.mockResolvedValue({ data: {} });
381
+ const trades = await exchange.fetchMyTrades();
382
+ expect(trades).toHaveLength(0);
383
+ });
384
+ describe("fetchClosedOrders", () => {
385
+ it("should map GetHistoricalOrders response to Order array", async () => {
386
+ const mockResponse = {
387
+ data: {
388
+ orders: [
389
+ {
390
+ order_id: "hist-order-1",
391
+ ticker: "TEST-MARKET",
392
+ side: "yes",
393
+ type: "limit",
394
+ yes_price: 60,
395
+ count: 8,
396
+ remaining_count: 0,
397
+ status: "executed",
398
+ created_time: "2026-01-10T10:00:00Z",
399
+ },
400
+ ],
401
+ },
402
+ };
403
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
404
+ const orders = await exchange.fetchClosedOrders();
405
+ expect(orders).toHaveLength(1);
406
+ expect(orders[0].id).toBe("hist-order-1");
407
+ expect(orders[0].marketId).toBe("TEST-MARKET");
408
+ expect(orders[0].side).toBe("buy"); // 'yes' => 'buy'
409
+ expect(orders[0].price).toBe(0.6); // 60 / 100
410
+ expect(orders[0].amount).toBe(8);
411
+ expect(orders[0].filled).toBe(8); // count - remaining_count (0)
412
+ expect(orders[0].remaining).toBe(0);
413
+ expect(orders[0].status).toBe("filled"); // 'executed' => 'filled'
414
+ });
415
+ it("should pass marketId as ticker and limit", async () => {
416
+ const mockResponse = { data: { orders: [] } };
417
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
418
+ await exchange.fetchClosedOrders({
419
+ marketId: "TEST-MARKET",
420
+ limit: 25,
421
+ });
422
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
423
+ params: expect.objectContaining({
424
+ ticker: "TEST-MARKET",
425
+ limit: 25,
426
+ }),
427
+ }));
428
+ });
429
+ });
430
+ describe("fetchAllOrders", () => {
431
+ it("should merge live and historical orders, dedup, and sort descending by timestamp", async () => {
432
+ const liveResponse = {
433
+ data: {
434
+ orders: [
435
+ {
436
+ order_id: "order-live-1",
437
+ ticker: "TEST",
438
+ side: "yes",
439
+ type: "limit",
440
+ yes_price: 50,
441
+ count: 5,
442
+ remaining_count: 5,
443
+ status: "resting",
444
+ created_time: "2026-01-15T10:00:00Z",
445
+ },
446
+ {
447
+ // duplicate that also appears in historical
448
+ order_id: "order-hist-1",
449
+ ticker: "TEST",
450
+ side: "no",
451
+ type: "limit",
452
+ yes_price: 40,
453
+ count: 3,
454
+ remaining_count: 0,
455
+ status: "executed",
456
+ created_time: "2026-01-10T08:00:00Z",
457
+ },
458
+ ],
459
+ },
460
+ };
461
+ const historicalResponse = {
462
+ data: {
463
+ orders: [
464
+ {
465
+ order_id: "order-hist-1", // duplicate
466
+ ticker: "TEST",
467
+ side: "no",
468
+ type: "limit",
469
+ yes_price: 40,
470
+ count: 3,
471
+ remaining_count: 0,
472
+ status: "executed",
473
+ created_time: "2026-01-10T08:00:00Z",
474
+ },
475
+ {
476
+ order_id: "order-hist-2",
477
+ ticker: "TEST",
478
+ side: "yes",
479
+ type: "limit",
480
+ yes_price: 55,
481
+ count: 10,
482
+ remaining_count: 0,
483
+ status: "executed",
484
+ created_time: "2026-01-05T06:00:00Z",
485
+ },
486
+ ],
487
+ },
488
+ };
489
+ mockAxiosInstance.request
490
+ .mockResolvedValueOnce(liveResponse)
491
+ .mockResolvedValueOnce(historicalResponse);
492
+ const orders = await exchange.fetchAllOrders();
493
+ // 3 unique orders (order-hist-1 deduped)
494
+ expect(orders).toHaveLength(3);
495
+ // sorted descending: order-live-1, order-hist-1, order-hist-2
496
+ expect(orders[0].id).toBe("order-live-1");
497
+ expect(orders[1].id).toBe("order-hist-1");
498
+ expect(orders[2].id).toBe("order-hist-2");
499
+ });
500
+ });
324
501
  });
325
- it('should map resting to open', async () => {
326
- const mockResponse = {
327
- data: {
328
- order: {
329
- order_id: 'order-123',
330
- ticker: 'TEST',
331
- status: 'resting',
332
- count: 10,
333
- created_time: '2026-01-13T12:00:00Z'
334
- }
335
- }
336
- };
337
- mockAxiosInstance.request.mockResolvedValue(mockResponse);
338
- const order = await exchange.fetchOrder('order-123');
339
- expect(order.status).toBe('open');
502
+ describe("buildOrder", () => {
503
+ it("should return correct raw body for a buy limit order without making HTTP call", async () => {
504
+ const params = {
505
+ marketId: "TEST-MARKET",
506
+ outcomeId: "yes",
507
+ side: "buy",
508
+ type: "limit",
509
+ amount: 10,
510
+ price: 0.55,
511
+ };
512
+ const built = await exchange.buildOrder(params);
513
+ expect(mockAxiosInstance.request).not.toHaveBeenCalled();
514
+ expect(built.exchange).toBe("Kalshi");
515
+ expect(built.params).toBe(params);
516
+ expect(built.raw).toEqual(expect.objectContaining({
517
+ ticker: "TEST-MARKET",
518
+ side: "yes",
519
+ action: "buy",
520
+ count: 10,
521
+ type: "limit",
522
+ yes_price: 55,
523
+ }));
524
+ });
525
+ it("should return correct raw body for a sell limit order", async () => {
526
+ const params = {
527
+ marketId: "TEST-MARKET",
528
+ outcomeId: "no",
529
+ side: "sell",
530
+ type: "limit",
531
+ amount: 5,
532
+ price: 0.45,
533
+ };
534
+ const built = await exchange.buildOrder(params);
535
+ expect(mockAxiosInstance.request).not.toHaveBeenCalled();
536
+ expect(built.exchange).toBe("Kalshi");
537
+ expect(built.raw).toEqual(expect.objectContaining({
538
+ ticker: "TEST-MARKET",
539
+ side: "no",
540
+ action: "sell",
541
+ count: 5,
542
+ type: "limit",
543
+ no_price: 45,
544
+ }));
545
+ });
546
+ it("should mirror input params in built.params", async () => {
547
+ const params = {
548
+ marketId: "SOME-MARKET",
549
+ outcomeId: "yes",
550
+ side: "buy",
551
+ type: "market",
552
+ amount: 3,
553
+ };
554
+ const built = await exchange.buildOrder(params);
555
+ expect(built.params).toBe(params);
556
+ });
340
557
  });
341
- it('should map executed to filled', async () => {
342
- const mockResponse = {
343
- data: {
344
- order: {
345
- order_id: 'order-123',
346
- ticker: 'TEST',
347
- status: 'executed',
558
+ describe("submitOrder", () => {
559
+ it("should POST built.raw to CreateOrder and return mapped order", async () => {
560
+ const params = {
561
+ marketId: "TEST-MARKET",
562
+ outcomeId: "yes",
563
+ side: "buy",
564
+ type: "limit",
565
+ amount: 10,
566
+ price: 0.55,
567
+ };
568
+ const built = await exchange.buildOrder(params);
569
+ const mockResponse = {
570
+ data: {
571
+ order: {
572
+ order_id: "order-789",
573
+ ticker: "TEST-MARKET",
574
+ side: "yes",
575
+ type: "limit",
576
+ yes_price: 55,
577
+ count: 10,
578
+ remaining_count: 10,
579
+ status: "resting",
580
+ created_time: "2026-01-13T12:00:00Z",
581
+ },
582
+ },
583
+ };
584
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
585
+ const order = await exchange.submitOrder(built);
586
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
587
+ method: "POST",
588
+ url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
589
+ data: expect.objectContaining({
590
+ ticker: "TEST-MARKET",
591
+ side: "yes",
592
+ action: "buy",
348
593
  count: 10,
349
- created_time: '2026-01-13T12:00:00Z'
350
- }
351
- }
352
- };
353
- mockAxiosInstance.request.mockResolvedValue(mockResponse);
354
- const order = await exchange.fetchOrder('order-123');
355
- expect(order.status).toBe('filled');
594
+ type: "limit",
595
+ yes_price: 55,
596
+ }),
597
+ }));
598
+ expect(order.id).toBe("order-789");
599
+ expect(order.status).toBe("open");
600
+ expect(order.price).toBe(0.55);
601
+ });
602
+ });
603
+ describe("Order Status Mapping", () => {
604
+ beforeEach(() => {
605
+ exchange = new kalshi_1.KalshiExchange(mockCredentials);
606
+ });
607
+ it("should map resting to open", async () => {
608
+ const mockResponse = {
609
+ data: {
610
+ order: {
611
+ order_id: "order-123",
612
+ ticker: "TEST",
613
+ status: "resting",
614
+ count: 10,
615
+ created_time: "2026-01-13T12:00:00Z",
616
+ },
617
+ },
618
+ };
619
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
620
+ const order = await exchange.fetchOrder("order-123");
621
+ expect(order.status).toBe("open");
622
+ });
623
+ it("should map executed to filled", async () => {
624
+ const mockResponse = {
625
+ data: {
626
+ order: {
627
+ order_id: "order-123",
628
+ ticker: "TEST",
629
+ status: "executed",
630
+ count: 10,
631
+ created_time: "2026-01-13T12:00:00Z",
632
+ },
633
+ },
634
+ };
635
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
636
+ const order = await exchange.fetchOrder("order-123");
637
+ expect(order.status).toBe("filled");
638
+ });
356
639
  });
357
640
  });
358
641
  });