pmxt-core 2.9.2 → 2.9.3

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.
Files changed (67) hide show
  1. package/dist/BaseExchange.d.ts +118 -4
  2. package/dist/BaseExchange.js +160 -7
  3. package/dist/exchanges/baozi/fetchEvents.js +16 -11
  4. package/dist/exchanges/baozi/index.d.ts +5 -0
  5. package/dist/exchanges/baozi/index.js +6 -0
  6. package/dist/exchanges/kalshi/api.d.ts +7 -1
  7. package/dist/exchanges/kalshi/api.js +11 -2
  8. package/dist/exchanges/kalshi/config.d.ts +103 -0
  9. package/dist/exchanges/kalshi/config.js +144 -0
  10. package/dist/exchanges/kalshi/fetchEvents.d.ts +2 -2
  11. package/dist/exchanges/kalshi/fetchEvents.js +138 -67
  12. package/dist/exchanges/kalshi/fetchMarkets.d.ts +2 -2
  13. package/dist/exchanges/kalshi/fetchMarkets.js +36 -25
  14. package/dist/exchanges/kalshi/fetchOHLCV.d.ts +3 -3
  15. package/dist/exchanges/kalshi/fetchOHLCV.js +20 -17
  16. package/dist/exchanges/kalshi/fetchOrderBook.d.ts +2 -0
  17. package/dist/exchanges/kalshi/fetchOrderBook.js +60 -0
  18. package/dist/exchanges/kalshi/fetchTrades.d.ts +3 -0
  19. package/dist/exchanges/kalshi/fetchTrades.js +32 -0
  20. package/dist/exchanges/kalshi/index.d.ts +20 -4
  21. package/dist/exchanges/kalshi/index.js +171 -90
  22. package/dist/exchanges/kalshi/kalshi.test.js +440 -157
  23. package/dist/exchanges/kalshi/utils.d.ts +1 -3
  24. package/dist/exchanges/kalshi/utils.js +15 -16
  25. package/dist/exchanges/kalshi/websocket.d.ts +4 -3
  26. package/dist/exchanges/kalshi/websocket.js +87 -61
  27. package/dist/exchanges/kalshi-demo/index.d.ts +10 -0
  28. package/dist/exchanges/kalshi-demo/index.js +23 -0
  29. package/dist/exchanges/limitless/api.d.ts +1 -1
  30. package/dist/exchanges/limitless/api.js +1 -1
  31. package/dist/exchanges/limitless/fetchEvents.d.ts +2 -1
  32. package/dist/exchanges/limitless/fetchEvents.js +95 -49
  33. package/dist/exchanges/limitless/fetchOHLCV.d.ts +2 -2
  34. package/dist/exchanges/limitless/index.d.ts +11 -3
  35. package/dist/exchanges/limitless/index.js +69 -1
  36. package/dist/exchanges/limitless/utils.js +1 -0
  37. package/dist/exchanges/myriad/api.d.ts +1 -1
  38. package/dist/exchanges/myriad/api.js +1 -1
  39. package/dist/exchanges/myriad/fetchOHLCV.d.ts +2 -2
  40. package/dist/exchanges/myriad/index.d.ts +9 -3
  41. package/dist/exchanges/myriad/index.js +34 -0
  42. package/dist/exchanges/myriad/utils.js +5 -1
  43. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  44. package/dist/exchanges/polymarket/api-clob.js +1 -1
  45. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  46. package/dist/exchanges/polymarket/api-data.js +1 -1
  47. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  48. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  49. package/dist/exchanges/polymarket/auth.js +3 -1
  50. package/dist/exchanges/polymarket/fetchEvents.js +116 -80
  51. package/dist/exchanges/polymarket/fetchOHLCV.d.ts +2 -2
  52. package/dist/exchanges/polymarket/index.d.ts +30 -6
  53. package/dist/exchanges/polymarket/index.js +101 -31
  54. package/dist/exchanges/polymarket/utils.js +1 -0
  55. package/dist/exchanges/probable/api.d.ts +1 -1
  56. package/dist/exchanges/probable/api.js +1 -1
  57. package/dist/exchanges/probable/index.d.ts +45 -3
  58. package/dist/exchanges/probable/index.js +61 -0
  59. package/dist/exchanges/probable/utils.js +5 -1
  60. package/dist/index.d.ts +4 -0
  61. package/dist/index.js +5 -1
  62. package/dist/server/app.js +56 -48
  63. package/dist/server/utils/port-manager.js +1 -1
  64. package/dist/types.d.ts +29 -0
  65. package/dist/utils/throttler.d.ts +17 -0
  66. package/dist/utils/throttler.js +50 -0
  67. package/package.json +7 -4
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ /**
3
+ * Kalshi runtime configuration.
4
+ *
5
+ * This file is hand-authored and is the single source of truth for:
6
+ * - Base URL constants for production and demo environments
7
+ * - API path constants (KALSHI_PATHS)
8
+ * - URL helper functions used by fetch functions and the exchange class
9
+ *
10
+ * The OpenAPI spec lives in core/specs/kalshi/Kalshi.yaml and is compiled
11
+ * into core/src/exchanges/kalshi/api.ts by `npm run fetch:openapi`.
12
+ * Do NOT put runtime config into api.ts — it will be overwritten.
13
+ *
14
+ * Environment mapping (aligns with the `{env}` server variable in Kalshi.yaml):
15
+ * env = "api" → production: https://api.elections.kalshi.com
16
+ * env = "demo-api" → demo/paper: https://demo-api.elections.kalshi.com
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.KALSHI_PATHS = exports.KALSHI_DEMO_WS_URL = exports.KALSHI_PROD_WS_URL = exports.KALSHI_DEMO_API_URL = exports.KALSHI_PROD_API_URL = void 0;
20
+ exports.getKalshiConfig = getKalshiConfig;
21
+ exports.getApiPath = getApiPath;
22
+ exports.getEventsUrl = getEventsUrl;
23
+ exports.getSeriesUrl = getSeriesUrl;
24
+ exports.getPortfolioUrl = getPortfolioUrl;
25
+ exports.getMarketsUrl = getMarketsUrl;
26
+ // ── Base URL constants ────────────────────────────────────────────────────────
27
+ exports.KALSHI_PROD_API_URL = "https://api.elections.kalshi.com";
28
+ exports.KALSHI_DEMO_API_URL = "https://demo-api.kalshi.co";
29
+ exports.KALSHI_PROD_WS_URL = "wss://api.elections.kalshi.com/trade-api/ws/v2";
30
+ exports.KALSHI_DEMO_WS_URL = "wss://demo-api.kalshi.co/trade-api/ws/v2";
31
+ // ── Path constants ────────────────────────────────────────────────────────────
32
+ exports.KALSHI_PATHS = {
33
+ TRADE_API: "/trade-api/v2",
34
+ EVENTS: "/events",
35
+ SERIES: "/series",
36
+ PORTFOLIO: "/portfolio",
37
+ MARKETS: "/markets",
38
+ BALANCE: "/balance",
39
+ ORDERS: "/orders",
40
+ POSITIONS: "/positions",
41
+ };
42
+ /**
43
+ * Return a typed config object for the requested environment.
44
+ *
45
+ * @param demoMode - Pass `true` to target demo-api.elections.kalshi.com.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const config = getKalshiConfig(true);
50
+ * // config.apiUrl === "https://demo-api.elections.kalshi.com"
51
+ * ```
52
+ */
53
+ function getKalshiConfig(demoMode = false) {
54
+ return {
55
+ apiUrl: demoMode ? exports.KALSHI_DEMO_API_URL : exports.KALSHI_PROD_API_URL,
56
+ wsUrl: demoMode ? exports.KALSHI_DEMO_WS_URL : exports.KALSHI_PROD_WS_URL,
57
+ demoMode,
58
+ };
59
+ }
60
+ // ── URL builder helpers ───────────────────────────────────────────────────────
61
+ /**
62
+ * Build a full URL from a base and an arbitrary list of path segments.
63
+ * Empty segments are filtered out; leading/trailing slashes are normalised.
64
+ */
65
+ function buildApiUrl(baseUrl, ...segments) {
66
+ const flatSegments = segments.flat().filter(Boolean);
67
+ const path = flatSegments.map((s) => s.replace(/^\/+|\/+$/g, "")).join("/");
68
+ return path ? `${baseUrl}/${path}` : baseUrl;
69
+ }
70
+ /**
71
+ * Build the full path (including `/trade-api/v2` prefix) for use in
72
+ * `KalshiAuth.getHeaders()` signing.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * getApiPath("/portfolio/balance")
77
+ * // → "/trade-api/v2/portfolio/balance"
78
+ * ```
79
+ */
80
+ function getApiPath(operationPath) {
81
+ return exports.KALSHI_PATHS.TRADE_API + operationPath;
82
+ }
83
+ /**
84
+ * Build the full URL for the events endpoint.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * getEventsUrl(baseUrl) // .../events
89
+ * getEventsUrl(baseUrl, ['FED-25']) // .../events/FED-25
90
+ * ```
91
+ */
92
+ function getEventsUrl(baseUrl, pathSegments = []) {
93
+ return buildApiUrl(baseUrl, exports.KALSHI_PATHS.TRADE_API, exports.KALSHI_PATHS.EVENTS, pathSegments);
94
+ }
95
+ /**
96
+ * Build the full URL for the series endpoint, with optional nested segments.
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * getSeriesUrl(baseUrl) // .../series
101
+ * getSeriesUrl(baseUrl, 'FED') // .../series/FED
102
+ * getSeriesUrl(baseUrl, 'FED', ['markets', 'FED-B4.75', 'candlesticks'])
103
+ * // .../series/FED/markets/FED-B4.75/candlesticks
104
+ * ```
105
+ */
106
+ function getSeriesUrl(baseUrl, seriesTicker, pathSegments = []) {
107
+ const segments = [
108
+ exports.KALSHI_PATHS.TRADE_API,
109
+ exports.KALSHI_PATHS.SERIES,
110
+ seriesTicker || "",
111
+ ...pathSegments,
112
+ ];
113
+ return buildApiUrl(baseUrl, ...segments);
114
+ }
115
+ /**
116
+ * Build the full URL for the portfolio endpoint.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * getPortfolioUrl(baseUrl, '/balance') // .../portfolio/balance
121
+ * ```
122
+ */
123
+ function getPortfolioUrl(baseUrl, subPath) {
124
+ return buildApiUrl(baseUrl, exports.KALSHI_PATHS.TRADE_API, exports.KALSHI_PATHS.PORTFOLIO, subPath || "");
125
+ }
126
+ /**
127
+ * Build the full URL for the markets endpoint, with optional nested segments.
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * getMarketsUrl(baseUrl) // .../markets
132
+ * getMarketsUrl(baseUrl, 'FED-B4.75') // .../markets/FED-B4.75
133
+ * getMarketsUrl(baseUrl, 'FED-B4.75', ['orderbook']) // .../markets/FED-B4.75/orderbook
134
+ * ```
135
+ */
136
+ function getMarketsUrl(baseUrl, marketId, pathSegments = []) {
137
+ const segments = [
138
+ exports.KALSHI_PATHS.TRADE_API,
139
+ exports.KALSHI_PATHS.MARKETS,
140
+ marketId || "",
141
+ ...pathSegments,
142
+ ];
143
+ return buildApiUrl(baseUrl, ...segments);
144
+ }
@@ -1,5 +1,5 @@
1
- import { EventFetchParams } from '../../BaseExchange';
2
- import { UnifiedEvent } from '../../types';
1
+ import { EventFetchParams } from "../../BaseExchange";
2
+ import { UnifiedEvent } from "../../types";
3
3
  type CallApi = (operationId: string, params?: Record<string, any>) => Promise<any>;
4
4
  export declare function fetchEvents(params: EventFetchParams, callApi: CallApi): Promise<UnifiedEvent[]>;
5
5
  export {};
@@ -5,7 +5,10 @@ const utils_1 = require("./utils");
5
5
  const errors_1 = require("./errors");
6
6
  async function fetchEventByTicker(eventTicker, callApi) {
7
7
  const normalizedTicker = eventTicker.toUpperCase();
8
- const data = await callApi('GetEvent', { event_ticker: normalizedTicker, with_nested_markets: true });
8
+ const data = await callApi("GetEvent", {
9
+ event_ticker: normalizedTicker,
10
+ with_nested_markets: true,
11
+ });
9
12
  const event = data.event;
10
13
  if (!event)
11
14
  return [];
@@ -21,16 +24,102 @@ async function fetchEventByTicker(eventTicker, callApi) {
21
24
  const unifiedEvent = {
22
25
  id: event.event_ticker,
23
26
  title: event.title,
24
- description: event.mututals_description || "",
27
+ description: event.mututals_description || deriveEventDescription(event.markets || []),
25
28
  slug: event.event_ticker,
26
29
  markets: markets,
30
+ volume24h: markets.reduce((sum, m) => sum + m.volume24h, 0),
31
+ volume: markets.some(m => m.volume !== undefined) ? markets.reduce((sum, m) => sum + (m.volume ?? 0), 0) : undefined,
27
32
  url: `https://kalshi.com/events/${event.event_ticker}`,
28
33
  image: event.image_url,
29
34
  category: event.category,
30
- tags: event.tags || []
35
+ tags: event.tags || [],
31
36
  };
32
37
  return [unifiedEvent];
33
38
  }
39
+ const PLACEHOLDERS = ["{x}", "{y}", "{z}"];
40
+ function deriveEventDescription(markets) {
41
+ const texts = markets
42
+ .map((m) => m.rules_primary)
43
+ .filter((t) => typeof t === "string" && t.length > 0);
44
+ if (texts.length === 0)
45
+ return "";
46
+ if (texts.length === 1)
47
+ return texts[0];
48
+ // Find longest common prefix
49
+ let prefix = texts[0];
50
+ for (const t of texts) {
51
+ while (!t.startsWith(prefix))
52
+ prefix = prefix.slice(0, -1);
53
+ if (!prefix)
54
+ break;
55
+ }
56
+ // Find longest common suffix (on the part after the prefix)
57
+ const suffixCandidates = texts.map((t) => t.slice(prefix.length));
58
+ let suffix = suffixCandidates[0];
59
+ for (const t of suffixCandidates) {
60
+ while (!t.endsWith(suffix))
61
+ suffix = suffix.slice(1);
62
+ if (!suffix)
63
+ break;
64
+ }
65
+ // Require a meaningful template (at least 20 chars of fixed text)
66
+ if (prefix.length + suffix.length < 20)
67
+ return texts[0];
68
+ // Verify all variable regions are distinct (i.e. something actually varies)
69
+ const variables = texts.map((t) => t.slice(prefix.length, suffix.length ? t.length - suffix.length : undefined));
70
+ if (new Set(variables).size === 1)
71
+ return texts[0];
72
+ return prefix + PLACEHOLDERS[0] + suffix;
73
+ }
74
+ function rawEventToUnified(event) {
75
+ const markets = [];
76
+ if (event.markets) {
77
+ for (const market of event.markets) {
78
+ const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market);
79
+ if (unifiedMarket) {
80
+ markets.push(unifiedMarket);
81
+ }
82
+ }
83
+ }
84
+ const unifiedEvent = {
85
+ id: event.event_ticker,
86
+ title: event.title,
87
+ description: event.mututals_description || deriveEventDescription(event.markets || []),
88
+ slug: event.event_ticker,
89
+ markets: markets,
90
+ volume24h: markets.reduce((sum, m) => sum + m.volume24h, 0),
91
+ volume: markets.some(m => m.volume !== undefined) ? markets.reduce((sum, m) => sum + (m.volume ?? 0), 0) : undefined,
92
+ url: `https://kalshi.com/events/${event.event_ticker}`,
93
+ image: event.image_url,
94
+ category: event.category,
95
+ tags: event.tags || [],
96
+ };
97
+ return unifiedEvent;
98
+ }
99
+ async function fetchAllWithStatus(callApi, apiStatus) {
100
+ let allEvents = [];
101
+ let cursor = null;
102
+ let page = 0;
103
+ const MAX_PAGES = 1000;
104
+ const BATCH_SIZE = 200;
105
+ do {
106
+ const queryParams = {
107
+ limit: BATCH_SIZE,
108
+ with_nested_markets: true,
109
+ status: apiStatus,
110
+ };
111
+ if (cursor)
112
+ queryParams.cursor = cursor;
113
+ const data = await callApi("GetEvents", queryParams);
114
+ const events = data.events || [];
115
+ if (events.length === 0)
116
+ break;
117
+ allEvents = allEvents.concat(events);
118
+ cursor = data.cursor;
119
+ page++;
120
+ } while (cursor && page < MAX_PAGES);
121
+ return allEvents;
122
+ }
34
123
  async function fetchEvents(params, callApi) {
35
124
  try {
36
125
  // Handle eventId lookup (direct API call)
@@ -41,85 +130,67 @@ async function fetchEvents(params, callApi) {
41
130
  if (params.slug) {
42
131
  return await fetchEventByTicker(params.slug, callApi);
43
132
  }
44
- const status = params?.status || 'active';
133
+ const status = params?.status || "active";
45
134
  const limit = params?.limit || 10000;
46
- const query = (params?.query || '').toLowerCase();
47
- const fetchAllWithStatus = async (apiStatus) => {
48
- let allEvents = [];
49
- let cursor = null;
50
- let page = 0;
51
- const MAX_PAGES = 1000; // Safety cap against infinite loops
52
- const BATCH_SIZE = 200; // Max limit per Kalshi API docs
53
- do {
54
- const queryParams = {
55
- limit: BATCH_SIZE,
56
- with_nested_markets: true,
57
- status: apiStatus
58
- };
59
- if (cursor)
60
- queryParams.cursor = cursor;
61
- const data = await callApi('GetEvents', queryParams);
62
- const events = data.events || [];
63
- if (events.length === 0)
64
- break;
65
- allEvents = allEvents.concat(events);
66
- cursor = data.cursor;
67
- page++;
68
- // If we have no search query and have fetched enough events, we can stop early
69
- if (!query && allEvents.length >= limit * 1.5) {
70
- break;
71
- }
72
- } while (cursor && page < MAX_PAGES);
73
- return allEvents;
74
- };
135
+ const query = (params?.query || "").toLowerCase();
75
136
  let events = [];
76
- if (status === 'all') {
137
+ if (status === "all") {
77
138
  const [openEvents, closedEvents, settledEvents] = await Promise.all([
78
- fetchAllWithStatus('open'),
79
- fetchAllWithStatus('closed'),
80
- fetchAllWithStatus('settled')
139
+ fetchAllWithStatus(callApi, "open"),
140
+ fetchAllWithStatus(callApi, "closed"),
141
+ fetchAllWithStatus(callApi, "settled"),
81
142
  ]);
82
143
  events = [...openEvents, ...closedEvents, ...settledEvents];
83
144
  }
84
- else if (status === 'closed' || status === 'inactive') {
145
+ else if (status === "closed" || status === "inactive") {
85
146
  const [closedEvents, settledEvents] = await Promise.all([
86
- fetchAllWithStatus('closed'),
87
- fetchAllWithStatus('settled')
147
+ fetchAllWithStatus(callApi, "closed"),
148
+ fetchAllWithStatus(callApi, "settled"),
88
149
  ]);
89
150
  events = [...closedEvents, ...settledEvents];
90
151
  }
91
152
  else {
92
- events = await fetchAllWithStatus('open');
153
+ events = await fetchAllWithStatus(callApi, "open");
93
154
  }
94
- const filtered = events.filter((event) => {
95
- return (event.title || '').toLowerCase().includes(query);
96
- });
97
- const unifiedEvents = filtered.map((event) => {
98
- const markets = [];
99
- if (event.markets) {
100
- for (const market of event.markets) {
101
- const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market);
102
- if (unifiedMarket) {
103
- markets.push(unifiedMarket);
104
- }
105
- }
106
- }
107
- const unifiedEvent = {
108
- id: event.event_ticker,
109
- title: event.title,
110
- description: event.mututals_description || "",
111
- slug: event.event_ticker,
112
- markets: markets,
113
- url: `https://kalshi.com/events/${event.event_ticker}`,
114
- image: event.image_url,
115
- category: event.category,
116
- tags: event.tags || []
117
- };
118
- return unifiedEvent;
119
- });
155
+ // Apply keyword filter if a query was provided
156
+ const filtered = query
157
+ ? events.filter((event) => (event.title || "").toLowerCase().includes(query))
158
+ : events;
159
+ // Client-side sort — Kalshi's /events endpoint has no sort param.
160
+ // We aggregate stats from nested markets and sort the full set before slicing.
161
+ const sort = params?.sort || "volume";
162
+ const sorted = sortRawEvents(filtered, sort);
163
+ const unifiedEvents = sorted.map(rawEventToUnified);
120
164
  return unifiedEvents.slice(0, limit);
121
165
  }
122
166
  catch (error) {
123
167
  throw errors_1.kalshiErrorMapper.mapError(error);
124
168
  }
125
169
  }
170
+ function eventVolume(event) {
171
+ return (event.markets || []).reduce((sum, m) => sum + Number(m.volume || 0), 0);
172
+ }
173
+ function eventLiquidity(event) {
174
+ return (event.markets || []).reduce((sum, m) => sum + Number(m.open_interest || m.liquidity || 0), 0);
175
+ }
176
+ function eventNewest(event) {
177
+ // Use the earliest close_time across markets as a proxy for "newness"
178
+ const times = (event.markets || [])
179
+ .map((m) => (m.close_time ? new Date(m.close_time).getTime() : 0))
180
+ .filter((t) => t > 0);
181
+ return times.length > 0 ? Math.min(...times) : 0;
182
+ }
183
+ function sortRawEvents(events, sort) {
184
+ const copy = [...events];
185
+ if (sort === "newest") {
186
+ copy.sort((a, b) => eventNewest(b) - eventNewest(a));
187
+ }
188
+ else if (sort === "liquidity") {
189
+ copy.sort((a, b) => eventLiquidity(b) - eventLiquidity(a));
190
+ }
191
+ else {
192
+ // Default: volume
193
+ copy.sort((a, b) => eventVolume(b) - eventVolume(a));
194
+ }
195
+ return copy;
196
+ }
@@ -1,5 +1,5 @@
1
- import { MarketFetchParams } from '../../BaseExchange';
2
- import { UnifiedMarket } from '../../types';
1
+ import { MarketFetchParams } from "../../BaseExchange";
2
+ import { UnifiedMarket } from "../../types";
3
3
  type CallApi = (operationId: string, params?: Record<string, any>) => Promise<any>;
4
4
  export declare function resetCache(): void;
5
5
  export declare function fetchMarkets(params: MarketFetchParams | undefined, callApi: CallApi): Promise<UnifiedMarket[]>;
@@ -4,7 +4,7 @@ exports.resetCache = resetCache;
4
4
  exports.fetchMarkets = fetchMarkets;
5
5
  const utils_1 = require("./utils");
6
6
  const errors_1 = require("./errors");
7
- async function fetchActiveEvents(callApi, targetMarketCount, status = 'open') {
7
+ async function fetchActiveEvents(callApi, targetMarketCount, status = "open") {
8
8
  let allEvents = [];
9
9
  let totalMarketCount = 0;
10
10
  let cursor = null;
@@ -19,11 +19,11 @@ async function fetchActiveEvents(callApi, targetMarketCount, status = 'open') {
19
19
  const queryParams = {
20
20
  limit: BATCH_SIZE,
21
21
  with_nested_markets: true,
22
- status: status // Filter by status (default 'open')
22
+ status: status, // Filter by status (default 'open')
23
23
  };
24
24
  if (cursor)
25
25
  queryParams.cursor = cursor;
26
- const data = await callApi('GetEvents', queryParams);
26
+ const data = await callApi("GetEvents", queryParams);
27
27
  const events = data.events || [];
28
28
  if (events.length === 0)
29
29
  break;
@@ -54,7 +54,7 @@ async function fetchActiveEvents(callApi, targetMarketCount, status = 'open') {
54
54
  }
55
55
  async function fetchSeriesMap(callApi) {
56
56
  try {
57
- const data = await callApi('GetSeriesList');
57
+ const data = await callApi("GetSeriesList");
58
58
  const seriesList = data.series || [];
59
59
  const map = new Map();
60
60
  for (const series of seriesList) {
@@ -91,7 +91,7 @@ async function fetchMarkets(params, callApi) {
91
91
  }
92
92
  // Handle outcomeId lookup (strip -NO suffix, use as ticker)
93
93
  if (params?.outcomeId) {
94
- const ticker = params.outcomeId.replace(/-NO$/, '');
94
+ const ticker = params.outcomeId.replace(/-NO$/, "");
95
95
  return await fetchMarketsBySlug(ticker, callApi);
96
96
  }
97
97
  // Handle eventId lookup (event ticker works the same way)
@@ -112,14 +112,19 @@ async function fetchMarkets(params, callApi) {
112
112
  async function fetchMarketsBySlug(eventTicker, callApi) {
113
113
  // Kalshi API expects uppercase tickers, but URLs use lowercase
114
114
  const normalizedTicker = eventTicker.toUpperCase();
115
- const data = await callApi('GetEvent', { event_ticker: normalizedTicker, with_nested_markets: true });
115
+ const data = await callApi("GetEvent", {
116
+ event_ticker: normalizedTicker,
117
+ with_nested_markets: true,
118
+ });
116
119
  const event = data.event;
117
120
  if (!event)
118
121
  return [];
119
122
  // Enrichment: Fetch series tags if they exist
120
123
  if (event.series_ticker) {
121
124
  try {
122
- const seriesData = await callApi('GetSeries', { series_ticker: event.series_ticker });
125
+ const seriesData = await callApi("GetSeries", {
126
+ series_ticker: event.series_ticker,
127
+ });
123
128
  const series = seriesData.series;
124
129
  if (series && series.tags && series.tags.length > 0) {
125
130
  if (!event.tags || event.tags.length === 0) {
@@ -146,13 +151,15 @@ async function searchMarkets(query, params, callApi) {
146
151
  const searchLimit = 250000;
147
152
  const markets = await fetchMarketsDefault({ ...params, limit: searchLimit }, callApi);
148
153
  const lowerQuery = query.toLowerCase();
149
- const searchIn = params?.searchIn || 'title'; // Default to title-only search
150
- const filtered = markets.filter(market => {
151
- const titleMatch = (market.title || '').toLowerCase().includes(lowerQuery);
152
- const descMatch = (market.description || '').toLowerCase().includes(lowerQuery);
153
- if (searchIn === 'title')
154
+ const searchIn = params?.searchIn || "title"; // Default to title-only search
155
+ const filtered = markets.filter((market) => {
156
+ const titleMatch = (market.title || "").toLowerCase().includes(lowerQuery);
157
+ const descMatch = (market.description || "")
158
+ .toLowerCase()
159
+ .includes(lowerQuery);
160
+ if (searchIn === "title")
154
161
  return titleMatch;
155
- if (searchIn === 'description')
162
+ if (searchIn === "description")
156
163
  return descMatch;
157
164
  return titleMatch || descMatch; // 'both'
158
165
  });
@@ -163,21 +170,24 @@ async function fetchMarketsDefault(params, callApi) {
163
170
  const limit = params?.limit || 250000;
164
171
  const offset = params?.offset || 0;
165
172
  const now = Date.now();
166
- const status = params?.status || 'active'; // Default to 'active'
173
+ const status = params?.status || "active"; // Default to 'active'
167
174
  // Map 'active' -> 'open', 'closed' -> 'closed'
168
175
  // Kalshi statuses: 'open', 'closed', 'settled'
169
- let apiStatus = 'open';
170
- if (status === 'closed' || status === 'inactive')
171
- apiStatus = 'closed';
172
- else if (status === 'all')
173
- apiStatus = 'open'; // Fallback for all? Or loop? For now default to open.
176
+ let apiStatus = "open";
177
+ if (status === "closed" || status === "inactive")
178
+ apiStatus = "closed";
179
+ else if (status === "all")
180
+ apiStatus = "open"; // Fallback for all? Or loop? For now default to open.
174
181
  try {
175
182
  let events;
176
183
  let seriesMap;
177
184
  // Check if we have valid cached data
178
185
  // Only use global cache for the default 'active'/'open' case
179
- const useCache = (status === 'active' || !params?.status);
180
- if (useCache && cachedEvents && cachedSeriesMap && (now - lastCacheTime < CACHE_TTL)) {
186
+ const useCache = status === "active" || !params?.status;
187
+ if (useCache &&
188
+ cachedEvents &&
189
+ cachedSeriesMap &&
190
+ now - lastCacheTime < CACHE_TTL) {
181
191
  events = cachedEvents;
182
192
  seriesMap = cachedSeriesMap;
183
193
  }
@@ -185,11 +195,12 @@ async function fetchMarketsDefault(params, callApi) {
185
195
  // Optimize fetch limit based on request parameters
186
196
  // If sorting is required (e.g. by volume), we need to fetch a larger set (or all) to sort correctly.
187
197
  // If no sorting is requested, we only need to fetch enough to satisfy the limit.
188
- const isSorted = params?.sort && (params.sort === 'volume' || params.sort === 'liquidity');
198
+ const isSorted = params?.sort &&
199
+ (params.sort === "volume" || params.sort === "liquidity");
189
200
  const fetchLimit = isSorted ? 1000 : limit;
190
201
  const [allEvents, fetchedSeriesMap] = await Promise.all([
191
202
  fetchActiveEvents(callApi, fetchLimit, apiStatus),
192
- fetchSeriesMap(callApi)
203
+ fetchSeriesMap(callApi),
193
204
  ]);
194
205
  events = allEvents;
195
206
  seriesMap = fetchedSeriesMap;
@@ -222,10 +233,10 @@ async function fetchMarketsDefault(params, callApi) {
222
233
  }
223
234
  }
224
235
  // Sort by 24h volume
225
- if (params?.sort === 'volume') {
236
+ if (params?.sort === "volume") {
226
237
  allMarkets.sort((a, b) => b.volume24h - a.volume24h);
227
238
  }
228
- else if (params?.sort === 'liquidity') {
239
+ else if (params?.sort === "liquidity") {
229
240
  allMarkets.sort((a, b) => b.liquidity - a.liquidity);
230
241
  }
231
242
  return allMarkets.slice(offset, offset + limit);
@@ -1,3 +1,3 @@
1
- import { HistoryFilterParams, OHLCVParams } from '../../BaseExchange';
2
- import { PriceCandle } from '../../types';
3
- export declare function fetchOHLCV(id: string, params: OHLCVParams | HistoryFilterParams, callApi: (operationId: string, params?: Record<string, any>) => Promise<any>): Promise<PriceCandle[]>;
1
+ import { OHLCVParams } from "../../BaseExchange";
2
+ import { PriceCandle } from "../../types";
3
+ export declare function fetchOHLCV(id: string, params: OHLCVParams, callApi: (operationId: string, params?: Record<string, any>) => Promise<any>): Promise<PriceCandle[]>;
@@ -5,34 +5,34 @@ const utils_1 = require("./utils");
5
5
  const validation_1 = require("../../utils/validation");
6
6
  const errors_1 = require("./errors");
7
7
  async function fetchOHLCV(id, params, callApi) {
8
- (0, validation_1.validateIdFormat)(id, 'OHLCV');
8
+ (0, validation_1.validateIdFormat)(id, "OHLCV");
9
9
  // Validate resolution is provided
10
10
  if (!params.resolution) {
11
- throw new Error('fetchOHLCV requires a resolution parameter. Use OHLCVParams with resolution specified.');
11
+ throw new Error("fetchOHLCV requires a resolution parameter. Use OHLCVParams with resolution specified.");
12
12
  }
13
13
  try {
14
14
  // Kalshi API expects uppercase tickers
15
15
  // Handle virtual "-NO" suffix by stripping it (fetching the underlying market history)
16
- const cleanedId = id.replace(/-NO$/, '');
16
+ const cleanedId = id.replace(/-NO$/, "");
17
17
  const normalizedId = cleanedId.toUpperCase();
18
18
  const interval = (0, utils_1.mapIntervalToKalshi)(params.resolution);
19
19
  // Heuristic for series_ticker
20
- const parts = normalizedId.split('-');
20
+ const parts = normalizedId.split("-");
21
21
  if (parts.length < 2) {
22
22
  throw new Error(`Invalid Kalshi Ticker format: "${id}". Expected format like "FED-25JAN29-B4.75".`);
23
23
  }
24
- const seriesTicker = parts.slice(0, -1).join('-');
24
+ const seriesTicker = parts.slice(0, -1).join("-");
25
25
  const now = Math.floor(Date.now() / 1000);
26
- let startTs = now - (24 * 60 * 60);
26
+ let startTs = now - 24 * 60 * 60;
27
27
  let endTs = now;
28
28
  // Helper to handle string dates (from JSON)
29
29
  // IMPORTANT: Python sends naive datetimes as ISO strings without 'Z' suffix.
30
30
  // We must treat these as UTC, not local time.
31
31
  const ensureDate = (d) => {
32
- if (typeof d === 'string') {
32
+ if (typeof d === "string") {
33
33
  // If string doesn't end with 'Z' and doesn't have timezone offset, append 'Z'
34
- if (!d.endsWith('Z') && !d.match(/[+-]\d{2}:\d{2}$/)) {
35
- return new Date(d + 'Z');
34
+ if (!d.endsWith("Z") && !d.match(/[+-]\d{2}:\d{2}$/)) {
35
+ return new Date(d + "Z");
36
36
  }
37
37
  return new Date(d);
38
38
  }
@@ -46,10 +46,10 @@ async function fetchOHLCV(id, params, callApi) {
46
46
  if (pEnd) {
47
47
  endTs = Math.floor(pEnd.getTime() / 1000);
48
48
  if (!pStart) {
49
- startTs = endTs - (24 * 60 * 60);
49
+ startTs = endTs - 24 * 60 * 60;
50
50
  }
51
51
  }
52
- const data = await callApi('GetMarketCandlesticks', {
52
+ const data = await callApi("GetMarketCandlesticks", {
53
53
  series_ticker: seriesTicker,
54
54
  ticker: normalizedId,
55
55
  period_interval: interval,
@@ -68,18 +68,21 @@ async function fetchOHLCV(id, params, callApi) {
68
68
  const getVal = (field) => {
69
69
  if (p[field] !== null && p[field] !== undefined)
70
70
  return p[field];
71
- if (ask[field] !== null && bid[field] !== null && ask[field] !== undefined && bid[field] !== undefined) {
71
+ if (ask[field] !== null &&
72
+ bid[field] !== null &&
73
+ ask[field] !== undefined &&
74
+ bid[field] !== undefined) {
72
75
  return (ask[field] + bid[field]) / 2;
73
76
  }
74
77
  return p.previous || 0;
75
78
  };
76
79
  return {
77
80
  timestamp: c.end_period_ts * 1000,
78
- open: getVal('open') / 100,
79
- high: getVal('high') / 100,
80
- low: getVal('low') / 100,
81
- close: getVal('close') / 100,
82
- volume: c.volume || 0
81
+ open: getVal("open") / 100,
82
+ high: getVal("high") / 100,
83
+ low: getVal("low") / 100,
84
+ close: getVal("close") / 100,
85
+ volume: c.volume || 0,
83
86
  };
84
87
  });
85
88
  if (params.limit && mappedCandles.length > params.limit) {
@@ -0,0 +1,2 @@
1
+ import { OrderBook } from "../../types";
2
+ export declare function fetchOrderBook(baseUrl: string, id: string): Promise<OrderBook>;