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
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MockExchange = void 0;
|
|
4
|
+
const BaseExchange_1 = require("../../BaseExchange");
|
|
5
|
+
const seededRng_1 = require("./seededRng");
|
|
6
|
+
const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
|
|
7
|
+
const round = (n, decimals = 3) => parseFloat(n.toFixed(decimals));
|
|
8
|
+
const CATEGORIES = ['Politics', 'Sports', 'Crypto', 'Finance', 'Science', 'Entertainment', 'Tech', 'World'];
|
|
9
|
+
const LOREM = `lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor
|
|
10
|
+
incididunt ut labore et dolore magna aliqua enim ad minim veniam quis nostrud
|
|
11
|
+
exercitation ullamco laboris nisi aliquip ex ea commodo consequat duis aute
|
|
12
|
+
irure dolor reprehenderit voluptate velit esse cillum dolore fugiat nulla
|
|
13
|
+
pariatur excepteur sint occaecat cupidatat non proident mollit anim id est
|
|
14
|
+
laborum sollicitudin ultricies tellus pellentesque curabitur elementum
|
|
15
|
+
hendrerit metus aenean pharetra magna accumsan`.split(/\s+/);
|
|
16
|
+
const ADJECTIVES = [
|
|
17
|
+
'rapid', 'quiet', 'major', 'sunny', 'clever', 'brisk', 'gentle', 'fierce', 'distant', 'noble', 'hollow', 'fuzzy',
|
|
18
|
+
];
|
|
19
|
+
const NOUNS = [
|
|
20
|
+
'event', 'market', 'race', 'trend', 'signal', 'storm', 'ledger', 'summit', 'forum', 'arena', 'harbor', 'vessel',
|
|
21
|
+
];
|
|
22
|
+
const FIRST_NAMES = ['Alex', 'Jordan', 'Sam', 'Riley', 'Morgan', 'Quinn', 'Avery', 'Parker', 'Drew', 'Sage', 'Jules', 'Remy'];
|
|
23
|
+
const LAST_NAMES = ['Nguyen', 'Garcia', 'Patel', 'Silva', 'Berg', 'Wright', 'Choi', 'Diaz', 'Reed', 'Stone', 'Singh', 'Cole'];
|
|
24
|
+
const BINARY_TEMPLATES = [
|
|
25
|
+
'Will {name} win the {year} {event}?',
|
|
26
|
+
'Will {country} GDP grow above {pct}% in {year}?',
|
|
27
|
+
'Will {asset} reach ${price}k by end of {year}?',
|
|
28
|
+
'Will {name} be elected {role}?',
|
|
29
|
+
'Will {company} IPO before {month} {year}?',
|
|
30
|
+
'Will {name} announce {product} at {event}?',
|
|
31
|
+
'Will {country} join {org} by {year}?',
|
|
32
|
+
'Will {sport} season start on time in {year}?',
|
|
33
|
+
];
|
|
34
|
+
const MULTI_TEMPLATES = [
|
|
35
|
+
'Who will win the {year} {event}?',
|
|
36
|
+
'Which party wins the {year} {country} election?',
|
|
37
|
+
'What will {asset} price be at end of {year}?',
|
|
38
|
+
];
|
|
39
|
+
const CANDIDATES = [
|
|
40
|
+
['Alice Johnson', 'Bob Smith', 'Carol Williams', 'David Brown'],
|
|
41
|
+
['Party A', 'Party B', 'Party C', 'Independent'],
|
|
42
|
+
['Below $50k', '$50k-$100k', '$100k-$150k', 'Above $150k'],
|
|
43
|
+
['Q1', 'Q2', 'Q3', 'Q4'],
|
|
44
|
+
];
|
|
45
|
+
const ASSETS = ['BTC', 'ETH', 'SOL', 'Gold'];
|
|
46
|
+
const ROLES = ['President', 'CEO', 'Governor', 'Mayor'];
|
|
47
|
+
const ORGS = ['NATO', 'EU', 'BRICS', 'G7'];
|
|
48
|
+
const SPORTS = ['NFL', 'NBA', 'MLB', 'NHL'];
|
|
49
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
50
|
+
const PICK_WORDS = {
|
|
51
|
+
company: ['Acme', 'Nimbus', 'Vanta', 'Quanta', 'Helio', 'Orbit', 'Vector', 'Prism', 'Axiom', 'Cipher'],
|
|
52
|
+
product: ['Widget', 'Console', 'Platform', 'Suite', 'Cloud', 'Network', 'Stack', 'Engine', 'Hub', 'Node'],
|
|
53
|
+
month: [...MONTHS],
|
|
54
|
+
};
|
|
55
|
+
function loremWords(r, count) {
|
|
56
|
+
const w = [];
|
|
57
|
+
for (let i = 0; i < count; i++)
|
|
58
|
+
w.push(r.pick(LOREM));
|
|
59
|
+
return w.join(' ');
|
|
60
|
+
}
|
|
61
|
+
const rng = (seed) => new seededRng_1.SeededRng(seed);
|
|
62
|
+
class MockExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
63
|
+
_marketCount;
|
|
64
|
+
_initialBalance;
|
|
65
|
+
_orderLatencyMs;
|
|
66
|
+
_limitOrderMode;
|
|
67
|
+
_generatedMarkets;
|
|
68
|
+
_generatedEvents;
|
|
69
|
+
_freeCash;
|
|
70
|
+
_ordSeq = 0;
|
|
71
|
+
_orders = new Map();
|
|
72
|
+
_lockedByBuy = new Map();
|
|
73
|
+
_positions = new Map();
|
|
74
|
+
_myTrades = [];
|
|
75
|
+
constructor(options) {
|
|
76
|
+
super();
|
|
77
|
+
this._marketCount = options?.marketCount ?? 50;
|
|
78
|
+
this._initialBalance = options?.balance ?? 1000;
|
|
79
|
+
this._freeCash = this._initialBalance;
|
|
80
|
+
this._orderLatencyMs = options?.orderLatencyMs ?? 100;
|
|
81
|
+
this._limitOrderMode = options?.limitOrderMode ?? 'immediate';
|
|
82
|
+
}
|
|
83
|
+
get name() {
|
|
84
|
+
return 'Mock';
|
|
85
|
+
}
|
|
86
|
+
_locked() {
|
|
87
|
+
let s = 0;
|
|
88
|
+
for (const v of this._lockedByBuy.values())
|
|
89
|
+
s += v;
|
|
90
|
+
return s;
|
|
91
|
+
}
|
|
92
|
+
_bookMidPrice(outcomeId) {
|
|
93
|
+
const r = new seededRng_1.SeededRng(outcomeId);
|
|
94
|
+
return round(r.float(0.1, 0.9), 3);
|
|
95
|
+
}
|
|
96
|
+
_generateMarket(seed, eventId, isBinary) {
|
|
97
|
+
const f = rng(seed);
|
|
98
|
+
const year = new Date().getFullYear() + f.int(0, 2);
|
|
99
|
+
const category = f.pick(CATEGORIES);
|
|
100
|
+
let title;
|
|
101
|
+
if (isBinary) {
|
|
102
|
+
const t = f.pick(BINARY_TEMPLATES);
|
|
103
|
+
const country = f.pick(NOUNS) + f.pick(['ia', 'land', 'stan']);
|
|
104
|
+
title = t
|
|
105
|
+
.replace('{name}', `${f.pick(FIRST_NAMES)} ${f.pick(LAST_NAMES)}`)
|
|
106
|
+
.replace('{year}', String(year))
|
|
107
|
+
.replace('{event}', f.pick(NOUNS))
|
|
108
|
+
.replace('{country}', country)
|
|
109
|
+
.replace('{pct}', String(f.int(1, 8)))
|
|
110
|
+
.replace('{asset}', f.pick(ASSETS))
|
|
111
|
+
.replace('{price}', String(f.int(50, 500)))
|
|
112
|
+
.replace('{role}', f.pick(ROLES))
|
|
113
|
+
.replace('{company}', f.pick(PICK_WORDS.company))
|
|
114
|
+
.replace('{month}', f.pick(MONTHS))
|
|
115
|
+
.replace('{product}', f.pick(PICK_WORDS.product))
|
|
116
|
+
.replace('{org}', f.pick(ORGS))
|
|
117
|
+
.replace('{sport}', f.pick(SPORTS));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
title = f
|
|
121
|
+
.pick(MULTI_TEMPLATES)
|
|
122
|
+
.replace('{name}', `${f.pick(FIRST_NAMES)} ${f.pick(LAST_NAMES)}`)
|
|
123
|
+
.replace('{year}', String(year))
|
|
124
|
+
.replace('{event}', f.pick(NOUNS))
|
|
125
|
+
.replace('{country}', f.pick(NOUNS) + f.pick(['ia', 'land']))
|
|
126
|
+
.replace('{asset}', f.pick(ASSETS));
|
|
127
|
+
}
|
|
128
|
+
const resolutionDate = new Date(Date.now() + f.int(30, 800) * 86_400_000);
|
|
129
|
+
const volume24h = round(f.float(0, 500_000), 2);
|
|
130
|
+
const volume = round(volume24h * f.float(1, 50), 2);
|
|
131
|
+
const liquidity = round(f.float(500, 200_000), 2);
|
|
132
|
+
const openInterest = round(f.float(100, 80_000), 2);
|
|
133
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 60);
|
|
134
|
+
const marketId = `mock-${seed}`;
|
|
135
|
+
const tags = [category.toLowerCase(), String(year)];
|
|
136
|
+
let outcomes;
|
|
137
|
+
if (isBinary) {
|
|
138
|
+
const yesPrice = round(f.float(0.05, 0.95), 3);
|
|
139
|
+
const noPrice = round(1 - yesPrice, 3);
|
|
140
|
+
const yesChange = round(f.float(-0.1, 0.1), 3);
|
|
141
|
+
outcomes = [
|
|
142
|
+
{
|
|
143
|
+
outcomeId: `${marketId}-yes`,
|
|
144
|
+
marketId,
|
|
145
|
+
label: 'Yes',
|
|
146
|
+
price: yesPrice,
|
|
147
|
+
priceChange24h: yesChange,
|
|
148
|
+
metadata: { clobTokenId: `mock-clob-${seed}-yes` },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
outcomeId: `${marketId}-no`,
|
|
152
|
+
marketId,
|
|
153
|
+
label: 'No',
|
|
154
|
+
price: noPrice,
|
|
155
|
+
priceChange24h: -yesChange,
|
|
156
|
+
metadata: { clobTokenId: `mock-clob-${seed}-no` },
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const candidates = f.pick(CANDIDATES);
|
|
162
|
+
const rawPrices = candidates.map(() => f.float(0.05, 0.9));
|
|
163
|
+
const total = rawPrices.reduce((s, p) => s + p, 0);
|
|
164
|
+
outcomes = candidates.map((label, i) => ({
|
|
165
|
+
outcomeId: `${marketId}-${i}`,
|
|
166
|
+
marketId,
|
|
167
|
+
label,
|
|
168
|
+
price: round(rawPrices[i] / total, 3),
|
|
169
|
+
priceChange24h: round(f.float(-0.05, 0.05), 3),
|
|
170
|
+
metadata: { clobTokenId: `mock-clob-${seed}-${i}` },
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
const market = {
|
|
174
|
+
marketId,
|
|
175
|
+
eventId,
|
|
176
|
+
title,
|
|
177
|
+
description: loremWords(f, 20 + f.int(0, 20)),
|
|
178
|
+
slug,
|
|
179
|
+
outcomes,
|
|
180
|
+
resolutionDate,
|
|
181
|
+
volume24h,
|
|
182
|
+
volume,
|
|
183
|
+
liquidity,
|
|
184
|
+
openInterest,
|
|
185
|
+
url: `https://mock.pmxt.dev/market/${slug}`,
|
|
186
|
+
image: `https://picsum.photos/seed/${encodeURIComponent(seed)}/400/200`,
|
|
187
|
+
category,
|
|
188
|
+
tags,
|
|
189
|
+
status: 'active',
|
|
190
|
+
tickSize: 0.01,
|
|
191
|
+
contractAddress: `0xmock${seed.replace(/[^a-z0-9]/gi, '')}`,
|
|
192
|
+
sourceExchange: 'mock',
|
|
193
|
+
};
|
|
194
|
+
if (isBinary) {
|
|
195
|
+
market.yes = outcomes[0];
|
|
196
|
+
market.no = outcomes[1];
|
|
197
|
+
}
|
|
198
|
+
if (!isBinary && outcomes.length === 2) {
|
|
199
|
+
market.up = outcomes[0];
|
|
200
|
+
market.down = outcomes[1];
|
|
201
|
+
}
|
|
202
|
+
return market;
|
|
203
|
+
}
|
|
204
|
+
_buildMarkets() {
|
|
205
|
+
if (this._generatedMarkets)
|
|
206
|
+
return this._generatedMarkets;
|
|
207
|
+
const markets = [];
|
|
208
|
+
for (let i = 0; i < this._marketCount; i++) {
|
|
209
|
+
const isBinary = i % 4 !== 0;
|
|
210
|
+
const eventIdx = Math.floor(i / 3);
|
|
211
|
+
markets.push(this._generateMarket(`m${i}`, `mock-event-${eventIdx}`, isBinary));
|
|
212
|
+
}
|
|
213
|
+
this._generatedMarkets = markets;
|
|
214
|
+
return markets;
|
|
215
|
+
}
|
|
216
|
+
_buildEvents() {
|
|
217
|
+
if (this._generatedEvents)
|
|
218
|
+
return this._generatedEvents;
|
|
219
|
+
const markets = this._buildMarkets();
|
|
220
|
+
const eventMap = new Map();
|
|
221
|
+
for (const m of markets) {
|
|
222
|
+
if (!m.eventId)
|
|
223
|
+
continue;
|
|
224
|
+
if (!eventMap.has(m.eventId))
|
|
225
|
+
eventMap.set(m.eventId, []);
|
|
226
|
+
eventMap.get(m.eventId).push(m);
|
|
227
|
+
}
|
|
228
|
+
const events = [];
|
|
229
|
+
for (const [eventId, eventMarkets] of eventMap) {
|
|
230
|
+
const f = rng(eventId);
|
|
231
|
+
const first = eventMarkets[0];
|
|
232
|
+
const volume24h = round(eventMarkets.reduce((s, m) => s + m.volume24h, 0), 2);
|
|
233
|
+
const volume = round(eventMarkets.reduce((s, m) => s + (m.volume ?? 0), 0), 2);
|
|
234
|
+
events.push({
|
|
235
|
+
id: eventId,
|
|
236
|
+
title: `Mock Event: ${f.pick(ADJECTIVES)} ${f.pick(NOUNS)}`,
|
|
237
|
+
description: loremWords(f, 18 + f.int(0, 15)),
|
|
238
|
+
slug: eventId,
|
|
239
|
+
markets: eventMarkets,
|
|
240
|
+
volume24h,
|
|
241
|
+
volume,
|
|
242
|
+
url: `https://mock.pmxt.dev/event/${eventId}`,
|
|
243
|
+
image: `https://picsum.photos/seed/${encodeURIComponent(eventId)}/800/400`,
|
|
244
|
+
category: first.category,
|
|
245
|
+
tags: first.tags,
|
|
246
|
+
sourceExchange: 'mock',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
this._generatedEvents = events;
|
|
250
|
+
return events;
|
|
251
|
+
}
|
|
252
|
+
async fetchMarketsImpl(params) {
|
|
253
|
+
let markets = this._buildMarkets();
|
|
254
|
+
if (params?.query) {
|
|
255
|
+
const q = params.query.toLowerCase();
|
|
256
|
+
markets = markets.filter(m => m.title.toLowerCase().includes(q));
|
|
257
|
+
}
|
|
258
|
+
if (params?.eventId) {
|
|
259
|
+
markets = markets.filter(m => m.eventId === params.eventId);
|
|
260
|
+
}
|
|
261
|
+
if (params?.marketId) {
|
|
262
|
+
markets = markets.filter(m => m.marketId === params.marketId);
|
|
263
|
+
}
|
|
264
|
+
const offset = params?.offset ?? 0;
|
|
265
|
+
const limit = params?.limit;
|
|
266
|
+
return limit !== undefined ? markets.slice(offset, offset + limit) : markets.slice(offset);
|
|
267
|
+
}
|
|
268
|
+
async fetchEventsImpl(params) {
|
|
269
|
+
let events = this._buildEvents();
|
|
270
|
+
if (params?.query) {
|
|
271
|
+
const q = params.query.toLowerCase();
|
|
272
|
+
events = events.filter(e => e.title.toLowerCase().includes(q));
|
|
273
|
+
}
|
|
274
|
+
if (params?.eventId) {
|
|
275
|
+
events = events.filter(e => e.id === params.eventId);
|
|
276
|
+
}
|
|
277
|
+
const offset = params?.offset ?? 0;
|
|
278
|
+
const limit = params?.limit;
|
|
279
|
+
return limit !== undefined ? events.slice(offset, offset + limit) : events.slice(offset);
|
|
280
|
+
}
|
|
281
|
+
async fetchOrderBook(id) {
|
|
282
|
+
const f = new seededRng_1.SeededRng(id);
|
|
283
|
+
const midPrice = round(f.float(0.1, 0.9), 3);
|
|
284
|
+
const spread = round(f.float(0.005, 0.03), 3);
|
|
285
|
+
const buildLevels = (startPrice, direction, count) => {
|
|
286
|
+
const levels = [];
|
|
287
|
+
let price = startPrice;
|
|
288
|
+
for (let i = 0; i < count; i++) {
|
|
289
|
+
price = clamp(round(price + direction * f.float(0.002, 0.01), 3), 0.01, 0.99);
|
|
290
|
+
const size = round(f.float(10, 2000), 0);
|
|
291
|
+
levels.push({ price, size });
|
|
292
|
+
}
|
|
293
|
+
return levels;
|
|
294
|
+
};
|
|
295
|
+
const askStart = clamp(round(midPrice + spread / 2, 3), 0.01, 0.99);
|
|
296
|
+
const bidStart = clamp(round(midPrice - spread / 2, 3), 0.01, 0.99);
|
|
297
|
+
return {
|
|
298
|
+
bids: buildLevels(bidStart, -1, 8).sort((a, b) => b.price - a.price),
|
|
299
|
+
asks: buildLevels(askStart, 1, 8).sort((a, b) => a.price - b.price),
|
|
300
|
+
timestamp: Date.now(),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async fetchOHLCV(id, params) {
|
|
304
|
+
const f = new seededRng_1.SeededRng(id);
|
|
305
|
+
const resolutionMs = {
|
|
306
|
+
'1m': 60_000,
|
|
307
|
+
'5m': 300_000,
|
|
308
|
+
'15m': 900_000,
|
|
309
|
+
'1h': 3_600_000,
|
|
310
|
+
'6h': 21_600_000,
|
|
311
|
+
'1d': 86_400_000,
|
|
312
|
+
};
|
|
313
|
+
const step = resolutionMs[params.resolution] ?? 3_600_000;
|
|
314
|
+
const limit = params.limit ?? 100;
|
|
315
|
+
const end = params.end ? params.end.getTime() : Date.now();
|
|
316
|
+
const start = params.start ? params.start.getTime() : end - step * limit;
|
|
317
|
+
const candles = [];
|
|
318
|
+
let price = round(f.float(0.2, 0.8), 3);
|
|
319
|
+
let t = start;
|
|
320
|
+
while (t <= end && candles.length < limit) {
|
|
321
|
+
const drift = f.float(-0.03, 0.03);
|
|
322
|
+
const open = clamp(price, 0.01, 0.99);
|
|
323
|
+
const high = clamp(round(open + Math.abs(f.float(0, 0.05)), 3), 0.01, 0.99);
|
|
324
|
+
const low = clamp(round(open - Math.abs(f.float(0, 0.05)), 3), 0.01, 0.99);
|
|
325
|
+
const close = clamp(round(open + drift, 3), 0.01, 0.99);
|
|
326
|
+
const vol = round(f.float(100, 50_000), 2);
|
|
327
|
+
candles.push({ timestamp: t, open, high, low, close, volume: vol });
|
|
328
|
+
price = close;
|
|
329
|
+
t += step;
|
|
330
|
+
}
|
|
331
|
+
return candles;
|
|
332
|
+
}
|
|
333
|
+
async fetchTrades(id, _params) {
|
|
334
|
+
const f = new seededRng_1.SeededRng(id);
|
|
335
|
+
const count = f.int(5, 30);
|
|
336
|
+
const trades = [];
|
|
337
|
+
let ts = Date.now() - count * 60_000;
|
|
338
|
+
for (let i = 0; i < count; i++) {
|
|
339
|
+
ts += f.int(5_000, 120_000);
|
|
340
|
+
trades.push({
|
|
341
|
+
id: f.uuid(),
|
|
342
|
+
timestamp: ts,
|
|
343
|
+
price: round(f.float(0.1, 0.9), 3),
|
|
344
|
+
amount: round(f.float(1, 500), 0),
|
|
345
|
+
side: f.pick(['buy', 'sell']),
|
|
346
|
+
outcomeId: id,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return trades.sort((a, b) => b.timestamp - a.timestamp);
|
|
350
|
+
}
|
|
351
|
+
async fetchBalance(_address) {
|
|
352
|
+
const locked = this._locked();
|
|
353
|
+
return [
|
|
354
|
+
{
|
|
355
|
+
currency: 'USDC',
|
|
356
|
+
total: round(this._freeCash + locked, 2),
|
|
357
|
+
available: round(this._freeCash, 2),
|
|
358
|
+
locked: round(locked, 2),
|
|
359
|
+
},
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
async fetchPositions(_address) {
|
|
363
|
+
return Array.from(this._positions.values());
|
|
364
|
+
}
|
|
365
|
+
_nextOrderId(f) {
|
|
366
|
+
this._ordSeq += 1;
|
|
367
|
+
return `mock-order-${this._ordSeq}-${f.alphanumeric(6)}`;
|
|
368
|
+
}
|
|
369
|
+
_setPosition(params, price, sizeDelta) {
|
|
370
|
+
const posKey = `${params.marketId}:${params.outcomeId}`;
|
|
371
|
+
const existing = this._positions.get(posKey);
|
|
372
|
+
const newSize = (existing ? existing.size : 0) + sizeDelta;
|
|
373
|
+
if (Math.abs(newSize) < 0.001) {
|
|
374
|
+
this._positions.delete(posKey);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const markets = this._buildMarkets();
|
|
378
|
+
const market = markets.find(m => m.marketId === params.marketId);
|
|
379
|
+
const outcome = market?.outcomes.find(o => o.outcomeId === params.outcomeId);
|
|
380
|
+
if (existing) {
|
|
381
|
+
const epx = existing.entryPrice * existing.size;
|
|
382
|
+
const npx = price * sizeDelta;
|
|
383
|
+
const newEntry = (epx + npx) / newSize;
|
|
384
|
+
const ep = round(newEntry, 4);
|
|
385
|
+
const cur = price;
|
|
386
|
+
this._positions.set(posKey, {
|
|
387
|
+
marketId: params.marketId,
|
|
388
|
+
outcomeId: params.outcomeId,
|
|
389
|
+
outcomeLabel: outcome?.label ?? params.outcomeId,
|
|
390
|
+
size: round(newSize, 4),
|
|
391
|
+
entryPrice: ep,
|
|
392
|
+
currentPrice: cur,
|
|
393
|
+
unrealizedPnL: round((cur - ep) * newSize, 4),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
this._positions.set(posKey, {
|
|
398
|
+
marketId: params.marketId,
|
|
399
|
+
outcomeId: params.outcomeId,
|
|
400
|
+
outcomeLabel: outcome?.label ?? params.outcomeId,
|
|
401
|
+
size: round(newSize, 4),
|
|
402
|
+
entryPrice: price,
|
|
403
|
+
currentPrice: price,
|
|
404
|
+
unrealizedPnL: 0,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
_pushTrade(f, params, orderId, price, amount, ts) {
|
|
409
|
+
this._myTrades.push({
|
|
410
|
+
id: f.uuid(),
|
|
411
|
+
timestamp: ts,
|
|
412
|
+
price,
|
|
413
|
+
amount,
|
|
414
|
+
side: params.side,
|
|
415
|
+
outcomeId: params.outcomeId,
|
|
416
|
+
orderId,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
_applyInstantFill(params, orderId, price, amount, ts, f) {
|
|
420
|
+
const cost = price * amount;
|
|
421
|
+
const order = {
|
|
422
|
+
id: orderId,
|
|
423
|
+
marketId: params.marketId,
|
|
424
|
+
outcomeId: params.outcomeId,
|
|
425
|
+
side: params.side,
|
|
426
|
+
type: params.type,
|
|
427
|
+
price,
|
|
428
|
+
amount,
|
|
429
|
+
status: 'filled',
|
|
430
|
+
filled: amount,
|
|
431
|
+
remaining: 0,
|
|
432
|
+
timestamp: ts,
|
|
433
|
+
fee: round(cost * 0.001, 4),
|
|
434
|
+
};
|
|
435
|
+
this._orders.set(orderId, order);
|
|
436
|
+
if (params.side === 'buy') {
|
|
437
|
+
this._freeCash = Math.max(0, this._freeCash - cost);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
this._freeCash += cost;
|
|
441
|
+
}
|
|
442
|
+
const sizeChange = params.side === 'buy' ? amount : -amount;
|
|
443
|
+
this._setPosition(params, price, sizeChange);
|
|
444
|
+
this._pushTrade(f, params, orderId, price, amount, ts);
|
|
445
|
+
return { ...this._orders.get(orderId) };
|
|
446
|
+
}
|
|
447
|
+
_placeRestingLimit(params, ts, price) {
|
|
448
|
+
const ro = new seededRng_1.SeededRng('oid:' + this._ordSeq);
|
|
449
|
+
const orderId = this._nextOrderId(ro);
|
|
450
|
+
if (params.side === 'buy') {
|
|
451
|
+
const cost = price * params.amount;
|
|
452
|
+
if (this._freeCash < cost) {
|
|
453
|
+
throw new Error('MockExchange: insufficient USDC for resting buy');
|
|
454
|
+
}
|
|
455
|
+
this._freeCash -= cost;
|
|
456
|
+
this._lockedByBuy.set(orderId, cost);
|
|
457
|
+
}
|
|
458
|
+
const order = {
|
|
459
|
+
id: orderId,
|
|
460
|
+
marketId: params.marketId,
|
|
461
|
+
outcomeId: params.outcomeId,
|
|
462
|
+
side: params.side,
|
|
463
|
+
type: 'limit',
|
|
464
|
+
price,
|
|
465
|
+
amount: params.amount,
|
|
466
|
+
status: 'open',
|
|
467
|
+
filled: 0,
|
|
468
|
+
remaining: params.amount,
|
|
469
|
+
timestamp: ts,
|
|
470
|
+
fee: 0,
|
|
471
|
+
};
|
|
472
|
+
this._orders.set(orderId, order);
|
|
473
|
+
return { ...order };
|
|
474
|
+
}
|
|
475
|
+
async createOrder(params) {
|
|
476
|
+
if (this._orderLatencyMs > 0) {
|
|
477
|
+
await new Promise(resolve => setTimeout(resolve, this._orderLatencyMs));
|
|
478
|
+
}
|
|
479
|
+
const f = rng(`co:${params.outcomeId}`);
|
|
480
|
+
const ts = Date.now();
|
|
481
|
+
const isResting = this._limitOrderMode === 'resting' && params.type === 'limit';
|
|
482
|
+
const mid = this._bookMidPrice(params.outcomeId);
|
|
483
|
+
const price = params.type === 'market' ? mid : (params.price ?? round(f.float(0.1, 0.9), 3));
|
|
484
|
+
if (isResting) {
|
|
485
|
+
return this._placeRestingLimit(params, ts, price);
|
|
486
|
+
}
|
|
487
|
+
const ro = new seededRng_1.SeededRng('oid:' + (this._ordSeq + 1));
|
|
488
|
+
const orderId = this._nextOrderId(ro);
|
|
489
|
+
return this._applyInstantFill(params, orderId, price, params.amount, ts, f);
|
|
490
|
+
}
|
|
491
|
+
async fillOrder(orderId, amount) {
|
|
492
|
+
const current = this._orders.get(orderId);
|
|
493
|
+
if (!current) {
|
|
494
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
495
|
+
}
|
|
496
|
+
if (current.status !== 'open' && current.status !== 'pending') {
|
|
497
|
+
throw new Error(`MockExchange#fillOrder: order is ${current.status}`);
|
|
498
|
+
}
|
|
499
|
+
const f = rng(`fill:${orderId}`);
|
|
500
|
+
const rem = current.remaining;
|
|
501
|
+
const fillAmt = amount === undefined ? rem : Math.min(rem, amount);
|
|
502
|
+
if (fillAmt <= 0) {
|
|
503
|
+
return { ...current };
|
|
504
|
+
}
|
|
505
|
+
const p = current.price ?? 0;
|
|
506
|
+
const newFilled = current.filled + fillAmt;
|
|
507
|
+
const newRem = rem - fillAmt;
|
|
508
|
+
const ts = Date.now();
|
|
509
|
+
const fillCost = p * fillAmt;
|
|
510
|
+
const done = newRem < 0.0001;
|
|
511
|
+
const next = {
|
|
512
|
+
...current,
|
|
513
|
+
status: done ? 'filled' : 'open',
|
|
514
|
+
filled: newFilled,
|
|
515
|
+
remaining: done ? 0 : newRem,
|
|
516
|
+
fee: round(p * newFilled * 0.001, 4),
|
|
517
|
+
timestamp: ts,
|
|
518
|
+
};
|
|
519
|
+
const cp = {
|
|
520
|
+
marketId: current.marketId,
|
|
521
|
+
outcomeId: current.outcomeId,
|
|
522
|
+
side: current.side,
|
|
523
|
+
type: current.type,
|
|
524
|
+
price: p,
|
|
525
|
+
amount: fillAmt,
|
|
526
|
+
};
|
|
527
|
+
if (current.side === 'buy') {
|
|
528
|
+
const lock = this._lockedByBuy.get(orderId) ?? 0;
|
|
529
|
+
const release = fillCost;
|
|
530
|
+
const left = lock - release;
|
|
531
|
+
if (left <= 0.0001) {
|
|
532
|
+
this._lockedByBuy.delete(orderId);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
this._lockedByBuy.set(orderId, left);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
this._freeCash += fillCost;
|
|
540
|
+
}
|
|
541
|
+
this._setPosition(cp, p, current.side === 'buy' ? fillAmt : -fillAmt);
|
|
542
|
+
this._pushTrade(f, cp, orderId, p, fillAmt, ts);
|
|
543
|
+
this._orders.set(orderId, next);
|
|
544
|
+
return { ...this._orders.get(orderId) };
|
|
545
|
+
}
|
|
546
|
+
async cancelOrder(orderId) {
|
|
547
|
+
const current = this._orders.get(orderId);
|
|
548
|
+
if (!current) {
|
|
549
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
550
|
+
}
|
|
551
|
+
if (current.status !== 'open' && current.status !== 'pending') {
|
|
552
|
+
throw new Error(`Cannot cancel order in status "${current.status}"`);
|
|
553
|
+
}
|
|
554
|
+
const rest = this._lockedByBuy.get(orderId);
|
|
555
|
+
if (rest !== undefined) {
|
|
556
|
+
this._freeCash += rest;
|
|
557
|
+
this._lockedByBuy.delete(orderId);
|
|
558
|
+
}
|
|
559
|
+
const u = { ...current, status: 'cancelled', remaining: 0, timestamp: Date.now() };
|
|
560
|
+
this._orders.set(orderId, u);
|
|
561
|
+
return { ...u };
|
|
562
|
+
}
|
|
563
|
+
async fetchOrder(orderId) {
|
|
564
|
+
const o = this._orders.get(orderId);
|
|
565
|
+
if (!o)
|
|
566
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
567
|
+
return { ...o };
|
|
568
|
+
}
|
|
569
|
+
async fetchOpenOrders(_marketId) {
|
|
570
|
+
return Array.from(this._orders.values())
|
|
571
|
+
.filter(o => o.status === 'open' || o.status === 'pending')
|
|
572
|
+
.map(o => ({ ...o }));
|
|
573
|
+
}
|
|
574
|
+
async fetchMyTrades(_params) {
|
|
575
|
+
let trades = [...this._myTrades];
|
|
576
|
+
if (_params?.outcomeId)
|
|
577
|
+
trades = trades.filter(t => t.outcomeId === _params.outcomeId);
|
|
578
|
+
return trades.sort((a, b) => b.timestamp - a.timestamp);
|
|
579
|
+
}
|
|
580
|
+
async fetchClosedOrders() {
|
|
581
|
+
return Array.from(this._orders.values())
|
|
582
|
+
.filter(o => o.status === 'filled' || o.status === 'cancelled' || o.status === 'rejected')
|
|
583
|
+
.map(o => ({ ...o }));
|
|
584
|
+
}
|
|
585
|
+
async fetchAllOrders() {
|
|
586
|
+
return Array.from(this._orders.values()).map(o => ({ ...o }));
|
|
587
|
+
}
|
|
588
|
+
async buildOrder(params) {
|
|
589
|
+
return { exchange: this.name, params, raw: params };
|
|
590
|
+
}
|
|
591
|
+
async submitOrder(built) {
|
|
592
|
+
return this.createOrder(built.params);
|
|
593
|
+
}
|
|
594
|
+
reset() {
|
|
595
|
+
this._freeCash = this._initialBalance;
|
|
596
|
+
this._ordSeq = 0;
|
|
597
|
+
this._orders.clear();
|
|
598
|
+
this._lockedByBuy.clear();
|
|
599
|
+
this._positions.clear();
|
|
600
|
+
this._myTrades = [];
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
exports.MockExchange = MockExchange;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class SeededRng {
|
|
2
|
+
private state;
|
|
3
|
+
constructor(seed: string);
|
|
4
|
+
next(): number;
|
|
5
|
+
int(min: number, max: number): number;
|
|
6
|
+
float(min: number, max: number): number;
|
|
7
|
+
pick<T>(arr: readonly T[]): T;
|
|
8
|
+
alphanumeric(len: number): string;
|
|
9
|
+
uuid(): string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SeededRng = void 0;
|
|
4
|
+
function hashString(s) {
|
|
5
|
+
let h = 0;
|
|
6
|
+
for (let i = 0; i < s.length; i++)
|
|
7
|
+
h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
|
|
8
|
+
return h || 1;
|
|
9
|
+
}
|
|
10
|
+
class SeededRng {
|
|
11
|
+
state;
|
|
12
|
+
constructor(seed) {
|
|
13
|
+
this.state = hashString(seed);
|
|
14
|
+
}
|
|
15
|
+
next() {
|
|
16
|
+
let t = (this.state + 0x6d2b79f5) | 0;
|
|
17
|
+
this.state = t;
|
|
18
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
19
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
20
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
21
|
+
}
|
|
22
|
+
int(min, max) {
|
|
23
|
+
return min + Math.floor(this.next() * (max - min + 1));
|
|
24
|
+
}
|
|
25
|
+
float(min, max) {
|
|
26
|
+
return min + this.next() * (max - min);
|
|
27
|
+
}
|
|
28
|
+
pick(arr) {
|
|
29
|
+
if (arr.length === 0)
|
|
30
|
+
throw new Error('SeededRng.pick: empty array');
|
|
31
|
+
return arr[this.int(0, arr.length - 1)];
|
|
32
|
+
}
|
|
33
|
+
alphanumeric(len) {
|
|
34
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
35
|
+
let s = '';
|
|
36
|
+
for (let i = 0; i < len; i++)
|
|
37
|
+
s += this.pick([...chars]);
|
|
38
|
+
return s;
|
|
39
|
+
}
|
|
40
|
+
uuid() {
|
|
41
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
42
|
+
const r = (this.next() * 16) | 0;
|
|
43
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
44
|
+
return v.toString(16);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.SeededRng = SeededRng;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-08T22:46:15.514Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const myriadApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.myriadApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-08T22:46:15.514Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.myriadApiSpec = {
|
|
@@ -7,8 +7,12 @@ export declare class MyriadWebSocket {
|
|
|
7
7
|
private orderBookTimers;
|
|
8
8
|
private tradeTimers;
|
|
9
9
|
private orderBookResolvers;
|
|
10
|
+
private orderBookRejecters;
|
|
10
11
|
private tradeResolvers;
|
|
12
|
+
private tradeRejecters;
|
|
11
13
|
private lastTradeTimestamp;
|
|
14
|
+
private orderBookFailureCount;
|
|
15
|
+
private tradeFailureCount;
|
|
12
16
|
private closed;
|
|
13
17
|
constructor(callApi: (operationId: string, params?: Record<string, any>) => Promise<any>, fetchOrderBook: FetchOrderBookFn, pollInterval?: number);
|
|
14
18
|
watchOrderBook(outcomeId: string): Promise<OrderBook>;
|