From a72305ab3c248b3e23b2317d7d3bc3a05669b6a4 Mon Sep 17 00:00:00 2001 From: Samuel Tinnerholm Date: Fri, 5 Jun 2026 14:36:05 +0000 Subject: [PATCH 1/2] fix: cap Kalshi pagination and serialize status=all --- core/src/exchanges/kalshi/fetcher.ts | 13 +++-- core/test/pipeline/kalshi-pagination.test.ts | 54 ++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/core/src/exchanges/kalshi/fetcher.ts b/core/src/exchanges/kalshi/fetcher.ts index 17ab5000..ee63a6c8 100644 --- a/core/src/exchanges/kalshi/fetcher.ts +++ b/core/src/exchanges/kalshi/fetcher.ts @@ -223,11 +223,9 @@ export class KalshiFetcher implements IExchangeFetcher= 10) break; + if (page >= 10) break; } catch (e: any) { throw kalshiErrorMapper.mapError(e); } diff --git a/core/test/pipeline/kalshi-pagination.test.ts b/core/test/pipeline/kalshi-pagination.test.ts index 53d24ad5..89004ea2 100644 --- a/core/test/pipeline/kalshi-pagination.test.ts +++ b/core/test/pipeline/kalshi-pagination.test.ts @@ -78,6 +78,29 @@ describe('Kalshi cursor pagination', () => { ]); }); + it('caps the default fetch limit at 10 pages', async () => { + const { fetcher, calls } = createFetcher([ + { events: buildEvents(200), cursor: 'cursor-200' }, + { events: buildEvents(200, 200), cursor: 'cursor-400' }, + { events: buildEvents(200, 400), cursor: 'cursor-600' }, + { events: buildEvents(200, 600), cursor: 'cursor-800' }, + { events: buildEvents(200, 800), cursor: 'cursor-1000' }, + { events: buildEvents(200, 1000), cursor: 'cursor-1200' }, + { events: buildEvents(200, 1200), cursor: 'cursor-1400' }, + { events: buildEvents(200, 1400), cursor: 'cursor-1600' }, + { events: buildEvents(200, 1600), cursor: 'cursor-1800' }, + { events: buildEvents(200, 1800), cursor: 'cursor-2000' }, + { events: buildEvents(200, 2000), cursor: 'cursor-2200' }, + ]); + + const events = await fetcher.fetchRawMarkets(); + + expect(events).toHaveLength(2000); + expect(calls).toHaveLength(10); + expect(calls[0]).toEqual({ limit: 200, with_nested_markets: true, status: 'open' }); + expect(calls[9]).toEqual({ limit: 200, with_nested_markets: true, status: 'open', cursor: 'cursor-1800' }); + }); + it('starts from a supplied cursor', async () => { const { fetcher, calls } = createFetcher([ { events: buildEvents(25, 500), cursor: 'cursor-525' }, @@ -92,6 +115,37 @@ describe('Kalshi cursor pagination', () => { ]); }); + it('runs status=all fetches sequentially instead of concurrently', async () => { + const active = { count: 0, max: 0 }; + const responses: Array<{ events: unknown[]; cursor?: string | null }> = [ + { events: buildEvents(1), cursor: null }, + { events: buildEvents(1, 100), cursor: null }, + { events: buildEvents(1, 200), cursor: null }, + ]; + const calls: Array> = []; + const ctx: any = { + http: {}, + getHeaders: () => ({}), + callApi: async (operation: string, params?: Record) => { + if (operation === 'GetSeriesList') return { series: [] }; + if (operation !== 'GetEvents') return {}; + calls.push(params ?? {}); + active.count += 1; + active.max = Math.max(active.max, active.count); + await new Promise((resolve) => setTimeout(resolve, 0)); + active.count -= 1; + return responses.shift() ?? { events: [], cursor: null }; + }, + }; + const fetcher = new KalshiFetcher(ctx); + + const events = await fetcher.fetchRawEvents({ status: 'all' } as any); + + expect(events).toHaveLength(3); + expect(active.max).toBe(1); + expect(calls.map(call => call.status)).toEqual(['open', 'closed', 'settled']); + }); + it('enriches paginated events with series title and tags', async () => { const { fetcher, calls } = createSeriesFetcher(); From b98e7d396398502656c8e297de393b6571687f71 Mon Sep 17 00:00:00 2001 From: Samuel Tinnerholm Date: Mon, 8 Jun 2026 08:00:47 +0000 Subject: [PATCH 2/2] chore: regenerate API references --- core/api-doc-config.generated.json | 36 +++++++++++++++--------------- sdks/python/API_REFERENCE.md | 2 +- sdks/typescript/API_REFERENCE.md | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/core/api-doc-config.generated.json b/core/api-doc-config.generated.json index 3e176c67..e420ab83 100644 --- a/core/api-doc-config.generated.json +++ b/core/api-doc-config.generated.json @@ -1,5 +1,5 @@ { - "_generated": "Auto-generated by extract-jsdoc.js on 2026-06-02T00:34:44.916Z. Do not edit manually.", + "_generated": "Auto-generated by extract-jsdoc.js on 2026-06-08T07:59:06.055Z. Do not edit manually.", "methods": { "has": { "summary": "HTTP verb for the endpoint (e.g. GET, POST). */", @@ -566,7 +566,7 @@ "type": "UnifiedEvent[]", "description": "Filtered array of events" }, - "source": "BaseExchange.ts:1317" + "source": "BaseExchange.ts:1318" }, "watchOrderBook": { "summary": "Watch order book updates in real-time via WebSocket.", @@ -595,7 +595,7 @@ "type": "OrderBook", "description": "Promise that resolves with the current orderbook state" }, - "source": "BaseExchange.ts:1413" + "source": "BaseExchange.ts:1414" }, "watchOrderBooks": { "summary": "Watch multiple order books simultaneously via WebSocket.", @@ -624,7 +624,7 @@ "type": "Record", "description": "Promise that resolves with order books keyed by ID" }, - "source": "BaseExchange.ts:1426" + "source": "BaseExchange.ts:1427" }, "unwatchOrderBook": { "summary": "Unsubscribe from a previously watched order book stream.", @@ -641,7 +641,7 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1454" + "source": "BaseExchange.ts:1455" }, "watchTrades": { "summary": "Watch trade executions in real-time via WebSocket.", @@ -676,7 +676,7 @@ "type": "Trade[]", "description": "Promise that resolves with recent trades" }, - "source": "BaseExchange.ts:1467" + "source": "BaseExchange.ts:1468" }, "watchAddress": { "summary": "Stream activity for a public wallet address", @@ -699,7 +699,7 @@ "type": "SubscribedAddressSnapshot", "description": "Promise that resolves with the latest SubscribedAddressSnapshot snapshot" }, - "source": "BaseExchange.ts:1481" + "source": "BaseExchange.ts:1482" }, "unwatchAddress": { "summary": "Stop watching a previously registered wallet address and release its resource updates.", @@ -716,7 +716,7 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1494" + "source": "BaseExchange.ts:1495" }, "close": { "summary": "Close all WebSocket connections and clean up resources.", @@ -726,7 +726,7 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1503" + "source": "BaseExchange.ts:1504" }, "fetchMarketMatches": { "summary": "Find the same or related market on other venues. Two modes:", @@ -743,7 +743,7 @@ "type": "MatchResult[]", "description": "Array of matched markets with relation and confidence" }, - "source": "BaseExchange.ts:1517" + "source": "BaseExchange.ts:1518" }, "fetchMatches": { "summary": "fetchMatches", @@ -760,7 +760,7 @@ "type": "MatchResult[]", "description": "Result" }, - "source": "BaseExchange.ts:1533" + "source": "BaseExchange.ts:1534" }, "fetchEventMatches": { "summary": "Find the same or related event on other venues. Two modes:", @@ -777,7 +777,7 @@ "type": "EventMatchResult[]", "description": "Array of matched events with market-level match details" }, - "source": "BaseExchange.ts:1541" + "source": "BaseExchange.ts:1542" }, "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.", @@ -794,7 +794,7 @@ "type": "PriceComparison[]", "description": "Array of price comparisons across venues" }, - "source": "BaseExchange.ts:1557" + "source": "BaseExchange.ts:1558" }, "fetchRelatedMarkets": { "summary": "Find related markets across venues. Discovers subset/superset market relationships", @@ -811,7 +811,7 @@ "type": "PriceComparison[]", "description": "Array of subset/superset matches with live prices" }, - "source": "BaseExchange.ts:1567" + "source": "BaseExchange.ts:1568" }, "fetchMatchedMarkets": { "summary": "fetchMatchedMarkets", @@ -828,7 +828,7 @@ "type": "MatchedMarketPair[]", "description": "Result" }, - "source": "BaseExchange.ts:1578" + "source": "BaseExchange.ts:1579" }, "fetchMatchedPrices": { "summary": "fetchMatchedPrices", @@ -845,7 +845,7 @@ "type": "MatchedPricePair[]", "description": "Array of matched market pairs with prices from each venue" }, - "source": "BaseExchange.ts:1586" + "source": "BaseExchange.ts:1587" }, "fetchHedges": { "summary": "fetchHedges", @@ -862,7 +862,7 @@ "type": "PriceComparison[]", "description": "Array of subset/superset matches with live prices" }, - "source": "BaseExchange.ts:1597" + "source": "BaseExchange.ts:1598" }, "fetchArbitrage": { "summary": "fetchArbitrage", @@ -879,7 +879,7 @@ "type": "ArbitrageOpportunity[]", "description": "Array of arbitrage opportunities sorted by spread" }, - "source": "BaseExchange.ts:1607" + "source": "BaseExchange.ts:1608" }, "watchPrices": { "summary": "Watch AMM price updates for a market address (Limitless only).", diff --git a/sdks/python/API_REFERENCE.md b/sdks/python/API_REFERENCE.md index dac681ce..b20f3aa2 100644 --- a/sdks/python/API_REFERENCE.md +++ b/sdks/python/API_REFERENCE.md @@ -1456,7 +1456,7 @@ title: str # The market title (e.g., "Will BTC close above $100k on Dec 31?"). description: str # Long-form market description or resolution criteria. slug: str # URL-friendly slug for the market. outcomes: List[MarketOutcome] # The possible outcomes for this market. -resolution_date: str # When the market is scheduled to resolve. +resolution_date: str # When the market is scheduled to resolve. Optional because some venues do not publish a cutoff for every market (e.g. Opinion categorical children) — emit `undefined` rather than coercing to epoch. volume24h: float # Trading volume over the past 24 hours (USD). volume: float # Total / Lifetime volume liquidity: float # Current market liquidity (USD). diff --git a/sdks/typescript/API_REFERENCE.md b/sdks/typescript/API_REFERENCE.md index f8504e0a..ec2f3280 100644 --- a/sdks/typescript/API_REFERENCE.md +++ b/sdks/typescript/API_REFERENCE.md @@ -1456,7 +1456,7 @@ title: string; // The market title (e.g., "Will BTC close above $100k on Dec 31? description: string; // Long-form market description or resolution criteria. slug: string; // URL-friendly slug for the market. outcomes: MarketOutcome[]; // The possible outcomes for this market. -resolutionDate: string; // When the market is scheduled to resolve. +resolutionDate: string; // When the market is scheduled to resolve. Optional because some venues do not publish a cutoff for every market (e.g. Opinion categorical children) — emit `undefined` rather than coercing to epoch. volume24h: number; // Trading volume over the past 24 hours (USD). volume: number; // Total / Lifetime volume liquidity: number; // Current market liquidity (USD).