Fix: a named multivariate combination series (for example KXMVESPORTSMULTIGAMEEXTENDED, 37 open markets at the source, or KXMVECROSSCATEGORY) is no longer reported as "has no open markets right now". A series flagged as a combination series in the catalog is resolved before the scan: the run fails when every named series is one, and otherwise skips it with a note in the run status. For a series without that flag, when the scan with mve_filter=exclude returns nothing, one extra request without the filter tells "no open markets" apart from "only combination markets" (named as such in the status); if that request shows single-outcome markets the filtered scan did not return, the run fails because the source no longer applies the filter as expected.
Fix: the order book guard no longer depends on the batch size. When includeOrderbook is on and none of the books of the first batch can be read, the run fails before any row is billed, also with maxItems 1 or 2 (before, rows with seven empty depth fields were billed at those sizes). Tested with all order book resources removed at maxItems 1, 2, 3 and 8.
Verified with three inputs (see the smoke report):
Typical: default 24-hour window, 20 markets sorted by soonest settlement with depth (live).
Edge: KXMVECROSSCATEGORY next to KXBTCD returns the KXBTCD rows and a note that the combination series was skipped; maxItems 2 with the order book resources removed fails.
No results: KXMVESPORTSMULTIGAMEEXTENDED alone fails with the combination series message, SCASE-MARCH2020 alone returns one unbilled status row saying it has no open markets right now.
0.5
Fix: memory no longer depends on the width of the settlement window. Only the best maxItems candidates are kept while the exchange is scanned (cut by the same comparator as the final sort), so a one-week window or a 10-year window with requireTwoSidedQuote: false no longer collects tens of thousands of markets. Measured on the live exchange (126,003 open markets), peak process memory: 24 h window about 260 MB, 168 h window 318 MB, 87,600 h window with requireTwoSidedQuote: false 293 MB, against a default of 512 MB (the 0.4 code needed 560 to 760 MB for the last two and was killed at a 192 MB heap limit).
Fix: HTTP/2 connections of the HTTP client held every response buffer for the whole run (external memory grew by about 5 MB per page of 1,000 markets, 350 MB by the end of a full scan). Requests now use HTTP/1.1, which keeps memory flat.
Fix: the categories filter uses the category membership Kalshi publishes (categories of each series, the same list GET /series?category= returns, verified identical for all 20 categories) instead of the single category field. 1,041 of 14,527 series are listed under several categories, so Financials had 695 and Economics 133 open markets that were silently left out, and Transportation reported "No open markets right now" while a Transportation market was open. Each requested category is also checked against GET /series?category= and the series it returns are added, so a category the catalog does not carry is still honoured.
New output field categories (all categories of the series); category stays the primary category.
Removed an unreachable order book check at the end of the run.
Verified with three inputs (see the smoke report):
Typical: default 24-hour window, 20 markets sorted by soonest settlement with depth (live).
Edge: categories: ["Transportation"] with a 10-year window and requireTwoSidedQuote: false returns the open Transportation market that 0.4 could not see; the widest input the schema allows runs at 293 MB.
No results: categories: ["transportation", "Nope"] with the default window returns one unbilled status row that says one market is in scope but settles outside the window and that "Nope" was ignored.
0.4
Fix: the exclusion of multivariate combination markets is now verified on every row instead of trusted from the request. A market is treated as a combination market when its series in the catalog is an "MVE" series or when the record carries mve_collection_ticker / mve_selected_legs; such markets are skipped and counted in the run status. If they make up more than 1% of the scan (Kalshi stopped applying mve_filter; a misspelled parameter returns a full page of combination markets with HTTP 200), the run fails before any row is billed.
Fix: the same check for the status. Only markets with status active are listed; markets with another status are skipped and counted, and above 1% of the scan the run fails (Kalshi stopped applying status=open).
Fix: a zero-result run never asks to relax filters when the cause is the source: if the source returned only combination or not-open markets, the run status says so before the client-side reasons.
Fix: the status counter of rows with settlement sources counts only rows that were actually emitted (not rows beyond the spending limit or duplicates).
Fix: one corrupt order book level is skipped and the rest of the book is counted; a book with no valid level at all still gives empty depth fields.
README and the differentiator now describe the combination check that the code enforces.
Verified with three inputs (see the smoke report):
Typical: default 24-hour window, 20 markets sorted by soonest settlement with depth (live).
Edge: a fixture where the source ignores mve_filter (60 combination markets next to single-outcome ones) fails the run with the default input, with requireTwoSidedQuote: false and with a 168-hour window, with an empty dataset; a single combination market and a single closed market in a 300-market scan are skipped and counted.
No results: an unmatched keyword returns one unbilled status row; a source that returns only combination markets ends the run as failed.
0.3
Fix: a zero-result run now names the real cause in the run status. A series without open markets (Kalshi keeps finished and seasonal series in its catalog) says "series X has no open markets right now" instead of asking you to relax filters; markets in scope that settle outside the window say to widen the window; markets inside the window removed by the liquidity and keyword filters say to relax those filters. Series without open markets are also named when other requested series returned rows.
Fix: a series ticker combined with a category that does not contain it fails the run before the scan, like an unknown category or an unknown series ticker.
Fix: the series of every row is derived from the market's event ticker against the series catalog and checked against the requested series. Markets that belong to another series are skipped and counted in the status message; if the source returns markets for a requested series and none belongs to it, the run fails before any row is billed (the source no longer applies the series filter).
Verified with three inputs (see the smoke report):
Typical: default 24-hour window, 20 markets sorted by soonest settlement with depth (live, 20 rows).
Edge: KXNEWTAYLOR-R (series with a dash) next to SCASE-MARCH2020 (catalog series with no open markets): one row from the first series, the second named as empty; KXBTCD with the category Politics fails before the scan.
No results: SCASE-MARCH2020 alone returns one unbilled status row saying it has no open markets right now; a series filter that returns only foreign markets ends the run as failed.
0.2
Fix: the series of a market is now taken from the longest event ticker prefix present in the series catalog, not from the text before the first dash. Series with a dash in the ticker (KXBTCD-B, KXNEWTAYLOR-R, KXNFLWINS-ANY) no longer get the title and settlement sources of a shorter series. A market that matches no series gets a null series and is counted in the status message. When series are named in the input, the named series is used directly.
Fix: the run fails, before any row is billed, when open interest is missing from every scanned market and a minimum open interest or the open interest sort is used, and when the settlement sources are missing from the whole series catalog. The 24h volume guard now also covers the volume sort.
The status message reports open interest coverage and the number of rows with settlement sources.
The differentiator now names what the other Kalshi Actors lack: the settlement-time window, the settlement sources of each series and the depth counted within a window of cents.
Verified with three inputs (see the smoke report):
Typical: default 24-hour window, 20 markets sorted by soonest settlement with depth.
Edge: series ticker with a dash (KXBTCD-B) next to the shorter series KXBTCD, every row carries the own title and settlement source of the longer series.
No results and broken source: an unmatched keyword returns one unbilled status row; a catalog without settlement sources or a feed without open interest ends the run as failed.
0.1
Initial release: open Kalshi markets in a settlement window with settlement sources, quotes, spread, volume, open interest and order book depth, filters and sorting.
Verified with three inputs (see the smoke report):
Typical: default 24-hour window, two-sided quotes, 20 markets sorted by soonest settlement with depth.
Edge: a negative lower bound with a category and a series ticker, minimum volume and maximum spread, rules text on, depth off.
No results: an unmatched keyword returns one unbilled status row with found: false; an unknown series ticker fails the run.