pmxt-core 2.39.1 → 2.40.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.
- package/dist/exchanges/baozi/fetcher.js +28 -8
- package/dist/exchanges/baozi/index.js +6 -4
- package/dist/exchanges/gemini-titan/auth.d.ts +34 -0
- package/dist/exchanges/gemini-titan/auth.js +80 -0
- package/dist/exchanges/gemini-titan/config.d.ts +15 -0
- package/dist/exchanges/gemini-titan/config.js +24 -0
- package/dist/exchanges/gemini-titan/errors.d.ts +20 -0
- package/dist/exchanges/gemini-titan/errors.js +75 -0
- package/dist/exchanges/gemini-titan/fetcher.d.ts +26 -0
- package/dist/exchanges/gemini-titan/fetcher.js +148 -0
- package/dist/exchanges/gemini-titan/index.d.ts +31 -0
- package/dist/exchanges/gemini-titan/index.js +188 -0
- package/dist/exchanges/gemini-titan/normalizer.d.ts +13 -0
- package/dist/exchanges/gemini-titan/normalizer.js +229 -0
- package/dist/exchanges/gemini-titan/types.d.ts +220 -0
- package/dist/exchanges/gemini-titan/types.js +6 -0
- package/dist/exchanges/gemini-titan/utils.d.ts +30 -0
- package/dist/exchanges/gemini-titan/utils.js +57 -0
- package/dist/exchanges/gemini-titan/websocket.d.ts +46 -0
- package/dist/exchanges/gemini-titan/websocket.js +295 -0
- package/dist/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/kalshi/fetcher.js +6 -2
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/limitless/index.js +3 -6
- package/dist/exchanges/limitless/utils.js +9 -1
- package/dist/exchanges/metaculus/fetchEvents.js +7 -2
- package/dist/exchanges/mock/index.d.ts +55 -0
- package/dist/exchanges/mock/index.js +603 -0
- package/dist/exchanges/mock/seededRng.d.ts +10 -0
- package/dist/exchanges/mock/seededRng.js +48 -0
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/myriad/websocket.d.ts +4 -0
- package/dist/exchanges/myriad/websocket.js +51 -6
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket/auth.js +5 -2
- package/dist/exchanges/polymarket/index.js +2 -1
- package/dist/exchanges/polymarket_us/normalizer.js +5 -1
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/probable/index.js +9 -6
- package/dist/exchanges/smarkets/fetcher.js +6 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -1
- package/dist/router/Router.js +55 -21
- package/dist/server/exchange-factory.js +9 -0
- package/dist/server/openapi.yaml +22 -0
- package/dist/server/ws-handler.js +13 -3
- package/package.json +3 -3
- package/dist/exchanges/baozi/price.test.d.ts +0 -1
- package/dist/exchanges/baozi/price.test.js +0 -33
- package/dist/exchanges/kalshi/kalshi.test.d.ts +0 -1
- package/dist/exchanges/kalshi/kalshi.test.js +0 -641
- package/dist/exchanges/kalshi/price.test.d.ts +0 -1
- package/dist/exchanges/kalshi/price.test.js +0 -24
- package/dist/exchanges/myriad/price.test.d.ts +0 -1
- package/dist/exchanges/myriad/price.test.js +0 -17
- package/dist/exchanges/polymarket_us/errors.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/errors.test.js +0 -54
- package/dist/exchanges/polymarket_us/index.test.d.ts +0 -8
- package/dist/exchanges/polymarket_us/index.test.js +0 -237
- package/dist/exchanges/polymarket_us/normalizer.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/normalizer.test.js +0 -224
- package/dist/exchanges/polymarket_us/price.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/price.test.js +0 -131
- package/dist/exchanges/polymarket_us/websocket.test.d.ts +0 -8
- package/dist/exchanges/polymarket_us/websocket.test.js +0 -162
- package/dist/exchanges/smarkets/price.test.d.ts +0 -1
- package/dist/exchanges/smarkets/price.test.js +0 -50
- package/dist/router/Router.test.d.ts +0 -1
- package/dist/router/Router.test.js +0 -328
- package/dist/router/client.test.d.ts +0 -1
- package/dist/router/client.test.js +0 -177
|
@@ -1,641 +0,0 @@
|
|
|
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
|
-
const kalshi_1 = require("../kalshi");
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
const auth_1 = require("./auth");
|
|
9
|
-
const config_1 = require("./config");
|
|
10
|
-
// Jest hoisting means we can't use outer variables in jest.mock factory
|
|
11
|
-
// unless they start with 'mock'. However, let's just define it inline to be safe and simple.
|
|
12
|
-
// To access the inner methods, we'll grab the instance returned by axios.create().
|
|
13
|
-
jest.mock("axios", () => {
|
|
14
|
-
const mockInstance = {
|
|
15
|
-
interceptors: {
|
|
16
|
-
request: { use: jest.fn() },
|
|
17
|
-
response: { use: jest.fn() },
|
|
18
|
-
},
|
|
19
|
-
get: jest.fn(),
|
|
20
|
-
post: jest.fn(),
|
|
21
|
-
delete: jest.fn(),
|
|
22
|
-
request: jest.fn(),
|
|
23
|
-
defaults: { headers: { common: {} } },
|
|
24
|
-
};
|
|
25
|
-
const actualAxios = jest.requireActual("axios");
|
|
26
|
-
const mockAxios = {
|
|
27
|
-
create: jest.fn(() => mockInstance),
|
|
28
|
-
isAxiosError: actualAxios.isAxiosError,
|
|
29
|
-
};
|
|
30
|
-
// Support both default and named exports
|
|
31
|
-
return {
|
|
32
|
-
__esModule: true,
|
|
33
|
-
...mockAxios,
|
|
34
|
-
default: mockAxios,
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
// Access the mocked instance for assertions
|
|
38
|
-
// Since our factory returns the same object reference, this works.
|
|
39
|
-
const mockAxiosInstance = axios_1.default.create();
|
|
40
|
-
const mockedAxios = axios_1.default;
|
|
41
|
-
// Mock KalshiAuth
|
|
42
|
-
jest.mock("./auth");
|
|
43
|
-
const MockedKalshiAuth = auth_1.KalshiAuth;
|
|
44
|
-
describe("KalshiExchange", () => {
|
|
45
|
-
let exchange;
|
|
46
|
-
const mockCredentials = {
|
|
47
|
-
apiKey: "test-api-key",
|
|
48
|
-
privateKey: "mock-private-key",
|
|
49
|
-
};
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
jest.clearAllMocks();
|
|
52
|
-
// Reset the mock instance methods to ensure clean state
|
|
53
|
-
mockAxiosInstance.get.mockReset();
|
|
54
|
-
mockAxiosInstance.post.mockReset();
|
|
55
|
-
mockAxiosInstance.delete.mockReset();
|
|
56
|
-
mockAxiosInstance.request.mockReset();
|
|
57
|
-
// Mock the getHeaders method
|
|
58
|
-
MockedKalshiAuth.prototype.getHeaders = jest.fn().mockReturnValue({
|
|
59
|
-
"KALSHI-ACCESS-KEY": "test-api-key",
|
|
60
|
-
"KALSHI-ACCESS-TIMESTAMP": "1234567890",
|
|
61
|
-
"KALSHI-ACCESS-SIGNATURE": "mock-signature",
|
|
62
|
-
"Content-Type": "application/json",
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
describe("Authentication", () => {
|
|
66
|
-
it("should throw error when trading without credentials", async () => {
|
|
67
|
-
exchange = new kalshi_1.KalshiExchange();
|
|
68
|
-
await expect(exchange.fetchBalance()).rejects.toThrow("Trading operations require authentication");
|
|
69
|
-
});
|
|
70
|
-
it("should initialize with credentials", () => {
|
|
71
|
-
exchange = new kalshi_1.KalshiExchange(mockCredentials);
|
|
72
|
-
expect(exchange).toBeDefined();
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
describe("Market Data Methods", () => {
|
|
76
|
-
beforeEach(() => {
|
|
77
|
-
exchange = new kalshi_1.KalshiExchange();
|
|
78
|
-
});
|
|
79
|
-
it("should fetch markets", async () => {
|
|
80
|
-
const mockResponse = {
|
|
81
|
-
data: {
|
|
82
|
-
markets: [
|
|
83
|
-
{
|
|
84
|
-
ticker: "TEST-MARKET",
|
|
85
|
-
title: "Test Market",
|
|
86
|
-
yes_bid: 50,
|
|
87
|
-
yes_ask: 52,
|
|
88
|
-
volume: 1000,
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
94
|
-
const markets = await exchange.fetchMarkets();
|
|
95
|
-
expect(markets).toBeDefined();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
describe("Trading Methods", () => {
|
|
99
|
-
beforeEach(() => {
|
|
100
|
-
exchange = new kalshi_1.KalshiExchange(mockCredentials);
|
|
101
|
-
});
|
|
102
|
-
describe("createOrder", () => {
|
|
103
|
-
it("should create buy order with yes_price for buy side", async () => {
|
|
104
|
-
const orderParams = {
|
|
105
|
-
marketId: "TEST-MARKET",
|
|
106
|
-
outcomeId: "yes",
|
|
107
|
-
side: "buy",
|
|
108
|
-
type: "limit",
|
|
109
|
-
amount: 10,
|
|
110
|
-
price: 0.55,
|
|
111
|
-
};
|
|
112
|
-
const mockResponse = {
|
|
113
|
-
data: {
|
|
114
|
-
order: {
|
|
115
|
-
order_id: "order-123",
|
|
116
|
-
ticker: "TEST-MARKET",
|
|
117
|
-
status: "resting",
|
|
118
|
-
count: 10,
|
|
119
|
-
remaining_count: 10,
|
|
120
|
-
created_time: "2026-01-13T12:00:00Z",
|
|
121
|
-
queue_position: 1,
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
126
|
-
const order = await exchange.createOrder(orderParams);
|
|
127
|
-
expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
|
|
128
|
-
method: "POST",
|
|
129
|
-
url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
|
|
130
|
-
data: expect.objectContaining({
|
|
131
|
-
ticker: "TEST-MARKET",
|
|
132
|
-
side: "yes",
|
|
133
|
-
action: "buy",
|
|
134
|
-
count: 10,
|
|
135
|
-
type: "limit",
|
|
136
|
-
yes_price: 55, // 0.55 * 100
|
|
137
|
-
}),
|
|
138
|
-
}));
|
|
139
|
-
expect(order.id).toBe("order-123");
|
|
140
|
-
expect(order.status).toBe("open");
|
|
141
|
-
});
|
|
142
|
-
it("should create sell order with no_price for sell side", async () => {
|
|
143
|
-
const orderParams = {
|
|
144
|
-
marketId: "TEST-MARKET",
|
|
145
|
-
outcomeId: "no",
|
|
146
|
-
side: "sell",
|
|
147
|
-
type: "limit",
|
|
148
|
-
amount: 5,
|
|
149
|
-
price: 0.45,
|
|
150
|
-
};
|
|
151
|
-
const mockResponse = {
|
|
152
|
-
data: {
|
|
153
|
-
order: {
|
|
154
|
-
order_id: "order-456",
|
|
155
|
-
ticker: "TEST-MARKET",
|
|
156
|
-
status: "resting",
|
|
157
|
-
count: 5,
|
|
158
|
-
remaining_count: 5,
|
|
159
|
-
created_time: "2026-01-13T12:00:00Z",
|
|
160
|
-
queue_position: 1,
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
165
|
-
await exchange.createOrder(orderParams);
|
|
166
|
-
expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
|
|
167
|
-
method: "POST",
|
|
168
|
-
url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
|
|
169
|
-
data: expect.objectContaining({
|
|
170
|
-
ticker: "TEST-MARKET",
|
|
171
|
-
side: "no",
|
|
172
|
-
action: "sell",
|
|
173
|
-
count: 5,
|
|
174
|
-
type: "limit",
|
|
175
|
-
no_price: 45, // 0.45 * 100
|
|
176
|
-
}),
|
|
177
|
-
}));
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
describe("fetchOpenOrders", () => {
|
|
181
|
-
it("should sign request without query parameters", async () => {
|
|
182
|
-
const mockResponse = {
|
|
183
|
-
data: {
|
|
184
|
-
orders: [
|
|
185
|
-
{
|
|
186
|
-
order_id: "order-123",
|
|
187
|
-
ticker: "TEST-MARKET",
|
|
188
|
-
side: "yes",
|
|
189
|
-
type: "limit",
|
|
190
|
-
yes_price: 55,
|
|
191
|
-
count: 10,
|
|
192
|
-
remaining_count: 10,
|
|
193
|
-
created_time: "2026-01-13T12:00:00Z",
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
199
|
-
await exchange.fetchOpenOrders();
|
|
200
|
-
// Verify the request includes the correct params
|
|
201
|
-
expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
|
|
202
|
-
method: "GET",
|
|
203
|
-
url: "https://api.elections.kalshi.com/trade-api/v2/portfolio/orders",
|
|
204
|
-
params: expect.objectContaining({ status: "resting" }),
|
|
205
|
-
}));
|
|
206
|
-
// Verify getHeaders was called with base path only (no query params)
|
|
207
|
-
expect(MockedKalshiAuth.prototype.getHeaders).toHaveBeenCalledWith("GET", (0, config_1.getApiPath)("/portfolio/orders"));
|
|
208
|
-
});
|
|
209
|
-
it("should include ticker in query params when marketId provided", async () => {
|
|
210
|
-
const mockResponse = { data: { orders: [] } };
|
|
211
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
212
|
-
await exchange.fetchOpenOrders("TEST-MARKET");
|
|
213
|
-
expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
|
|
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
|
-
}),
|
|
220
|
-
}));
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
describe("fetchPositions", () => {
|
|
224
|
-
it("should handle positions with zero contracts", async () => {
|
|
225
|
-
const mockResponse = {
|
|
226
|
-
data: {
|
|
227
|
-
market_positions: [
|
|
228
|
-
{
|
|
229
|
-
ticker: "TEST-MARKET",
|
|
230
|
-
position: 0,
|
|
231
|
-
total_cost: 0,
|
|
232
|
-
market_exposure: 0,
|
|
233
|
-
realized_pnl: 0,
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
239
|
-
const positions = await exchange.fetchPositions();
|
|
240
|
-
expect(positions).toHaveLength(1);
|
|
241
|
-
expect(positions[0].size).toBe(0);
|
|
242
|
-
expect(positions[0].entryPrice).toBe(0); // Should not throw division by zero
|
|
243
|
-
});
|
|
244
|
-
it("should correctly calculate average price and PnL", async () => {
|
|
245
|
-
const mockResponse = {
|
|
246
|
-
data: {
|
|
247
|
-
market_positions: [
|
|
248
|
-
{
|
|
249
|
-
ticker: "TEST-MARKET",
|
|
250
|
-
position: 10,
|
|
251
|
-
total_cost: 550, // 10 contracts at $0.55 each = $5.50 = 550 cents
|
|
252
|
-
market_exposure: 100, // $1.00 unrealized PnL
|
|
253
|
-
realized_pnl: 50, // $0.50 realized PnL
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
259
|
-
const positions = await exchange.fetchPositions();
|
|
260
|
-
expect(positions).toHaveLength(1);
|
|
261
|
-
expect(positions[0].size).toBe(10);
|
|
262
|
-
expect(positions[0].entryPrice).toBe(0.55); // 550 / 10 / 100
|
|
263
|
-
expect(positions[0].unrealizedPnL).toBe(1.0); // 100 / 100
|
|
264
|
-
expect(positions[0].realizedPnL).toBe(0.5); // 50 / 100
|
|
265
|
-
});
|
|
266
|
-
it("should handle short positions", async () => {
|
|
267
|
-
const mockResponse = {
|
|
268
|
-
data: {
|
|
269
|
-
market_positions: [
|
|
270
|
-
{
|
|
271
|
-
ticker: "TEST-MARKET",
|
|
272
|
-
position: -5, // Short position
|
|
273
|
-
total_cost: 250,
|
|
274
|
-
market_exposure: -50,
|
|
275
|
-
realized_pnl: 25,
|
|
276
|
-
},
|
|
277
|
-
],
|
|
278
|
-
},
|
|
279
|
-
};
|
|
280
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
281
|
-
const positions = await exchange.fetchPositions();
|
|
282
|
-
expect(positions[0].size).toBe(-5); // Negative for short
|
|
283
|
-
expect(Math.abs(positions[0].size)).toBe(5); // Absolute value
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
describe("fetchBalance", () => {
|
|
287
|
-
it("should correctly convert cents to dollars", async () => {
|
|
288
|
-
const mockResponse = {
|
|
289
|
-
data: {
|
|
290
|
-
balance: 10000, // $100.00 available
|
|
291
|
-
portfolio_value: 15000, // $150.00 total
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
295
|
-
const balances = await exchange.fetchBalance();
|
|
296
|
-
expect(balances).toHaveLength(1);
|
|
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
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
describe("cancelOrder", () => {
|
|
304
|
-
it("should cancel order successfully", async () => {
|
|
305
|
-
const mockResponse = {
|
|
306
|
-
data: {
|
|
307
|
-
order: {
|
|
308
|
-
order_id: "order-123",
|
|
309
|
-
ticker: "TEST-MARKET",
|
|
310
|
-
side: "yes",
|
|
311
|
-
count: 10,
|
|
312
|
-
remaining_count: 5,
|
|
313
|
-
created_time: "2026-01-13T12:00:00Z",
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
mockAxiosInstance.request.mockResolvedValue(mockResponse);
|
|
318
|
-
const order = await exchange.cancelOrder("order-123");
|
|
319
|
-
expect(order.status).toBe("cancelled");
|
|
320
|
-
expect(order.filled).toBe(5); // count - remaining_count
|
|
321
|
-
expect(order.remaining).toBe(0);
|
|
322
|
-
});
|
|
323
|
-
});
|
|
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
|
-
});
|
|
501
|
-
});
|
|
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
|
-
});
|
|
557
|
-
});
|
|
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",
|
|
593
|
-
count: 10,
|
|
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
|
-
});
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const price_1 = require("./price");
|
|
4
|
-
describe("fromKalshiCents", () => {
|
|
5
|
-
test("converts cents to a decimal probability", () => {
|
|
6
|
-
expect((0, price_1.fromKalshiCents)(55)).toBe(0.55);
|
|
7
|
-
expect((0, price_1.fromKalshiCents)(0)).toBe(0);
|
|
8
|
-
expect((0, price_1.fromKalshiCents)(100)).toBe(1);
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
describe("invertKalshiCents", () => {
|
|
12
|
-
test("returns the complement of a cent value", () => {
|
|
13
|
-
expect((0, price_1.invertKalshiCents)(45)).toBeCloseTo(0.55);
|
|
14
|
-
expect((0, price_1.invertKalshiCents)(0)).toBe(1);
|
|
15
|
-
expect((0, price_1.invertKalshiCents)(100)).toBe(0);
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
describe("invertKalshiUnified", () => {
|
|
19
|
-
test("returns the complement of a normalized price", () => {
|
|
20
|
-
expect((0, price_1.invertKalshiUnified)(0.45)).toBeCloseTo(0.55);
|
|
21
|
-
expect((0, price_1.invertKalshiUnified)(0)).toBe(1);
|
|
22
|
-
expect((0, price_1.invertKalshiUnified)(1)).toBe(0);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|