pmxt-core 2.21.2 → 2.22.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/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/opinion/api.d.ts +412 -0
- package/dist/exchanges/opinion/api.js +477 -0
- package/dist/exchanges/opinion/auth.d.ts +15 -0
- package/dist/exchanges/opinion/auth.js +107 -0
- package/dist/exchanges/opinion/config.d.ts +5 -0
- package/dist/exchanges/opinion/config.js +9 -0
- package/dist/exchanges/opinion/errors.d.ts +8 -0
- package/dist/exchanges/opinion/errors.js +39 -0
- package/dist/exchanges/opinion/fetcher.d.ts +203 -0
- package/dist/exchanges/opinion/fetcher.js +300 -0
- package/dist/exchanges/opinion/index.d.ts +69 -0
- package/dist/exchanges/opinion/index.js +406 -0
- package/dist/exchanges/opinion/normalizer.d.ts +19 -0
- package/dist/exchanges/opinion/normalizer.js +231 -0
- package/dist/exchanges/opinion/utils.d.ts +23 -0
- package/dist/exchanges/opinion/utils.js +95 -0
- package/dist/exchanges/opinion/websocket.d.ts +83 -0
- package/dist/exchanges/opinion/websocket.js +365 -0
- 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/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -2
- package/dist/server/app.js +8 -0
- package/package.json +5 -4
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OPINION_ORDER_STATUS = exports.OPINION_MARKET_STATUS = void 0;
|
|
4
|
+
exports.mapStatusToOpinion = mapStatusToOpinion;
|
|
5
|
+
exports.mapSortToOpinion = mapSortToOpinion;
|
|
6
|
+
exports.mapIntervalToOpinion = mapIntervalToOpinion;
|
|
7
|
+
exports.mapOrderStatus = mapOrderStatus;
|
|
8
|
+
exports.parseNumStr = parseNumStr;
|
|
9
|
+
exports.toMillis = toMillis;
|
|
10
|
+
exports.intervalToMs = intervalToMs;
|
|
11
|
+
// Opinion market status codes
|
|
12
|
+
exports.OPINION_MARKET_STATUS = {
|
|
13
|
+
CREATED: 1,
|
|
14
|
+
ACTIVATED: 2,
|
|
15
|
+
RESOLVING: 3,
|
|
16
|
+
RESOLVED: 4,
|
|
17
|
+
FAILED: 5,
|
|
18
|
+
DELETED: 6,
|
|
19
|
+
};
|
|
20
|
+
// Opinion order status codes
|
|
21
|
+
exports.OPINION_ORDER_STATUS = {
|
|
22
|
+
PENDING: 1,
|
|
23
|
+
FILLED: 2,
|
|
24
|
+
CANCELED: 3,
|
|
25
|
+
EXPIRED: 4,
|
|
26
|
+
FAILED: 5,
|
|
27
|
+
};
|
|
28
|
+
// Map pmxt status strings to Opinion API status filter values
|
|
29
|
+
function mapStatusToOpinion(status) {
|
|
30
|
+
switch (status) {
|
|
31
|
+
case 'active': return 'activated';
|
|
32
|
+
case 'closed':
|
|
33
|
+
case 'inactive': return 'resolved';
|
|
34
|
+
default: return undefined; // 'all' -> no filter
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Map pmxt sort to Opinion sortBy integer
|
|
38
|
+
function mapSortToOpinion(sort) {
|
|
39
|
+
switch (sort) {
|
|
40
|
+
case 'volume': return 5; // volume24h desc
|
|
41
|
+
case 'liquidity': return 3; // volume desc (closest proxy)
|
|
42
|
+
case 'newest': return 1; // new
|
|
43
|
+
default: return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Map pmxt CandleInterval to Opinion interval string.
|
|
47
|
+
// Opinion supports: 1m, 1h, 1d, 1w, max
|
|
48
|
+
function mapIntervalToOpinion(interval) {
|
|
49
|
+
const mapping = {
|
|
50
|
+
'1m': '1m',
|
|
51
|
+
'5m': '1m', // closest available
|
|
52
|
+
'15m': '1h', // closest available
|
|
53
|
+
'1h': '1h',
|
|
54
|
+
'6h': '1d', // closest available
|
|
55
|
+
'1d': '1d',
|
|
56
|
+
};
|
|
57
|
+
return mapping[interval];
|
|
58
|
+
}
|
|
59
|
+
// Map Opinion order status code to pmxt status string
|
|
60
|
+
function mapOrderStatus(status) {
|
|
61
|
+
switch (status) {
|
|
62
|
+
case exports.OPINION_ORDER_STATUS.PENDING: return 'pending';
|
|
63
|
+
case exports.OPINION_ORDER_STATUS.FILLED: return 'filled';
|
|
64
|
+
case exports.OPINION_ORDER_STATUS.CANCELED: return 'cancelled';
|
|
65
|
+
case exports.OPINION_ORDER_STATUS.EXPIRED: return 'cancelled'; // expired -> cancelled
|
|
66
|
+
case exports.OPINION_ORDER_STATUS.FAILED: return 'rejected'; // failed -> rejected
|
|
67
|
+
default: return 'pending';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Parse string number safely, returning 0 for invalid/empty
|
|
71
|
+
function parseNumStr(value) {
|
|
72
|
+
if (!value)
|
|
73
|
+
return 0;
|
|
74
|
+
const parsed = parseFloat(value);
|
|
75
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
76
|
+
}
|
|
77
|
+
// Convert a raw timestamp to milliseconds.
|
|
78
|
+
// If the value is under 10_000_000_000 it is assumed to be in seconds.
|
|
79
|
+
function toMillis(ts) {
|
|
80
|
+
if (!ts)
|
|
81
|
+
return 0;
|
|
82
|
+
return ts < 10_000_000_000 ? ts * 1000 : ts;
|
|
83
|
+
}
|
|
84
|
+
// Return the bucket size in milliseconds for a given CandleInterval
|
|
85
|
+
function intervalToMs(interval) {
|
|
86
|
+
const map = {
|
|
87
|
+
'1m': 60_000,
|
|
88
|
+
'5m': 300_000,
|
|
89
|
+
'15m': 900_000,
|
|
90
|
+
'1h': 3_600_000,
|
|
91
|
+
'6h': 21_600_000,
|
|
92
|
+
'1d': 86_400_000,
|
|
93
|
+
};
|
|
94
|
+
return map[interval];
|
|
95
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { OrderBook, Trade } from "../../types";
|
|
2
|
+
export interface OpinionWebSocketConfig {
|
|
3
|
+
/** Reconnection interval in milliseconds (default: 5000) */
|
|
4
|
+
reconnectIntervalMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Opinion Trade WebSocket implementation for real-time order book and trade streaming.
|
|
8
|
+
* Follows CCXT Pro-style async iterator pattern.
|
|
9
|
+
*
|
|
10
|
+
* Connection: wss://ws.opinion.trade?apikey={API_KEY}
|
|
11
|
+
* Channels: market.depth.diff, market.last.trade
|
|
12
|
+
*/
|
|
13
|
+
export declare class OpinionWebSocket {
|
|
14
|
+
private ws?;
|
|
15
|
+
private readonly wsUrl;
|
|
16
|
+
private readonly config;
|
|
17
|
+
private readonly orderBookResolvers;
|
|
18
|
+
private readonly tradeResolvers;
|
|
19
|
+
private readonly orderBooks;
|
|
20
|
+
private readonly subscribedDepthMarketIds;
|
|
21
|
+
private readonly subscribedTradeMarketIds;
|
|
22
|
+
private isConnecting;
|
|
23
|
+
private isConnected;
|
|
24
|
+
private isTerminated;
|
|
25
|
+
private reconnectTimer?;
|
|
26
|
+
private connectionPromise?;
|
|
27
|
+
constructor(wsUrl: string, config?: OpinionWebSocketConfig);
|
|
28
|
+
private connect;
|
|
29
|
+
private scheduleReconnect;
|
|
30
|
+
private resubscribeAll;
|
|
31
|
+
private sendSubscribe;
|
|
32
|
+
private sendUnsubscribe;
|
|
33
|
+
private handleMessage;
|
|
34
|
+
/**
|
|
35
|
+
* Handle an incremental orderbook update from Opinion.
|
|
36
|
+
*
|
|
37
|
+
* Example message:
|
|
38
|
+
* {
|
|
39
|
+
* "marketId": 2764,
|
|
40
|
+
* "tokenId": "191204...",
|
|
41
|
+
* "outcomeSide": 1,
|
|
42
|
+
* "side": "bids",
|
|
43
|
+
* "price": "0.2",
|
|
44
|
+
* "size": "50",
|
|
45
|
+
* "msgType": "market.depth.diff"
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* `side` is "bids" or "asks".
|
|
49
|
+
* `size` is the new absolute size at that price level (0 means remove).
|
|
50
|
+
*/
|
|
51
|
+
private handleDepthDiff;
|
|
52
|
+
private resolveOrderBook;
|
|
53
|
+
/**
|
|
54
|
+
* Handle a last-trade message from Opinion.
|
|
55
|
+
*
|
|
56
|
+
* Example message:
|
|
57
|
+
* {
|
|
58
|
+
* "tokenId": "191204...",
|
|
59
|
+
* "side": "Buy",
|
|
60
|
+
* "outcomeSide": 1,
|
|
61
|
+
* "price": "0.85",
|
|
62
|
+
* "shares": "10",
|
|
63
|
+
* "amount": "8.5",
|
|
64
|
+
* "marketId": 2764,
|
|
65
|
+
* "msgType": "market.last.trade"
|
|
66
|
+
* }
|
|
67
|
+
*/
|
|
68
|
+
private handleLastTrade;
|
|
69
|
+
/**
|
|
70
|
+
* Watch orderbook updates for a given binary market.
|
|
71
|
+
* Returns a promise that resolves on the next orderbook update.
|
|
72
|
+
*/
|
|
73
|
+
watchOrderBook(marketId: number): Promise<OrderBook>;
|
|
74
|
+
/**
|
|
75
|
+
* Watch trade updates for a given binary market.
|
|
76
|
+
* Returns a promise that resolves on the next trade.
|
|
77
|
+
*/
|
|
78
|
+
watchTrades(marketId: number): Promise<Trade[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Close the WebSocket connection and reject all pending promises.
|
|
81
|
+
*/
|
|
82
|
+
close(): Promise<void>;
|
|
83
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
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.OpinionWebSocket = void 0;
|
|
7
|
+
const ws_1 = __importDefault(require("ws"));
|
|
8
|
+
/**
|
|
9
|
+
* Opinion Trade WebSocket implementation for real-time order book and trade streaming.
|
|
10
|
+
* Follows CCXT Pro-style async iterator pattern.
|
|
11
|
+
*
|
|
12
|
+
* Connection: wss://ws.opinion.trade?apikey={API_KEY}
|
|
13
|
+
* Channels: market.depth.diff, market.last.trade
|
|
14
|
+
*/
|
|
15
|
+
class OpinionWebSocket {
|
|
16
|
+
ws;
|
|
17
|
+
wsUrl;
|
|
18
|
+
config;
|
|
19
|
+
orderBookResolvers = new Map();
|
|
20
|
+
tradeResolvers = new Map();
|
|
21
|
+
orderBooks = new Map();
|
|
22
|
+
subscribedDepthMarketIds = new Set();
|
|
23
|
+
subscribedTradeMarketIds = new Set();
|
|
24
|
+
isConnecting = false;
|
|
25
|
+
isConnected = false;
|
|
26
|
+
isTerminated = false;
|
|
27
|
+
reconnectTimer;
|
|
28
|
+
connectionPromise;
|
|
29
|
+
constructor(wsUrl, config = {}) {
|
|
30
|
+
this.wsUrl = wsUrl;
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Connection management
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
async connect() {
|
|
37
|
+
if (this.isConnected) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (this.isTerminated) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (this.connectionPromise) {
|
|
44
|
+
return this.connectionPromise;
|
|
45
|
+
}
|
|
46
|
+
this.isConnecting = true;
|
|
47
|
+
this.connectionPromise = new Promise((resolve, reject) => {
|
|
48
|
+
try {
|
|
49
|
+
this.ws = new ws_1.default(this.wsUrl);
|
|
50
|
+
this.ws.on("open", () => {
|
|
51
|
+
this.isConnected = true;
|
|
52
|
+
this.isConnecting = false;
|
|
53
|
+
this.connectionPromise = undefined;
|
|
54
|
+
console.log("Opinion WebSocket connected");
|
|
55
|
+
this.resubscribeAll();
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
this.ws.on("message", (data) => {
|
|
59
|
+
try {
|
|
60
|
+
const message = JSON.parse(data.toString());
|
|
61
|
+
this.handleMessage(message);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error("Error parsing Opinion WebSocket message:", error);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
this.ws.on("error", (error) => {
|
|
68
|
+
console.error("Opinion WebSocket error:", error);
|
|
69
|
+
this.isConnecting = false;
|
|
70
|
+
this.connectionPromise = undefined;
|
|
71
|
+
reject(error);
|
|
72
|
+
});
|
|
73
|
+
this.ws.on("close", () => {
|
|
74
|
+
if (!this.isTerminated) {
|
|
75
|
+
console.log("Opinion WebSocket closed, scheduling reconnect");
|
|
76
|
+
this.scheduleReconnect();
|
|
77
|
+
}
|
|
78
|
+
this.isConnected = false;
|
|
79
|
+
this.isConnecting = false;
|
|
80
|
+
this.connectionPromise = undefined;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.isConnecting = false;
|
|
85
|
+
this.connectionPromise = undefined;
|
|
86
|
+
reject(error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return this.connectionPromise;
|
|
90
|
+
}
|
|
91
|
+
scheduleReconnect() {
|
|
92
|
+
if (this.isTerminated) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (this.reconnectTimer) {
|
|
96
|
+
clearTimeout(this.reconnectTimer);
|
|
97
|
+
}
|
|
98
|
+
this.reconnectTimer = setTimeout(() => {
|
|
99
|
+
console.log("Attempting to reconnect Opinion WebSocket...");
|
|
100
|
+
this.connect().catch(console.error);
|
|
101
|
+
}, this.config.reconnectIntervalMs ?? 5000);
|
|
102
|
+
}
|
|
103
|
+
resubscribeAll() {
|
|
104
|
+
for (const marketId of this.subscribedDepthMarketIds) {
|
|
105
|
+
this.sendSubscribe("market.depth.diff", marketId);
|
|
106
|
+
}
|
|
107
|
+
for (const marketId of this.subscribedTradeMarketIds) {
|
|
108
|
+
this.sendSubscribe("market.last.trade", marketId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Subscribe / Unsubscribe
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
sendSubscribe(channel, marketId) {
|
|
115
|
+
if (!this.ws || !this.isConnected) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.ws.send(JSON.stringify({ action: "SUBSCRIBE", channel, marketId }));
|
|
119
|
+
}
|
|
120
|
+
sendUnsubscribe(channel, marketId) {
|
|
121
|
+
if (!this.ws || !this.isConnected) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.ws.send(JSON.stringify({ action: "UNSUBSCRIBE", channel, marketId }));
|
|
125
|
+
}
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Message handling
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
handleMessage(message) {
|
|
130
|
+
const msgType = message.msgType;
|
|
131
|
+
if (!msgType) {
|
|
132
|
+
// Could be a subscription confirmation or unknown payload
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
switch (msgType) {
|
|
136
|
+
case "market.depth.diff":
|
|
137
|
+
this.handleDepthDiff(message);
|
|
138
|
+
break;
|
|
139
|
+
case "market.last.trade":
|
|
140
|
+
this.handleLastTrade(message);
|
|
141
|
+
break;
|
|
142
|
+
default:
|
|
143
|
+
// Ignore unknown message types
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Orderbook depth diff
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
/**
|
|
151
|
+
* Handle an incremental orderbook update from Opinion.
|
|
152
|
+
*
|
|
153
|
+
* Example message:
|
|
154
|
+
* {
|
|
155
|
+
* "marketId": 2764,
|
|
156
|
+
* "tokenId": "191204...",
|
|
157
|
+
* "outcomeSide": 1,
|
|
158
|
+
* "side": "bids",
|
|
159
|
+
* "price": "0.2",
|
|
160
|
+
* "size": "50",
|
|
161
|
+
* "msgType": "market.depth.diff"
|
|
162
|
+
* }
|
|
163
|
+
*
|
|
164
|
+
* `side` is "bids" or "asks".
|
|
165
|
+
* `size` is the new absolute size at that price level (0 means remove).
|
|
166
|
+
*/
|
|
167
|
+
handleDepthDiff(data) {
|
|
168
|
+
const marketId = data.marketId;
|
|
169
|
+
if (marketId === undefined) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const price = parseFloat(data.price);
|
|
173
|
+
const size = parseFloat(data.size);
|
|
174
|
+
const side = data.side; // "bids" | "asks"
|
|
175
|
+
if (isNaN(price)) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const existing = this.orderBooks.get(marketId);
|
|
179
|
+
const book = existing
|
|
180
|
+
? { ...existing }
|
|
181
|
+
: { bids: [], asks: [], timestamp: Date.now() };
|
|
182
|
+
if (side === "bids") {
|
|
183
|
+
book.bids = applyLevelUpdate(book.bids, price, size, "desc");
|
|
184
|
+
}
|
|
185
|
+
else if (side === "asks") {
|
|
186
|
+
book.asks = applyLevelUpdate(book.asks, price, size, "asc");
|
|
187
|
+
}
|
|
188
|
+
book.timestamp = Date.now();
|
|
189
|
+
this.orderBooks.set(marketId, book);
|
|
190
|
+
this.resolveOrderBook(marketId, book);
|
|
191
|
+
}
|
|
192
|
+
resolveOrderBook(marketId, orderBook) {
|
|
193
|
+
const resolvers = this.orderBookResolvers.get(marketId);
|
|
194
|
+
if (resolvers && resolvers.length > 0) {
|
|
195
|
+
const snapshot = {
|
|
196
|
+
bids: [...orderBook.bids],
|
|
197
|
+
asks: [...orderBook.asks],
|
|
198
|
+
timestamp: orderBook.timestamp,
|
|
199
|
+
};
|
|
200
|
+
resolvers.forEach((r) => r.resolve(snapshot));
|
|
201
|
+
this.orderBookResolvers.set(marketId, []);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Trade stream
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
/**
|
|
208
|
+
* Handle a last-trade message from Opinion.
|
|
209
|
+
*
|
|
210
|
+
* Example message:
|
|
211
|
+
* {
|
|
212
|
+
* "tokenId": "191204...",
|
|
213
|
+
* "side": "Buy",
|
|
214
|
+
* "outcomeSide": 1,
|
|
215
|
+
* "price": "0.85",
|
|
216
|
+
* "shares": "10",
|
|
217
|
+
* "amount": "8.5",
|
|
218
|
+
* "marketId": 2764,
|
|
219
|
+
* "msgType": "market.last.trade"
|
|
220
|
+
* }
|
|
221
|
+
*/
|
|
222
|
+
handleLastTrade(data) {
|
|
223
|
+
const marketId = data.marketId;
|
|
224
|
+
if (marketId === undefined) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const timestamp = Date.now();
|
|
228
|
+
const price = parseFloat(data.price);
|
|
229
|
+
const shares = parseFloat(data.shares);
|
|
230
|
+
const trade = {
|
|
231
|
+
id: `${timestamp}-${Math.random().toString(36).slice(2, 10)}`,
|
|
232
|
+
timestamp,
|
|
233
|
+
price: isNaN(price) ? 0 : price,
|
|
234
|
+
amount: isNaN(shares) ? 0 : shares,
|
|
235
|
+
side: mapTradeSide(data.side),
|
|
236
|
+
};
|
|
237
|
+
const resolvers = this.tradeResolvers.get(marketId);
|
|
238
|
+
if (resolvers && resolvers.length > 0) {
|
|
239
|
+
resolvers.forEach((r) => r.resolve([trade]));
|
|
240
|
+
this.tradeResolvers.set(marketId, []);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Public API
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
/**
|
|
247
|
+
* Watch orderbook updates for a given binary market.
|
|
248
|
+
* Returns a promise that resolves on the next orderbook update.
|
|
249
|
+
*/
|
|
250
|
+
async watchOrderBook(marketId) {
|
|
251
|
+
if (!this.isConnected) {
|
|
252
|
+
await this.connect();
|
|
253
|
+
}
|
|
254
|
+
if (!this.subscribedDepthMarketIds.has(marketId)) {
|
|
255
|
+
this.subscribedDepthMarketIds.add(marketId);
|
|
256
|
+
this.sendSubscribe("market.depth.diff", marketId);
|
|
257
|
+
}
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
if (!this.orderBookResolvers.has(marketId)) {
|
|
260
|
+
this.orderBookResolvers.set(marketId, []);
|
|
261
|
+
}
|
|
262
|
+
this.orderBookResolvers.get(marketId).push({ resolve, reject });
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Watch trade updates for a given binary market.
|
|
267
|
+
* Returns a promise that resolves on the next trade.
|
|
268
|
+
*/
|
|
269
|
+
async watchTrades(marketId) {
|
|
270
|
+
if (!this.isConnected) {
|
|
271
|
+
await this.connect();
|
|
272
|
+
}
|
|
273
|
+
if (!this.subscribedTradeMarketIds.has(marketId)) {
|
|
274
|
+
this.subscribedTradeMarketIds.add(marketId);
|
|
275
|
+
this.sendSubscribe("market.last.trade", marketId);
|
|
276
|
+
}
|
|
277
|
+
return new Promise((resolve, reject) => {
|
|
278
|
+
if (!this.tradeResolvers.has(marketId)) {
|
|
279
|
+
this.tradeResolvers.set(marketId, []);
|
|
280
|
+
}
|
|
281
|
+
this.tradeResolvers.get(marketId).push({ resolve, reject });
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Close the WebSocket connection and reject all pending promises.
|
|
286
|
+
*/
|
|
287
|
+
async close() {
|
|
288
|
+
this.isTerminated = true;
|
|
289
|
+
if (this.reconnectTimer) {
|
|
290
|
+
clearTimeout(this.reconnectTimer);
|
|
291
|
+
this.reconnectTimer = undefined;
|
|
292
|
+
}
|
|
293
|
+
// Unsubscribe from all channels before closing
|
|
294
|
+
for (const marketId of this.subscribedDepthMarketIds) {
|
|
295
|
+
this.sendUnsubscribe("market.depth.diff", marketId);
|
|
296
|
+
}
|
|
297
|
+
for (const marketId of this.subscribedTradeMarketIds) {
|
|
298
|
+
this.sendUnsubscribe("market.last.trade", marketId);
|
|
299
|
+
}
|
|
300
|
+
// Reject all pending resolvers
|
|
301
|
+
this.orderBookResolvers.forEach((resolvers, marketId) => {
|
|
302
|
+
resolvers.forEach((r) => r.reject(new Error(`WebSocket closed for market ${marketId}`)));
|
|
303
|
+
});
|
|
304
|
+
this.orderBookResolvers.clear();
|
|
305
|
+
this.tradeResolvers.forEach((resolvers, marketId) => {
|
|
306
|
+
resolvers.forEach((r) => r.reject(new Error(`WebSocket closed for market ${marketId}`)));
|
|
307
|
+
});
|
|
308
|
+
this.tradeResolvers.clear();
|
|
309
|
+
if (this.ws) {
|
|
310
|
+
const ws = this.ws;
|
|
311
|
+
this.ws = undefined;
|
|
312
|
+
if (ws.readyState !== ws_1.default.CLOSED &&
|
|
313
|
+
ws.readyState !== ws_1.default.CLOSING) {
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
ws.once("close", () => {
|
|
316
|
+
this.isConnected = false;
|
|
317
|
+
this.isConnecting = false;
|
|
318
|
+
resolve();
|
|
319
|
+
});
|
|
320
|
+
ws.close();
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
this.isConnected = false;
|
|
325
|
+
this.isConnecting = false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
exports.OpinionWebSocket = OpinionWebSocket;
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Pure helpers (module-private)
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
/**
|
|
333
|
+
* Apply an absolute-size level update to one side of the book.
|
|
334
|
+
* A size of 0 (or NaN) removes the level. Returns a new sorted array.
|
|
335
|
+
*/
|
|
336
|
+
function applyLevelUpdate(levels, price, size, sortOrder) {
|
|
337
|
+
const updated = levels.filter((l) => Math.abs(l.price - price) >= 0.0001);
|
|
338
|
+
if (!isNaN(size) && size > 0) {
|
|
339
|
+
updated.push({ price, size });
|
|
340
|
+
}
|
|
341
|
+
if (sortOrder === "desc") {
|
|
342
|
+
updated.sort((a, b) => b.price - a.price);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
updated.sort((a, b) => a.price - b.price);
|
|
346
|
+
}
|
|
347
|
+
return updated;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Map Opinion's trade side string to our unified type.
|
|
351
|
+
* Opinion uses "Buy" | "Sell" | "Split" | "Merge".
|
|
352
|
+
*/
|
|
353
|
+
function mapTradeSide(side) {
|
|
354
|
+
if (!side) {
|
|
355
|
+
return "unknown";
|
|
356
|
+
}
|
|
357
|
+
const lower = side.toLowerCase();
|
|
358
|
+
if (lower === "buy") {
|
|
359
|
+
return "buy";
|
|
360
|
+
}
|
|
361
|
+
if (lower === "sell") {
|
|
362
|
+
return "sell";
|
|
363
|
+
}
|
|
364
|
+
return "unknown";
|
|
365
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
3
|
-
* Generated at: 2026-03-
|
|
3
|
+
* Generated at: 2026-03-22T18:41:21.601Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketClobSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketClobSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
6
|
-
* Generated at: 2026-03-
|
|
6
|
+
* Generated at: 2026-03-22T18:41:21.601Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketClobSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
3
|
-
* Generated at: 2026-03-
|
|
3
|
+
* Generated at: 2026-03-22T18:41:21.621Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketDataSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketDataSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
6
|
-
* Generated at: 2026-03-
|
|
6
|
+
* Generated at: 2026-03-22T18:41:21.621Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketDataSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
3
|
-
* Generated at: 2026-03-
|
|
3
|
+
* Generated at: 2026-03-22T18:41:21.617Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketGammaSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketGammaSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
6
|
-
* Generated at: 2026-03-
|
|
6
|
+
* Generated at: 2026-03-22T18:41:21.617Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketGammaSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
|
|
3
|
-
* Generated at: 2026-03-
|
|
3
|
+
* Generated at: 2026-03-22T18:41:21.653Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const probableApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.probableApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
|
|
6
|
-
* Generated at: 2026-03-
|
|
6
|
+
* Generated at: 2026-03-22T18:41:21.653Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.probableApiSpec = {
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './exchanges/kalshi-demo';
|
|
|
10
10
|
export * from './exchanges/probable';
|
|
11
11
|
export * from './exchanges/baozi';
|
|
12
12
|
export * from './exchanges/myriad';
|
|
13
|
+
export * from './exchanges/opinion';
|
|
13
14
|
export * from './server/app';
|
|
14
15
|
export * from './server/utils/port-manager';
|
|
15
16
|
export * from './server/utils/lock-file';
|
|
@@ -20,6 +21,7 @@ import { KalshiDemoExchange } from './exchanges/kalshi-demo';
|
|
|
20
21
|
import { ProbableExchange } from './exchanges/probable';
|
|
21
22
|
import { BaoziExchange } from './exchanges/baozi';
|
|
22
23
|
import { MyriadExchange } from './exchanges/myriad';
|
|
24
|
+
import { OpinionExchange } from './exchanges/opinion';
|
|
23
25
|
declare const pmxt: {
|
|
24
26
|
Polymarket: typeof PolymarketExchange;
|
|
25
27
|
Limitless: typeof LimitlessExchange;
|
|
@@ -28,6 +30,7 @@ declare const pmxt: {
|
|
|
28
30
|
Probable: typeof ProbableExchange;
|
|
29
31
|
Baozi: typeof BaoziExchange;
|
|
30
32
|
Myriad: typeof MyriadExchange;
|
|
33
|
+
Opinion: typeof OpinionExchange;
|
|
31
34
|
};
|
|
32
35
|
export declare const Polymarket: typeof PolymarketExchange;
|
|
33
36
|
export declare const Limitless: typeof LimitlessExchange;
|
|
@@ -36,4 +39,5 @@ export declare const KalshiDemo: typeof KalshiDemoExchange;
|
|
|
36
39
|
export declare const Probable: typeof ProbableExchange;
|
|
37
40
|
export declare const Baozi: typeof BaoziExchange;
|
|
38
41
|
export declare const Myriad: typeof MyriadExchange;
|
|
42
|
+
export declare const Opinion: typeof OpinionExchange;
|
|
39
43
|
export default pmxt;
|