diff --git a/core/api-doc-config.generated.json b/core/api-doc-config.generated.json index 410e1883..3e176c67 100644 --- a/core/api-doc-config.generated.json +++ b/core/api-doc-config.generated.json @@ -1,25 +1,25 @@ { - "_generated": "Auto-generated by extract-jsdoc.js on 2026-05-22T19:00:19.281Z. Do not edit manually.", + "_generated": "Auto-generated by extract-jsdoc.js on 2026-06-02T00:34:44.916Z. Do not edit manually.", "methods": { "has": { "summary": "HTTP verb for the endpoint (e.g. GET, POST). */", - "description": "method: string;\n /** URL path template, relative to the descriptor's baseUrl. */\n path: string;\n /** Whether this endpoint requires authenticated credentials. */\n isPrivate?: boolean;\n /** Identifier used to generate the implicit API method name. */\n operationId?: string;\n /**\nWhen set, requests use this base URL instead of the descriptor default\n(OpenAPI path- or operation-level `servers` override).\n/\n baseUrl?: string;\n}\n\nexport interface ApiDescriptor {\n /** Base URL that all endpoint paths are resolved against. */\n baseUrl: string;\n /** Map of endpoint key to endpoint definition used by the implicit API machinery. */\n endpoints: Record;\n}\n\nexport interface ImplicitApiMethodInfo {\n /** Generated method name exposed on the exchange instance. */\n name: string;\n /** HTTP verb for the underlying endpoint. */\n method: string;\n /** URL path template for the underlying endpoint. */\n path: string;\n /** Whether the underlying endpoint requires authenticated credentials. */\n isPrivate: boolean;\n}\n\nexport interface MarketFilterParams {\n /** Maximum number of results to return */\n limit?: number;\n /** Pagination offset — number of results to skip */\n offset?: number;\n /** Sort order for results */\n sort?: 'volume' | 'liquidity' | 'newest';\n status?: 'active' | 'inactive' | 'closed' | 'all'; // Filter by market status (default: 'active', 'inactive' and 'closed' are interchangeable)\n searchIn?: 'title' | 'description' | 'both'; // Where to search (default: 'title')\n query?: string; // For keyword search\n slug?: string; // For slug/ticker lookup\n marketId?: string; // Direct lookup by market ID\n outcomeId?: string; // Reverse lookup -- find market containing this outcome\n eventId?: string; // Find markets belonging to an event\n page?: number; // For pagination (used by Limitless)\n similarityThreshold?: number; // For semantic search (used by Limitless)\n}\n\nexport interface MarketFetchParams extends MarketFilterParams {\n /** Optional client-side filter applied after fetching */\n filter?: MarketFilterCriteria;\n /** Filter by category. Each market belongs to a venue-assigned category such as \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Filter by tags. Returns markets matching ANY of the provided tags. Tags are more specific than categories -- for example a \"Sports\" market might carry tags [\"Sports\", \"FIFA World Cup\", \"2026 FIFA World Cup\"]. Common tags include \"Crypto\", \"Politics\", \"Elections\", \"Geopolitics\", \"Fed Rates\", \"Trump\". */\n tags?: string[];\n}\n\nexport interface EventFetchParams {\n query?: string; // For keyword search\n /** Maximum number of results to return */\n limit?: number;\n /** Pagination offset — number of results to skip */\n offset?: number;\n /** Sort order for results */\n sort?: 'volume' | 'liquidity' | 'newest';\n status?: 'active' | 'inactive' | 'closed' | 'all'; // Filter by event status (default: 'active', 'inactive' and 'closed' are interchangeable)\n /** Where to search (default: 'title') */\n searchIn?: 'title' | 'description' | 'both';\n eventId?: string; // Direct lookup by event ID\n slug?: string; // Lookup by event slug\n /** Optional client-side filter applied after fetching */\n filter?: EventFilterCriteria;\n /** Filter by category. Each event belongs to a venue-assigned category such as \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Filter by tags. Returns events matching ANY of the provided tags. Tags are more specific than categories -- for example a \"Politics\" event might carry tags [\"Politics\", \"Geopolitics\", \"Middle East\", \"Iran\"]. Common tags include \"Crypto\", \"Elections\", \"Fed Rates\", \"FIFA World Cup\", \"Trump\". */\n tags?: string[];\n}\n\n/**\nDeprecated - use OHLCVParams or TradesParams instead. Resolution is optional for backward compatibility.\n/\nexport interface HistoryFilterParams {\n resolution?: CandleInterval; // Optional for backward compatibility\n /** Start of the time range */\n start?: Date;\n /** End of the time range */\n end?: Date;\n /** Maximum number of results to return */\n limit?: number;\n}\n\nexport interface OHLCVParams {\n resolution: CandleInterval; // Required for candle aggregation\n /** Start of the time range */\n start?: Date;\n /** End of the time range */\n end?: Date;\n /** Maximum number of results to return */\n limit?: number;\n}\n\n/**\nParameters for fetching trade history. No resolution parameter - trades are discrete events.\n/\nexport interface TradesParams {\n // No resolution - trades are discrete events, not aggregated\n /** Start of the time range */\n start?: Date;\n /** End of the time range */\n end?: Date;\n /** Maximum number of results to return */\n limit?: number;\n}\n\nexport interface MyTradesParams {\n outcomeId?: string; // filter to specific outcome/ticker\n marketId?: string; // filter to specific market\n /** Only return records after this date */\n since?: Date;\n /** Only return records before this date */\n until?: Date;\n /** Maximum number of results to return */\n limit?: number;\n cursor?: string; // for Kalshi cursor pagination\n}\n\nexport interface OrderHistoryParams {\n marketId?: string; // required for Limitless (slug)\n /** Only return records after this date */\n since?: Date;\n /** Only return records before this date */\n until?: Date;\n /** Maximum number of results to return */\n limit?: number;\n /** Opaque pagination cursor from a previous response */\n cursor?: string;\n}\n\n// ----------------------------------------------------------------------------\n// Filtering Types\n// ----------------------------------------------------------------------------\n\nexport interface MarketFilterCriteria {\n // Text search\n text?: string;\n searchIn?: ('title' | 'description' | 'category' | 'tags' | 'outcomes')[]; // Default: ['title']\n\n // Numeric range filters\n volume24h?: { min?: number; max?: number };\n /** Filter by total (lifetime) volume range */\n volume?: { min?: number; max?: number };\n /** Filter by current liquidity range */\n liquidity?: { min?: number; max?: number };\n /** Filter by open interest range */\n openInterest?: { min?: number; max?: number };\n\n // Date filters\n resolutionDate?: {\n before?: Date;\n after?: Date;\n };\n\n // Category/tag filters\n /** Filter by category. Common values: \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Match markets that have ANY of these tags. Examples: [\"Crypto\", \"Crypto Prices\"], [\"Politics\", \"Elections\"], [\"Sports\", \"FIFA World Cup\"]. */\n tags?: string[];\n\n // Price filters (for binary markets)\n price?: {\n outcome: 'yes' | 'no' | 'up' | 'down';\n min?: number; // 0.0 to 1.0\n max?: number;\n };\n\n // Price change filters\n priceChange24h?: {\n outcome: 'yes' | 'no' | 'up' | 'down';\n min?: number; // e.g., -0.1 for 10% drop\n max?: number;\n };\n}\n\nexport type MarketFilterFunction = (market: UnifiedMarket) => boolean;\n\nexport interface EventFilterCriteria {\n // Text search\n text?: string;\n searchIn?: ('title' | 'description' | 'category' | 'tags')[]; // Default: ['title']\n\n // Category/tag filters\n /** Filter by category. Common values: \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Match events that have ANY of these tags. Examples: [\"Crypto\"], [\"Politics\", \"Geopolitics\", \"Middle East\"], [\"Sports\", \"FIFA World Cup\"]. */\n tags?: string[];\n\n // Filter by contained markets\n marketCount?: { min?: number; max?: number };\n totalVolume?: { min?: number; max?: number }; // Sum of market volumes\n}\n\nexport type EventFilterFunction = (event: UnifiedEvent) => boolean;\n\n// ----------------------------------------------------------------------------\n// Capability Map (ccxt-style exchange.has)\n// ----------------------------------------------------------------------------\n\nexport type ExchangeCapability = true | false | 'emulated';\n\nexport interface ExchangeHas {\n /** Whether this exchange supports fetching markets. */\n fetchMarkets: ExchangeCapability;\n /** Whether this exchange supports fetching events. */\n fetchEvents: ExchangeCapability;\n /** Whether this exchange supports fetching OHLCV candles. */\n fetchOHLCV: ExchangeCapability;\n /** Whether this exchange supports fetching the order book. */\n fetchOrderBook: ExchangeCapability;\n /** Whether this exchange supports fetching multiple market order books. */\n fetchOrderBooks: ExchangeCapability;\n /** Whether this exchange supports fetching public trades. */\n fetchTrades: ExchangeCapability;\n /** Whether this exchange supports creating orders. */\n createOrder: ExchangeCapability;\n /** Whether this exchange supports cancelling orders. */\n cancelOrder: ExchangeCapability;\n /** Whether this exchange supports fetching a single order by id. */\n fetchOrder: ExchangeCapability;\n /** Whether this exchange supports fetching open orders. */\n fetchOpenOrders: ExchangeCapability;\n /** Whether this exchange supports fetching account positions. */\n fetchPositions: ExchangeCapability;\n /** Whether this exchange supports fetching account balances. */\n fetchBalance: ExchangeCapability;\n /** Whether this exchange supports subscribing to an on-chain address for updates. */\n watchAddress: ExchangeCapability;\n /** Whether this exchange supports unsubscribing from a watched address. */\n unwatchAddress: ExchangeCapability;\n /** Whether this exchange supports streaming order book updates. */\n watchOrderBook: ExchangeCapability;\n /** Whether this exchange supports batch-subscribing to multiple order book streams. */\n watchOrderBooks: ExchangeCapability;\n /** Whether this exchange supports unsubscribing from an order book stream. */\n unwatchOrderBook: ExchangeCapability;\n /** Whether this exchange supports streaming trade updates. */\n watchTrades: ExchangeCapability;\n /** Whether this exchange supports fetching the authenticated user's trade history. */\n fetchMyTrades: ExchangeCapability;\n /** Whether this exchange supports fetching closed orders. */\n fetchClosedOrders: ExchangeCapability;\n /** Whether this exchange supports fetching all orders (open and closed). */\n fetchAllOrders: ExchangeCapability;\n /** Whether this exchange supports building a signed order without submitting it. */\n buildOrder: ExchangeCapability;\n /** Whether this exchange supports submitting a pre-built order. */\n submitOrder: ExchangeCapability;\n /** Whether this exchange supports fetching cross-venue market matches. */\n fetchMarketMatches: ExchangeCapability;\n /** @deprecated Use {@link fetchMarketMatches} instead. */\n fetchMatches: ExchangeCapability;\n /** Whether this exchange supports fetching cross-venue event matches. */\n fetchEventMatches: ExchangeCapability;\n /** Whether this exchange supports comparing prices across venues. */\n compareMarketPrices: ExchangeCapability;\n /** Whether this exchange supports finding related markets across venues. */\n fetchRelatedMarkets: ExchangeCapability;\n /** Whether this exchange supports fetching matched markets across venues. */\n fetchMatchedMarkets: ExchangeCapability;\n /** @deprecated Use {@link fetchMatchedMarkets} instead. */\n fetchMatchedPrices: ExchangeCapability;\n /** @deprecated Use {@link fetchRelatedMarkets} instead. */\n fetchHedges: ExchangeCapability;\n /** @deprecated Use {@link fetchMatchedMarkets} instead. */\n fetchArbitrage: ExchangeCapability;\n}\n\n/**\nOptional authentication credentials for exchange operations.\n/\nexport interface ExchangeCredentials {\n // Standard API authentication (Kalshi, etc.)\n apiKey?: string;\n /** Standard API secret for HMAC-authenticated exchanges */\n apiSecret?: string;\n /** Standard API passphrase for HMAC-authenticated exchanges */\n passphrase?: string;\n /** Metaculus: `Authorization: Token ` for higher rate limits */\n apiToken?: string;\n\n // Blockchain-based authentication (Polymarket)\n privateKey?: string; // Required for Polymarket L1 auth\n\n // Polymarket-specific L2 fields\n signatureType?: number | string; // 0 = EOA, 1 = Poly Proxy, 2 = Gnosis Safe (Can also use 'eoa', 'polyproxy', 'gnosis_safe')\n funderAddress?: string; // The address funding the trades (defaults to signer address)\n\n // Limitless: wallet address for delegated signing profile lookup\n walletAddress?: string;\n\n // Optional base URL override for venue API (e.g., proxy for geo-restricted venues)\n baseUrl?: string;\n}\n\nexport interface ExchangeOptions {\n /**\nHow long (ms) a market snapshot created by `fetchMarketsPaginated` remains valid\nbefore being discarded and re-fetched from the API on the next call.\nDefaults to 0 (no TTL — the snapshot is re-fetched on every initial call).\n/\n snapshotTTL?: number;\n}\n\n/** Shape returned by fetchMarketsPaginated */\nexport interface PaginatedMarketsResult {\n /** The page of unified markets */\n data: UnifiedMarket[];\n /** Total number of markets in the snapshot */\n total: number;\n /** Cursor to pass to the next call, or undefined if this is the last page */\n nextCursor?: string;\n}\n\n/** Shape returned by fetchEventsPaginated */\nexport interface PaginatedEventsResult {\n /** The page of unified events */\n data: UnifiedEvent[];\n /** Total number of events in the snapshot */\n total: number;\n /** Cursor to pass to the next call, or undefined if this is the last page */\n nextCursor?: string;\n}\n\n// ----------------------------------------------------------------------------\n// Base Exchange Class\n// ----------------------------------------------------------------------------\n\nexport abstract class PredictionMarketExchange {\n [key: string]: any; // Allow dynamic method assignment for implicit API\n\n public verbose: boolean = false;\n public http: AxiosInstance;\n public enableRateLimit: boolean = true;\n // Market Cache\n public markets: Record = {};\n public marketsBySlug: Record = {};\n public loadedMarkets: boolean = false;\n /**\nCapability map derived automatically from method overrides at runtime.\nExchanges do NOT need to declare this manually -- if a subclass overrides\na method (and the override does not throw \"not supported\"), it is `true`.\nTo mark a capability as `'emulated'`, add its key to `emulatedCapabilities`.", + "description": "method: string;\n /** URL path template, relative to the descriptor's baseUrl. */\n path: string;\n /** Whether this endpoint requires authenticated credentials. */\n isPrivate?: boolean;\n /** Identifier used to generate the implicit API method name. */\n operationId?: string;\n /**\nWhen set, requests use this base URL instead of the descriptor default\n(OpenAPI path- or operation-level `servers` override).\n/\n baseUrl?: string;\n}\n\nexport interface ApiDescriptor {\n /** Base URL that all endpoint paths are resolved against. */\n baseUrl: string;\n /** Map of endpoint key to endpoint definition used by the implicit API machinery. */\n endpoints: Record;\n}\n\nexport interface ImplicitApiMethodInfo {\n /** Generated method name exposed on the exchange instance. */\n name: string;\n /** HTTP verb for the underlying endpoint. */\n method: string;\n /** URL path template for the underlying endpoint. */\n path: string;\n /** Whether the underlying endpoint requires authenticated credentials. */\n isPrivate: boolean;\n}\n\nexport interface MarketFilterParams {\n /** Maximum number of results to return */\n limit?: number;\n /** Pagination offset — number of results to skip */\n offset?: number;\n /** Sort order for results */\n sort?: 'volume' | 'liquidity' | 'newest';\n status?: 'active' | 'inactive' | 'closed' | 'all'; // Filter by market status (default: 'active', 'inactive' and 'closed' are interchangeable)\n searchIn?: 'title' | 'description' | 'both'; // Where to search (default: 'title')\n query?: string; // For keyword search\n slug?: string; // For slug/ticker lookup\n marketId?: string; // Direct lookup by market ID\n outcomeId?: string; // Reverse lookup -- find market containing this outcome\n eventId?: string; // Find markets belonging to an event\n page?: number; // For pagination (used by Limitless)\n similarityThreshold?: number; // For semantic search (used by Limitless)\n}\n\nexport interface MarketFetchParams extends MarketFilterParams {\n /** Optional client-side filter applied after fetching */\n filter?: MarketFilterCriteria;\n /** Filter by category. Each market belongs to a venue-assigned category such as \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Filter by tags. Returns markets matching ANY of the provided tags. Tags are more specific than categories -- for example a \"Sports\" market might carry tags [\"Sports\", \"FIFA World Cup\", \"2026 FIFA World Cup\"]. Common tags include \"Crypto\", \"Politics\", \"Elections\", \"Geopolitics\", \"Fed Rates\", \"Trump\". */\n tags?: string[];\n}\n\nexport interface EventFetchParams {\n query?: string; // For keyword search\n /** Maximum number of results to return */\n limit?: number;\n /** Opaque venue pagination cursor, where supported. */\n cursor?: string;\n /** Pagination offset — number of results to skip */\n offset?: number;\n /** Sort order for results */\n sort?: 'volume' | 'liquidity' | 'newest';\n status?: 'active' | 'inactive' | 'closed' | 'all'; // Filter by event status (default: 'active', 'inactive' and 'closed' are interchangeable)\n /** Where to search (default: 'title') */\n searchIn?: 'title' | 'description' | 'both';\n eventId?: string; // Direct lookup by event ID\n slug?: string; // Lookup by event slug\n /** Filter events by their parent series. Accepts the venue-native series id / ticker / slug (e.g. Kalshi `\"KXATPMATCH\"`, Polymarket `\"wta\"`). Passed through to the vendor where supported, otherwise applied to `sourceMetadata` after fetch. */\n series?: string;\n /** Optional client-side filter applied after fetching */\n filter?: EventFilterCriteria;\n /** Filter by category. Each event belongs to a venue-assigned category such as \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Filter by tags. Returns events matching ANY of the provided tags. Tags are more specific than categories -- for example a \"Politics\" event might carry tags [\"Politics\", \"Geopolitics\", \"Middle East\", \"Iran\"]. Common tags include \"Crypto\", \"Elections\", \"Fed Rates\", \"FIFA World Cup\", \"Trump\". */\n tags?: string[];\n}\n\n/**\nParameters for `fetchSeries`. Venues that don't expose a series concept\nreturn an empty array regardless of the filters.\n/\nexport interface SeriesFetchParams {\n /** Direct lookup by venue-native series id (e.g. \"KXATPMATCH\" on Kalshi, \"atp\" or \"1\" on Polymarket Gamma). When set, the result is the matching series with its events populated where the venue supports it. */\n id?: string;\n /** Lookup by series slug (e.g. \"wta\", \"nfl\"). */\n slug?: string;\n /** Keyword search across series title / description. */\n query?: string;\n /** Filter by recurrence cadence ('daily', 'weekly', 'annual', ...). */\n recurrence?: string;\n /** Maximum number of results to return. */\n limit?: number;\n /** Pagination offset. */\n offset?: number;\n}\n\n/**\nDeprecated - use OHLCVParams or TradesParams instead. Resolution is optional for backward compatibility.\n/\nexport interface HistoryFilterParams {\n resolution?: CandleInterval; // Optional for backward compatibility\n /** Start of the time range */\n start?: Date;\n /** End of the time range */\n end?: Date;\n /** Maximum number of results to return */\n limit?: number;\n}\n\nexport interface OHLCVParams {\n resolution: CandleInterval; // Required for candle aggregation\n /** Start of the time range */\n start?: Date;\n /** End of the time range */\n end?: Date;\n /** Maximum number of results to return */\n limit?: number;\n}\n\n/**\nParameters for fetching trade history. No resolution parameter - trades are discrete events.\n/\n/** Maximum allowed value for `TradesParams.limit`. */\nexport const MAX_TRADES_LIMIT = 1000;\n\nexport interface TradesParams {\n // No resolution - trades are discrete events, not aggregated\n /** Start of the time range */\n start?: Date;\n /** End of the time range */\n end?: Date;\n /** Maximum number of results to return (max {@link MAX_TRADES_LIMIT}) */\n limit?: number;\n}\n\nexport interface MyTradesParams {\n outcomeId?: string; // filter to specific outcome/ticker\n marketId?: string; // filter to specific market\n /** Only return records after this date */\n since?: Date;\n /** Only return records before this date */\n until?: Date;\n /** Maximum number of results to return */\n limit?: number;\n cursor?: string; // for Kalshi cursor pagination\n}\n\nexport interface FetchOrderBookParams {\n /** Outcome side: 'yes' or 'no'. Required for exchanges like Limitless\n where the API returns a single orderbook per market. */\n side?: 'yes' | 'no';\n /** Outcome alias: 'yes' or 'no', or an outcome token ID. When set,\n the first argument is treated as a market ID and this value selects\n which outcome's order book to fetch. Accepts the literal strings\n 'yes'/'no' (resolved via a market lookup) or a raw outcome token ID. */\n outcome?: string;\n /** Unix timestamp (ms) — fetch a historical snapshot at or before this\n time, or the start of a range when combined with `until` (hosted API only). */\n since?: number;\n /** Unix timestamp (ms) — end of a historical range. When combined with\n `since`, returns an array of reconstructed L2 OrderBook snapshots\n between `since` and `until` (hosted API only). */\n until?: number;\n}\n\nexport interface OrderHistoryParams {\n marketId?: string; // required for Limitless (slug)\n /** Only return records after this date */\n since?: Date;\n /** Only return records before this date */\n until?: Date;\n /** Maximum number of results to return */\n limit?: number;\n /** Opaque pagination cursor from a previous response */\n cursor?: string;\n}\n\n// ----------------------------------------------------------------------------\n// Filtering Types\n// ----------------------------------------------------------------------------\n\nexport interface MarketFilterCriteria {\n // Text search\n text?: string;\n searchIn?: ('title' | 'description' | 'category' | 'tags' | 'outcomes')[]; // Default: ['title']\n\n // Numeric range filters\n volume24h?: { min?: number; max?: number };\n /** Filter by total (lifetime) volume range */\n volume?: { min?: number; max?: number };\n /** Filter by current liquidity range */\n liquidity?: { min?: number; max?: number };\n /** Filter by open interest range */\n openInterest?: { min?: number; max?: number };\n\n // Date filters\n resolutionDate?: {\n before?: Date;\n after?: Date;\n };\n\n // Category/tag filters\n /** Filter by category. Common values: \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Match markets that have ANY of these tags. Examples: [\"Crypto\", \"Crypto Prices\"], [\"Politics\", \"Elections\"], [\"Sports\", \"FIFA World Cup\"]. */\n tags?: string[];\n\n // Price filters (for binary markets)\n price?: {\n outcome: 'yes' | 'no' | 'up' | 'down';\n min?: number; // 0.0 to 1.0\n max?: number;\n };\n\n // Price change filters\n priceChange24h?: {\n outcome: 'yes' | 'no' | 'up' | 'down';\n min?: number; // e.g., -0.1 for 10% drop\n max?: number;\n };\n}\n\nexport type MarketFilterFunction = (market: UnifiedMarket) => boolean;\n\nexport interface EventFilterCriteria {\n // Text search\n text?: string;\n searchIn?: ('title' | 'description' | 'category' | 'tags')[]; // Default: ['title']\n\n // Category/tag filters\n /** Filter by category. Common values: \"Sports\", \"Politics\", \"Crypto\", \"Bitcoin\", \"Soccer\", \"Economic Policy\" (Polymarket) or \"Sports\", \"Mentions\" (Kalshi). */\n category?: string;\n /** Match events that have ANY of these tags. Examples: [\"Crypto\"], [\"Politics\", \"Geopolitics\", \"Middle East\"], [\"Sports\", \"FIFA World Cup\"]. */\n tags?: string[];\n\n // Filter by contained markets\n marketCount?: { min?: number; max?: number };\n totalVolume?: { min?: number; max?: number }; // Sum of market volumes\n}\n\nexport type EventFilterFunction = (event: UnifiedEvent) => boolean;\n\n// ----------------------------------------------------------------------------\n// Capability Map (ccxt-style exchange.has)\n// ----------------------------------------------------------------------------\n\nexport type ExchangeCapability = true | false | 'emulated';\n\nexport interface ExchangeHas {\n /** Whether this exchange supports fetching markets. */\n fetchMarkets: ExchangeCapability;\n /** Whether this exchange supports fetching events. */\n fetchEvents: ExchangeCapability;\n /** Whether this exchange exposes a recurring-series concept (Series -> Event -> Market -> Outcome). Venues without one return `false` and an empty array from `fetchSeries`. */\n fetchSeries: ExchangeCapability;\n /** Whether this exchange supports fetching OHLCV candles. */\n fetchOHLCV: ExchangeCapability;\n /** Whether this exchange supports fetching the order book. */\n fetchOrderBook: ExchangeCapability;\n /** Whether this exchange supports fetching multiple market order books. */\n fetchOrderBooks: ExchangeCapability;\n /** Whether this exchange supports fetching public trades. */\n fetchTrades: ExchangeCapability;\n /** Whether this exchange supports creating orders. */\n createOrder: ExchangeCapability;\n /** Whether this exchange supports cancelling orders. */\n cancelOrder: ExchangeCapability;\n /** Whether this exchange supports fetching a single order by id. */\n fetchOrder: ExchangeCapability;\n /** Whether this exchange supports fetching open orders. */\n fetchOpenOrders: ExchangeCapability;\n /** Whether this exchange supports fetching account positions. */\n fetchPositions: ExchangeCapability;\n /** Whether this exchange supports fetching account balances. */\n fetchBalance: ExchangeCapability;\n /** Whether this exchange supports subscribing to an on-chain address for updates. */\n watchAddress: ExchangeCapability;\n /** Whether this exchange supports unsubscribing from a watched address. */\n unwatchAddress: ExchangeCapability;\n /** Whether this exchange supports streaming order book updates. */\n watchOrderBook: ExchangeCapability;\n /** Whether this exchange supports batch-subscribing to multiple order book streams. */\n watchOrderBooks: ExchangeCapability;\n /** Whether this exchange supports unsubscribing from an order book stream. */\n unwatchOrderBook: ExchangeCapability;\n /** Whether this exchange supports streaming trade updates. */\n watchTrades: ExchangeCapability;\n /** Whether this exchange supports fetching the authenticated user's trade history. */\n fetchMyTrades: ExchangeCapability;\n /** Whether this exchange supports fetching closed orders. */\n fetchClosedOrders: ExchangeCapability;\n /** Whether this exchange supports fetching all orders (open and closed). */\n fetchAllOrders: ExchangeCapability;\n /** Whether this exchange supports building a signed order without submitting it. */\n buildOrder: ExchangeCapability;\n /** Whether this exchange supports submitting a pre-built order. */\n submitOrder: ExchangeCapability;\n /** Whether this exchange supports fetching cross-venue market matches. */\n fetchMarketMatches: ExchangeCapability;\n /** @deprecated Use {@link fetchMarketMatches} instead. */\n fetchMatches: ExchangeCapability;\n /** Whether this exchange supports fetching cross-venue event matches. */\n fetchEventMatches: ExchangeCapability;\n /** Whether this exchange supports comparing prices across venues. */\n compareMarketPrices: ExchangeCapability;\n /** Whether this exchange supports finding related markets across venues. */\n fetchRelatedMarkets: ExchangeCapability;\n /** Whether this exchange supports fetching matched markets across venues. */\n fetchMatchedMarkets: ExchangeCapability;\n /** @deprecated Use {@link fetchMatchedMarkets} instead. */\n fetchMatchedPrices: ExchangeCapability;\n /** @deprecated Use {@link fetchRelatedMarkets} instead. */\n fetchHedges: ExchangeCapability;\n /** @deprecated Use {@link fetchMatchedMarkets} instead. */\n fetchArbitrage: ExchangeCapability;\n}\n\n/**\nOptional authentication credentials for exchange operations.\n/\nexport interface ExchangeCredentials {\n // Standard API authentication (Kalshi, etc.)\n apiKey?: string;\n /** Standard API secret for HMAC-authenticated exchanges */\n apiSecret?: string;\n /** Standard API passphrase for HMAC-authenticated exchanges */\n passphrase?: string;\n /** Metaculus: `Authorization: Token ` for higher rate limits */\n apiToken?: string;\n\n // Blockchain-based authentication (Polymarket)\n privateKey?: string; // Required for Polymarket L1 auth\n\n // Polymarket-specific L2 fields\n signatureType?: number | string; // 0 = EOA, 1 = Poly Proxy, 2 = Gnosis Safe (Can also use 'eoa', 'polyproxy', 'gnosis_safe')\n funderAddress?: string; // The address funding the trades (defaults to signer address)\n\n // Limitless: wallet address for delegated signing profile lookup\n walletAddress?: string;\n\n // Optional base URL override for venue API (e.g., proxy for geo-restricted venues)\n baseUrl?: string;\n}\n\nexport interface ExchangeOptions {\n /**\nHow long (ms) a market snapshot created by `fetchMarketsPaginated` remains valid\nbefore being discarded and re-fetched from the API on the next call.\nDefaults to 0 (no TTL — the snapshot is re-fetched on every initial call).\n/\n snapshotTTL?: number;\n}\n\n/** Shape returned by fetchMarketsPaginated */\nexport interface PaginatedMarketsResult {\n /** The page of unified markets */\n data: UnifiedMarket[];\n /** Total number of markets in the snapshot */\n total: number;\n /** Cursor to pass to the next call, or undefined if this is the last page */\n nextCursor?: string;\n}\n\n/** Shape returned by fetchEventsPaginated */\nexport interface PaginatedEventsResult {\n /** The page of unified events */\n data: UnifiedEvent[];\n /** Total number of events in the snapshot */\n total: number;\n /** Cursor to pass to the next call, or undefined if this is the last page */\n nextCursor?: string;\n}\n\n// ----------------------------------------------------------------------------\n// Base Exchange Class\n// ----------------------------------------------------------------------------\n\nexport abstract class PredictionMarketExchange {\n [key: string]: any; // Allow dynamic method assignment for implicit API\n\n public verbose: boolean = false;\n public http: AxiosInstance;\n public enableRateLimit: boolean = true;\n // Market Cache\n public markets: Record = {};\n public marketsBySlug: Record = {};\n public loadedMarkets: boolean = false;\n /**\nCapability map derived automatically from method overrides at runtime.\nExchanges do NOT need to declare this manually -- if a subclass overrides\na method (and the override does not throw \"not supported\"), it is `true`.\nTo mark a capability as `'emulated'`, add its key to `emulatedCapabilities`.", "params": [], "returns": { "type": "ExchangeHas", "description": "Result" }, - "source": "BaseExchange.ts:40" + "source": "BaseExchange.ts:42" }, "implicitApi": { "summary": "Override in subclasses to force specific capability values.", - "description": "Use `'emulated'` for methods backed by a non-native mechanism,\nor `false` for methods that override the base only to throw a\nbetter error message (e.g. \"pari-mutuel bets cannot be cancelled\").\n/\n protected readonly capabilityOverrides: Partial> = {};\n\n protected credentials?: ExchangeCredentials;\n // Implicit API (merged across multiple defineImplicitApi calls)\n protected apiDescriptor?: ApiDescriptor;\n private _throttler: Throttler;\n // Snapshot state for cursor-based pagination\n private _snapshotTTL: number;\n private _snapshot?: { markets: UnifiedMarket[]; takenAt: number; id: string };\n private _eventSnapshot?: { events: UnifiedEvent[]; takenAt: number; id: string };\n private apiDescriptors: ApiDescriptor[] = [];\n\n constructor(credentials?: ExchangeCredentials, options?: ExchangeOptions) {\n this.credentials = credentials;\n this._snapshotTTL = options?.snapshotTTL ?? 0;\n this.http = axios.create({\n headers: {\n 'User-Agent': `pmxt (https://github.com/pmxt-dev/pmxt)`\n },\n paramsSerializer: {\n serialize: (params) => {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null) continue;\n if (Array.isArray(v)) v.forEach((x) => sp.append(k, String(x)));\n else sp.append(k, String(v));\n }\n return sp.toString();\n },\n },\n });\n this._throttler = new Throttler({\n refillRate: 1 / this._rateLimit,\n capacity: 1,\n delay: 1,\n });\n\n // Rate Limit Interceptor\n this.http.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {\n if (this.enableRateLimit) {\n await this._throttler.throttle();\n }\n return config;\n });\n\n // Request Interceptor\n this.http.interceptors.request.use((config: InternalAxiosRequestConfig) => {\n if (this.verbose) {\n console.log(`\\n[pmxt] → ${config.method?.toUpperCase()} ${config.url}`);\n if (config.params) console.log('[pmxt] params:', config.params);\n if (config.data) console.log('[pmxt] body:', JSON.stringify(config.data, null, 2));\n }\n return config;\n });\n\n // Response Interceptor\n this.http.interceptors.response.use(\n (response: AxiosResponse) => {\n if (this.verbose) {\n console.log(`\\n[pmxt] ← ${response.status} ${response.statusText} ${response.config.url}`);\n // console.log('[pmxt] response:', JSON.stringify(response.data, null, 2));\n // Commented out full body log to avoid spam, but headers might be useful\n }\n return response;\n },\n (error: any) => {\n if (this.verbose) {\n console.log(`\\n[pmxt] ✖ REQUEST FAILED: ${error.config?.url}`);\n console.log('[pmxt] error:', error.message);\n if (error.response) {\n console.log('[pmxt] status:', error.response.status);\n console.log('[pmxt] data:', JSON.stringify(error.response.data, null, 2));\n }\n }\n return Promise.reject(error);\n }\n );\n }\n\n private _rateLimit: number = 1000;\n\n get rateLimit(): number {\n return this._rateLimit;\n }\n\n set rateLimit(value: number) {\n this._rateLimit = value;\n this._throttler = new Throttler({\n refillRate: 1 / value,\n capacity: 1,\n delay: 1,\n });\n }\n\n abstract get name(): string;\n\n /**\nIntrospection getter: returns info about all implicit API methods.", + "description": "Use `'emulated'` for methods backed by a non-native mechanism,\nor `false` for methods that override the base only to throw a\nbetter error message (e.g. \"pari-mutuel bets cannot be cancelled\").\n/\n protected readonly capabilityOverrides: Partial> = {};\n\n protected credentials?: ExchangeCredentials;\n // Implicit API (merged across multiple defineImplicitApi calls)\n protected apiDescriptor?: ApiDescriptor;\n private _throttler: Throttler;\n // Snapshot state for cursor-based pagination\n private _snapshotTTL: number;\n private _snapshot?: { markets: UnifiedMarket[]; takenAt: number; id: string };\n private _eventSnapshot?: { events: UnifiedEvent[]; takenAt: number; id: string };\n private apiDescriptors: ApiDescriptor[] = [];\n\n constructor(credentials?: ExchangeCredentials, options?: ExchangeOptions) {\n this.credentials = credentials;\n this._snapshotTTL = options?.snapshotTTL ?? 0;\n this.http = axios.create({\n headers: {\n 'User-Agent': `pmxt (https://github.com/pmxt-dev/pmxt)`\n },\n paramsSerializer: {\n serialize: (params) => {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null) continue;\n if (Array.isArray(v)) v.forEach((x) => sp.append(k, String(x)));\n else sp.append(k, String(v));\n }\n return sp.toString();\n },\n },\n });\n this._throttler = new Throttler({\n refillRate: 1 / this._rateLimit,\n capacity: 1,\n delay: 1,\n });\n\n // Rate Limit Interceptor\n this.http.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {\n if (this.enableRateLimit) {\n await this._throttler.throttle();\n }\n return config;\n });\n\n // Request Interceptor\n this.http.interceptors.request.use((config: InternalAxiosRequestConfig) => {\n if (this.verbose) {\n logger.debug(`-> ${config.method?.toUpperCase()} ${config.url}`);\n if (config.params) logger.debug('params:', { params: config.params });\n if (config.data) logger.debug('body:', { body: config.data });\n }\n return config;\n });\n\n // Response Interceptor\n this.http.interceptors.response.use(\n (response: AxiosResponse) => {\n if (this.verbose) {\n logger.debug(`<- ${response.status} ${response.statusText} ${response.config.url}`);\n }\n return response;\n },\n (error: any) => {\n if (this.verbose) {\n logger.debug(`REQUEST FAILED: ${error.config?.url}`, {\n error: error.message,\n status: error.response?.status,\n data: error.response?.data,\n });\n }\n return Promise.reject(error);\n }\n );\n }\n\n private _rateLimit: number = 1000;\n\n get rateLimit(): number {\n return this._rateLimit;\n }\n\n set rateLimit(value: number) {\n this._rateLimit = value;\n this._throttler = new Throttler({\n refillRate: 1 / value,\n capacity: 1,\n delay: 1,\n });\n }\n\n abstract get name(): string;\n\n /**\nIntrospection getter: returns info about all implicit API methods.", "params": [], "returns": { "type": "ImplicitApiMethodInfo[]", "description": "Result" }, - "source": "BaseExchange.ts:403" + "source": "BaseExchange.ts:451" }, "loadMarkets": { "summary": "Load and cache all markets from the exchange into `this.markets` and `this.marketsBySlug`.", @@ -36,7 +36,7 @@ "type": "Record", "description": "Dictionary of markets indexed by marketId" }, - "source": "BaseExchange.ts:519" + "source": "BaseExchange.ts:564" }, "fetchMarkets": { "summary": "Fetch markets with optional filtering, search, or slug lookup.", @@ -83,7 +83,7 @@ "ordering — exchanges may reorder or add markets between requests. For stable iteration\nacross pages, use `loadMarkets()` and paginate over `Object.values(exchange.markets)`.", "Some exchanges (like Limitless) may only support status 'active' for search results." ], - "source": "BaseExchange.ts:556" + "source": "BaseExchange.ts:601" }, "fetchMarketsPaginated": { "summary": "Fetch markets with cursor-based pagination backed by a stable in-memory snapshot.", @@ -110,7 +110,7 @@ "type": "PaginatedMarketsResult", "description": "PaginatedMarketsResult with data, total, and optional nextCursor" }, - "source": "BaseExchange.ts:605" + "source": "BaseExchange.ts:650" }, "fetchEventsPaginated": { "summary": "Paginated variant of {@link fetchEvents}.", @@ -137,7 +137,7 @@ "type": "PaginatedEventsResult", "description": "PaginatedEventsResult with data, total, and optional nextCursor" }, - "source": "BaseExchange.ts:674" + "source": "BaseExchange.ts:719" }, "fetchEvents": { "summary": "Fetch events with optional keyword search.", @@ -175,7 +175,24 @@ "notes": [ "Some exchanges (like Limitless) may only support status 'active' for search results." ], - "source": "BaseExchange.ts:743" + "source": "BaseExchange.ts:788" + }, + "fetchSeries": { + "summary": "Fetch the recurring series (fourth tier above Event -> Market -> Outcome)", + "description": "that this venue exposes. Returns an empty array on venues without a\nseries concept (Limitless, Smarkets, Probable, Metaculus, Baozi,\nHyperliquid, SuiBets, Polymarket US).\n\n- `params.id` -> a single matching series with its events populated where supported.\n- no params -> the full list, typically without nested events for payload size.", + "params": [ + { + "name": "params", + "type": "SeriesFetchParams", + "optional": true, + "description": "params" + } + ], + "returns": { + "type": "UnifiedSeries[]", + "description": "Array of unified series. Always an array, including the singular-lookup case." + }, + "source": "BaseExchange.ts:823" }, "fetchMarket": { "summary": "Fetch a single market by lookup parameters.", @@ -192,7 +209,7 @@ "type": "UnifiedMarket", "description": "A single unified market" }, - "source": "BaseExchange.ts:778" + "source": "BaseExchange.ts:841" }, "fetchEvent": { "summary": "Fetch a single event by lookup parameters.", @@ -209,7 +226,7 @@ "type": "UnifiedEvent", "description": "A single unified event" }, - "source": "BaseExchange.ts:809" + "source": "BaseExchange.ts:941" }, "fetchOHLCV": { "summary": "Fetch historical OHLCV (candlestick) price data for a specific market outcome.", @@ -237,11 +254,11 @@ "Polymarket: outcomeId is the CLOB Token ID. Kalshi: outcomeId is the Market Ticker.", "Common resolutions: '1m' | '5m' | '15m' | '1h' | '6h' | '1d'. Arbitrary intervals (e.g. '30s', '120s', '3h') accepted by venues that support them." ], - "source": "BaseExchange.ts:826" + "source": "BaseExchange.ts:958" }, "fetchOrderBook": { "summary": "Fetch the order book (bids/asks) for a specific outcome.", - "description": "Fetch the order book (bids/asks) for a specific outcome.", + "description": "Supports live and historical queries. For historical data, pass `since`\nto get a single snapshot, or `since` + `until` to get an array of fully\nreconstructed L2 books from the archive. Range queries return up to\n`limit` snapshots (default 100, max 1000).", "params": [ { "name": "outcomeId", @@ -253,11 +270,11 @@ "name": "limit", "type": "number", "optional": true, - "description": "Max number of bid/ask levels to return (CCXT-style)." + "description": "Max number of bid/ask levels to return. For range" }, { "name": "params", - "type": "Record", + "type": "FetchOrderBookParams", "optional": true, "description": "Optional parameters:" } @@ -266,7 +283,7 @@ "type": "OrderBook", "description": "Order book with bids and asks. Returns OrderBook[] when" }, - "source": "BaseExchange.ts:841" + "source": "BaseExchange.ts:973" }, "fetchOrderBooks": { "summary": "Batch variant of {@link fetchOrderBook}. Fetches order books for", @@ -283,7 +300,7 @@ "type": "Record", "description": "A map keyed by the input id (preserving the caller's exact" }, - "source": "BaseExchange.ts:863" + "source": "BaseExchange.ts:1001" }, "fetchTrades": { "summary": "Fetch raw trade history for a specific outcome.", @@ -309,7 +326,7 @@ "notes": [ "Polymarket requires an API key for trade history. Use fetchOHLCV for public historical data." ], - "source": "BaseExchange.ts:876" + "source": "BaseExchange.ts:1014" }, "createOrder": { "summary": "Place a new order on the exchange.", @@ -326,7 +343,7 @@ "type": "Order", "description": "The created order" }, - "source": "BaseExchange.ts:896" + "source": "BaseExchange.ts:1031" }, "buildOrder": { "summary": "Build an order payload without submitting it to the exchange.", @@ -343,7 +360,7 @@ "type": "BuiltOrder", "description": "A BuiltOrder containing the exchange-native payload" }, - "source": "BaseExchange.ts:910" + "source": "BaseExchange.ts:1045" }, "submitOrder": { "summary": "Submit a pre-built order returned by buildOrder().", @@ -360,7 +377,7 @@ "type": "Order", "description": "The submitted order" }, - "source": "BaseExchange.ts:922" + "source": "BaseExchange.ts:1057" }, "cancelOrder": { "summary": "Cancel an existing open order.", @@ -377,7 +394,7 @@ "type": "Order", "description": "The cancelled order" }, - "source": "BaseExchange.ts:932" + "source": "BaseExchange.ts:1067" }, "fetchOrder": { "summary": "Fetch a specific order by ID.", @@ -394,7 +411,7 @@ "type": "Order", "description": "The order details" }, - "source": "BaseExchange.ts:942" + "source": "BaseExchange.ts:1077" }, "fetchOpenOrders": { "summary": "Fetch all open orders, optionally filtered by market.", @@ -411,7 +428,7 @@ "type": "Order[]", "description": "Array of open orders" }, - "source": "BaseExchange.ts:952" + "source": "BaseExchange.ts:1087" }, "fetchPositions": { "summary": "Fetch current user positions across all markets.", @@ -428,7 +445,7 @@ "type": "Position[]", "description": "Array of user positions" }, - "source": "BaseExchange.ts:974" + "source": "BaseExchange.ts:1109" }, "fetchBalance": { "summary": "Fetch account balances.", @@ -445,7 +462,7 @@ "type": "Balance[]", "description": "Array of account balances" }, - "source": "BaseExchange.ts:984" + "source": "BaseExchange.ts:1119" }, "getExecutionPrice": { "summary": "Calculate the volume-weighted average execution price for a given order size.", @@ -474,7 +491,7 @@ "type": "number", "description": "Average execution price, or 0 if insufficient liquidity" }, - "source": "BaseExchange.ts:994" + "source": "BaseExchange.ts:1129" }, "getExecutionPriceDetailed": { "summary": "Calculate detailed execution price information including partial fill data.", @@ -503,7 +520,7 @@ "type": "ExecutionPriceResult", "description": "Detailed execution result with price, filled amount, and fill status" }, - "source": "BaseExchange.ts:1007" + "source": "BaseExchange.ts:1142" }, "filterMarkets": { "summary": "Filter a list of markets by criteria.", @@ -526,7 +543,7 @@ "type": "UnifiedMarket[]", "description": "Filtered array of markets" }, - "source": "BaseExchange.ts:1023" + "source": "BaseExchange.ts:1158" }, "filterEvents": { "summary": "Filter a list of events by criteria.", @@ -549,7 +566,7 @@ "type": "UnifiedEvent[]", "description": "Filtered array of events" }, - "source": "BaseExchange.ts:1182" + "source": "BaseExchange.ts:1317" }, "watchOrderBook": { "summary": "Watch order book updates in real-time via WebSocket.", @@ -578,7 +595,7 @@ "type": "OrderBook", "description": "Promise that resolves with the current orderbook state" }, - "source": "BaseExchange.ts:1278" + "source": "BaseExchange.ts:1413" }, "watchOrderBooks": { "summary": "Watch multiple order books simultaneously via WebSocket.", @@ -607,7 +624,7 @@ "type": "Record", "description": "Promise that resolves with order books keyed by ID" }, - "source": "BaseExchange.ts:1291" + "source": "BaseExchange.ts:1426" }, "unwatchOrderBook": { "summary": "Unsubscribe from a previously watched order book stream.", @@ -624,7 +641,7 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1319" + "source": "BaseExchange.ts:1454" }, "watchTrades": { "summary": "Watch trade executions in real-time via WebSocket.", @@ -659,7 +676,7 @@ "type": "Trade[]", "description": "Promise that resolves with recent trades" }, - "source": "BaseExchange.ts:1332" + "source": "BaseExchange.ts:1467" }, "watchAddress": { "summary": "Stream activity for a public wallet address", @@ -682,7 +699,7 @@ "type": "SubscribedAddressSnapshot", "description": "Promise that resolves with the latest SubscribedAddressSnapshot snapshot" }, - "source": "BaseExchange.ts:1346" + "source": "BaseExchange.ts:1481" }, "unwatchAddress": { "summary": "Stop watching a previously registered wallet address and release its resource updates.", @@ -699,24 +716,17 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1359" + "source": "BaseExchange.ts:1494" }, - "testDummyMethod": { + "close": { "summary": "Close all WebSocket connections and clean up resources.", - "description": "Call this when you're done streaming to properly release connections.\n/\n\n /**\nTest method for auto-generation verification.", - "params": [ - { - "name": "param", - "type": "string", - "optional": true, - "description": "param" - } - ], + "description": "Call this when you're done streaming to properly release connections.", + "params": [], "returns": { - "type": "string", + "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1368" + "source": "BaseExchange.ts:1503" }, "fetchMarketMatches": { "summary": "Find the same or related market on other venues. Two modes:", @@ -733,7 +743,7 @@ "type": "MatchResult[]", "description": "Array of matched markets with relation and confidence" }, - "source": "BaseExchange.ts:1389" + "source": "BaseExchange.ts:1517" }, "fetchMatches": { "summary": "fetchMatches", @@ -750,7 +760,7 @@ "type": "MatchResult[]", "description": "Result" }, - "source": "BaseExchange.ts:1405" + "source": "BaseExchange.ts:1533" }, "fetchEventMatches": { "summary": "Find the same or related event on other venues. Two modes:", @@ -767,7 +777,7 @@ "type": "EventMatchResult[]", "description": "Array of matched events with market-level match details" }, - "source": "BaseExchange.ts:1413" + "source": "BaseExchange.ts:1541" }, "compareMarketPrices": { "summary": "Compare live prices for the same market across venues. Finds identity matches and returns side-by-side best bid/ask prices so you can spot price differences at a glance.", @@ -784,7 +794,7 @@ "type": "PriceComparison[]", "description": "Array of price comparisons across venues" }, - "source": "BaseExchange.ts:1429" + "source": "BaseExchange.ts:1557" }, "fetchRelatedMarkets": { "summary": "Find related markets across venues. Discovers subset/superset market relationships", @@ -801,7 +811,7 @@ "type": "PriceComparison[]", "description": "Array of subset/superset matches with live prices" }, - "source": "BaseExchange.ts:1439" + "source": "BaseExchange.ts:1567" }, "fetchMatchedMarkets": { "summary": "fetchMatchedMarkets", @@ -818,7 +828,7 @@ "type": "MatchedMarketPair[]", "description": "Result" }, - "source": "BaseExchange.ts:1450" + "source": "BaseExchange.ts:1578" }, "fetchMatchedPrices": { "summary": "fetchMatchedPrices", @@ -835,7 +845,7 @@ "type": "MatchedPricePair[]", "description": "Array of matched market pairs with prices from each venue" }, - "source": "BaseExchange.ts:1458" + "source": "BaseExchange.ts:1586" }, "fetchHedges": { "summary": "fetchHedges", @@ -852,7 +862,7 @@ "type": "PriceComparison[]", "description": "Array of subset/superset matches with live prices" }, - "source": "BaseExchange.ts:1469" + "source": "BaseExchange.ts:1597" }, "fetchArbitrage": { "summary": "fetchArbitrage", @@ -869,7 +879,7 @@ "type": "ArbitrageOpportunity[]", "description": "Array of arbitrage opportunities sorted by spread" }, - "source": "BaseExchange.ts:1479" + "source": "BaseExchange.ts:1607" }, "watchPrices": { "summary": "Watch AMM price updates for a market address (Limitless only).", @@ -893,7 +903,7 @@ "description": "Result" }, "exchangeOnly": "limitless", - "source": "index.ts:466" + "source": "index.ts:488" }, "watchUserPositions": { "summary": "Watch user positions in real-time (Limitless only).", @@ -911,7 +921,7 @@ "description": "Result" }, "exchangeOnly": "limitless", - "source": "index.ts:478" + "source": "index.ts:500" }, "watchUserTransactions": { "summary": "Watch user transactions in real-time (Limitless only).", @@ -929,7 +939,7 @@ "description": "Result" }, "exchangeOnly": "limitless", - "source": "index.ts:490" + "source": "index.ts:512" }, "initAuth": { "summary": "Initialize L2 API credentials for implicit API signing.", @@ -940,7 +950,7 @@ "description": "Result" }, "exchangeOnly": "polymarket", - "source": "index.ts:136" + "source": "index.ts:143" }, "preWarmMarket": { "summary": "Pre-warm the SDK's internal caches for a market outcome.", @@ -958,7 +968,7 @@ "description": "Result" }, "exchangeOnly": "polymarket", - "source": "index.ts:201" + "source": "index.ts:209" }, "getEventById": { "summary": "Fetch a single event by its numeric ID (Probable only).", @@ -976,7 +986,7 @@ "description": "The UnifiedEvent, or null if not found" }, "exchangeOnly": "probable", - "source": "index.ts:141" + "source": "index.ts:151" }, "getEventBySlug": { "summary": "Fetch a single event by its URL slug (Probable only).", @@ -994,7 +1004,7 @@ "description": "The UnifiedEvent, or null if not found" }, "exchangeOnly": "probable", - "source": "index.ts:160" + "source": "index.ts:170" } }, "workflowExample": { diff --git a/sdks/python/API_REFERENCE.md b/sdks/python/API_REFERENCE.md index 37a4a598..dac681ce 100644 --- a/sdks/python/API_REFERENCE.md +++ b/sdks/python/API_REFERENCE.md @@ -297,6 +297,31 @@ exchange.fetch_events(query="Trump", limit=10, offset=0) **Notes:** Some exchanges (like Limitless) may only support status 'active' for search results. +--- +### `fetch_series` + +Fetch the recurring series (fourth tier above Event -> Market -> Outcome) + + +**Signature:** + +```python +def fetch_series(params: Optional[SeriesFetchParams] = None) -> List[UnifiedSeries]: +``` + +**Parameters:** + +- `params` (SeriesFetchParams) - **Optional**: params + +**Returns:** List[[UnifiedSeries](#unifiedseries)] - Array of unified series. Always an array, including the singular-lookup case. + +**Example:** + +```python +exchange.fetch_series() +``` + + --- ### `fetch_market` @@ -1443,6 +1468,7 @@ tags: List[string] # Optional list of tags. More granular than category — e.g. tick_size: float # Minimum price increment (e.g., 0.01, 0.001) status: str # Venue-native lifecycle status (e.g. 'active', 'closed', 'archived'). contract_address: str # On-chain contract / condition identifier where applicable (Polymarket conditionId, etc.). +source_metadata: object # Raw venue-specific metadata not captured by first-class fields (e.g. Kalshi series_ticker / series_title from the parent event, Polymarket series). Passed through verbatim so downstream consumers can recover anything the unified shape omits. Each venue populates what it has. source_exchange: str # The exchange/venue this market originates from (e.g. 'polymarket', 'kalshi'). Populated by the Router. yes: Any # Convenience accessor for the YES outcome on a binary market. no: Any # Convenience accessor for the NO outcome on a binary market. @@ -1485,9 +1511,31 @@ url: str # Canonical URL to view the event on the venue. image: str # Optional image URL for the event. category: str # Optional category label. Venue-defined — common values include "Sports", "Politics", "Crypto", "Economics", "Science", "Culture". Polymarket uses finer-grained categories like "Bitcoin", "Soccer", "Economic Policy"; Kalshi uses broader ones like "Sports" or "Mentions". tags: List[string] # Optional list of tags. More granular than category — e.g. ["Sports", "FIFA World Cup", "2026 FIFA World Cup"] or ["Politics", "Geopolitics", "Middle East"]. Tags vary by venue: Polymarket markets carry several, Kalshi typically one. +source_metadata: object # Raw venue-specific metadata not captured by first-class fields (e.g. Kalshi series_ticker / series_title, Polymarket series). Passed through verbatim so downstream consumers can recover anything the unified shape omits. Each venue populates what it has. source_exchange: str # The exchange/venue this event originates from (e.g. 'polymarket', 'kalshi'). Populated by the Router. ``` +--- +### `UnifiedSeries` + +A recurring grouping of events on a venue — the fourth tier above Event -> Market -> Outcome. Examples: Kalshi `KXATPMATCH` (every ATP tennis match), Polymarket `wta` (every WTA match), Opinion's daily `collection`. Series only exists where the venue exposes a recurring-event concept; venues without one return an empty array from `fetchSeries`. + +```python +@dataclass +class UnifiedSeries: +id: str # Stable venue-native series identifier (e.g. "KXATPMATCH" on Kalshi, "atp" on Polymarket Gamma, numeric Gamma id). +ticker: str # Venue-native ticker, when distinct from `id`. +slug: str # Venue-native slug. +title: str # Human-readable series title (e.g. "ATP Match Winner", "WTA"). +description: Any # Long-form series description. +recurrence: Any # Recurrence cadence the venue reports ('daily', 'weekly', 'annual', ...). +events: List[UnifiedEvent] # Child events. Populated when fetched by id; the list form usually omits this to keep payloads small. +url: Any # Canonical venue URL for the series. +image: Any # Venue-hosted image. +source_exchange: str # The exchange this series originates from. Populated by the Router. +source_metadata: object # Raw venue-specific fields not promoted to first-class columns. +``` + --- ### `PriceCandle` @@ -1934,6 +1982,7 @@ status: str # Filter by event status (default: 'active', 'inactive' and 'closed' search_in: str # Where to search (default: 'title') event_id: str # Direct lookup by event ID slug: str # Lookup by event slug +series: str # Filter events by their parent series. Accepts the venue-native series id / ticker / slug (e.g. Kalshi `"KXATPMATCH"`, Polymarket `"wta"`). Passed through to the vendor where supported, otherwise applied to `sourceMetadata` after fetch. filter: Any # Optional client-side filter applied after fetching category: str # Filter by category. Each event belongs to a venue-assigned category such as "Sports", "Politics", "Crypto", "Bitcoin", "Soccer", "Economic Policy" (Polymarket) or "Sports", "Mentions" (Kalshi). tags: List[string] # Filter by tags. Returns events matching ANY of the provided tags. Tags are more specific than categories -- for example a "Politics" event might carry tags ["Politics", "Geopolitics", "Middle East", "Iran"]. Common tags include "Crypto", "Elections", "Fed Rates", "FIFA World Cup", "Trump". diff --git a/sdks/python/pmxt/__init__.py b/sdks/python/pmxt/__init__.py index 2dde3055..171a6ca8 100644 --- a/sdks/python/pmxt/__init__.py +++ b/sdks/python/pmxt/__init__.py @@ -21,6 +21,7 @@ from .client import Exchange from ._exchanges import Polymarket, Limitless, Kalshi, KalshiDemo, Probable, Baozi, Myriad, Opinion, Metaculus, Smarkets, PolymarketUS, Polymarket_us, Hyperliquid, GeminiTitan, SuiBets, Suibets, Mock, Router from .router import Router +from .feed_client import FeedClient from .server_manager import ServerManager from .errors import ( PmxtError, @@ -168,6 +169,7 @@ def restart_server() -> None: "Mock", "Router", "Exchange", + "FeedClient", # Server Management "ServerManager", "server", diff --git a/sdks/python/tests/test_public_exports.py b/sdks/python/tests/test_public_exports.py index e1896959..c3ba6321 100644 --- a/sdks/python/tests/test_public_exports.py +++ b/sdks/python/tests/test_public_exports.py @@ -69,6 +69,36 @@ def test_legacy_polymarket_us_alias_stays_public(): assert aliases["Polymarket_us"] == "PolymarketUS" +def test_feed_client_is_top_level_public_export(): + init_path = Path(__file__).resolve().parents[1] / "pmxt" / "__init__.py" + tree = ast.parse(init_path.read_text(encoding="utf-8")) + + imported_modules = { + alias.name: node.module + for node in tree.body + if isinstance(node, ast.ImportFrom) + for alias in node.names + } + public_exports = set() + + for node in tree.body: + if ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id == "__all__" + and isinstance(node.value, ast.List) + ): + public_exports.update( + item.value + for item in node.value.elts + if isinstance(item, ast.Constant) and isinstance(item.value, str) + ) + + assert imported_modules["FeedClient"] == "feed_client" + assert "FeedClient" in public_exports + + def test_polymarket_init_auth_is_generated(): exchanges_path = Path(__file__).resolve().parents[1] / "pmxt" / "_exchanges.py" tree = ast.parse(exchanges_path.read_text(encoding="utf-8")) diff --git a/sdks/typescript/API_REFERENCE.md b/sdks/typescript/API_REFERENCE.md index 44b6bce9..f8504e0a 100644 --- a/sdks/typescript/API_REFERENCE.md +++ b/sdks/typescript/API_REFERENCE.md @@ -300,6 +300,31 @@ await exchange.fetchEvents({ query: "Trump", limit: 10, offset: 0 }) **Notes:** Some exchanges (like Limitless) may only support status 'active' for search results. +--- +### `fetchSeries` + +Fetch the recurring series (fourth tier above Event -> Market -> Outcome) + + +**Signature:** + +```typescript +async fetchSeries(params?: SeriesFetchParams): Promise +``` + +**Parameters:** + +- `params` (SeriesFetchParams) - **Optional**: params + +**Returns:** Promise<[UnifiedSeries](#unifiedseries)[]> - Array of unified series. Always an array, including the singular-lookup case. + +**Example:** + +```typescript +await exchange.fetchSeries() +``` + + --- ### `fetchMarket` @@ -1443,6 +1468,7 @@ tags: string[]; // Optional list of tags. More granular than category — e.g. [ tickSize: number; // Minimum price increment (e.g., 0.01, 0.001) status: string; // Venue-native lifecycle status (e.g. 'active', 'closed', 'archived'). contractAddress: string; // On-chain contract / condition identifier where applicable (Polymarket conditionId, etc.). +sourceMetadata: object; // Raw venue-specific metadata not captured by first-class fields (e.g. Kalshi series_ticker / series_title from the parent event, Polymarket series). Passed through verbatim so downstream consumers can recover anything the unified shape omits. Each venue populates what it has. sourceExchange: string; // The exchange/venue this market originates from (e.g. 'polymarket', 'kalshi'). Populated by the Router. yes: any; // Convenience accessor for the YES outcome on a binary market. no: any; // Convenience accessor for the NO outcome on a binary market. @@ -1485,10 +1511,32 @@ url: string; // Canonical URL to view the event on the venue. image: string; // Optional image URL for the event. category: string; // Optional category label. Venue-defined — common values include "Sports", "Politics", "Crypto", "Economics", "Science", "Culture". Polymarket uses finer-grained categories like "Bitcoin", "Soccer", "Economic Policy"; Kalshi uses broader ones like "Sports" or "Mentions". tags: string[]; // Optional list of tags. More granular than category — e.g. ["Sports", "FIFA World Cup", "2026 FIFA World Cup"] or ["Politics", "Geopolitics", "Middle East"]. Tags vary by venue: Polymarket markets carry several, Kalshi typically one. +sourceMetadata: object; // Raw venue-specific metadata not captured by first-class fields (e.g. Kalshi series_ticker / series_title, Polymarket series). Passed through verbatim so downstream consumers can recover anything the unified shape omits. Each venue populates what it has. sourceExchange: string; // The exchange/venue this event originates from (e.g. 'polymarket', 'kalshi'). Populated by the Router. } ``` +--- +### `UnifiedSeries` + +A recurring grouping of events on a venue — the fourth tier above Event -> Market -> Outcome. Examples: Kalshi `KXATPMATCH` (every ATP tennis match), Polymarket `wta` (every WTA match), Opinion's daily `collection`. Series only exists where the venue exposes a recurring-event concept; venues without one return an empty array from `fetchSeries`. + +```typescript +interface UnifiedSeries { +id: string; // Stable venue-native series identifier (e.g. "KXATPMATCH" on Kalshi, "atp" on Polymarket Gamma, numeric Gamma id). +ticker: string; // Venue-native ticker, when distinct from `id`. +slug: string; // Venue-native slug. +title: string; // Human-readable series title (e.g. "ATP Match Winner", "WTA"). +description: any; // Long-form series description. +recurrence: any; // Recurrence cadence the venue reports ('daily', 'weekly', 'annual', ...). +events: UnifiedEvent[]; // Child events. Populated when fetched by id; the list form usually omits this to keep payloads small. +url: any; // Canonical venue URL for the series. +image: any; // Venue-hosted image. +sourceExchange: string; // The exchange this series originates from. Populated by the Router. +sourceMetadata: object; // Raw venue-specific fields not promoted to first-class columns. +} +``` + --- ### `PriceCandle` @@ -1934,6 +1982,7 @@ status?: string; // Filter by event status (default: 'active', 'inactive' and 'c searchIn?: string; // Where to search (default: 'title') eventId?: string; // Direct lookup by event ID slug?: string; // Lookup by event slug +series?: string; // Filter events by their parent series. Accepts the venue-native series id / ticker / slug (e.g. Kalshi `"KXATPMATCH"`, Polymarket `"wta"`). Passed through to the vendor where supported, otherwise applied to `sourceMetadata` after fetch. filter?: any; // Optional client-side filter applied after fetching category?: string; // Filter by category. Each event belongs to a venue-assigned category such as "Sports", "Politics", "Crypto", "Bitcoin", "Soccer", "Economic Policy" (Polymarket) or "Sports", "Mentions" (Kalshi). tags?: string[]; // Filter by tags. Returns events matching ANY of the provided tags. Tags are more specific than categories -- for example a "Politics" event might carry tags ["Politics", "Geopolitics", "Middle East", "Iran"]. Common tags include "Crypto", "Elections", "Fed Rates", "FIFA World Cup", "Trump". diff --git a/sdks/typescript/index.ts b/sdks/typescript/index.ts index fea03d4b..778adf78 100644 --- a/sdks/typescript/index.ts +++ b/sdks/typescript/index.ts @@ -22,10 +22,13 @@ import { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Mock } from "./pmxt/client.js"; import { Router } from "./pmxt/router.js"; import { ServerManager } from "./pmxt/server-manager.js"; +import { FeedClient } from "./pmxt/feed-client.js"; import * as models from "./pmxt/models.js"; import * as errors from "./pmxt/errors.js"; export { Exchange, Polymarket, Kalshi, KalshiDemo, Limitless, Myriad, Probable, Baozi, Opinion, Metaculus, Smarkets, PolymarketUS, GeminiTitan, Hyperliquid, SuiBets, Mock, PolymarketOptions } from "./pmxt/client.js"; +export { FeedClient } from "./pmxt/feed-client.js"; +export type { Ticker, Tickers, OHLCV, Market as FeedMarket, OracleRound, FeedClientOptions } from "./pmxt/feed-client.js"; export { Router } from "./pmxt/router.js"; export { ServerManager } from "./pmxt/server-manager.js"; export { HOSTED_URL, LOCAL_URL, ENV, resolvePmxtBaseUrl } from "./pmxt/constants.js"; @@ -87,6 +90,7 @@ const pmxt = { Mock, Router, ServerManager, + FeedClient, server, stopServer, restartServer, diff --git a/sdks/typescript/tests/public-exports.test.ts b/sdks/typescript/tests/public-exports.test.ts new file mode 100644 index 00000000..acc17a0f --- /dev/null +++ b/sdks/typescript/tests/public-exports.test.ts @@ -0,0 +1,19 @@ +import * as pmxt from '../index'; +import { FeedClient as DirectFeedClient } from '../pmxt/feed-client'; + +describe('public exports', () => { + it('exports FeedClient as a top-level named export', () => { + expect(pmxt.FeedClient).toBeDefined(); + expect(pmxt.FeedClient).toBe(DirectFeedClient); + }); + + it('exposes FeedClient on the default pmxt object', () => { + expect(pmxt.default.FeedClient).toBeDefined(); + expect(pmxt.default.FeedClient).toBe(DirectFeedClient); + }); + + it('FeedClient is constructable from the top-level export', () => { + const client = new pmxt.FeedClient('chainlink'); + expect(client).toBeInstanceOf(DirectFeedClient); + }); +});