pmxt-core 2.46.14 → 2.48.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.
Files changed (82) hide show
  1. package/dist/BaseExchange.d.ts +42 -1
  2. package/dist/BaseExchange.js +29 -2
  3. package/dist/exchanges/baozi/index.d.ts +1 -0
  4. package/dist/exchanges/baozi/index.js +5 -0
  5. package/dist/exchanges/baozi/normalizer.js +2 -0
  6. package/dist/exchanges/baozi/utils.js +28 -0
  7. package/dist/exchanges/gemini-titan/index.d.ts +6 -2
  8. package/dist/exchanges/gemini-titan/index.js +81 -1
  9. package/dist/exchanges/gemini-titan/normalizer.d.ts +7 -1
  10. package/dist/exchanges/gemini-titan/normalizer.js +54 -0
  11. package/dist/exchanges/hyperliquid/index.d.ts +3 -0
  12. package/dist/exchanges/hyperliquid/index.js +7 -0
  13. package/dist/exchanges/hyperliquid/normalizer.js +20 -0
  14. package/dist/exchanges/kalshi/api.d.ts +1 -1
  15. package/dist/exchanges/kalshi/api.js +1 -1
  16. package/dist/exchanges/kalshi/fetcher.d.ts +10 -0
  17. package/dist/exchanges/kalshi/fetcher.js +36 -0
  18. package/dist/exchanges/kalshi/index.d.ts +3 -2
  19. package/dist/exchanges/kalshi/index.js +33 -0
  20. package/dist/exchanges/kalshi/normalizer.d.ts +3 -2
  21. package/dist/exchanges/kalshi/normalizer.js +35 -0
  22. package/dist/exchanges/limitless/api.d.ts +1 -1
  23. package/dist/exchanges/limitless/api.js +1 -1
  24. package/dist/exchanges/limitless/index.d.ts +1 -0
  25. package/dist/exchanges/limitless/index.js +5 -0
  26. package/dist/exchanges/limitless/normalizer.js +10 -0
  27. package/dist/exchanges/limitless/utils.js +17 -0
  28. package/dist/exchanges/metaculus/fetchEvents.js +6 -0
  29. package/dist/exchanges/metaculus/index.d.ts +3 -0
  30. package/dist/exchanges/metaculus/index.js +3 -0
  31. package/dist/exchanges/metaculus/utils.d.ts +2 -1
  32. package/dist/exchanges/metaculus/utils.js +32 -3
  33. package/dist/exchanges/myriad/api.d.ts +1 -1
  34. package/dist/exchanges/myriad/api.js +1 -1
  35. package/dist/exchanges/myriad/index.d.ts +1 -0
  36. package/dist/exchanges/myriad/index.js +5 -0
  37. package/dist/exchanges/myriad/normalizer.js +14 -0
  38. package/dist/exchanges/myriad/utils.js +14 -0
  39. package/dist/exchanges/opinion/api.d.ts +1 -1
  40. package/dist/exchanges/opinion/api.js +1 -1
  41. package/dist/exchanges/opinion/index.d.ts +6 -2
  42. package/dist/exchanges/opinion/index.js +54 -1
  43. package/dist/exchanges/opinion/normalizer.d.ts +8 -2
  44. package/dist/exchanges/opinion/normalizer.js +42 -0
  45. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  46. package/dist/exchanges/polymarket/api-clob.js +1 -1
  47. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  48. package/dist/exchanges/polymarket/api-data.js +1 -1
  49. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  50. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  51. package/dist/exchanges/polymarket/index.d.ts +13 -2
  52. package/dist/exchanges/polymarket/index.js +49 -0
  53. package/dist/exchanges/polymarket/normalizer.d.ts +2 -1
  54. package/dist/exchanges/polymarket/normalizer.js +48 -0
  55. package/dist/exchanges/polymarket/utils.js +28 -0
  56. package/dist/exchanges/polymarket_us/index.d.ts +3 -2
  57. package/dist/exchanges/polymarket_us/index.js +57 -0
  58. package/dist/exchanges/polymarket_us/normalizer.d.ts +9 -2
  59. package/dist/exchanges/polymarket_us/normalizer.js +42 -0
  60. package/dist/exchanges/probable/api.d.ts +1 -1
  61. package/dist/exchanges/probable/api.js +1 -1
  62. package/dist/exchanges/probable/index.d.ts +3 -0
  63. package/dist/exchanges/probable/index.js +7 -0
  64. package/dist/exchanges/probable/utils.js +17 -0
  65. package/dist/exchanges/smarkets/index.d.ts +1 -0
  66. package/dist/exchanges/smarkets/index.js +5 -0
  67. package/dist/exchanges/smarkets/normalizer.js +20 -0
  68. package/dist/exchanges/suibets/index.d.ts +1 -0
  69. package/dist/exchanges/suibets/index.js +5 -0
  70. package/dist/exchanges/suibets/normalizer.js +20 -0
  71. package/dist/router/Router.d.ts +29 -2
  72. package/dist/router/Router.js +145 -0
  73. package/dist/router/index.d.ts +1 -0
  74. package/dist/router/index.js +1 -0
  75. package/dist/router/series-map.d.ts +32 -0
  76. package/dist/router/series-map.js +146 -0
  77. package/dist/server/method-verbs.json +10 -0
  78. package/dist/server/openapi.yaml +132 -0
  79. package/dist/types.d.ts +35 -0
  80. package/dist/utils/metadata.d.ts +14 -0
  81. package/dist/utils/metadata.js +33 -0
  82. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
- import { EventFetchParams, ExchangeCredentials, HistoryFilterParams, MarketFilterParams, MyTradesParams, OHLCVParams, OrderHistoryParams, PredictionMarketExchange, TradesParams } from '../../BaseExchange';
2
- import { Balance, BuiltOrder, CreateOrderParams, Order, OrderBook, Position, PriceCandle, Trade, UnifiedEvent, UnifiedMarket, UserTrade } from '../../types';
1
+ import { EventFetchParams, ExchangeCredentials, HistoryFilterParams, MarketFilterParams, MyTradesParams, OHLCVParams, OrderHistoryParams, PredictionMarketExchange, SeriesFetchParams, TradesParams } from '../../BaseExchange';
2
+ import { Balance, BuiltOrder, CreateOrderParams, Order, OrderBook, Position, PriceCandle, Trade, UnifiedEvent, UnifiedMarket, UnifiedSeries, UserTrade } from '../../types';
3
3
  import { KalshiWebSocketConfig } from './websocket';
4
4
  export type { KalshiWebSocketConfig };
5
5
  export interface KalshiExchangeOptions {
@@ -23,6 +23,7 @@ export declare class KalshiExchange extends PredictionMarketExchange {
23
23
  private ensureAuth;
24
24
  protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
25
25
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
26
+ protected fetchSeriesImpl(params: SeriesFetchParams): Promise<UnifiedSeries[]>;
26
27
  fetchEventsPage(params?: EventFetchParams): Promise<{
27
28
  events: UnifiedEvent[];
28
29
  cursor: string | null;
@@ -118,6 +118,39 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
118
118
  .filter((e) => e !== null)
119
119
  .slice(0, limit);
120
120
  }
121
+ async fetchSeriesImpl(params) {
122
+ const rawList = await this.fetcher.fetchRawSeriesList();
123
+ // If a specific series id is requested, fetch its events and attach them.
124
+ // We still iterate the full list so filters apply uniformly.
125
+ let eventsById = null;
126
+ if (params.id) {
127
+ const rawEvents = await this.fetcher.fetchRawEvents({ series: params.id });
128
+ const normalizedEvents = rawEvents
129
+ .map((raw) => this.normalizer.normalizeEvent(raw))
130
+ .filter((e) => e !== null);
131
+ eventsById = new Map([[params.id, normalizedEvents]]);
132
+ }
133
+ const normalized = rawList.map((raw) => {
134
+ const events = eventsById?.get(raw.ticker);
135
+ return this.normalizer.normalizeSeries(raw, events);
136
+ });
137
+ // Client-side filters
138
+ let filtered = normalized;
139
+ if (params.id) {
140
+ filtered = filtered.filter((s) => s.id === params.id);
141
+ }
142
+ else if (params.slug) {
143
+ filtered = filtered.filter((s) => s.ticker === params.slug || s.slug === params.slug);
144
+ }
145
+ if (params.query) {
146
+ const lowerQuery = params.query.toLowerCase();
147
+ filtered = filtered.filter((s) => s.title.toLowerCase().includes(lowerQuery));
148
+ }
149
+ if (params.recurrence) {
150
+ filtered = filtered.filter((s) => s.recurrence === params.recurrence);
151
+ }
152
+ return filtered;
153
+ }
121
154
  async fetchEventsPage(params = {}) {
122
155
  const page = await this.fetcher.fetchRawEventPage(params);
123
156
  const query = (params?.query || '').toLowerCase();
@@ -1,12 +1,13 @@
1
1
  import { OHLCVParams } from '../../BaseExchange';
2
- import { UnifiedMarket, UnifiedEvent, PriceCandle, OrderBook, Trade, UserTrade, Position, Balance } from '../../types';
2
+ import { UnifiedMarket, UnifiedEvent, UnifiedSeries, PriceCandle, OrderBook, Trade, UserTrade, Position, Balance } from '../../types';
3
3
  import { IExchangeNormalizer } from '../interfaces';
4
- import { KalshiRawEvent, KalshiRawMarket, KalshiRawCandlestick, KalshiRawTrade, KalshiRawFill, KalshiRawOrder, KalshiRawPosition, KalshiRawOrderBookFp } from './fetcher';
4
+ import { KalshiRawEvent, KalshiRawMarket, KalshiRawCandlestick, KalshiRawTrade, KalshiRawFill, KalshiRawOrder, KalshiRawPosition, KalshiRawOrderBookFp, KalshiRawSeries } from './fetcher';
5
5
  export declare class KalshiNormalizer implements IExchangeNormalizer<KalshiRawEvent, KalshiRawEvent> {
6
6
  normalizeMarket(raw: KalshiRawEvent): UnifiedMarket | null;
7
7
  normalizeMarketsFromEvent(rawEvent: KalshiRawEvent): UnifiedMarket[];
8
8
  normalizeRawMarket(event: KalshiRawEvent, market: KalshiRawMarket): UnifiedMarket | null;
9
9
  normalizeEvent(raw: KalshiRawEvent): UnifiedEvent | null;
10
+ normalizeSeries(raw: KalshiRawSeries, events?: UnifiedEvent[]): UnifiedSeries;
10
11
  normalizeOHLCV(rawCandles: KalshiRawCandlestick[], params: OHLCVParams): PriceCandle[];
11
12
  normalizeOrderBook(raw: {
12
13
  orderbook_fp: KalshiRawOrderBookFp;
@@ -3,7 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KalshiNormalizer = void 0;
4
4
  exports.sortRawEvents = sortRawEvents;
5
5
  const market_utils_1 = require("../../utils/market-utils");
6
+ const metadata_1 = require("../../utils/metadata");
6
7
  const price_1 = require("./price");
8
+ // Raw Kalshi fields already promoted to first-class Unified columns — excluded
9
+ // from sourceMetadata so we capture only what the unified shape would drop.
10
+ const KALSHI_PROMOTED_EVENT_KEYS = [
11
+ 'event_ticker', 'title', 'markets', 'category', 'image_url', 'tags',
12
+ ];
13
+ const KALSHI_PROMOTED_SERIES_KEYS = [
14
+ 'ticker', 'title', 'tags', 'frequency', 'category',
15
+ ];
16
+ const KALSHI_PROMOTED_MARKET_KEYS = [
17
+ 'ticker', 'title', 'rules_primary', 'rules_secondary', 'expiration_time',
18
+ 'volume_24h_fp', 'volume_24h', 'volume', 'volume_fp',
19
+ 'liquidity_dollars', 'liquidity', 'open_interest_fp', 'open_interest',
20
+ 'status', 'last_price_dollars', 'previous_price_dollars',
21
+ 'yes_ask_dollars', 'yes_bid_dollars', 'last_price', 'yes_ask', 'yes_bid',
22
+ ];
7
23
  class KalshiNormalizer {
8
24
  normalizeMarket(raw) {
9
25
  // This normalizes a single-market event. For multi-market events, use normalizeMarketsFromEvent.
@@ -92,6 +108,11 @@ class KalshiNormalizer {
92
108
  category: event.category,
93
109
  tags: unifiedTags,
94
110
  status: this.cleanLabel(market.status) || undefined,
111
+ // series_ticker/series_title live on the parent event, not the raw
112
+ // market, and aren't promoted to a column — attach them here so
113
+ // markets are queryable by series. event_ticker is omitted (already
114
+ // promoted to eventId).
115
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(market, KALSHI_PROMOTED_MARKET_KEYS, { series_ticker: event.series_ticker, series_title: event.series_title }),
95
116
  };
96
117
  (0, market_utils_1.addBinaryOutcomes)(um);
97
118
  return um;
@@ -114,6 +135,20 @@ class KalshiNormalizer {
114
135
  image: raw.image_url ?? undefined,
115
136
  category: raw.category,
116
137
  tags: raw.tags || [],
138
+ // Keeps non-promoted event fields (series_ticker, series_title,
139
+ // sub_title, strike_period, ...); raw markets array is promoted.
140
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, KALSHI_PROMOTED_EVENT_KEYS),
141
+ };
142
+ }
143
+ normalizeSeries(raw, events) {
144
+ return {
145
+ id: raw.ticker,
146
+ ticker: raw.ticker,
147
+ title: (typeof raw.title === 'string' && raw.title.trim()) ? raw.title.trim() : raw.ticker,
148
+ recurrence: (typeof raw.frequency === 'string' && raw.frequency.trim()) ? raw.frequency.trim() : null,
149
+ url: `https://kalshi.com/events?series=${raw.ticker}`,
150
+ events: events ?? undefined,
151
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, KALSHI_PROMOTED_SERIES_KEYS),
117
152
  };
118
153
  }
119
154
  normalizeOHLCV(rawCandles, params) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
3
- * Generated at: 2026-05-26T15:22:20.044Z
3
+ * Generated at: 2026-05-30T13:35:15.569Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const limitlessApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.limitlessApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
6
- * Generated at: 2026-05-26T15:22:20.044Z
6
+ * Generated at: 2026-05-30T13:35:15.569Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -13,6 +13,7 @@ export interface LimitlessExchangeOptions {
13
13
  export declare class LimitlessExchange extends PredictionMarketExchange {
14
14
  protected readonly capabilityOverrides: {
15
15
  fetchOrder: false;
16
+ fetchSeries: false;
16
17
  };
17
18
  private auth?;
18
19
  private client?;
@@ -22,6 +22,7 @@ const logger_1 = require("../../utils/logger");
22
22
  class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
23
23
  capabilityOverrides = {
24
24
  fetchOrder: false,
25
+ fetchSeries: false,
25
26
  };
26
27
  auth;
27
28
  client;
@@ -146,6 +147,10 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
146
147
  return marketsAfterOffset.slice(0, limit);
147
148
  }
148
149
  async fetchEventsImpl(params) {
150
+ // Venue does not expose a series concept; honoring `params.series` by
151
+ // returning [] rather than ignoring the filter.
152
+ if (params.series !== undefined)
153
+ return [];
149
154
  const rawEvents = await this.fetcher.fetchRawEvents(params);
150
155
  return rawEvents
151
156
  .map((raw) => this.normalizer.normalizeEvent(raw))
@@ -2,6 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LimitlessNormalizer = void 0;
4
4
  const utils_1 = require("./utils");
5
+ const metadata_1 = require("../../utils/metadata");
6
+ // Raw Limitless event fields already promoted to first-class Unified columns —
7
+ // excluded from sourceMetadata so we capture only what the unified shape drops.
8
+ const LIMITLESS_PROMOTED_EVENT_KEYS = [
9
+ 'slug', 'title', 'question', 'description',
10
+ 'logo',
11
+ 'categories', 'tags',
12
+ 'markets',
13
+ ];
5
14
  // Limitless uses USDC with 6 decimals
6
15
  const USDC_DECIMALS = 6;
7
16
  const USDC_SCALE = Math.pow(10, USDC_DECIMALS);
@@ -47,6 +56,7 @@ class LimitlessNormalizer {
47
56
  image: raw.logo || `https://limitless.exchange/api/og?slug=${raw.slug}`,
48
57
  category: raw.categories?.[0],
49
58
  tags: raw.tags || [],
59
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, LIMITLESS_PROMOTED_EVENT_KEYS),
50
60
  };
51
61
  }
52
62
  normalizeOHLCV(rawPrices, params) {
@@ -5,7 +5,23 @@ exports.mapMarketToUnified = mapMarketToUnified;
5
5
  exports.mapIntervalToFidelity = mapIntervalToFidelity;
6
6
  exports.paginateLimitlessMarkets = paginateLimitlessMarkets;
7
7
  const market_utils_1 = require("../../utils/market-utils");
8
+ const metadata_1 = require("../../utils/metadata");
8
9
  exports.DEFAULT_LIMITLESS_API_URL = 'https://api.limitless.exchange';
10
+ // Raw Limitless market fields already promoted to first-class Unified columns —
11
+ // excluded from sourceMetadata so we capture only what the unified shape drops.
12
+ // Also excludes __pmxt* internal injection keys (not raw vendor data).
13
+ const LIMITLESS_PROMOTED_MARKET_KEYS = [
14
+ 'slug', 'title', 'question', 'description',
15
+ 'tokens', 'prices',
16
+ 'expirationTimestamp',
17
+ 'volumeFormatted', 'volume',
18
+ 'logo',
19
+ 'categories', 'tags',
20
+ 'expired', 'status',
21
+ 'markets',
22
+ '__pmxtEventId', '__pmxtEventTitle', '__pmxtEventDescription',
23
+ '__pmxtCategories', '__pmxtTags',
24
+ ];
9
25
  function mapMarketToUnified(market, context = {}) {
10
26
  if (!market)
11
27
  return null;
@@ -76,6 +92,7 @@ function mapMarketToUnified(market, context = {}) {
76
92
  category: market.categories?.[0] || resolvedContext.categories?.[0],
77
93
  tags: market.tags || resolvedContext.tags || [],
78
94
  status,
95
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(market, LIMITLESS_PROMOTED_MARKET_KEYS),
79
96
  };
80
97
  (0, market_utils_1.addBinaryOutcomes)(um);
81
98
  return um;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fetchEvents = fetchEvents;
4
4
  const utils_1 = require("./utils");
5
5
  const errors_1 = require("./errors");
6
+ const metadata_1 = require("../../utils/metadata");
6
7
  const BATCH_SIZE = 100;
7
8
  const MAX_PAGES = 200;
8
9
  /**
@@ -72,6 +73,7 @@ function postToEvent(post) {
72
73
  : post.projects.category[0]?.name
73
74
  : undefined,
74
75
  tags: markets[0]?.tags ?? [],
76
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(post, utils_1.METACULUS_PROMOTED_EVENT_KEYS),
75
77
  };
76
78
  }
77
79
  /**
@@ -141,6 +143,10 @@ async function fetchEventBySlug(slug, callApi) {
141
143
  return [];
142
144
  }
143
145
  async function fetchEvents(params, callApi) {
146
+ // Venue does not expose a series concept; honoring `params.series` by returning [] rather than ignoring the filter.
147
+ if (params.series !== undefined) {
148
+ return [];
149
+ }
144
150
  try {
145
151
  // Direct lookup by slug (post ID, tournament slug, or url_title)
146
152
  if (params.slug) {
@@ -37,6 +37,9 @@ import { UnifiedMarket, UnifiedEvent, CreateOrderParams, Order } from "../../typ
37
37
  * | Continuous/numeric/date | Yes (read-only HIGHER/LOWER) | No (requires 201-point CDF) |
38
38
  */
39
39
  export declare class MetaculusExchange extends PredictionMarketExchange {
40
+ protected readonly capabilityOverrides: {
41
+ fetchSeries: false;
42
+ };
40
43
  private readonly apiToken?;
41
44
  private readonly baseUrl;
42
45
  constructor(credentials?: ExchangeCredentials);
@@ -48,6 +48,9 @@ const cancelOrder_1 = require("./cancelOrder");
48
48
  * | Continuous/numeric/date | Yes (read-only HIGHER/LOWER) | No (requires 201-point CDF) |
49
49
  */
50
50
  class MetaculusExchange extends BaseExchange_1.PredictionMarketExchange {
51
+ capabilityOverrides = {
52
+ fetchSeries: false,
53
+ };
51
54
  apiToken;
52
55
  baseUrl;
53
56
  constructor(credentials) {
@@ -1,4 +1,5 @@
1
1
  import { UnifiedMarket } from "../../types";
2
+ export declare const METACULUS_PROMOTED_EVENT_KEYS: readonly ["id", "slug", "url_title", "title", "question", "group_of_questions", "projects", "status"];
2
3
  /**
3
4
  * Base URL passed to parseOpenApiSpec to override the spec's servers[0].url.
4
5
  * The generated api.ts already has "https://www.metaculus.com/api" as its server URL,
@@ -23,7 +24,7 @@ export declare function mapStatus(status: string): "active" | "closed";
23
24
  * @param eventId Optional parent event ID (tournament slug) to override
24
25
  * the value derived from post.projects.tournament.
25
26
  */
26
- export declare function mapMarketToUnified(post: any, eventId?: string): UnifiedMarket | null;
27
+ export declare function mapMarketToUnified(post: any, eventId?: string, groupPostId?: number): UnifiedMarket | null;
27
28
  /**
28
29
  * Convert a raw Metaculus post into one or more `UnifiedMarket` objects.
29
30
  *
@@ -1,10 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_BASE_URL = void 0;
3
+ exports.DEFAULT_BASE_URL = exports.METACULUS_PROMOTED_EVENT_KEYS = void 0;
4
4
  exports.mapStatus = mapStatus;
5
5
  exports.mapMarketToUnified = mapMarketToUnified;
6
6
  exports.expandPost = expandPost;
7
7
  const market_utils_1 = require("../../utils/market-utils");
8
+ const metadata_1 = require("../../utils/metadata");
9
+ // Raw Metaculus Post fields already promoted to first-class UnifiedMarket columns
10
+ // — excluded from sourceMetadata so we capture only what the unified shape drops.
11
+ const METACULUS_PROMOTED_MARKET_KEYS = [
12
+ // identity / slug
13
+ 'id', 'slug', 'url_title',
14
+ // title
15
+ 'title',
16
+ // description lives inside question / group_of_questions — those are excluded below
17
+ // resolution timing -> resolutionDate
18
+ 'scheduled_resolve_time', 'scheduled_close_time', 'actual_close_time',
19
+ // forecaster count -> liquidity / openInterest
20
+ 'nr_forecasters',
21
+ // child objects whose fields are promoted individually
22
+ 'question', 'group_of_questions',
23
+ // project sub-tree fields that map to image / category / tags / eventId
24
+ 'projects',
25
+ // status -> mapStatus
26
+ 'status',
27
+ ];
28
+ // Raw Metaculus Post fields already promoted to first-class UnifiedEvent columns.
29
+ exports.METACULUS_PROMOTED_EVENT_KEYS = [
30
+ 'id', 'slug', 'url_title',
31
+ 'title',
32
+ 'question', 'group_of_questions',
33
+ 'projects',
34
+ 'status',
35
+ ];
8
36
  /**
9
37
  * Base URL passed to parseOpenApiSpec to override the spec's servers[0].url.
10
38
  * The generated api.ts already has "https://www.metaculus.com/api" as its server URL,
@@ -185,7 +213,7 @@ function buildOutcomes(question, postId, medianProb) {
185
213
  * @param eventId Optional parent event ID (tournament slug) to override
186
214
  * the value derived from post.projects.tournament.
187
215
  */
188
- function mapMarketToUnified(post, eventId) {
216
+ function mapMarketToUnified(post, eventId, groupPostId) {
189
217
  if (!post || !post.id)
190
218
  return null;
191
219
  // Group-of-questions posts have no top-level question -- they must be
@@ -240,6 +268,7 @@ function mapMarketToUnified(post, eventId) {
240
268
  image: post.projects?.default_project?.header_image ?? undefined,
241
269
  category,
242
270
  tags,
271
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(post, METACULUS_PROMOTED_MARKET_KEYS, groupPostId !== undefined ? { group_post_id: groupPostId } : undefined),
243
272
  };
244
273
  (0, market_utils_1.addBinaryOutcomes)(um);
245
274
  return um;
@@ -281,7 +310,7 @@ function mapGroupPostToMarkets(post, eventId) {
281
310
  actual_close_time: subQuestion.actual_close_time ?? post.actual_close_time,
282
311
  status: post.status,
283
312
  };
284
- const market = mapMarketToUnified(syntheticPost, groupEventId);
313
+ const market = mapMarketToUnified(syntheticPost, groupEventId, Number(parentPostId));
285
314
  if (market) {
286
315
  // Tag each outcome with the parent group post ID for traceability
287
316
  for (const outcome of market.outcomes) {
@@ -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-26T15:22:20.057Z
3
+ * Generated at: 2026-05-30T13:35:15.586Z
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-26T15:22:20.057Z
6
+ * Generated at: 2026-05-30T13:35:15.586Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.myriadApiSpec = {
@@ -10,6 +10,7 @@ export declare class MyriadExchange extends PredictionMarketExchange {
10
10
  fetchBalance: "emulated";
11
11
  watchOrderBook: "emulated";
12
12
  watchTrades: "emulated";
13
+ fetchSeries: false;
13
14
  };
14
15
  private auth?;
15
16
  private ws?;
@@ -23,6 +23,7 @@ class MyriadExchange extends BaseExchange_1.PredictionMarketExchange {
23
23
  fetchBalance: 'emulated',
24
24
  watchOrderBook: 'emulated',
25
25
  watchTrades: 'emulated',
26
+ fetchSeries: false,
26
27
  };
27
28
  auth;
28
29
  ws;
@@ -76,6 +77,10 @@ class MyriadExchange extends BaseExchange_1.PredictionMarketExchange {
76
77
  .filter((m) => m !== null);
77
78
  }
78
79
  async fetchEventsImpl(params) {
80
+ // Venue does not expose a series concept; honoring `params.series` by
81
+ // returning [] rather than ignoring the filter.
82
+ if (params.series !== undefined)
83
+ return [];
79
84
  const rawQuestions = await this.fetcher.fetchRawEvents(params);
80
85
  return rawQuestions
81
86
  .map((raw) => this.normalizer.normalizeEvent(raw))
@@ -2,8 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MyriadNormalizer = void 0;
4
4
  const market_utils_1 = require("../../utils/market-utils");
5
+ const metadata_1 = require("../../utils/metadata");
5
6
  const price_1 = require("./price");
6
7
  const utils_1 = require("./utils");
8
+ // Raw Myriad fields already promoted to first-class Unified columns — excluded
9
+ // from sourceMetadata so we capture only what the unified shape would drop.
10
+ const MYRIAD_PROMOTED_MARKET_KEYS = [
11
+ 'id', 'networkId', 'title', 'description', 'slug', 'imageUrl',
12
+ 'expiresAt', 'volume24h', 'volume', 'liquidity', 'eventId',
13
+ 'topics', 'outcomes', 'state',
14
+ ];
15
+ const MYRIAD_PROMOTED_EVENT_KEYS = [
16
+ 'id', 'title', 'markets',
17
+ ];
7
18
  function selectTimeframe(interval) {
8
19
  switch (interval) {
9
20
  case '1m':
@@ -45,6 +56,7 @@ class MyriadNormalizer {
45
56
  image: raw.imageUrl,
46
57
  tags: raw.topics || [],
47
58
  status,
59
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, MYRIAD_PROMOTED_MARKET_KEYS),
48
60
  };
49
61
  (0, market_utils_1.addBinaryOutcomes)(um);
50
62
  return um;
@@ -97,6 +109,8 @@ class MyriadNormalizer {
97
109
  ? markets.reduce((sum, m) => sum + (m.volume ?? 0), 0)
98
110
  : undefined,
99
111
  url: `https://myriad.markets`,
112
+ // Keeps non-promoted question fields; raw markets array is promoted.
113
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, MYRIAD_PROMOTED_EVENT_KEYS),
100
114
  };
101
115
  }
102
116
  normalizeOHLCV(raw, params, outcomeId) {
@@ -6,6 +6,17 @@ exports.mapStatusToMyriad = mapStatusToMyriad;
6
6
  exports.mapMarketToUnified = mapMarketToUnified;
7
7
  exports.mapQuestionToEvent = mapQuestionToEvent;
8
8
  const market_utils_1 = require("../../utils/market-utils");
9
+ const metadata_1 = require("../../utils/metadata");
10
+ // Raw Myriad fields already promoted to first-class Unified columns — excluded
11
+ // from sourceMetadata so we capture only what the unified shape would drop.
12
+ const MYRIAD_PROMOTED_MARKET_KEYS = [
13
+ 'id', 'networkId', 'title', 'description', 'slug', 'imageUrl',
14
+ 'expiresAt', 'volume24h', 'volume', 'liquidity', 'eventId',
15
+ 'topics', 'outcomes', 'state',
16
+ ];
17
+ const MYRIAD_PROMOTED_EVENT_KEYS = [
18
+ 'id', 'title', 'markets',
19
+ ];
9
20
  exports.DEFAULT_BASE_URL = 'https://api-v2.myriadprotocol.com';
10
21
  // Mainnet network IDs
11
22
  exports.NETWORKS = {
@@ -76,6 +87,7 @@ function mapMarketToUnified(market) {
76
87
  url: `https://myriad.markets/markets/${market.slug || market.id}`,
77
88
  image: market.imageUrl,
78
89
  tags: market.topics || [],
90
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(market, MYRIAD_PROMOTED_MARKET_KEYS),
79
91
  };
80
92
  (0, market_utils_1.addBinaryOutcomes)(um);
81
93
  return um;
@@ -98,6 +110,8 @@ function mapQuestionToEvent(question) {
98
110
  volume24h: markets.reduce((sum, m) => sum + m.volume24h, 0),
99
111
  volume: markets.some(m => m.volume !== undefined) ? markets.reduce((sum, m) => sum + (m.volume ?? 0), 0) : undefined,
100
112
  url: `https://myriad.markets`,
113
+ // Keeps non-promoted question fields; raw markets array is promoted.
114
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(question, MYRIAD_PROMOTED_EVENT_KEYS),
101
115
  };
102
116
  return unifiedEvent;
103
117
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
3
- * Generated at: 2026-05-26T15:22:20.063Z
3
+ * Generated at: 2026-05-30T13:35:15.591Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const opinionApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.opinionApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
6
- * Generated at: 2026-05-26T15:22:20.063Z
6
+ * Generated at: 2026-05-30T13:35:15.591Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.opinionApiSpec = {
@@ -1,5 +1,5 @@
1
- import { PredictionMarketExchange, MarketFilterParams, OHLCVParams, ExchangeCredentials, EventFetchParams, MyTradesParams, OrderHistoryParams } from '../../BaseExchange';
2
- import { UnifiedMarket, UnifiedEvent, PriceCandle, OrderBook, Trade, Order, Position, UserTrade, CreateOrderParams, BuiltOrder } from '../../types';
1
+ import { PredictionMarketExchange, MarketFilterParams, OHLCVParams, ExchangeCredentials, EventFetchParams, SeriesFetchParams, MyTradesParams, OrderHistoryParams } from '../../BaseExchange';
2
+ import { UnifiedMarket, UnifiedEvent, UnifiedSeries, PriceCandle, OrderBook, Trade, Order, Position, UserTrade, CreateOrderParams, BuiltOrder } from '../../types';
3
3
  import { OpinionWebSocketConfig } from './websocket';
4
4
  export type { OpinionWebSocketConfig };
5
5
  export interface OpinionExchangeOptions {
@@ -15,12 +15,16 @@ export declare class OpinionExchange extends PredictionMarketExchange {
15
15
  private readonly normalizer;
16
16
  private ws?;
17
17
  private readonly outcomeToMarketId;
18
+ protected readonly capabilityOverrides: {
19
+ fetchSeries: "emulated";
20
+ };
18
21
  constructor(options?: ExchangeCredentials | OpinionExchangeOptions);
19
22
  get name(): string;
20
23
  protected sign(_method: string, _path: string, _params: Record<string, any>): Record<string, string>;
21
24
  protected mapImplicitApiError(error: any): any;
22
25
  protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
23
26
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
27
+ protected fetchSeriesImpl(params: SeriesFetchParams): Promise<UnifiedSeries[]>;
24
28
  fetchOHLCV(outcomeId: string, params: OHLCVParams): Promise<PriceCandle[]>;
25
29
  fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook>;
26
30
  fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]>;
@@ -60,6 +60,9 @@ class OpinionExchange extends BaseExchange_1.PredictionMarketExchange {
60
60
  ws;
61
61
  // Maps outcomeId (token ID) → numeric marketId for WebSocket subscriptions
62
62
  outcomeToMarketId = new Map();
63
+ capabilityOverrides = {
64
+ fetchSeries: 'emulated',
65
+ };
63
66
  constructor(options) {
64
67
  let credentials;
65
68
  let walletAddress;
@@ -154,9 +157,15 @@ class OpinionExchange extends BaseExchange_1.PredictionMarketExchange {
154
157
  const rawEvents = await this.fetcher.fetchRawEvents(params);
155
158
  const limit = params.limit || 250000;
156
159
  const query = (params.query || '').toLowerCase();
157
- const filtered = query
160
+ let filtered = query
158
161
  ? rawEvents.filter((raw) => (raw.marketTitle || '').toLowerCase().includes(query))
159
162
  : rawEvents;
163
+ // Client-side series filter: keep only events whose collection symbol
164
+ // matches the requested series identifier.
165
+ if (params.series) {
166
+ const seriesId = params.series;
167
+ filtered = filtered.filter((raw) => raw.collection?.symbol === seriesId);
168
+ }
160
169
  const events = filtered
161
170
  .map((raw) => this.normalizer.normalizeEvent(raw))
162
171
  .filter((e) => e !== null)
@@ -165,6 +174,50 @@ class OpinionExchange extends BaseExchange_1.PredictionMarketExchange {
165
174
  await this.enrichPrices(allMarkets);
166
175
  return events;
167
176
  }
177
+ async fetchSeriesImpl(params) {
178
+ // Opinion has no dedicated /series endpoint. Derive the catalog by
179
+ // fetching all markets and grouping by collection.symbol.
180
+ const rawMarkets = await this.fetcher.fetchRawMarkets();
181
+ // Build a map from collection.symbol -> { collection, rawEvents[] }
182
+ const seriesMap = new Map();
183
+ for (const raw of rawMarkets) {
184
+ if (!raw.collection)
185
+ continue;
186
+ const sym = raw.collection.symbol;
187
+ const existing = seriesMap.get(sym);
188
+ if (existing) {
189
+ existing.raws.push(raw);
190
+ }
191
+ else {
192
+ seriesMap.set(sym, { collection: raw.collection, raws: [raw] });
193
+ }
194
+ }
195
+ let entries = Array.from(seriesMap.values());
196
+ // Apply params.id filter
197
+ if (params.id) {
198
+ entries = entries.filter((e) => e.collection.symbol === params.id);
199
+ }
200
+ // Apply params.query filter (title match)
201
+ if (params.query) {
202
+ const lowerQuery = params.query.toLowerCase();
203
+ entries = entries.filter((e) => e.collection.title.toLowerCase().includes(lowerQuery));
204
+ }
205
+ // Apply params.recurrence filter
206
+ if (params.recurrence) {
207
+ const recurrence = params.recurrence;
208
+ entries = entries.filter((e) => e.collection.frequency === recurrence);
209
+ }
210
+ return entries.map((e) => {
211
+ let events;
212
+ // When fetching by id, populate the events field.
213
+ if (params.id) {
214
+ events = e.raws
215
+ .map((raw) => this.normalizer.normalizeEvent(raw))
216
+ .filter((ev) => ev !== null);
217
+ }
218
+ return this.normalizer.normalizeSeries(e.collection, events);
219
+ });
220
+ }
168
221
  async fetchOHLCV(outcomeId, params) {
169
222
  const rawPoints = await this.fetcher.fetchRawOHLCV(outcomeId, params);
170
223
  return this.normalizer.normalizeOHLCV({ history: rawPoints }, params);
@@ -1,10 +1,16 @@
1
1
  import { OHLCVParams } from '../../BaseExchange';
2
- import { UnifiedMarket, UnifiedEvent, PriceCandle, OrderBook, Trade, UserTrade, Position, Order } from '../../types';
2
+ import { UnifiedMarket, UnifiedEvent, UnifiedSeries, PriceCandle, OrderBook, Trade, UserTrade, Position, Order } from '../../types';
3
3
  import { IExchangeNormalizer } from '../interfaces';
4
- import { OpinionRawMarket, OpinionRawOrderBook, OpinionRawPricePoint, OpinionRawLatestPrice, OpinionRawUserTrade, OpinionRawPosition, OpinionRawOrder } from './fetcher';
4
+ import { OpinionRawMarket, OpinionRawCollection, OpinionRawOrderBook, OpinionRawPricePoint, OpinionRawLatestPrice, OpinionRawUserTrade, OpinionRawPosition, OpinionRawOrder } from './fetcher';
5
5
  export declare class OpinionNormalizer implements IExchangeNormalizer<OpinionRawMarket, OpinionRawMarket> {
6
6
  normalizeMarket(raw: OpinionRawMarket): UnifiedMarket | null;
7
7
  normalizeMarketsFromEvent(raw: OpinionRawMarket): UnifiedMarket[];
8
+ /**
9
+ * Produce a UnifiedSeries from an Opinion `collection` object.
10
+ * `id` is the canonical series identifier (collection.symbol).
11
+ * `events` is optionally injected by the caller when doing a single-id lookup.
12
+ */
13
+ normalizeSeries(raw: OpinionRawCollection, events?: UnifiedEvent[]): UnifiedSeries;
8
14
  normalizeEvent(raw: OpinionRawMarket): UnifiedEvent | null;
9
15
  normalizeOHLCV(raw: {
10
16
  history: OpinionRawPricePoint[];