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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toMarketId = toMarketId;
|
|
4
|
+
exports.fromMarketId = fromMarketId;
|
|
5
|
+
exports.toOutcomeId = toOutcomeId;
|
|
6
|
+
exports.fromOutcomeId = fromOutcomeId;
|
|
7
|
+
exports.isGeminiInstrument = isGeminiInstrument;
|
|
8
|
+
const config_1 = require("./config");
|
|
9
|
+
/**
|
|
10
|
+
* Build a unique market ID from a Gemini instrumentSymbol.
|
|
11
|
+
* Format: "gemi-{instrumentSymbol}"
|
|
12
|
+
*
|
|
13
|
+
* A market encompasses both YES and NO sides of a single contract.
|
|
14
|
+
*/
|
|
15
|
+
function toMarketId(instrumentSymbol) {
|
|
16
|
+
return `gemi-${instrumentSymbol}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extract the instrumentSymbol from our market ID format.
|
|
20
|
+
*/
|
|
21
|
+
function fromMarketId(marketId) {
|
|
22
|
+
const prefix = 'gemi-';
|
|
23
|
+
if (!marketId.startsWith(prefix)) {
|
|
24
|
+
throw new Error(`Invalid Gemini Titan market ID: ${marketId}`);
|
|
25
|
+
}
|
|
26
|
+
return marketId.slice(prefix.length);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build an outcome ID that encodes both the instrumentSymbol and side.
|
|
30
|
+
* Format: "GEMI:{instrumentSymbol}:{side}"
|
|
31
|
+
*
|
|
32
|
+
* The side is needed because Gemini orders require an explicit
|
|
33
|
+
* "outcome" field ("yes" or "no").
|
|
34
|
+
*/
|
|
35
|
+
function toOutcomeId(instrumentSymbol, side) {
|
|
36
|
+
return `GEMI:${instrumentSymbol}:${side}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Decode an outcome ID back into instrumentSymbol and side.
|
|
40
|
+
*/
|
|
41
|
+
function fromOutcomeId(outcomeId) {
|
|
42
|
+
const parts = outcomeId.split(':');
|
|
43
|
+
if (parts.length !== 3 || parts[0] !== 'GEMI') {
|
|
44
|
+
throw new Error(`Invalid Gemini Titan outcome ID: ${outcomeId}`);
|
|
45
|
+
}
|
|
46
|
+
const side = parts[2];
|
|
47
|
+
if (side !== 'yes' && side !== 'no') {
|
|
48
|
+
throw new Error(`Invalid side in outcome ID: ${outcomeId}`);
|
|
49
|
+
}
|
|
50
|
+
return { instrumentSymbol: parts[1], side };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a string looks like a Gemini prediction market instrument symbol.
|
|
54
|
+
*/
|
|
55
|
+
function isGeminiInstrument(symbol) {
|
|
56
|
+
return symbol.startsWith(config_1.INSTRUMENT_PREFIX);
|
|
57
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { OrderBook, Trade } from '../../types';
|
|
2
|
+
import { GeminiAuth } from './auth';
|
|
3
|
+
export interface GeminiWebSocketConfig {
|
|
4
|
+
wsUrl: string;
|
|
5
|
+
reconnectIntervalMs?: number;
|
|
6
|
+
watchTimeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Gemini Titan WebSocket for real-time order book and trade streaming.
|
|
10
|
+
*
|
|
11
|
+
* Subscribes to:
|
|
12
|
+
* - {symbol}@depth20 (L2 partial depth snapshots at 1s intervals)
|
|
13
|
+
* - {symbol}@trade (executed trades)
|
|
14
|
+
*
|
|
15
|
+
* Auth headers are sent during the handshake if credentials are provided
|
|
16
|
+
* (needed for account-scoped streams, optional for public data).
|
|
17
|
+
*/
|
|
18
|
+
export declare class GeminiWebSocket {
|
|
19
|
+
private ws?;
|
|
20
|
+
private readonly auth;
|
|
21
|
+
private readonly config;
|
|
22
|
+
private readonly orderBookResolvers;
|
|
23
|
+
private readonly tradeResolvers;
|
|
24
|
+
private readonly orderBooks;
|
|
25
|
+
private readonly subscribedDepthSymbols;
|
|
26
|
+
private readonly subscribedTradeSymbols;
|
|
27
|
+
private messageIdCounter;
|
|
28
|
+
private isConnecting;
|
|
29
|
+
private isConnected;
|
|
30
|
+
private reconnectTimer?;
|
|
31
|
+
private connectionPromise?;
|
|
32
|
+
private isTerminated;
|
|
33
|
+
constructor(auth: GeminiAuth | undefined, config: GeminiWebSocketConfig);
|
|
34
|
+
private connect;
|
|
35
|
+
private scheduleReconnect;
|
|
36
|
+
private sendSubscribe;
|
|
37
|
+
private handleMessage;
|
|
38
|
+
private handleDepthSnapshot;
|
|
39
|
+
private handleDepthUpdate;
|
|
40
|
+
private applyDelta;
|
|
41
|
+
private handleTrade;
|
|
42
|
+
private resolveOrderBook;
|
|
43
|
+
watchOrderBook(symbol: string): Promise<OrderBook>;
|
|
44
|
+
watchTrades(symbol: string): Promise<Trade[]>;
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
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.GeminiWebSocket = void 0;
|
|
7
|
+
const ws_1 = __importDefault(require("ws"));
|
|
8
|
+
const watch_timeout_1 = require("../../utils/watch-timeout");
|
|
9
|
+
/**
|
|
10
|
+
* Gemini Titan WebSocket for real-time order book and trade streaming.
|
|
11
|
+
*
|
|
12
|
+
* Subscribes to:
|
|
13
|
+
* - {symbol}@depth20 (L2 partial depth snapshots at 1s intervals)
|
|
14
|
+
* - {symbol}@trade (executed trades)
|
|
15
|
+
*
|
|
16
|
+
* Auth headers are sent during the handshake if credentials are provided
|
|
17
|
+
* (needed for account-scoped streams, optional for public data).
|
|
18
|
+
*/
|
|
19
|
+
class GeminiWebSocket {
|
|
20
|
+
ws;
|
|
21
|
+
auth;
|
|
22
|
+
config;
|
|
23
|
+
orderBookResolvers = new Map();
|
|
24
|
+
tradeResolvers = new Map();
|
|
25
|
+
orderBooks = new Map();
|
|
26
|
+
subscribedDepthSymbols = new Set();
|
|
27
|
+
subscribedTradeSymbols = new Set();
|
|
28
|
+
messageIdCounter = 1;
|
|
29
|
+
isConnecting = false;
|
|
30
|
+
isConnected = false;
|
|
31
|
+
reconnectTimer;
|
|
32
|
+
connectionPromise;
|
|
33
|
+
isTerminated = false;
|
|
34
|
+
constructor(auth, config) {
|
|
35
|
+
this.auth = auth;
|
|
36
|
+
this.config = config;
|
|
37
|
+
}
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
// Connection
|
|
40
|
+
// -------------------------------------------------------------------------
|
|
41
|
+
async connect() {
|
|
42
|
+
if (this.isConnected || this.isTerminated)
|
|
43
|
+
return;
|
|
44
|
+
if (this.connectionPromise)
|
|
45
|
+
return this.connectionPromise;
|
|
46
|
+
this.isConnecting = true;
|
|
47
|
+
this.connectionPromise = new Promise((resolve, reject) => {
|
|
48
|
+
try {
|
|
49
|
+
const headers = this.auth
|
|
50
|
+
? this.auth.buildWsHeaders()
|
|
51
|
+
: {};
|
|
52
|
+
this.ws = new ws_1.default(this.config.wsUrl, { headers });
|
|
53
|
+
this.ws.on('open', () => {
|
|
54
|
+
this.isConnected = true;
|
|
55
|
+
this.isConnecting = false;
|
|
56
|
+
this.connectionPromise = undefined;
|
|
57
|
+
// Resubscribe on reconnect
|
|
58
|
+
const allStreams = [];
|
|
59
|
+
for (const sym of this.subscribedDepthSymbols) {
|
|
60
|
+
allStreams.push(`${sym}@depth20`);
|
|
61
|
+
}
|
|
62
|
+
for (const sym of this.subscribedTradeSymbols) {
|
|
63
|
+
allStreams.push(`${sym}@trade`);
|
|
64
|
+
}
|
|
65
|
+
if (allStreams.length > 0) {
|
|
66
|
+
this.sendSubscribe(allStreams);
|
|
67
|
+
}
|
|
68
|
+
resolve();
|
|
69
|
+
});
|
|
70
|
+
this.ws.on('message', (data) => {
|
|
71
|
+
try {
|
|
72
|
+
const message = JSON.parse(data.toString());
|
|
73
|
+
this.handleMessage(message);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Ignore unparseable messages
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
this.ws.on('error', (error) => {
|
|
80
|
+
this.isConnecting = false;
|
|
81
|
+
this.connectionPromise = undefined;
|
|
82
|
+
reject(error);
|
|
83
|
+
});
|
|
84
|
+
this.ws.on('close', () => {
|
|
85
|
+
this.isConnected = false;
|
|
86
|
+
this.isConnecting = false;
|
|
87
|
+
this.connectionPromise = undefined;
|
|
88
|
+
if (!this.isTerminated) {
|
|
89
|
+
this.scheduleReconnect();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.isConnecting = false;
|
|
95
|
+
this.connectionPromise = undefined;
|
|
96
|
+
reject(error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return this.connectionPromise;
|
|
100
|
+
}
|
|
101
|
+
scheduleReconnect() {
|
|
102
|
+
if (this.isTerminated)
|
|
103
|
+
return;
|
|
104
|
+
if (this.reconnectTimer)
|
|
105
|
+
clearTimeout(this.reconnectTimer);
|
|
106
|
+
this.reconnectTimer = setTimeout(() => {
|
|
107
|
+
this.connect().catch((err) => {
|
|
108
|
+
console.warn(`[gemini-titan] reconnect failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
109
|
+
});
|
|
110
|
+
}, this.config.reconnectIntervalMs ?? 5000);
|
|
111
|
+
}
|
|
112
|
+
// -------------------------------------------------------------------------
|
|
113
|
+
// Subscription
|
|
114
|
+
// -------------------------------------------------------------------------
|
|
115
|
+
sendSubscribe(streams) {
|
|
116
|
+
if (!this.ws || !this.isConnected)
|
|
117
|
+
return;
|
|
118
|
+
this.ws.send(JSON.stringify({
|
|
119
|
+
id: String(this.messageIdCounter++),
|
|
120
|
+
method: 'subscribe',
|
|
121
|
+
params: streams,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
// Message handling
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
handleMessage(message) {
|
|
128
|
+
// Gemini sends flat objects, NOT wrapped in { stream, data }.
|
|
129
|
+
// Depth snapshots: { lastUpdateId, symbol, bids, asks }
|
|
130
|
+
// Depth deltas: { e, E, s, U, u, b, a }
|
|
131
|
+
// Trades: { E, s, t, p, q, m }
|
|
132
|
+
// Confirmations: { id, status: 200 }
|
|
133
|
+
if (message.lastUpdateId !== undefined && message.bids) {
|
|
134
|
+
// Depth snapshot — symbol comes back lowercase
|
|
135
|
+
this.handleDepthSnapshot(message);
|
|
136
|
+
}
|
|
137
|
+
else if (message.e === 'depthUpdate' || (message.U !== undefined && message.b)) {
|
|
138
|
+
this.handleDepthUpdate(message);
|
|
139
|
+
}
|
|
140
|
+
else if (message.t !== undefined && message.p !== undefined && message.q !== undefined) {
|
|
141
|
+
this.handleTrade(message);
|
|
142
|
+
}
|
|
143
|
+
// Subscription confirmations ({ id, status }) are ignored
|
|
144
|
+
}
|
|
145
|
+
handleDepthSnapshot(data) {
|
|
146
|
+
// symbol comes back lowercase from the API, but we subscribed with
|
|
147
|
+
// uppercase. Normalize to uppercase for resolver lookup.
|
|
148
|
+
const symbol = data.symbol.toUpperCase();
|
|
149
|
+
const bids = (data.bids ?? []).map((level) => ({
|
|
150
|
+
price: parseFloat(level[0]),
|
|
151
|
+
size: parseFloat(level[1]),
|
|
152
|
+
}));
|
|
153
|
+
const asks = (data.asks ?? []).map((level) => ({
|
|
154
|
+
price: parseFloat(level[0]),
|
|
155
|
+
size: parseFloat(level[1]),
|
|
156
|
+
}));
|
|
157
|
+
bids.sort((a, b) => b.price - a.price);
|
|
158
|
+
asks.sort((a, b) => a.price - b.price);
|
|
159
|
+
const orderBook = { bids, asks, timestamp: Date.now() };
|
|
160
|
+
this.orderBooks.set(symbol, orderBook);
|
|
161
|
+
this.resolveOrderBook(symbol, orderBook);
|
|
162
|
+
}
|
|
163
|
+
handleDepthUpdate(data) {
|
|
164
|
+
const symbol = data.s.toUpperCase();
|
|
165
|
+
const existing = this.orderBooks.get(symbol);
|
|
166
|
+
if (!existing)
|
|
167
|
+
return; // No snapshot yet, discard delta
|
|
168
|
+
for (const [priceStr, sizeStr] of (data.b ?? [])) {
|
|
169
|
+
this.applyDelta(existing.bids, parseFloat(priceStr), parseFloat(sizeStr), 'desc');
|
|
170
|
+
}
|
|
171
|
+
for (const [priceStr, sizeStr] of (data.a ?? [])) {
|
|
172
|
+
this.applyDelta(existing.asks, parseFloat(priceStr), parseFloat(sizeStr), 'asc');
|
|
173
|
+
}
|
|
174
|
+
existing.timestamp = Date.now();
|
|
175
|
+
this.resolveOrderBook(symbol, existing);
|
|
176
|
+
}
|
|
177
|
+
applyDelta(levels, price, size, sortOrder) {
|
|
178
|
+
const idx = levels.findIndex(l => Math.abs(l.price - price) < 0.0001);
|
|
179
|
+
if (size === 0) {
|
|
180
|
+
if (idx !== -1)
|
|
181
|
+
levels.splice(idx, 1);
|
|
182
|
+
}
|
|
183
|
+
else if (idx !== -1) {
|
|
184
|
+
levels[idx] = { price, size };
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
levels.push({ price, size });
|
|
188
|
+
if (sortOrder === 'desc') {
|
|
189
|
+
levels.sort((a, b) => b.price - a.price);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
levels.sort((a, b) => a.price - b.price);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
handleTrade(data) {
|
|
197
|
+
const symbol = data.s.toUpperCase();
|
|
198
|
+
const trade = {
|
|
199
|
+
id: String(data.t ?? Date.now()),
|
|
200
|
+
timestamp: data.E ? Math.floor(data.E / 1_000_000) : Date.now(), // E is nanoseconds
|
|
201
|
+
price: parseFloat(data.p),
|
|
202
|
+
amount: parseFloat(data.q),
|
|
203
|
+
side: data.m ? 'sell' : 'buy', // m = true means buyer is maker (taker sold)
|
|
204
|
+
};
|
|
205
|
+
const resolvers = this.tradeResolvers.get(symbol);
|
|
206
|
+
if (resolvers && resolvers.length > 0) {
|
|
207
|
+
resolvers.forEach(r => r.resolve([trade]));
|
|
208
|
+
this.tradeResolvers.set(symbol, []);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
resolveOrderBook(symbol, orderBook) {
|
|
212
|
+
const resolvers = this.orderBookResolvers.get(symbol);
|
|
213
|
+
if (resolvers && resolvers.length > 0) {
|
|
214
|
+
resolvers.forEach(r => r.resolve(orderBook));
|
|
215
|
+
this.orderBookResolvers.set(symbol, []);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// -------------------------------------------------------------------------
|
|
219
|
+
// Public API
|
|
220
|
+
// -------------------------------------------------------------------------
|
|
221
|
+
async watchOrderBook(symbol) {
|
|
222
|
+
if (this.isTerminated) {
|
|
223
|
+
throw new Error(`WebSocket terminated, cannot watch ${symbol}`);
|
|
224
|
+
}
|
|
225
|
+
this.subscribedDepthSymbols.add(symbol);
|
|
226
|
+
if (!this.isConnected) {
|
|
227
|
+
this.connect().catch((err) => {
|
|
228
|
+
console.warn(`[gemini-titan] connect failed during watchOrderBook('${symbol}'): ${err instanceof Error ? err.message : String(err)}`);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
this.sendSubscribe([`${symbol}@depth20`]);
|
|
233
|
+
}
|
|
234
|
+
const dataPromise = new Promise((resolve, reject) => {
|
|
235
|
+
if (!this.orderBookResolvers.has(symbol)) {
|
|
236
|
+
this.orderBookResolvers.set(symbol, []);
|
|
237
|
+
}
|
|
238
|
+
this.orderBookResolvers.get(symbol).push({ resolve, reject });
|
|
239
|
+
});
|
|
240
|
+
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${symbol}')`);
|
|
241
|
+
}
|
|
242
|
+
async watchTrades(symbol) {
|
|
243
|
+
if (this.isTerminated) {
|
|
244
|
+
throw new Error(`WebSocket terminated, cannot watch trades for ${symbol}`);
|
|
245
|
+
}
|
|
246
|
+
this.subscribedTradeSymbols.add(symbol);
|
|
247
|
+
if (!this.isConnected) {
|
|
248
|
+
this.connect().catch((err) => {
|
|
249
|
+
console.warn(`[gemini-titan] connect failed during watchTrades('${symbol}'): ${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
this.sendSubscribe([`${symbol}@trade`]);
|
|
254
|
+
}
|
|
255
|
+
const dataPromise = new Promise((resolve, reject) => {
|
|
256
|
+
if (!this.tradeResolvers.has(symbol)) {
|
|
257
|
+
this.tradeResolvers.set(symbol, []);
|
|
258
|
+
}
|
|
259
|
+
this.tradeResolvers.get(symbol).push({ resolve, reject });
|
|
260
|
+
});
|
|
261
|
+
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${symbol}')`);
|
|
262
|
+
}
|
|
263
|
+
async close() {
|
|
264
|
+
this.isTerminated = true;
|
|
265
|
+
if (this.reconnectTimer) {
|
|
266
|
+
clearTimeout(this.reconnectTimer);
|
|
267
|
+
this.reconnectTimer = undefined;
|
|
268
|
+
}
|
|
269
|
+
for (const [symbol, resolvers] of this.orderBookResolvers) {
|
|
270
|
+
resolvers.forEach(r => r.reject(new Error(`WebSocket closed for ${symbol}`)));
|
|
271
|
+
}
|
|
272
|
+
this.orderBookResolvers.clear();
|
|
273
|
+
for (const [symbol, resolvers] of this.tradeResolvers) {
|
|
274
|
+
resolvers.forEach(r => r.reject(new Error(`WebSocket closed for ${symbol}`)));
|
|
275
|
+
}
|
|
276
|
+
this.tradeResolvers.clear();
|
|
277
|
+
if (this.ws) {
|
|
278
|
+
const ws = this.ws;
|
|
279
|
+
this.ws = undefined;
|
|
280
|
+
if (ws.readyState !== ws_1.default.CLOSED && ws.readyState !== ws_1.default.CLOSING) {
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
ws.once('close', () => {
|
|
283
|
+
this.isConnected = false;
|
|
284
|
+
this.isConnecting = false;
|
|
285
|
+
resolve();
|
|
286
|
+
});
|
|
287
|
+
ws.close();
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
this.isConnected = false;
|
|
292
|
+
this.isConnecting = false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
exports.GeminiWebSocket = GeminiWebSocket;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-08T23:06:00.095Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const kalshiApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.kalshiApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-08T23:06:00.095Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.kalshiApiSpec = {
|
|
@@ -217,8 +217,12 @@ class KalshiFetcher {
|
|
|
217
217
|
event.tags = series.tags;
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
-
catch {
|
|
221
|
-
// Non-critical
|
|
220
|
+
catch (err) {
|
|
221
|
+
// Non-critical — tags are enrichment only.
|
|
222
|
+
console.warn('[kalshi] series tag fetch failed', {
|
|
223
|
+
series_ticker: event.series_ticker,
|
|
224
|
+
error: err instanceof Error ? err.message : String(err),
|
|
225
|
+
});
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
228
|
return [event];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-08T23:06:00.134Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const limitlessApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.limitlessApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-08T23:06:00.134Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.limitlessApiSpec = {
|
|
@@ -273,8 +273,7 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
273
273
|
const client = this.ensureClient();
|
|
274
274
|
try {
|
|
275
275
|
if (!marketId) {
|
|
276
|
-
|
|
277
|
-
return [];
|
|
276
|
+
throw new Error('Limitless: fetchOpenOrders requires marketId (slug).');
|
|
278
277
|
}
|
|
279
278
|
const orders = await client.getOrders(marketId, ['LIVE']);
|
|
280
279
|
return orders.map((o) => ({
|
|
@@ -298,8 +297,7 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
298
297
|
async fetchClosedOrders(params) {
|
|
299
298
|
const client = this.ensureClient();
|
|
300
299
|
if (!params?.marketId) {
|
|
301
|
-
|
|
302
|
-
return [];
|
|
300
|
+
throw new Error('Limitless: fetchClosedOrders requires marketId (slug).');
|
|
303
301
|
}
|
|
304
302
|
const orders = await client.getOrders(params.marketId, ['MATCHED']);
|
|
305
303
|
return orders.map((o) => ({
|
|
@@ -319,8 +317,7 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
319
317
|
async fetchAllOrders(params) {
|
|
320
318
|
const client = this.ensureClient();
|
|
321
319
|
if (!params?.marketId) {
|
|
322
|
-
|
|
323
|
-
return [];
|
|
320
|
+
throw new Error('Limitless: fetchAllOrders requires marketId (slug).');
|
|
324
321
|
}
|
|
325
322
|
const orders = await client.getOrders(params.marketId, ['LIVE', 'MATCHED']);
|
|
326
323
|
return orders.map((o) => ({
|
|
@@ -36,6 +36,13 @@ function mapMarketToUnified(market) {
|
|
|
36
36
|
metadata: { clobTokenId: market.tokens.no },
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
+
// Limitless returns status='FUNDED' for active markets and expired=true
|
|
40
|
+
// when the market has ended. Map to the same canonical values Polymarket uses.
|
|
41
|
+
let status;
|
|
42
|
+
if (market.expired === true)
|
|
43
|
+
status = 'closed';
|
|
44
|
+
else if (market.status === 'FUNDED')
|
|
45
|
+
status = 'active';
|
|
39
46
|
const um = {
|
|
40
47
|
id: market.slug,
|
|
41
48
|
marketId: market.slug,
|
|
@@ -52,7 +59,8 @@ function mapMarketToUnified(market) {
|
|
|
52
59
|
url: `https://limitless.exchange/markets/${market.slug}`,
|
|
53
60
|
image: market.logo || `https://limitless.exchange/api/og?slug=${market.slug}`,
|
|
54
61
|
category: market.categories?.[0],
|
|
55
|
-
tags: market.tags || []
|
|
62
|
+
tags: market.tags || [],
|
|
63
|
+
status,
|
|
56
64
|
};
|
|
57
65
|
(0, market_utils_1.addBinaryOutcomes)(um);
|
|
58
66
|
return um;
|
|
@@ -120,8 +120,13 @@ async function fetchEventBySlug(slug, callApi) {
|
|
|
120
120
|
];
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
catch {
|
|
124
|
-
// fall through
|
|
123
|
+
catch (err) {
|
|
124
|
+
// A 404 means this slug is not a known tournament — fall through to
|
|
125
|
+
// the next lookup strategy. Any other error (network, auth, etc.)
|
|
126
|
+
// is a real failure and must propagate.
|
|
127
|
+
if (!(err instanceof Error) || !('status' in err) || err.status !== 404) {
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
125
130
|
}
|
|
126
131
|
// Finally try slug match against post.slug / post.url_title
|
|
127
132
|
const posts = await fetchPostPages(callApi, { with_cp: true, order_by: "-forecasts_count" }, 500);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { EventFetchParams, MarketFetchParams, OHLCVParams, PredictionMarketExchange, TradesParams } from '../../BaseExchange';
|
|
2
|
+
import { Balance, BuiltOrder, CreateOrderParams, Order, OrderBook, Position, PriceCandle, Trade, UnifiedEvent, UnifiedMarket, UserTrade } from '../../types';
|
|
3
|
+
export interface MockExchangeOptions {
|
|
4
|
+
marketCount?: number;
|
|
5
|
+
balance?: number;
|
|
6
|
+
orderLatencyMs?: number;
|
|
7
|
+
limitOrderMode?: 'immediate' | 'resting';
|
|
8
|
+
}
|
|
9
|
+
export declare class MockExchange extends PredictionMarketExchange {
|
|
10
|
+
private readonly _marketCount;
|
|
11
|
+
private readonly _initialBalance;
|
|
12
|
+
private readonly _orderLatencyMs;
|
|
13
|
+
private readonly _limitOrderMode;
|
|
14
|
+
private _generatedMarkets?;
|
|
15
|
+
private _generatedEvents?;
|
|
16
|
+
private _freeCash;
|
|
17
|
+
private _ordSeq;
|
|
18
|
+
private _orders;
|
|
19
|
+
private _lockedByBuy;
|
|
20
|
+
private _positions;
|
|
21
|
+
private _myTrades;
|
|
22
|
+
constructor(options?: MockExchangeOptions);
|
|
23
|
+
get name(): string;
|
|
24
|
+
private _locked;
|
|
25
|
+
private _bookMidPrice;
|
|
26
|
+
private _generateMarket;
|
|
27
|
+
private _buildMarkets;
|
|
28
|
+
private _buildEvents;
|
|
29
|
+
protected fetchMarketsImpl(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
|
|
30
|
+
protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
|
|
31
|
+
fetchOrderBook(id: string): Promise<OrderBook>;
|
|
32
|
+
fetchOHLCV(id: string, params: OHLCVParams): Promise<PriceCandle[]>;
|
|
33
|
+
fetchTrades(id: string, _params: TradesParams): Promise<Trade[]>;
|
|
34
|
+
fetchBalance(_address?: string): Promise<Balance[]>;
|
|
35
|
+
fetchPositions(_address?: string): Promise<Position[]>;
|
|
36
|
+
private _nextOrderId;
|
|
37
|
+
private _setPosition;
|
|
38
|
+
private _pushTrade;
|
|
39
|
+
private _applyInstantFill;
|
|
40
|
+
private _placeRestingLimit;
|
|
41
|
+
createOrder(params: CreateOrderParams): Promise<Order>;
|
|
42
|
+
fillOrder(orderId: string, amount?: number): Promise<Order>;
|
|
43
|
+
cancelOrder(orderId: string): Promise<Order>;
|
|
44
|
+
fetchOrder(orderId: string): Promise<Order>;
|
|
45
|
+
fetchOpenOrders(_marketId?: string): Promise<Order[]>;
|
|
46
|
+
fetchMyTrades(_params?: {
|
|
47
|
+
outcomeId?: string;
|
|
48
|
+
marketId?: string;
|
|
49
|
+
}): Promise<UserTrade[]>;
|
|
50
|
+
fetchClosedOrders(): Promise<Order[]>;
|
|
51
|
+
fetchAllOrders(): Promise<Order[]>;
|
|
52
|
+
buildOrder(params: CreateOrderParams): Promise<BuiltOrder>;
|
|
53
|
+
submitOrder(built: BuiltOrder): Promise<Order>;
|
|
54
|
+
reset(): void;
|
|
55
|
+
}
|