@mostlyrightmd/markets 0.1.0-rc.7
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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/index.cjs +477 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.global.js +467 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.mjs +442 -0
- package/dist/index.mjs.map +1 -0
- package/dist/polymarket/index.cjs +893 -0
- package/dist/polymarket/index.cjs.map +1 -0
- package/dist/polymarket/index.d.cts +317 -0
- package/dist/polymarket/index.d.ts +317 -0
- package/dist/polymarket/index.mjs +842 -0
- package/dist/polymarket/index.mjs.map +1 -0
- package/dist/trades/index.cjs +644 -0
- package/dist/trades/index.cjs.map +1 -0
- package/dist/trades/index.d.cts +210 -0
- package/dist/trades/index.d.ts +210 -0
- package/dist/trades/index.mjs +602 -0
- package/dist/trades/index.mjs.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
// src/trades/kalshi-client.ts
|
|
2
|
+
import { fetchWithRetry } from "@mostlyrightmd/core";
|
|
3
|
+
var KALSHI_API_BASE = "https://api.elections.kalshi.com/trade-api/v2";
|
|
4
|
+
var DEFAULT_SLEEP_BETWEEN_MS = 100;
|
|
5
|
+
var DEFAULT_USER_AGENT = "mostlyright-ts/0.2.0 (+https://github.com/helloiamvu/tradewinds)";
|
|
6
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
7
|
+
var DEFAULT_TRADES_PAGE_LIMIT = 1e3;
|
|
8
|
+
var DEFAULT_MAX_TRADES_PAGES = 1e4;
|
|
9
|
+
async function sleep(ms, signal) {
|
|
10
|
+
if (ms <= 0) return;
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const timer = setTimeout(() => {
|
|
13
|
+
cleanup();
|
|
14
|
+
resolve();
|
|
15
|
+
}, ms);
|
|
16
|
+
const onAbort = () => {
|
|
17
|
+
cleanup();
|
|
18
|
+
reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
19
|
+
};
|
|
20
|
+
function cleanup() {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
signal?.removeEventListener("abort", onAbort);
|
|
23
|
+
}
|
|
24
|
+
if (signal) {
|
|
25
|
+
if (signal.aborted) {
|
|
26
|
+
cleanup();
|
|
27
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
signal.addEventListener("abort", onAbort);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function buildUrl(path, params) {
|
|
35
|
+
const url = new URL(`${KALSHI_API_BASE}${path}`);
|
|
36
|
+
if (params) {
|
|
37
|
+
for (const [key, value] of Object.entries(params)) {
|
|
38
|
+
if (value === void 0 || value === null) continue;
|
|
39
|
+
url.searchParams.set(key, String(value));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return url.toString();
|
|
43
|
+
}
|
|
44
|
+
async function getJson(path, params, opts) {
|
|
45
|
+
const url = buildUrl(path, params);
|
|
46
|
+
const headers = {
|
|
47
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
48
|
+
Accept: "application/json"
|
|
49
|
+
};
|
|
50
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
51
|
+
let resp;
|
|
52
|
+
if (opts.fetchFn !== void 0) {
|
|
53
|
+
const init = { method: "GET", headers };
|
|
54
|
+
if (opts.signal) init.signal = opts.signal;
|
|
55
|
+
resp = await opts.fetchFn(url, init);
|
|
56
|
+
if (!resp.ok) {
|
|
57
|
+
throw new Error(`kalshi GET ${url} failed: HTTP ${resp.status}`);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
const retryOpts = {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers,
|
|
63
|
+
timeoutMs
|
|
64
|
+
};
|
|
65
|
+
if (opts.signal) retryOpts.signal = opts.signal;
|
|
66
|
+
resp = await fetchWithRetry(url, retryOpts);
|
|
67
|
+
}
|
|
68
|
+
const json = await resp.json();
|
|
69
|
+
const sleepMs = opts.sleepBetweenMs ?? DEFAULT_SLEEP_BETWEEN_MS;
|
|
70
|
+
if (sleepMs > 0) {
|
|
71
|
+
await sleep(sleepMs, opts.signal);
|
|
72
|
+
}
|
|
73
|
+
return json;
|
|
74
|
+
}
|
|
75
|
+
async function fetchCandlesticks(ticker, args, opts = {}) {
|
|
76
|
+
if (!ticker.includes("-")) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`kalshi ticker must contain '-' to derive series prefix; got ${JSON.stringify(ticker)}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const series = ticker.split("-", 1)[0];
|
|
82
|
+
const path = `/series/${series}/markets/${ticker}/candlesticks`;
|
|
83
|
+
const payload = await getJson(
|
|
84
|
+
path,
|
|
85
|
+
{
|
|
86
|
+
start_ts: args.startTs,
|
|
87
|
+
end_ts: args.endTs,
|
|
88
|
+
period_interval: args.periodIntervalMinutes
|
|
89
|
+
},
|
|
90
|
+
opts
|
|
91
|
+
);
|
|
92
|
+
return payload?.candlesticks ?? [];
|
|
93
|
+
}
|
|
94
|
+
async function fetchTrades(ticker, args = {}, opts = {}) {
|
|
95
|
+
const limit = args.limit ?? DEFAULT_TRADES_PAGE_LIMIT;
|
|
96
|
+
const maxPages = args.maxPages ?? DEFAULT_MAX_TRADES_PAGES;
|
|
97
|
+
const out = [];
|
|
98
|
+
let cursor;
|
|
99
|
+
let pages = 0;
|
|
100
|
+
while (true) {
|
|
101
|
+
const params = {
|
|
102
|
+
ticker,
|
|
103
|
+
limit
|
|
104
|
+
};
|
|
105
|
+
if (args.minTs !== void 0) params.min_ts = args.minTs;
|
|
106
|
+
if (args.maxTs !== void 0) params.max_ts = args.maxTs;
|
|
107
|
+
if (cursor) params.cursor = cursor;
|
|
108
|
+
const payload = await getJson("/markets/trades", params, opts);
|
|
109
|
+
const trades = payload?.trades ?? [];
|
|
110
|
+
if (trades.length > 0) out.push(...trades);
|
|
111
|
+
cursor = payload?.cursor && payload.cursor.length > 0 ? payload.cursor : void 0;
|
|
112
|
+
pages += 1;
|
|
113
|
+
if (!cursor || trades.length === 0) break;
|
|
114
|
+
if (pages >= maxPages) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`kalshi fetchTrades(${ticker}) exceeded maxPages=${maxPages}; narrow the window or raise the cap`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
async function fetchOrderbook(ticker, args = {}, opts = {}) {
|
|
123
|
+
const depth = args.depth ?? 50;
|
|
124
|
+
if (depth < 1 || depth > 1e3) {
|
|
125
|
+
throw new Error(`depth out of range [1, 1000]: ${depth}`);
|
|
126
|
+
}
|
|
127
|
+
const path = `/markets/${ticker}/orderbook`;
|
|
128
|
+
const payload = await getJson(path, { depth }, opts);
|
|
129
|
+
if (!payload || typeof payload !== "object") {
|
|
130
|
+
throw new Error(`kalshi orderbook for ${ticker}: bad payload`);
|
|
131
|
+
}
|
|
132
|
+
return payload;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/trades/types.ts
|
|
136
|
+
var KALSHI_INTERVALS = ["1m", "1h", "1d"];
|
|
137
|
+
|
|
138
|
+
// src/trades/kalshi.ts
|
|
139
|
+
var SOURCE = "kalshi";
|
|
140
|
+
var INTERVAL_TO_MINUTES = {
|
|
141
|
+
"1m": 1,
|
|
142
|
+
"1h": 60,
|
|
143
|
+
"1d": 1440
|
|
144
|
+
};
|
|
145
|
+
function validateDate(d, name) {
|
|
146
|
+
if (!(d instanceof Date) || Number.isNaN(d.getTime())) {
|
|
147
|
+
throw new TypeError(`${name} must be a valid Date; got ${String(d)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function toIsoOrNull(epochSeconds) {
|
|
151
|
+
if (typeof epochSeconds === "number" && Number.isFinite(epochSeconds)) {
|
|
152
|
+
return new Date(epochSeconds * 1e3).toISOString();
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function stringTsToIso(value) {
|
|
157
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
158
|
+
return toIsoOrNull(value);
|
|
159
|
+
}
|
|
160
|
+
if (typeof value === "string") {
|
|
161
|
+
const parsed = Date.parse(value);
|
|
162
|
+
if (!Number.isNaN(parsed)) {
|
|
163
|
+
return new Date(parsed).toISOString();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function maybeNumber(v) {
|
|
169
|
+
if (v === null || v === void 0) return null;
|
|
170
|
+
const n = typeof v === "string" ? Number(v) : v;
|
|
171
|
+
return typeof n === "number" && Number.isFinite(n) ? n : null;
|
|
172
|
+
}
|
|
173
|
+
function maybeInt(v) {
|
|
174
|
+
const n = maybeNumber(v);
|
|
175
|
+
if (n === null) return null;
|
|
176
|
+
return Math.trunc(n);
|
|
177
|
+
}
|
|
178
|
+
function dollarsToCents(v) {
|
|
179
|
+
const n = maybeNumber(v);
|
|
180
|
+
if (n === null) return null;
|
|
181
|
+
return n * 100;
|
|
182
|
+
}
|
|
183
|
+
function fpStringToInt(v) {
|
|
184
|
+
const n = maybeNumber(v);
|
|
185
|
+
if (n === null) return null;
|
|
186
|
+
return Math.trunc(n);
|
|
187
|
+
}
|
|
188
|
+
function pickPrice(d, base) {
|
|
189
|
+
if (d === void 0) return null;
|
|
190
|
+
const dollarsKey = `${base}_dollars`;
|
|
191
|
+
if (Object.prototype.hasOwnProperty.call(d, dollarsKey)) {
|
|
192
|
+
return dollarsToCents(d[dollarsKey]);
|
|
193
|
+
}
|
|
194
|
+
return maybeNumber(d[base]);
|
|
195
|
+
}
|
|
196
|
+
function pickFp(d, base) {
|
|
197
|
+
if (d === void 0) return null;
|
|
198
|
+
const fpKey = `${base}_fp`;
|
|
199
|
+
if (Object.prototype.hasOwnProperty.call(d, fpKey)) {
|
|
200
|
+
return fpStringToInt(d[fpKey]);
|
|
201
|
+
}
|
|
202
|
+
return maybeInt(d[base]);
|
|
203
|
+
}
|
|
204
|
+
async function kalshiCandles(ticker, args, opts = {}) {
|
|
205
|
+
validateDate(args.from, "from");
|
|
206
|
+
validateDate(args.to, "to");
|
|
207
|
+
if (args.from.getTime() >= args.to.getTime()) {
|
|
208
|
+
throw new RangeError(
|
|
209
|
+
`from (${args.from.toISOString()}) must be < to (${args.to.toISOString()})`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (!KALSHI_INTERVALS.includes(args.interval)) {
|
|
213
|
+
throw new RangeError(
|
|
214
|
+
`interval must be one of ${JSON.stringify([...KALSHI_INTERVALS])}; got ${JSON.stringify(args.interval)}`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const raw = await fetchCandlesticks(
|
|
218
|
+
ticker,
|
|
219
|
+
{
|
|
220
|
+
startTs: Math.trunc(args.from.getTime() / 1e3),
|
|
221
|
+
endTs: Math.trunc(args.to.getTime() / 1e3),
|
|
222
|
+
periodIntervalMinutes: INTERVAL_TO_MINUTES[args.interval]
|
|
223
|
+
},
|
|
224
|
+
opts
|
|
225
|
+
);
|
|
226
|
+
const rows = raw.map((c) => {
|
|
227
|
+
const priceObj = c.price;
|
|
228
|
+
const top = c;
|
|
229
|
+
return {
|
|
230
|
+
ts: toIsoOrNull(c.end_period_ts),
|
|
231
|
+
open: pickPrice(priceObj, "open"),
|
|
232
|
+
high: pickPrice(priceObj, "high"),
|
|
233
|
+
low: pickPrice(priceObj, "low"),
|
|
234
|
+
close: pickPrice(priceObj, "close"),
|
|
235
|
+
volume: pickFp(top, "volume"),
|
|
236
|
+
openInterest: pickFp(top, "open_interest"),
|
|
237
|
+
source: SOURCE
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
return Object.freeze({
|
|
241
|
+
rows: Object.freeze(rows),
|
|
242
|
+
source: SOURCE,
|
|
243
|
+
retrievedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
244
|
+
ticker,
|
|
245
|
+
interval: args.interval
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async function kalshiFills(ticker, args = {}, opts = {}) {
|
|
249
|
+
if (args.since !== void 0) validateDate(args.since, "since");
|
|
250
|
+
if (args.until !== void 0) validateDate(args.until, "until");
|
|
251
|
+
if (args.since !== void 0 && args.until !== void 0 && args.since.getTime() >= args.until.getTime()) {
|
|
252
|
+
throw new RangeError(
|
|
253
|
+
`since (${args.since.toISOString()}) must be < until (${args.until.toISOString()})`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const fetchArgs = {};
|
|
257
|
+
if (args.since) fetchArgs.minTs = Math.trunc(args.since.getTime() / 1e3);
|
|
258
|
+
if (args.until) fetchArgs.maxTs = Math.trunc(args.until.getTime() / 1e3);
|
|
259
|
+
if (args.maxPages !== void 0) fetchArgs.maxPages = args.maxPages;
|
|
260
|
+
const raw = await fetchTrades(ticker, fetchArgs, opts);
|
|
261
|
+
const rows = raw.map((t) => {
|
|
262
|
+
const obj = t;
|
|
263
|
+
const takerRaw = t.taker_outcome_side ?? t.taker_side;
|
|
264
|
+
const takerSide = takerRaw === "yes" || takerRaw === "no" ? takerRaw : null;
|
|
265
|
+
return {
|
|
266
|
+
tradeId: t.trade_id ?? null,
|
|
267
|
+
ts: stringTsToIso(t.created_time),
|
|
268
|
+
yesPrice: pickPrice(obj, "yes_price"),
|
|
269
|
+
noPrice: pickPrice(obj, "no_price"),
|
|
270
|
+
count: pickFp(obj, "count"),
|
|
271
|
+
takerSide,
|
|
272
|
+
source: SOURCE
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
return Object.freeze({
|
|
276
|
+
rows: Object.freeze(rows),
|
|
277
|
+
source: SOURCE,
|
|
278
|
+
retrievedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
279
|
+
ticker
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
async function kalshiOrderbook(ticker, args = {}, opts = {}) {
|
|
283
|
+
const payload = await fetchOrderbook(ticker, args, opts);
|
|
284
|
+
let useFp;
|
|
285
|
+
let levelsBySide;
|
|
286
|
+
if (payload.orderbook_fp !== void 0) {
|
|
287
|
+
useFp = true;
|
|
288
|
+
levelsBySide = {
|
|
289
|
+
yes: payload.orderbook_fp.yes_dollars ?? [],
|
|
290
|
+
no: payload.orderbook_fp.no_dollars ?? []
|
|
291
|
+
};
|
|
292
|
+
} else {
|
|
293
|
+
useFp = false;
|
|
294
|
+
levelsBySide = {
|
|
295
|
+
yes: payload.orderbook?.yes ?? [],
|
|
296
|
+
no: payload.orderbook?.no ?? []
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const rows = [];
|
|
300
|
+
for (const side of ["yes", "no"]) {
|
|
301
|
+
const levels = levelsBySide[side];
|
|
302
|
+
for (const level of levels) {
|
|
303
|
+
if (Array.isArray(level) && level.length >= 2) {
|
|
304
|
+
rows.push({
|
|
305
|
+
side,
|
|
306
|
+
price: useFp ? dollarsToCents(level[0]) : maybeNumber(level[0]),
|
|
307
|
+
size: useFp ? fpStringToInt(level[1]) : maybeInt(level[1]),
|
|
308
|
+
source: SOURCE
|
|
309
|
+
});
|
|
310
|
+
} else if (level !== null && typeof level === "object") {
|
|
311
|
+
const obj = level;
|
|
312
|
+
rows.push({
|
|
313
|
+
side,
|
|
314
|
+
price: useFp ? dollarsToCents(obj.price) : maybeNumber(obj.price),
|
|
315
|
+
size: useFp ? fpStringToInt(obj.size ?? obj.contracts) : maybeInt(obj.size ?? obj.contracts),
|
|
316
|
+
source: SOURCE
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const snapshotAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
322
|
+
return Object.freeze({
|
|
323
|
+
rows: Object.freeze(rows),
|
|
324
|
+
source: SOURCE,
|
|
325
|
+
retrievedAt: snapshotAt,
|
|
326
|
+
snapshotAt,
|
|
327
|
+
ticker
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/trades/polymarket.ts
|
|
332
|
+
import { fetchWithRetry as fetchWithRetry2 } from "@mostlyrightmd/core";
|
|
333
|
+
var GAMMA_BASE = "https://gamma-api.polymarket.com";
|
|
334
|
+
var CLOB_BASE = "https://clob.polymarket.com";
|
|
335
|
+
var DEFAULT_USER_AGENT2 = "mostlyright-ts/0.2.0 (+https://github.com/helloiamvu/tradewinds)";
|
|
336
|
+
var DEFAULT_SLEEP_BETWEEN_MS2 = 200;
|
|
337
|
+
var SOURCE_SNAPSHOT = "polymarket.gamma";
|
|
338
|
+
var SOURCE_HISTORY = "polymarket.clob";
|
|
339
|
+
function validateDate2(d, name) {
|
|
340
|
+
if (!(d instanceof Date) || Number.isNaN(d.getTime())) {
|
|
341
|
+
throw new TypeError(`${name} must be a valid Date; got ${String(d)}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function maybeNumber2(v) {
|
|
345
|
+
if (v === null || v === void 0) return null;
|
|
346
|
+
const n = typeof v === "string" ? Number(v) : v;
|
|
347
|
+
return typeof n === "number" && Number.isFinite(n) ? n : null;
|
|
348
|
+
}
|
|
349
|
+
async function sleep2(ms, signal) {
|
|
350
|
+
if (ms <= 0) return;
|
|
351
|
+
return new Promise((resolve, reject) => {
|
|
352
|
+
const timer = setTimeout(() => {
|
|
353
|
+
cleanup();
|
|
354
|
+
resolve();
|
|
355
|
+
}, ms);
|
|
356
|
+
const onAbort = () => {
|
|
357
|
+
cleanup();
|
|
358
|
+
reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
359
|
+
};
|
|
360
|
+
function cleanup() {
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
signal?.removeEventListener("abort", onAbort);
|
|
363
|
+
}
|
|
364
|
+
if (signal) {
|
|
365
|
+
if (signal.aborted) {
|
|
366
|
+
cleanup();
|
|
367
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
signal.addEventListener("abort", onAbort);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function buildUrl2(baseUrl, path, params) {
|
|
375
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
376
|
+
if (params) {
|
|
377
|
+
for (const [key, value] of Object.entries(params)) {
|
|
378
|
+
if (value === void 0 || value === null) continue;
|
|
379
|
+
url.searchParams.set(key, String(value));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return url.toString();
|
|
383
|
+
}
|
|
384
|
+
async function getJson2(baseUrl, path, params, opts) {
|
|
385
|
+
const url = buildUrl2(baseUrl, path, params);
|
|
386
|
+
const headers = {
|
|
387
|
+
"User-Agent": DEFAULT_USER_AGENT2,
|
|
388
|
+
Accept: "application/json"
|
|
389
|
+
};
|
|
390
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
391
|
+
let resp;
|
|
392
|
+
if (opts.fetchFn !== void 0) {
|
|
393
|
+
const init = { method: "GET", headers };
|
|
394
|
+
if (opts.signal) init.signal = opts.signal;
|
|
395
|
+
resp = await opts.fetchFn(url, init);
|
|
396
|
+
if (!resp.ok) {
|
|
397
|
+
throw new Error(`polymarket GET ${url} failed: HTTP ${resp.status}`);
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
const retryOpts = {
|
|
401
|
+
method: "GET",
|
|
402
|
+
headers,
|
|
403
|
+
timeoutMs
|
|
404
|
+
};
|
|
405
|
+
if (opts.signal) retryOpts.signal = opts.signal;
|
|
406
|
+
resp = await fetchWithRetry2(url, retryOpts);
|
|
407
|
+
}
|
|
408
|
+
const json = await resp.json();
|
|
409
|
+
const sleepMs = opts.sleepBetweenMs ?? DEFAULT_SLEEP_BETWEEN_MS2;
|
|
410
|
+
if (sleepMs > 0) {
|
|
411
|
+
await sleep2(sleepMs, opts.signal);
|
|
412
|
+
}
|
|
413
|
+
return json;
|
|
414
|
+
}
|
|
415
|
+
function coerceStringList(v) {
|
|
416
|
+
if (v === null || v === void 0) return [];
|
|
417
|
+
if (Array.isArray(v)) return v.map((x) => String(x));
|
|
418
|
+
if (typeof v === "string") {
|
|
419
|
+
try {
|
|
420
|
+
const parsed = JSON.parse(v);
|
|
421
|
+
if (Array.isArray(parsed)) return parsed.map((x) => String(x));
|
|
422
|
+
} catch {
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
async function polymarketHistory(tokenId, args, opts = {}) {
|
|
429
|
+
if (typeof tokenId !== "string" || tokenId.length === 0) {
|
|
430
|
+
throw new TypeError(`tokenId must be a non-empty string; got ${JSON.stringify(tokenId)}`);
|
|
431
|
+
}
|
|
432
|
+
validateDate2(args.from, "from");
|
|
433
|
+
validateDate2(args.to, "to");
|
|
434
|
+
if (args.from.getTime() >= args.to.getTime()) {
|
|
435
|
+
throw new RangeError(
|
|
436
|
+
`from (${args.from.toISOString()}) must be < to (${args.to.toISOString()})`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
const fidelity = args.fidelityMinutes ?? 60;
|
|
440
|
+
if (fidelity < 1 || !Number.isInteger(fidelity)) {
|
|
441
|
+
throw new RangeError(`fidelityMinutes must be a positive integer; got ${fidelity}`);
|
|
442
|
+
}
|
|
443
|
+
const payload = await getJson2(
|
|
444
|
+
CLOB_BASE,
|
|
445
|
+
"/prices-history",
|
|
446
|
+
{
|
|
447
|
+
market: tokenId,
|
|
448
|
+
startTs: Math.trunc(args.from.getTime() / 1e3),
|
|
449
|
+
endTs: Math.trunc(args.to.getTime() / 1e3),
|
|
450
|
+
fidelity
|
|
451
|
+
},
|
|
452
|
+
opts
|
|
453
|
+
);
|
|
454
|
+
let points;
|
|
455
|
+
if (Array.isArray(payload)) {
|
|
456
|
+
points = payload;
|
|
457
|
+
} else if (payload !== null && typeof payload === "object") {
|
|
458
|
+
points = payload.history ?? [];
|
|
459
|
+
} else {
|
|
460
|
+
points = [];
|
|
461
|
+
}
|
|
462
|
+
const rows = [];
|
|
463
|
+
for (const p of points) {
|
|
464
|
+
if (p === null || typeof p !== "object") continue;
|
|
465
|
+
const tsEpoch = p.t;
|
|
466
|
+
const ts = typeof tsEpoch === "number" && Number.isFinite(tsEpoch) ? new Date(tsEpoch * 1e3).toISOString() : null;
|
|
467
|
+
rows.push({
|
|
468
|
+
ts,
|
|
469
|
+
price: maybeNumber2(p.p ?? p.price),
|
|
470
|
+
volume: maybeNumber2(p.v ?? p.volume),
|
|
471
|
+
source: SOURCE_HISTORY
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return Object.freeze({
|
|
475
|
+
rows: Object.freeze(rows),
|
|
476
|
+
source: SOURCE_HISTORY,
|
|
477
|
+
retrievedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
478
|
+
tokenId,
|
|
479
|
+
fidelityMinutes: fidelity
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
async function polymarketSnapshot(eventId, opts = {}) {
|
|
483
|
+
if (typeof eventId !== "string" || eventId.length === 0) {
|
|
484
|
+
throw new TypeError(`eventId must be a non-empty string; got ${JSON.stringify(eventId)}`);
|
|
485
|
+
}
|
|
486
|
+
const payload = await getJson2(
|
|
487
|
+
GAMMA_BASE,
|
|
488
|
+
`/events/${encodeURIComponent(eventId)}`,
|
|
489
|
+
void 0,
|
|
490
|
+
opts
|
|
491
|
+
);
|
|
492
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
`polymarket snapshot: bad event payload for ${JSON.stringify(eventId)}; expected object`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const event = payload;
|
|
498
|
+
const markets = event.markets ?? [];
|
|
499
|
+
const rows = [];
|
|
500
|
+
for (const m of markets) {
|
|
501
|
+
if (m === null || typeof m !== "object") continue;
|
|
502
|
+
const outcomes = coerceStringList(m.outcomes);
|
|
503
|
+
const prices = coerceStringList(m.outcomePrices);
|
|
504
|
+
const volume = maybeNumber2(m.volume);
|
|
505
|
+
const liquidity = maybeNumber2(m.liquidity);
|
|
506
|
+
const marketIdStr = m.id !== void 0 && m.id !== null ? String(m.id) : null;
|
|
507
|
+
for (let i = 0; i < outcomes.length; i++) {
|
|
508
|
+
const priceStr = i < prices.length ? prices[i] : null;
|
|
509
|
+
rows.push({
|
|
510
|
+
marketId: marketIdStr,
|
|
511
|
+
outcome: outcomes[i] ?? "",
|
|
512
|
+
lastPrice: maybeNumber2(priceStr),
|
|
513
|
+
volume,
|
|
514
|
+
liquidity,
|
|
515
|
+
source: SOURCE_SNAPSHOT
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const snapshotAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
520
|
+
return Object.freeze({
|
|
521
|
+
rows: Object.freeze(rows),
|
|
522
|
+
source: SOURCE_SNAPSHOT,
|
|
523
|
+
retrievedAt: snapshotAt,
|
|
524
|
+
snapshotAt,
|
|
525
|
+
eventId
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/trades/cache.ts
|
|
530
|
+
var ISSUER_RE = /^[a-z][a-z0-9._-]{0,31}$/;
|
|
531
|
+
var TICKER_RE = /^(?!\.+$)[A-Za-z0-9._-]{1,128}$/;
|
|
532
|
+
function tradesCacheKey(args) {
|
|
533
|
+
if (typeof args.issuer !== "string" || !ISSUER_RE.test(args.issuer)) {
|
|
534
|
+
throw new RangeError(
|
|
535
|
+
`invalid issuer for cache key: ${JSON.stringify(args.issuer)}; must match ${ISSUER_RE}`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
if (typeof args.ticker !== "string" || !TICKER_RE.test(args.ticker)) {
|
|
539
|
+
throw new RangeError(
|
|
540
|
+
`invalid ticker for cache key: ${JSON.stringify(args.ticker)}; must match ${TICKER_RE}`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (!Number.isInteger(args.year) || args.year < 2e3 || args.year > 2100) {
|
|
544
|
+
throw new RangeError(`year out of range [2000, 2100]: ${args.year}`);
|
|
545
|
+
}
|
|
546
|
+
if (!Number.isInteger(args.month) || args.month < 1 || args.month > 12) {
|
|
547
|
+
throw new RangeError(`month out of range [1, 12]: ${args.month}`);
|
|
548
|
+
}
|
|
549
|
+
const mm = args.month.toString().padStart(2, "0");
|
|
550
|
+
return `trades/${args.issuer}/${args.ticker}/${args.year}-${mm}`;
|
|
551
|
+
}
|
|
552
|
+
function isCurrentUtcMonth(year, month, now = /* @__PURE__ */ new Date()) {
|
|
553
|
+
return year === now.getUTCFullYear() && month === now.getUTCMonth() + 1;
|
|
554
|
+
}
|
|
555
|
+
function isFutureUtcMonth(year, month, now = /* @__PURE__ */ new Date()) {
|
|
556
|
+
const yNow = now.getUTCFullYear();
|
|
557
|
+
const mNow = now.getUTCMonth() + 1;
|
|
558
|
+
return year > yNow || year === yNow && month > mNow;
|
|
559
|
+
}
|
|
560
|
+
async function readTradesCache(cache, args, opts = {}) {
|
|
561
|
+
if (isCurrentUtcMonth(args.year, args.month, opts.now) || isFutureUtcMonth(args.year, args.month, opts.now)) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
const key = tradesCacheKey(args);
|
|
565
|
+
const value = await cache.get(key);
|
|
566
|
+
return value ?? null;
|
|
567
|
+
}
|
|
568
|
+
async function writeTradesCache(cache, args, rows, opts = {}) {
|
|
569
|
+
if (isCurrentUtcMonth(args.year, args.month, opts.now) || isFutureUtcMonth(args.year, args.month, opts.now)) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
if (rows.length === 0) return false;
|
|
573
|
+
const key = tradesCacheKey(args);
|
|
574
|
+
await cache.set(key, rows);
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
async function invalidateTradesCache(cache, args) {
|
|
578
|
+
const key = tradesCacheKey(args);
|
|
579
|
+
const before = await cache.get(key);
|
|
580
|
+
if (before === null) return false;
|
|
581
|
+
await cache.delete(key);
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
export {
|
|
585
|
+
KALSHI_API_BASE,
|
|
586
|
+
KALSHI_INTERVALS,
|
|
587
|
+
fetchCandlesticks,
|
|
588
|
+
fetchOrderbook,
|
|
589
|
+
fetchTrades,
|
|
590
|
+
invalidateTradesCache,
|
|
591
|
+
isCurrentUtcMonth,
|
|
592
|
+
isFutureUtcMonth,
|
|
593
|
+
kalshiCandles,
|
|
594
|
+
kalshiFills,
|
|
595
|
+
kalshiOrderbook,
|
|
596
|
+
polymarketHistory,
|
|
597
|
+
polymarketSnapshot,
|
|
598
|
+
readTradesCache,
|
|
599
|
+
tradesCacheKey,
|
|
600
|
+
writeTradesCache
|
|
601
|
+
};
|
|
602
|
+
//# sourceMappingURL=index.mjs.map
|