pmxt-core 2.39.0 → 2.40.0
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/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.d.ts +2 -2
- package/dist/exchanges/polymarket/index.js +2 -1
- package/dist/exchanges/polymarket/websocket.d.ts +51 -0
- package/dist/exchanges/polymarket/websocket.js +125 -0
- 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
package/dist/router/Router.js
CHANGED
|
@@ -50,7 +50,10 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
|
|
|
50
50
|
offset: params?.offset,
|
|
51
51
|
closed: params?.status === 'closed' || params?.status === 'inactive',
|
|
52
52
|
});
|
|
53
|
-
|
|
53
|
+
if (!Array.isArray(response)) {
|
|
54
|
+
throw new Error(`fetchMarketsImpl: expected array from searchMarkets but received ${typeof response}`);
|
|
55
|
+
}
|
|
56
|
+
return response;
|
|
54
57
|
}
|
|
55
58
|
async fetchEventsImpl(params) {
|
|
56
59
|
const response = await this.client.searchEvents({
|
|
@@ -59,7 +62,10 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
|
|
|
59
62
|
limit: params?.limit,
|
|
60
63
|
offset: params?.offset,
|
|
61
64
|
});
|
|
62
|
-
|
|
65
|
+
if (!Array.isArray(response)) {
|
|
66
|
+
throw new Error(`fetchEventsImpl: expected array from searchEvents but received ${typeof response}`);
|
|
67
|
+
}
|
|
68
|
+
return response;
|
|
63
69
|
}
|
|
64
70
|
// -----------------------------------------------------------------------
|
|
65
71
|
// Unified orderbook (cross-exchange merge)
|
|
@@ -86,19 +92,33 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
|
|
|
86
92
|
const outcome = findOutcomeForSide(match.market, resolvedSide);
|
|
87
93
|
if (!outcome)
|
|
88
94
|
continue;
|
|
89
|
-
fetchPromises.push(exchange
|
|
95
|
+
fetchPromises.push(exchange
|
|
96
|
+
.fetchOrderBook(outcome.outcomeId, resolvedSide)
|
|
97
|
+
.then((book) => ({ book, venue: venueName, error: null }))
|
|
98
|
+
.catch((error) => ({ book: null, venue: venueName, error })));
|
|
90
99
|
}
|
|
91
100
|
// Fetch the source market's orderbook (try remaining exchanges with the raw ID)
|
|
92
101
|
for (const [name, exchange] of Object.entries(this.exchanges)) {
|
|
93
102
|
if (matchedVenues.has(name))
|
|
94
103
|
continue;
|
|
95
|
-
fetchPromises.push(exchange
|
|
104
|
+
fetchPromises.push(exchange
|
|
105
|
+
.fetchOrderBook(outcomeId, resolvedSide)
|
|
106
|
+
.then((book) => ({ book, venue: name, error: null }))
|
|
107
|
+
.catch((error) => ({ book: null, venue: name, error })));
|
|
108
|
+
}
|
|
109
|
+
const results = await Promise.all(fetchPromises);
|
|
110
|
+
const books = results.filter((r) => r.book !== null);
|
|
111
|
+
const failures = results.filter((r) => r.book === null && r.error !== null);
|
|
112
|
+
if (books.length === 0 && failures.length > 0) {
|
|
113
|
+
const reasons = failures
|
|
114
|
+
.map((f) => `${f.venue}: ${f.error instanceof Error ? f.error.message : String(f.error)}`)
|
|
115
|
+
.join('; ');
|
|
116
|
+
throw new Error(`fetchOrderBook failed on all exchanges for outcomeId "${outcomeId}": ${reasons}`);
|
|
96
117
|
}
|
|
97
|
-
const books = (await Promise.all(fetchPromises)).filter((b) => b !== null);
|
|
98
118
|
if (books.length === 0) {
|
|
99
|
-
|
|
119
|
+
throw new Error(`fetchOrderBook: no exchange returned an orderbook for outcomeId "${outcomeId}"`);
|
|
100
120
|
}
|
|
101
|
-
return mergeOrderBooks(books);
|
|
121
|
+
return mergeOrderBooks(books.map((r) => r.book));
|
|
102
122
|
}
|
|
103
123
|
// -----------------------------------------------------------------------
|
|
104
124
|
// Cross-exchange market matches
|
|
@@ -259,9 +279,16 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
|
|
|
259
279
|
try {
|
|
260
280
|
return await this.fetchArbitrageBulk(params);
|
|
261
281
|
}
|
|
262
|
-
catch {
|
|
263
|
-
//
|
|
264
|
-
|
|
282
|
+
catch (error) {
|
|
283
|
+
// Only fall back when the bulk endpoint is genuinely not available (404/501).
|
|
284
|
+
// All other errors (network failures, 5xx, parsing errors) propagate so
|
|
285
|
+
// callers are not silently given stale N+1 data.
|
|
286
|
+
const status = error?.status ?? error?.response?.status;
|
|
287
|
+
if (status === 404 || status === 501) {
|
|
288
|
+
console.warn('[pmxt] Router: bulk arbitrage endpoint unavailable, falling back to N+1 approach');
|
|
289
|
+
return this.fetchArbitrageFallback(params);
|
|
290
|
+
}
|
|
291
|
+
throw error;
|
|
265
292
|
}
|
|
266
293
|
}
|
|
267
294
|
/**
|
|
@@ -280,17 +307,24 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
|
|
|
280
307
|
const res = await this.client.getArbitrage(query);
|
|
281
308
|
// getArbitrage already unwraps .data — res is the opportunities array.
|
|
282
309
|
const items = Array.isArray(res) ? res : (res?.data ?? []);
|
|
283
|
-
return items.map((r) =>
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
310
|
+
return items.map((r) => {
|
|
311
|
+
if (r.spread == null || r.buyPrice == null || r.sellPrice == null) {
|
|
312
|
+
throw new Error(`fetchArbitrageBulk: arbitrage record is missing required price fields ` +
|
|
313
|
+
`(spread=${r.spread}, buyPrice=${r.buyPrice}, sellPrice=${r.sellPrice}) ` +
|
|
314
|
+
`for markets ${r.marketA?.marketId ?? '?'} / ${r.marketB?.marketId ?? '?'}`);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
marketA: r.marketA,
|
|
318
|
+
marketB: r.marketB,
|
|
319
|
+
spread: r.spread,
|
|
320
|
+
buyVenue: r.buyVenue ?? '',
|
|
321
|
+
sellVenue: r.sellVenue ?? '',
|
|
322
|
+
buyPrice: r.buyPrice,
|
|
323
|
+
sellPrice: r.sellPrice,
|
|
324
|
+
relation: r.relation,
|
|
325
|
+
confidence: r.confidence,
|
|
326
|
+
};
|
|
327
|
+
});
|
|
294
328
|
}
|
|
295
329
|
/**
|
|
296
330
|
* Legacy N+1 fallback: fetch markets, then fetch matches per-market.
|
|
@@ -13,6 +13,8 @@ const metaculus_1 = require("../exchanges/metaculus");
|
|
|
13
13
|
const smarkets_1 = require("../exchanges/smarkets");
|
|
14
14
|
const polymarket_us_1 = require("../exchanges/polymarket_us");
|
|
15
15
|
const hyperliquid_1 = require("../exchanges/hyperliquid");
|
|
16
|
+
const gemini_titan_1 = require("../exchanges/gemini-titan");
|
|
17
|
+
const mock_1 = require("../exchanges/mock");
|
|
16
18
|
const router_1 = require("../router");
|
|
17
19
|
function createExchange(name, credentials, bearerToken) {
|
|
18
20
|
switch (name) {
|
|
@@ -96,6 +98,13 @@ function createExchange(name, credentials, bearerToken) {
|
|
|
96
98
|
apiKey: credentials?.apiKey || process.env.HYPERLIQUID_WALLET_ADDRESS,
|
|
97
99
|
privateKey: credentials?.privateKey || process.env.HYPERLIQUID_PRIVATE_KEY,
|
|
98
100
|
});
|
|
101
|
+
case "gemini-titan":
|
|
102
|
+
return new gemini_titan_1.GeminiTitanExchange({
|
|
103
|
+
apiKey: credentials?.apiKey || process.env.GEMINI_API_KEY,
|
|
104
|
+
apiSecret: credentials?.apiSecret || process.env.GEMINI_API_SECRET,
|
|
105
|
+
});
|
|
106
|
+
case "mock":
|
|
107
|
+
return new mock_1.MockExchange();
|
|
99
108
|
case "router":
|
|
100
109
|
return new router_1.Router({
|
|
101
110
|
apiKey: bearerToken,
|
package/dist/server/openapi.yaml
CHANGED
|
@@ -3684,6 +3684,28 @@ x-sdk-constructors:
|
|
|
3684
3684
|
tsName: privateKey
|
|
3685
3685
|
type: string
|
|
3686
3686
|
description: Private key for authentication
|
|
3687
|
+
gemini-titan:
|
|
3688
|
+
className: GeminiTitan
|
|
3689
|
+
params:
|
|
3690
|
+
- name: pmxt_api_key
|
|
3691
|
+
tsName: pmxtApiKey
|
|
3692
|
+
type: string
|
|
3693
|
+
description: PMXT API key for hosted access
|
|
3694
|
+
- name: api_key
|
|
3695
|
+
tsName: apiKey
|
|
3696
|
+
type: string
|
|
3697
|
+
description: API key for authentication
|
|
3698
|
+
- name: api_secret
|
|
3699
|
+
tsName: apiSecret
|
|
3700
|
+
type: string
|
|
3701
|
+
description: API secret for authentication
|
|
3702
|
+
mock:
|
|
3703
|
+
className: Mock
|
|
3704
|
+
params:
|
|
3705
|
+
- name: pmxt_api_key
|
|
3706
|
+
tsName: pmxtApiKey
|
|
3707
|
+
type: string
|
|
3708
|
+
description: PMXT API key for hosted access
|
|
3687
3709
|
router:
|
|
3688
3710
|
className: Router
|
|
3689
3711
|
params:
|
|
@@ -196,7 +196,9 @@ function createWebSocketHandler(options = {}) {
|
|
|
196
196
|
// Close all exchange instances
|
|
197
197
|
for (const [, exchange] of state.exchanges) {
|
|
198
198
|
if (typeof exchange.close === "function") {
|
|
199
|
-
exchange.close().catch(() => {
|
|
199
|
+
exchange.close().catch((err) => {
|
|
200
|
+
console.warn('[ws-handler] exchange close() failed', { error: err instanceof Error ? err.message : String(err) });
|
|
201
|
+
});
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
state.exchanges.clear();
|
|
@@ -254,8 +256,16 @@ function handleSubscribe(ws, state, msg, exchangeName) {
|
|
|
254
256
|
state.subscriptions.set(key, { abortController });
|
|
255
257
|
const streamFn = method === "watchOrderBooks" ? streamBatch : streamSingle;
|
|
256
258
|
// Fire and forget -- the loop runs until aborted or WS closes
|
|
257
|
-
streamFn(exchange, method, args || [], id, ws, abortController.signal).catch(() => {
|
|
258
|
-
//
|
|
259
|
+
streamFn(exchange, method, args || [], id, ws, abortController.signal).catch((err) => {
|
|
260
|
+
// Unexpected stream rejection (programming error — exchange errors are
|
|
261
|
+
// caught and reported to the client inside streamSingle/streamBatch).
|
|
262
|
+
console.warn('[ws-handler] stream ended with unexpected error', {
|
|
263
|
+
exchange: exchangeName,
|
|
264
|
+
method,
|
|
265
|
+
id,
|
|
266
|
+
error: err instanceof Error ? err.message : String(err),
|
|
267
|
+
});
|
|
268
|
+
sendError(ws, id, err instanceof Error ? err.message : 'Streaming error');
|
|
259
269
|
state.subscriptions.delete(key);
|
|
260
270
|
});
|
|
261
271
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxt-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.40.0",
|
|
4
4
|
"description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"test": "jest -c jest.config.js",
|
|
30
30
|
"server": "tsx watch src/server/index.ts",
|
|
31
31
|
"server:prod": "node dist/server/index.js",
|
|
32
|
-
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.
|
|
33
|
-
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.
|
|
32
|
+
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.40.0,library=urllib3",
|
|
33
|
+
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.40.0,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
|
|
34
34
|
"fetch:openapi": "node scripts/fetch-openapi-specs.js",
|
|
35
35
|
"extract:jsdoc": "node ../scripts/extract-jsdoc.js",
|
|
36
36
|
"generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const price_1 = require("./price");
|
|
4
|
-
describe("clampBaoziPrice", () => {
|
|
5
|
-
test("clamps values below 0 to 0", () => {
|
|
6
|
-
expect((0, price_1.clampBaoziPrice)(-0.1)).toBe(0);
|
|
7
|
-
});
|
|
8
|
-
test("clamps values above 1 to 1", () => {
|
|
9
|
-
expect((0, price_1.clampBaoziPrice)(1.2)).toBe(1);
|
|
10
|
-
});
|
|
11
|
-
test("leaves values in range unchanged", () => {
|
|
12
|
-
expect((0, price_1.clampBaoziPrice)(0.3)).toBe(0.3);
|
|
13
|
-
expect((0, price_1.clampBaoziPrice)(0)).toBe(0);
|
|
14
|
-
expect((0, price_1.clampBaoziPrice)(1)).toBe(1);
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
describe("normalizeBaoziOutcomes", () => {
|
|
18
|
-
function makeOutcome(price) {
|
|
19
|
-
return { outcomeId: "x", marketId: "m", label: "X", price };
|
|
20
|
-
}
|
|
21
|
-
test("normalizes prices to sum to 1", () => {
|
|
22
|
-
const outcomes = [makeOutcome(2), makeOutcome(3)];
|
|
23
|
-
(0, price_1.normalizeBaoziOutcomes)(outcomes);
|
|
24
|
-
expect(outcomes[0].price).toBeCloseTo(0.4);
|
|
25
|
-
expect(outcomes[1].price).toBeCloseTo(0.6);
|
|
26
|
-
});
|
|
27
|
-
test("does nothing when sum is zero", () => {
|
|
28
|
-
const outcomes = [makeOutcome(0), makeOutcome(0)];
|
|
29
|
-
(0, price_1.normalizeBaoziOutcomes)(outcomes);
|
|
30
|
-
expect(outcomes[0].price).toBe(0);
|
|
31
|
-
expect(outcomes[1].price).toBe(0);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|