What Missing Historical Data Means for a Prediction Market Backtest
A returned equity curve does not establish that the intended historical data was available or that hypothetical orders had execution coverage. Inspect the dataset, exact instruments and fill assumptions before interpreting an empty, flat or apparently successful result.
This post covers what candle coverage actually guarantees, where intervals go missing, and how to inspect the raw response before you believe a number.
What a candle does and does not tell you
The current Kalshi candlestick endpoint accepts native intervals of 1, 60 or 1440 minutes. A request for 5 or 15 is not a supported native interval. You can separately resample suitable data, but must label that transformation and preserve gaps rather than calling it a native venue response.
Kalshi separates bid/ask quote fields from trade-price fields and reports contract volume as volume_fp. The current price object uses fields such as open_dollars. Quote coverage, traded contract volume, and the number of individual trades are different measurements. Do not treat a quote-bearing bucket or a bucket with volume as a count of trades.
Polymarket's CLOB /prices-history response contains timestamp/price pairs {t, p}. It does not include a trade count. An unchanged sampled price can coexist with executions at that same price, so a flat series cannot establish that no fills occurred. Inspect actual trade records for the relevant market and window when that distinction matters.
Where the intervals actually go missing
Inspect the following distinct sources of incomplete coverage rather than treating them as an exhaustive ranking of causes:
- Live and historical endpoints cover different windows. Use the cutoff field for the requested data type; trades and market/candle history need not share a boundary.
- Settled markets and their candles older than market_settled_ts require the documented historical paths. Live and historical single-market candle paths have different shapes.
- The archive boundaries advance. Save the observed cutoff, request path and period with the experiment; an error alone does not prove why a record is absent.
For Polymarket price history, the documented startTs, endTs, interval, and fidelity parameters describe the requested window and resolution. Check the returned timestamps and observed coverage rather than assuming every contract has complete history. This article does not assert fixed retention guarantees.
Request caps quietly truncate your window
Read the limits for the specific endpoint you call. If a window cannot be returned in one request, split it into smaller windows and check for gaps or duplicates. A 400 response requires inspecting the error details; it does not by itself prove a particular candle-count limit.
For Polymarket CLOB /prices-history, request the documented resolution and window, then compare the first and last returned timestamps with the intended range. Do not infer completeness merely because the request succeeded.
Sparse event-contract history limits the simulation itself
An ordinary binary market can stop trading before determination and settlement; other event payouts require their own rules. Sparse or incomplete history limits what a simulator can infer. Inspect these possible failure modes without generalizing that every market is thin:
- No demonstrated execution coverage. Inspect reported volume and actual trades before assuming that the quotes support your fill model. No reported volume is a data finding, not proof that every possible order lacked a counterparty.
- Survivorship in your market list. If you only enumerate markets still visible on live endpoints, you skew toward recently active or still-open contracts and miss the settled ones that define base rates.
- Illusory precision from bid/ask OHLC. Kalshi candles expose quote OHLC even when no trade happened, so a strategy reading midpoint can look tradeable on a market that printed nothing.
Inspect the evidence instead of the equity curve
Before trusting any result, answer a few questions directly from the raw response rather than from the summary metrics.
- How many buckets were returned, and which reported nonzero contract volume? Use trade records if you need a count of individual executions.
- Does the returned time window cover the window you requested? Inspect end_period_ts and account for the endpoint's bucket boundaries.
- Did you cross Kalshi's historical cutoff? Check GET /historical/cutoff and use the documented live or historical endpoint.
- If a request fails or is incomplete, inspect its documented limits and error details before changing the request size.
A schema-aware check can distinguish returned buckets, buckets with trade-price data, and buckets with reported contract volume. None of those bucket counts is a count of individual trades:
from decimal import Decimal
def coverage_observations(candles):
# Counts buckets, NOT individual trades or hypothetical executable fills.
if not candles:
raise ValueError("empty window: inspect coverage")
volumes, endpoints = [], []
for candle in candles:
if candle.get("volume_fp") is None:
raise ValueError("missing volume_fp: verify schema")
volume = Decimal(str(candle["volume_fp"]))
endpoint = candle.get("end_period_ts")
if not volume.is_finite() or volume < 0:
raise ValueError("invalid reported volume")
if not isinstance(endpoint, int) or isinstance(endpoint, bool):
raise ValueError("missing/invalid bucket endpoint")
volumes.append(volume)
endpoints.append(endpoint)
return {"buckets": len(candles),
"price_buckets": sum((c.get("price") or {}).get("open_dollars")
is not None for c in candles),
"volume_buckets": sum(v > 0 for v in volumes),
"first_endpoint": min(endpoints), "last_endpoint": max(endpoints),
"duplicate_endpoints": len(endpoints) - len(set(endpoints))}
# After fetching and inspecting a current-format response:
# print(coverage_observations(candles))
# Compare endpoints with the requested window; this does not prove continuity.This helper was tested offline for empty, missing, non-finite, negative-volume and duplicate-endpoint records. Zero volume-bearing buckets is a coverage finding, not a count of zero individual trades. Positive reported volume and matching outer endpoints also cannot prove complete coverage or executable size.
Carrying the discipline into paper trading
Banger's public beta supports paper research on Kalshi. Historical-data checks and forward paper tests answer different questions. A no-order observer is intentionally expected to produce zero fills; a strategy expected to trade needs a separate review of its decisions, simulated fills, and data coverage. Paper results do not establish profitability or live execution.
Make missing and transformed data explicit in the result. Preserve exact identity, requested and returned windows, schema, cutoff and execution assumptions. An empty simulation is not evidence of an effective strategy; a nonempty one still needs a model-dependent interpretation.