How a market settles
A crypto Up/Down market resolves against a Chainlink stream, not against the order book. Which stream is a property of the market, and the archive carries everything you need to recompute the result yourself.
Two conventions you need
The settlement rule has changed — read it off the market, not the date
Before 2026-08-07(UTC) the instantaneous Chainlink stream decided the outcome; after it, the TWAP streams did — a 30s lookback for 5min markets, 60s for 15min. After 2026-08-14(UTC), 5min markets have since moved to 60s as well, so both durations now settle on twap60s. Take the stream from the market’s own raw.cryptoMarketConfig (twapEnabled, and twapLookbackSeconds of 30 or 60) rather than from the date — markets on different configs coexist on the same day. twap30s is still archived daily; it is simply no longer any market’s settlement line.
Frames are stored as received
Out-of-order included; we do not reorder an archive. Sort at query time if you need chronological order.
Recomputing an outcome yourself
Nothing here needs our word for it — the strike, the window and the deciding stream are all in files you downloaded.
| 1 | Read the market line: start_sec, end_sec, strike_value, and raw.cryptoMarketConfig.twapLookbackSeconds. |
| 2 | Pick the stream that lookback names — 60 means twap60s, 30 means twap30s — and take its value at the close of the window. |
| 3 | Compare against strike_value. Both are fixed point at 1e18 on the Polymarket side, so parse both to integers and compare those rather than the floats: value is there for convenience and full_accuracy_value is the one to settle on, whenever the relay published it. |
| 4 | Check yourself against outcome_prices on the same line. ["1","0"] is Up, ["0","1"] is Down. |
The same four steps, as code
Reads the two files you downloaded and prints a verdict per market. It takes the closing tick by timestamp rather than by position in the file, because frames are archived in the order they arrived.
import gzip, json, csv
MARKETS = "BTC-5m-markets.jsonl.gz" # one line per market, as downloaded
STREAMS = { # each market names the one it settles on
60: "BTCUSD-twap60s-prices.csv.gz",
30: "BTCUSD-twap30s-prices.csv.gz",
0: "BTCUSD-prices.csv.gz", # before TWAP, the instantaneous feed
}
_loaded = {}
def stream(lookback):
if lookback not in _loaded:
with gzip.open(STREAMS[lookback], "rt") as f:
_loaded[lookback] = list(csv.DictReader(f))
return _loaded[lookback]
for line in gzip.open(MARKETS, "rt"):
m = json.loads(line)
# strike_value is null when it could not be established honestly
if not m.get("resolved") or m.get("strike_value") is None:
continue
# Read the stream off the MARKET, never off the date: configs coexist on
# one day. A MISSING config is a record we could not read — not evidence
# of the pre-TWAP era — so it is skipped. Only an explicit null or 0 says
# "this one settled on the instantaneous stream".
raw = m.get("raw")
cfg = raw.get("cryptoMarketConfig") if isinstance(raw, dict) else None
if not isinstance(cfg, dict) or "twapLookbackSeconds" not in cfg:
continue
lookback = cfg["twapLookbackSeconds"]
if lookback is None:
lookback = 0
if lookback not in STREAMS:
continue # unknown config: refuse rather than guess
open_ms, close_ms = m["start_sec"] * 1000, m["end_sec"] * 1000
window = [t for t in stream(lookback)
if open_ms <= int(t["feed_ts_ms"]) <= close_ms]
if not window:
continue
# by timestamp, not by file order: frames are archived as received
last = max(window, key=lambda t: int(t["feed_ts_ms"]))
# full_accuracy_value is 1e18 fixed point ONLY when the relay published it;
# otherwise the collector wrote str(value) and the archive cannot tell you
fixed = last["full_accuracy_value"]
if not fixed.isdigit():
continue # a decimal fallback, not fixed point
settled = int(fixed)
if abs(settled / 1e18 - float(last["value"])) > 1e-6:
continue # an integer, but not at the 1e18 scale
# only a COMPLETE binary pair is an outcome; anything else is bad metadata
prices = m.get("outcome_prices")
if prices not in (["1", "0"], ["0", "1"]):
continue
# the market resolves Up when the close is >= the strike, not > it
mine = "UP" if settled >= int(m["strike_value"]) else "DOWN"
theirs = "UP" if prices[0] == "1" else "DOWN"
print(m["slug"], mine, "matches" if mine == theirs else "DIFFERS")Predict.fun settles differently
A Predict.fun market line carries start_price and end_price as ordinary decimals, and the outcome follows from comparing them. Read settlement off end_price rather than off status: status is whatever upstream last reported, and a market leaves the re-read queue as soon as its end_price lands, so status can stay OPEN on a market that has settled. A market whose end_price equals its start_price is a tie, which is a third outcome rather than a win for either side.
Related: Polymarket order book data · Chainlink settlement data · Predict.fun historical data · Compared with other providers