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.
- package/API_REFERENCE.md +230 -1
- package/dist/BaseExchange.d.ts +36 -2
- package/dist/BaseExchange.js +43 -1
- package/dist/exchanges/kalshi/auth.d.ts +23 -0
- package/dist/exchanges/kalshi/auth.js +99 -0
- package/dist/exchanges/kalshi/fetchMarkets.d.ts +3 -0
- package/dist/exchanges/kalshi/fetchMarkets.js +110 -0
- package/dist/exchanges/kalshi/fetchOHLCV.d.ts +3 -0
- package/dist/exchanges/kalshi/fetchOHLCV.js +78 -0
- package/dist/exchanges/kalshi/fetchOrderBook.d.ts +2 -0
- package/dist/exchanges/kalshi/fetchOrderBook.js +32 -0
- package/dist/exchanges/kalshi/fetchTrades.d.ts +3 -0
- package/dist/exchanges/kalshi/fetchTrades.js +31 -0
- package/dist/exchanges/kalshi/getMarketsBySlug.d.ts +7 -0
- package/dist/exchanges/kalshi/getMarketsBySlug.js +62 -0
- package/dist/exchanges/kalshi/index.d.ts +21 -0
- package/dist/exchanges/kalshi/index.js +273 -0
- package/dist/exchanges/kalshi/kalshi.test.d.ts +1 -0
- package/dist/exchanges/kalshi/kalshi.test.js +309 -0
- package/dist/exchanges/kalshi/searchMarkets.d.ts +3 -0
- package/dist/exchanges/kalshi/searchMarkets.js +28 -0
- package/dist/exchanges/kalshi/utils.d.ts +5 -0
- package/dist/exchanges/kalshi/utils.js +85 -0
- package/dist/exchanges/polymarket/auth.d.ts +32 -0
- package/dist/exchanges/polymarket/auth.js +98 -0
- package/dist/exchanges/polymarket/fetchMarkets.d.ts +3 -0
- package/dist/exchanges/polymarket/fetchMarkets.js +75 -0
- package/dist/exchanges/polymarket/fetchOHLCV.d.ts +7 -0
- package/dist/exchanges/polymarket/fetchOHLCV.js +73 -0
- package/dist/exchanges/polymarket/fetchOrderBook.d.ts +6 -0
- package/dist/exchanges/polymarket/fetchOrderBook.js +38 -0
- package/dist/exchanges/polymarket/fetchPositions.d.ts +2 -0
- package/dist/exchanges/polymarket/fetchPositions.js +27 -0
- package/dist/exchanges/polymarket/fetchTrades.d.ts +11 -0
- package/dist/exchanges/polymarket/fetchTrades.js +59 -0
- package/dist/exchanges/polymarket/getMarketsBySlug.d.ts +7 -0
- package/dist/exchanges/polymarket/getMarketsBySlug.js +39 -0
- package/dist/exchanges/polymarket/index.d.ts +23 -0
- package/dist/exchanges/polymarket/index.js +216 -0
- package/dist/exchanges/polymarket/searchMarkets.d.ts +3 -0
- package/dist/exchanges/polymarket/searchMarkets.js +35 -0
- package/dist/exchanges/polymarket/utils.d.ts +7 -0
- package/dist/exchanges/polymarket/utils.js +95 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +8 -8
- package/dist/types.d.ts +38 -0
- package/package.json +5 -1
- package/readme.md +37 -3
|
@@ -0,0 +1,309 @@
|
|
|
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
|
+
// Mock axios
|
|
10
|
+
jest.mock('axios');
|
|
11
|
+
const mockedAxios = axios_1.default;
|
|
12
|
+
// Mock KalshiAuth
|
|
13
|
+
jest.mock('./auth');
|
|
14
|
+
const MockedKalshiAuth = auth_1.KalshiAuth;
|
|
15
|
+
describe('KalshiExchange', () => {
|
|
16
|
+
let exchange;
|
|
17
|
+
const mockCredentials = {
|
|
18
|
+
apiKey: 'test-api-key',
|
|
19
|
+
privateKey: 'mock-private-key'
|
|
20
|
+
};
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
// Mock the getHeaders method
|
|
24
|
+
MockedKalshiAuth.prototype.getHeaders = jest.fn().mockReturnValue({
|
|
25
|
+
'KALSHI-ACCESS-KEY': 'test-api-key',
|
|
26
|
+
'KALSHI-ACCESS-TIMESTAMP': '1234567890',
|
|
27
|
+
'KALSHI-ACCESS-SIGNATURE': 'mock-signature',
|
|
28
|
+
'Content-Type': 'application/json'
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('Authentication', () => {
|
|
32
|
+
it('should throw error when trading without credentials', async () => {
|
|
33
|
+
exchange = new kalshi_1.KalshiExchange();
|
|
34
|
+
await expect(exchange.fetchBalance()).rejects.toThrow('Trading operations require authentication');
|
|
35
|
+
});
|
|
36
|
+
it('should initialize with credentials', () => {
|
|
37
|
+
exchange = new kalshi_1.KalshiExchange(mockCredentials);
|
|
38
|
+
expect(exchange).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('Market Data Methods', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
exchange = new kalshi_1.KalshiExchange();
|
|
44
|
+
});
|
|
45
|
+
it('should fetch markets', async () => {
|
|
46
|
+
const mockResponse = {
|
|
47
|
+
data: {
|
|
48
|
+
markets: [
|
|
49
|
+
{
|
|
50
|
+
ticker: 'TEST-MARKET',
|
|
51
|
+
title: 'Test Market',
|
|
52
|
+
yes_bid: 50,
|
|
53
|
+
yes_ask: 52,
|
|
54
|
+
volume: 1000
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
60
|
+
const markets = await exchange.fetchMarkets();
|
|
61
|
+
expect(markets).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('Trading Methods', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
exchange = new kalshi_1.KalshiExchange(mockCredentials);
|
|
67
|
+
});
|
|
68
|
+
describe('createOrder', () => {
|
|
69
|
+
it('should create buy order with yes_price for buy side', async () => {
|
|
70
|
+
const orderParams = {
|
|
71
|
+
marketId: 'TEST-MARKET',
|
|
72
|
+
outcomeId: 'yes',
|
|
73
|
+
side: 'buy',
|
|
74
|
+
type: 'limit',
|
|
75
|
+
amount: 10,
|
|
76
|
+
price: 0.55
|
|
77
|
+
};
|
|
78
|
+
const mockResponse = {
|
|
79
|
+
data: {
|
|
80
|
+
order: {
|
|
81
|
+
order_id: 'order-123',
|
|
82
|
+
ticker: 'TEST-MARKET',
|
|
83
|
+
status: 'resting',
|
|
84
|
+
count: 10,
|
|
85
|
+
remaining_count: 10,
|
|
86
|
+
created_time: '2026-01-13T12:00:00Z',
|
|
87
|
+
queue_position: 1
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
mockedAxios.post.mockResolvedValue(mockResponse);
|
|
92
|
+
const order = await exchange.createOrder(orderParams);
|
|
93
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
|
|
94
|
+
ticker: 'TEST-MARKET',
|
|
95
|
+
side: 'yes',
|
|
96
|
+
action: 'buy',
|
|
97
|
+
count: 10,
|
|
98
|
+
type: 'limit',
|
|
99
|
+
yes_price: 55 // 0.55 * 100
|
|
100
|
+
}), expect.any(Object));
|
|
101
|
+
expect(order.id).toBe('order-123');
|
|
102
|
+
expect(order.status).toBe('open');
|
|
103
|
+
});
|
|
104
|
+
it('should create sell order with no_price for sell side', async () => {
|
|
105
|
+
const orderParams = {
|
|
106
|
+
marketId: 'TEST-MARKET',
|
|
107
|
+
outcomeId: 'no',
|
|
108
|
+
side: 'sell',
|
|
109
|
+
type: 'limit',
|
|
110
|
+
amount: 5,
|
|
111
|
+
price: 0.45
|
|
112
|
+
};
|
|
113
|
+
const mockResponse = {
|
|
114
|
+
data: {
|
|
115
|
+
order: {
|
|
116
|
+
order_id: 'order-456',
|
|
117
|
+
ticker: 'TEST-MARKET',
|
|
118
|
+
status: 'resting',
|
|
119
|
+
count: 5,
|
|
120
|
+
remaining_count: 5,
|
|
121
|
+
created_time: '2026-01-13T12:00:00Z',
|
|
122
|
+
queue_position: 1
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
mockedAxios.post.mockResolvedValue(mockResponse);
|
|
127
|
+
await exchange.createOrder(orderParams);
|
|
128
|
+
expect(mockedAxios.post).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
|
|
129
|
+
ticker: 'TEST-MARKET',
|
|
130
|
+
side: 'no',
|
|
131
|
+
action: 'sell',
|
|
132
|
+
count: 5,
|
|
133
|
+
type: 'limit',
|
|
134
|
+
no_price: 45 // 0.45 * 100
|
|
135
|
+
}), expect.any(Object));
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe('fetchOpenOrders', () => {
|
|
139
|
+
it('should sign request without query parameters', async () => {
|
|
140
|
+
const mockResponse = {
|
|
141
|
+
data: {
|
|
142
|
+
orders: [
|
|
143
|
+
{
|
|
144
|
+
order_id: 'order-123',
|
|
145
|
+
ticker: 'TEST-MARKET',
|
|
146
|
+
side: 'yes',
|
|
147
|
+
type: 'limit',
|
|
148
|
+
yes_price: 55,
|
|
149
|
+
count: 10,
|
|
150
|
+
remaining_count: 10,
|
|
151
|
+
created_time: '2026-01-13T12:00:00Z'
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
157
|
+
await exchange.fetchOpenOrders();
|
|
158
|
+
// Verify the request URL includes query params
|
|
159
|
+
expect(mockedAxios.get).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders?status=resting', expect.any(Object));
|
|
160
|
+
// Verify getHeaders was called with base path only (no query params)
|
|
161
|
+
expect(MockedKalshiAuth.prototype.getHeaders).toHaveBeenCalledWith('GET', '/trade-api/v2/portfolio/orders');
|
|
162
|
+
});
|
|
163
|
+
it('should include ticker in query params when marketId provided', async () => {
|
|
164
|
+
const mockResponse = { data: { orders: [] } };
|
|
165
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
166
|
+
await exchange.fetchOpenOrders('TEST-MARKET');
|
|
167
|
+
expect(mockedAxios.get).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders?status=resting&ticker=TEST-MARKET', expect.any(Object));
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe('fetchPositions', () => {
|
|
171
|
+
it('should handle positions with zero contracts', async () => {
|
|
172
|
+
const mockResponse = {
|
|
173
|
+
data: {
|
|
174
|
+
market_positions: [
|
|
175
|
+
{
|
|
176
|
+
ticker: 'TEST-MARKET',
|
|
177
|
+
position: 0,
|
|
178
|
+
total_cost: 0,
|
|
179
|
+
market_exposure: 0,
|
|
180
|
+
realized_pnl: 0
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
186
|
+
const positions = await exchange.fetchPositions();
|
|
187
|
+
expect(positions).toHaveLength(1);
|
|
188
|
+
expect(positions[0].size).toBe(0);
|
|
189
|
+
expect(positions[0].entryPrice).toBe(0); // Should not throw division by zero
|
|
190
|
+
});
|
|
191
|
+
it('should correctly calculate average price and PnL', async () => {
|
|
192
|
+
const mockResponse = {
|
|
193
|
+
data: {
|
|
194
|
+
market_positions: [
|
|
195
|
+
{
|
|
196
|
+
ticker: 'TEST-MARKET',
|
|
197
|
+
position: 10,
|
|
198
|
+
total_cost: 550, // 10 contracts at $0.55 each = $5.50 = 550 cents
|
|
199
|
+
market_exposure: 100, // $1.00 unrealized PnL
|
|
200
|
+
realized_pnl: 50 // $0.50 realized PnL
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
206
|
+
const positions = await exchange.fetchPositions();
|
|
207
|
+
expect(positions).toHaveLength(1);
|
|
208
|
+
expect(positions[0].size).toBe(10);
|
|
209
|
+
expect(positions[0].entryPrice).toBe(0.55); // 550 / 10 / 100
|
|
210
|
+
expect(positions[0].unrealizedPnL).toBe(1.00); // 100 / 100
|
|
211
|
+
expect(positions[0].realizedPnL).toBe(0.50); // 50 / 100
|
|
212
|
+
});
|
|
213
|
+
it('should handle short positions', async () => {
|
|
214
|
+
const mockResponse = {
|
|
215
|
+
data: {
|
|
216
|
+
market_positions: [
|
|
217
|
+
{
|
|
218
|
+
ticker: 'TEST-MARKET',
|
|
219
|
+
position: -5, // Short position
|
|
220
|
+
total_cost: 250,
|
|
221
|
+
market_exposure: -50,
|
|
222
|
+
realized_pnl: 25
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
228
|
+
const positions = await exchange.fetchPositions();
|
|
229
|
+
expect(positions[0].size).toBe(-5); // Negative for short
|
|
230
|
+
expect(Math.abs(positions[0].size)).toBe(5); // Absolute value
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
describe('fetchBalance', () => {
|
|
234
|
+
it('should correctly convert cents to dollars', async () => {
|
|
235
|
+
const mockResponse = {
|
|
236
|
+
data: {
|
|
237
|
+
balance: 10000, // $100.00 available
|
|
238
|
+
portfolio_value: 15000 // $150.00 total
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
242
|
+
const balances = await exchange.fetchBalance();
|
|
243
|
+
expect(balances).toHaveLength(1);
|
|
244
|
+
expect(balances[0].currency).toBe('USD');
|
|
245
|
+
expect(balances[0].available).toBe(100.00);
|
|
246
|
+
expect(balances[0].total).toBe(150.00);
|
|
247
|
+
expect(balances[0].locked).toBe(50.00); // 150 - 100
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe('cancelOrder', () => {
|
|
251
|
+
it('should cancel order successfully', async () => {
|
|
252
|
+
const mockResponse = {
|
|
253
|
+
data: {
|
|
254
|
+
order: {
|
|
255
|
+
order_id: 'order-123',
|
|
256
|
+
ticker: 'TEST-MARKET',
|
|
257
|
+
side: 'yes',
|
|
258
|
+
count: 10,
|
|
259
|
+
remaining_count: 5,
|
|
260
|
+
created_time: '2026-01-13T12:00:00Z'
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
mockedAxios.delete.mockResolvedValue(mockResponse);
|
|
265
|
+
const order = await exchange.cancelOrder('order-123');
|
|
266
|
+
expect(order.status).toBe('cancelled');
|
|
267
|
+
expect(order.filled).toBe(5); // count - remaining_count
|
|
268
|
+
expect(order.remaining).toBe(0);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe('Order Status Mapping', () => {
|
|
273
|
+
beforeEach(() => {
|
|
274
|
+
exchange = new kalshi_1.KalshiExchange(mockCredentials);
|
|
275
|
+
});
|
|
276
|
+
it('should map resting to open', async () => {
|
|
277
|
+
const mockResponse = {
|
|
278
|
+
data: {
|
|
279
|
+
order: {
|
|
280
|
+
order_id: 'order-123',
|
|
281
|
+
ticker: 'TEST',
|
|
282
|
+
status: 'resting',
|
|
283
|
+
count: 10,
|
|
284
|
+
created_time: '2026-01-13T12:00:00Z'
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
289
|
+
const order = await exchange.fetchOrder('order-123');
|
|
290
|
+
expect(order.status).toBe('open');
|
|
291
|
+
});
|
|
292
|
+
it('should map executed to filled', async () => {
|
|
293
|
+
const mockResponse = {
|
|
294
|
+
data: {
|
|
295
|
+
order: {
|
|
296
|
+
order_id: 'order-123',
|
|
297
|
+
ticker: 'TEST',
|
|
298
|
+
status: 'executed',
|
|
299
|
+
count: 10,
|
|
300
|
+
created_time: '2026-01-13T12:00:00Z'
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
mockedAxios.get.mockResolvedValue(mockResponse);
|
|
305
|
+
const order = await exchange.fetchOrder('order-123');
|
|
306
|
+
expect(order.status).toBe('filled');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchMarkets = searchMarkets;
|
|
4
|
+
const fetchMarkets_1 = require("./fetchMarkets");
|
|
5
|
+
async function searchMarkets(query, params) {
|
|
6
|
+
// We must fetch ALL markets to search them locally since we don't have server-side search
|
|
7
|
+
const fetchLimit = 100000;
|
|
8
|
+
try {
|
|
9
|
+
const markets = await (0, fetchMarkets_1.fetchMarkets)({ ...params, limit: fetchLimit });
|
|
10
|
+
const lowerQuery = query.toLowerCase();
|
|
11
|
+
const searchIn = params?.searchIn || 'title'; // Default to title-only search
|
|
12
|
+
const filtered = markets.filter(market => {
|
|
13
|
+
const titleMatch = (market.title || '').toLowerCase().includes(lowerQuery);
|
|
14
|
+
const descMatch = (market.description || '').toLowerCase().includes(lowerQuery);
|
|
15
|
+
if (searchIn === 'title')
|
|
16
|
+
return titleMatch;
|
|
17
|
+
if (searchIn === 'description')
|
|
18
|
+
return descMatch;
|
|
19
|
+
return titleMatch || descMatch; // 'both'
|
|
20
|
+
});
|
|
21
|
+
const limit = params?.limit || 20;
|
|
22
|
+
return filtered.slice(0, limit);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error("Error searching Kalshi data:", error);
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { UnifiedMarket, CandleInterval } from '../../types';
|
|
2
|
+
export declare const KALSHI_API_URL = "https://api.elections.kalshi.com/trade-api/v2/events";
|
|
3
|
+
export declare const KALSHI_SERIES_URL = "https://api.elections.kalshi.com/trade-api/v2/series";
|
|
4
|
+
export declare function mapMarketToUnified(event: any, market: any): UnifiedMarket | null;
|
|
5
|
+
export declare function mapIntervalToKalshi(interval: CandleInterval): number;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KALSHI_SERIES_URL = exports.KALSHI_API_URL = void 0;
|
|
4
|
+
exports.mapMarketToUnified = mapMarketToUnified;
|
|
5
|
+
exports.mapIntervalToKalshi = mapIntervalToKalshi;
|
|
6
|
+
exports.KALSHI_API_URL = "https://api.elections.kalshi.com/trade-api/v2/events";
|
|
7
|
+
exports.KALSHI_SERIES_URL = "https://api.elections.kalshi.com/trade-api/v2/series";
|
|
8
|
+
function mapMarketToUnified(event, market) {
|
|
9
|
+
if (!market)
|
|
10
|
+
return null;
|
|
11
|
+
// Calculate price
|
|
12
|
+
let price = 0.5;
|
|
13
|
+
if (market.last_price) {
|
|
14
|
+
price = market.last_price / 100;
|
|
15
|
+
}
|
|
16
|
+
else if (market.yes_ask && market.yes_bid) {
|
|
17
|
+
price = (market.yes_ask + market.yes_bid) / 200;
|
|
18
|
+
}
|
|
19
|
+
else if (market.yes_ask) {
|
|
20
|
+
price = market.yes_ask / 100;
|
|
21
|
+
}
|
|
22
|
+
// Extract candidate name
|
|
23
|
+
let candidateName = null;
|
|
24
|
+
if (market.subtitle || market.yes_sub_title) {
|
|
25
|
+
candidateName = market.subtitle || market.yes_sub_title;
|
|
26
|
+
}
|
|
27
|
+
// Calculate 24h change
|
|
28
|
+
let priceChange = 0;
|
|
29
|
+
if (market.previous_price_dollars !== undefined && market.last_price_dollars !== undefined) {
|
|
30
|
+
priceChange = market.last_price_dollars - market.previous_price_dollars;
|
|
31
|
+
}
|
|
32
|
+
const outcomes = [
|
|
33
|
+
{
|
|
34
|
+
id: market.ticker, // The actual market ticker (primary tradeable)
|
|
35
|
+
label: candidateName || 'Yes',
|
|
36
|
+
price: price,
|
|
37
|
+
priceChange24h: priceChange
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: `${market.ticker}-NO`, // Virtual ID for the No outcome
|
|
41
|
+
label: candidateName ? `Not ${candidateName}` : 'No',
|
|
42
|
+
price: 1 - price,
|
|
43
|
+
priceChange24h: -priceChange // Inverse change for No? simplified assumption
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
// Combine category and tags into a unified tags array
|
|
47
|
+
const unifiedTags = [];
|
|
48
|
+
// Add category first (if it exists)
|
|
49
|
+
if (event.category) {
|
|
50
|
+
unifiedTags.push(event.category);
|
|
51
|
+
}
|
|
52
|
+
// Add tags (if they exist and avoid duplicates)
|
|
53
|
+
if (event.tags && Array.isArray(event.tags)) {
|
|
54
|
+
for (const tag of event.tags) {
|
|
55
|
+
if (!unifiedTags.includes(tag)) {
|
|
56
|
+
unifiedTags.push(tag);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
id: market.ticker,
|
|
62
|
+
title: event.title,
|
|
63
|
+
description: event.sub_title || market.subtitle || "",
|
|
64
|
+
outcomes: outcomes,
|
|
65
|
+
resolutionDate: new Date(market.expiration_time),
|
|
66
|
+
volume24h: Number(market.volume_24h || market.volume || 0),
|
|
67
|
+
volume: Number(market.volume || 0),
|
|
68
|
+
liquidity: Number(market.liquidity || 0), // Kalshi 'liquidity' might need specific mapping if available, otherwise 0 to avoid conflation
|
|
69
|
+
openInterest: Number(market.open_interest || 0),
|
|
70
|
+
url: `https://kalshi.com/events/${event.event_ticker}`,
|
|
71
|
+
category: event.category,
|
|
72
|
+
tags: unifiedTags
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function mapIntervalToKalshi(interval) {
|
|
76
|
+
const mapping = {
|
|
77
|
+
'1m': 1,
|
|
78
|
+
'5m': 1,
|
|
79
|
+
'15m': 1,
|
|
80
|
+
'1h': 60,
|
|
81
|
+
'6h': 60,
|
|
82
|
+
'1d': 1440
|
|
83
|
+
};
|
|
84
|
+
return mapping[interval];
|
|
85
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ClobClient } from '@polymarket/clob-client';
|
|
2
|
+
import type { ApiKeyCreds } from '@polymarket/clob-client';
|
|
3
|
+
import { ExchangeCredentials } from '../../BaseExchange';
|
|
4
|
+
/**
|
|
5
|
+
* Manages Polymarket authentication and CLOB client initialization.
|
|
6
|
+
* Handles both L1 (wallet-based) and L2 (API credentials) authentication.
|
|
7
|
+
*/
|
|
8
|
+
export declare class PolymarketAuth {
|
|
9
|
+
private credentials;
|
|
10
|
+
private signer?;
|
|
11
|
+
private clobClient?;
|
|
12
|
+
private apiCreds?;
|
|
13
|
+
constructor(credentials: ExchangeCredentials);
|
|
14
|
+
/**
|
|
15
|
+
* Get or create API credentials using L1 authentication.
|
|
16
|
+
* This uses the private key to derive/create API credentials.
|
|
17
|
+
*/
|
|
18
|
+
getApiCredentials(): Promise<ApiKeyCreds>;
|
|
19
|
+
/**
|
|
20
|
+
* Get an authenticated CLOB client for L2 operations (trading).
|
|
21
|
+
* This client can place orders, cancel orders, query positions, etc.
|
|
22
|
+
*/
|
|
23
|
+
getClobClient(): Promise<ClobClient>;
|
|
24
|
+
/**
|
|
25
|
+
* Get the signer's address.
|
|
26
|
+
*/
|
|
27
|
+
getAddress(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Reset cached credentials and client (useful for testing or credential rotation).
|
|
30
|
+
*/
|
|
31
|
+
reset(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PolymarketAuth = void 0;
|
|
4
|
+
const clob_client_1 = require("@polymarket/clob-client");
|
|
5
|
+
const ethers_1 = require("ethers");
|
|
6
|
+
const POLYMARKET_HOST = 'https://clob.polymarket.com';
|
|
7
|
+
const POLYGON_CHAIN_ID = 137;
|
|
8
|
+
/**
|
|
9
|
+
* Manages Polymarket authentication and CLOB client initialization.
|
|
10
|
+
* Handles both L1 (wallet-based) and L2 (API credentials) authentication.
|
|
11
|
+
*/
|
|
12
|
+
class PolymarketAuth {
|
|
13
|
+
constructor(credentials) {
|
|
14
|
+
this.credentials = credentials;
|
|
15
|
+
if (!credentials.privateKey) {
|
|
16
|
+
throw new Error('Polymarket requires a privateKey for authentication');
|
|
17
|
+
}
|
|
18
|
+
// Initialize the signer
|
|
19
|
+
this.signer = new ethers_1.Wallet(credentials.privateKey);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get or create API credentials using L1 authentication.
|
|
23
|
+
* This uses the private key to derive/create API credentials.
|
|
24
|
+
*/
|
|
25
|
+
async getApiCredentials() {
|
|
26
|
+
// Return cached credentials if available
|
|
27
|
+
if (this.apiCreds) {
|
|
28
|
+
return this.apiCreds;
|
|
29
|
+
}
|
|
30
|
+
// If credentials were provided, use them
|
|
31
|
+
if (this.credentials.apiKey && this.credentials.apiSecret && this.credentials.passphrase) {
|
|
32
|
+
this.apiCreds = {
|
|
33
|
+
key: this.credentials.apiKey,
|
|
34
|
+
secret: this.credentials.apiSecret,
|
|
35
|
+
passphrase: this.credentials.passphrase,
|
|
36
|
+
};
|
|
37
|
+
return this.apiCreds;
|
|
38
|
+
}
|
|
39
|
+
// Otherwise, derive/create them using L1 auth
|
|
40
|
+
const l1Client = new clob_client_1.ClobClient(POLYMARKET_HOST, POLYGON_CHAIN_ID, this.signer);
|
|
41
|
+
// Robust derivation strategy:
|
|
42
|
+
// 1. Try to DERIVE existing credentials first (most common case).
|
|
43
|
+
// 2. If that fails (e.g. 404 or 400), try to CREATE new ones.
|
|
44
|
+
let creds;
|
|
45
|
+
try {
|
|
46
|
+
// console.log('Trying to derive existing API key...');
|
|
47
|
+
creds = await l1Client.deriveApiKey();
|
|
48
|
+
}
|
|
49
|
+
catch (deriveError) {
|
|
50
|
+
// console.log('Derivation failed, trying to create new API key...');
|
|
51
|
+
try {
|
|
52
|
+
creds = await l1Client.createApiKey();
|
|
53
|
+
}
|
|
54
|
+
catch (createError) {
|
|
55
|
+
console.error('Failed to both derive and create API key:', createError?.message || createError);
|
|
56
|
+
throw new Error('Authentication failed: Could not create or derive API key.');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!creds) {
|
|
60
|
+
throw new Error('Authentication failed: Credentials are empty.');
|
|
61
|
+
}
|
|
62
|
+
this.apiCreds = creds;
|
|
63
|
+
return creds;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get an authenticated CLOB client for L2 operations (trading).
|
|
67
|
+
* This client can place orders, cancel orders, query positions, etc.
|
|
68
|
+
*/
|
|
69
|
+
async getClobClient() {
|
|
70
|
+
// Return cached client if available
|
|
71
|
+
if (this.clobClient) {
|
|
72
|
+
return this.clobClient;
|
|
73
|
+
}
|
|
74
|
+
// Get API credentials (L1 auth)
|
|
75
|
+
const apiCreds = await this.getApiCredentials();
|
|
76
|
+
// Determine signature type (default to EOA = 0)
|
|
77
|
+
const signatureType = this.credentials.signatureType ?? 0;
|
|
78
|
+
// Determine funder address (defaults to signer's address)
|
|
79
|
+
const funderAddress = this.credentials.funderAddress ?? this.signer.address;
|
|
80
|
+
// Create L2-authenticated client
|
|
81
|
+
this.clobClient = new clob_client_1.ClobClient(POLYMARKET_HOST, POLYGON_CHAIN_ID, this.signer, apiCreds, signatureType, funderAddress);
|
|
82
|
+
return this.clobClient;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the signer's address.
|
|
86
|
+
*/
|
|
87
|
+
getAddress() {
|
|
88
|
+
return this.signer.address;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reset cached credentials and client (useful for testing or credential rotation).
|
|
92
|
+
*/
|
|
93
|
+
reset() {
|
|
94
|
+
this.apiCreds = undefined;
|
|
95
|
+
this.clobClient = undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.PolymarketAuth = PolymarketAuth;
|
|
@@ -0,0 +1,75 @@
|
|
|
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.fetchMarkets = fetchMarkets;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
async function fetchMarkets(params) {
|
|
10
|
+
const limit = params?.limit || 200; // Higher default for better coverage
|
|
11
|
+
const offset = params?.offset || 0;
|
|
12
|
+
// Map generic sort params to Polymarket Gamma API params
|
|
13
|
+
let queryParams = {
|
|
14
|
+
active: 'true',
|
|
15
|
+
closed: 'false',
|
|
16
|
+
limit: limit,
|
|
17
|
+
offset: offset,
|
|
18
|
+
};
|
|
19
|
+
// Gamma API uses 'order' and 'ascending' for sorting
|
|
20
|
+
if (params?.sort === 'volume') {
|
|
21
|
+
queryParams.order = 'volume';
|
|
22
|
+
queryParams.ascending = 'false';
|
|
23
|
+
}
|
|
24
|
+
else if (params?.sort === 'newest') {
|
|
25
|
+
queryParams.order = 'startDate';
|
|
26
|
+
queryParams.ascending = 'false';
|
|
27
|
+
}
|
|
28
|
+
else if (params?.sort === 'liquidity') {
|
|
29
|
+
// queryParams.order = 'liquidity';
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Default: do not send order param to avoid 422
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
// Fetch active events from Gamma
|
|
36
|
+
const response = await axios_1.default.get(utils_1.GAMMA_API_URL, {
|
|
37
|
+
params: queryParams
|
|
38
|
+
});
|
|
39
|
+
const events = response.data;
|
|
40
|
+
const unifiedMarkets = [];
|
|
41
|
+
for (const event of events) {
|
|
42
|
+
// Each event is a container (e.g. "US Election").
|
|
43
|
+
// It contains specific "markets" (e.g. "Winner", "Pop Vote").
|
|
44
|
+
if (!event.markets)
|
|
45
|
+
continue;
|
|
46
|
+
for (const market of event.markets) {
|
|
47
|
+
const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market);
|
|
48
|
+
if (unifiedMarket) {
|
|
49
|
+
unifiedMarkets.push(unifiedMarket);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Client-side Sort capability to ensure contract fulfillment
|
|
54
|
+
// Often API filters are "good effort" or apply to the 'event' but not the 'market'
|
|
55
|
+
if (params?.sort === 'volume') {
|
|
56
|
+
unifiedMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
57
|
+
}
|
|
58
|
+
else if (params?.sort === 'newest') {
|
|
59
|
+
// unifiedMarkets.sort((a, b) => b.resolutionDate.getTime() - a.resolutionDate.getTime()); // Not quite 'newest'
|
|
60
|
+
}
|
|
61
|
+
else if (params?.sort === 'liquidity') {
|
|
62
|
+
unifiedMarkets.sort((a, b) => b.liquidity - a.liquidity);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Default volume sort
|
|
66
|
+
unifiedMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
67
|
+
}
|
|
68
|
+
// Respect limit strictly after flattening
|
|
69
|
+
return unifiedMarkets.slice(0, limit);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error("Error fetching Polymarket data:", error);
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HistoryFilterParams } from '../../BaseExchange';
|
|
2
|
+
import { PriceCandle } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* Fetch historical price data (OHLCV candles) for a specific token.
|
|
5
|
+
* @param id - The CLOB token ID (e.g., outcome token ID)
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetchOHLCV(id: string, params: HistoryFilterParams): Promise<PriceCandle[]>;
|