probalytics 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: probalytics
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Python client for Probalytics market, fill, and orderbook data
5
5
  Project-URL: Homepage, https://github.com/Probalytics/probalytics-python
6
6
  Project-URL: Repository, https://github.com/Probalytics/probalytics-python
@@ -218,7 +218,13 @@ book = market.orderbook_snapshots(
218
218
  )
219
219
  ```
220
220
 
221
- Snapshots include market identifiers, outcome, bids, asks, and timestamp.
221
+ Each snapshot is a complete replacement book for one outcome. Rows include
222
+ `indexed_at`, `hash`, `state`, `continuity`, and `path_index` in addition to the
223
+ market identifiers, outcome, bids, asks, and source timestamp.
224
+
225
+ Use `state="VERIFIED"` when you only need books reconstructed without gaps. Use
226
+ `continuity` separately to distinguish rows connected to the preceding
227
+ published state from fresh starting points.
222
228
 
223
229
  ## Choose Polars or pandas
224
230
 
@@ -303,6 +309,8 @@ directly into SQL strings.
303
309
  - `market`
304
310
  - `market_id` or list of market IDs
305
311
  - `market_platform_id` or list of market platform IDs
312
+ - `state` or list of states
313
+ - `continuity` or list of continuity values
306
314
  - `limit`
307
315
 
308
316
  ## Local Development
@@ -186,7 +186,13 @@ book = market.orderbook_snapshots(
186
186
  )
187
187
  ```
188
188
 
189
- Snapshots include market identifiers, outcome, bids, asks, and timestamp.
189
+ Each snapshot is a complete replacement book for one outcome. Rows include
190
+ `indexed_at`, `hash`, `state`, `continuity`, and `path_index` in addition to the
191
+ market identifiers, outcome, bids, asks, and source timestamp.
192
+
193
+ Use `state="VERIFIED"` when you only need books reconstructed without gaps. Use
194
+ `continuity` separately to distinguish rows connected to the preceding
195
+ published state from fresh starting points.
190
196
 
191
197
  ## Choose Polars or pandas
192
198
 
@@ -271,6 +277,8 @@ directly into SQL strings.
271
277
  - `market`
272
278
  - `market_id` or list of market IDs
273
279
  - `market_platform_id` or list of market platform IDs
280
+ - `state` or list of states
281
+ - `continuity` or list of continuity values
274
282
  - `limit`
275
283
 
276
284
  ## Local Development
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "probalytics"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Python client for Probalytics market, fill, and orderbook data"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,7 +7,9 @@ from probalytics.models import (
7
7
  MarketStatus,
8
8
  MarketType,
9
9
  OrderSide,
10
+ OrderbookContinuity,
10
11
  OrderbookSnapshot,
12
+ OrderbookState,
11
13
  Outcome,
12
14
  Platform,
13
15
  Resolution,
@@ -21,7 +23,9 @@ __all__ = [
21
23
  "MarketStatus",
22
24
  "MarketType",
23
25
  "OrderSide",
26
+ "OrderbookContinuity",
24
27
  "OrderbookSnapshot",
28
+ "OrderbookState",
25
29
  "Outcome",
26
30
  "ClickHouseClient",
27
31
  "Platform",
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  from collections.abc import Sequence
4
5
  from datetime import datetime
5
6
  from enum import Enum
@@ -156,7 +157,9 @@ class ClickHouseClient:
156
157
  SELECT
157
158
  id, market_id, market_platform_id, platform, platform_id, outcome,
158
159
  size, price, normalized_price, taker_side, taker_cash_flow,
159
- maker_cash_flow, taker_id, maker_id, fee, timestamp
160
+ maker_cash_flow, taker_id, maker_id, fee,
161
+ source_block_number, source_tx_hash, source_log_index,
162
+ timestamp, indexed_at, toJSONString(metadata) AS metadata
160
163
  FROM fills
161
164
  {where}
162
165
  ORDER BY timestamp ASC, id ASC
@@ -176,6 +179,8 @@ class ClickHouseClient:
176
179
  market: Market | str | UUID | None = None,
177
180
  market_id: IDFilter = None,
178
181
  market_platform_id: StringFilter = None,
182
+ state: StringFilter = None,
183
+ continuity: StringFilter = None,
179
184
  limit: int = 1000,
180
185
  frame: FrameKind = "polars",
181
186
  ) -> Any:
@@ -188,6 +193,8 @@ class ClickHouseClient:
188
193
  _filter("platform", "platform", platform),
189
194
  _filter("market_id", "market_id", market_id),
190
195
  _filter("market_platform_id", "market_platform_id", market_platform_id),
196
+ _filter("state", "state", state),
197
+ _filter("continuity", "continuity", continuity),
191
198
  ]
192
199
  )
193
200
  params["limit"] = limit
@@ -195,10 +202,10 @@ class ClickHouseClient:
195
202
  SELECT
196
203
  market_id, market_platform_id, platform,
197
204
  {OUTCOME_FRAME_EXPR} AS outcome,
198
- bids, asks, timestamp
205
+ bids, asks, timestamp, indexed_at, hash, state, continuity, path_index
199
206
  FROM orderbook_snapshots
200
207
  {where}
201
- ORDER BY timestamp ASC
208
+ ORDER BY timestamp ASC, path_index ASC, indexed_at ASC
202
209
  LIMIT %(limit)s
203
210
  """
204
211
  return self._query_frame(query, params, frame)
@@ -285,7 +292,9 @@ class ClickHouseClient:
285
292
  id, market_id, market_platform_id, platform, platform_id,
286
293
  {OUTCOME_FRAME_EXPR} AS outcome,
287
294
  size, price, normalized_price, taker_side, taker_cash_flow,
288
- maker_cash_flow, taker_id, maker_id, fee, timestamp
295
+ maker_cash_flow, taker_id, maker_id, fee,
296
+ source_block_number, source_tx_hash, source_log_index,
297
+ timestamp, indexed_at, toJSONString(metadata) AS metadata
289
298
  FROM fills
290
299
  {where}
291
300
  ORDER BY timestamp ASC, id ASC
@@ -312,6 +321,8 @@ class ClickHouseClient:
312
321
  columnar=True,
313
322
  )
314
323
  data = {column[0]: values for column, values in zip(columns, columns_data, strict=True)}
324
+ if "metadata" in data:
325
+ data["metadata"] = [_json_object(value) for value in data["metadata"]]
315
326
  return dataframe_to_frame(data, frame)
316
327
 
317
328
 
@@ -378,6 +389,8 @@ def _market_row(row: dict[str, Any]) -> dict[str, Any]:
378
389
  "outcome_payouts": [_outcome_payout(value) for value in row.get("resolution_outcome_payouts") or []],
379
390
  "resolved_by": row.get("resolution_resolved_by") or "",
380
391
  "resolved_at": row.get("resolution_resolved_at"),
392
+ "source_block_number": row.get("resolution_source_block_number"),
393
+ "source_tx_hash": row.get("resolution_source_tx_hash"),
381
394
  }
382
395
  return {
383
396
  "id": row["id"],
@@ -389,7 +402,7 @@ def _market_row(row: dict[str, Any]) -> dict[str, Any]:
389
402
  "description": row.get("description", ""),
390
403
  "category": row.get("category", ""),
391
404
  "tags": row.get("tags", []),
392
- "market_type": row.get("market_type", "UNKNOWN"),
405
+ "market_type": row["market_type"],
393
406
  "outcomes": [_outcome(value) for value in row.get("outcomes", [])],
394
407
  "status": row["status"],
395
408
  "created_at": row["created_at"],
@@ -399,6 +412,9 @@ def _market_row(row: dict[str, Any]) -> dict[str, Any]:
399
412
  "end_date": row.get("end_date"),
400
413
  "reset_at": row.get("reset_at"),
401
414
  "resolution": resolution,
415
+ "source_block_number": row.get("source_block_number", 0),
416
+ "source_tx_hash": row.get("source_tx_hash", ""),
417
+ "indexed_at": row["indexed_at"],
402
418
  }
403
419
 
404
420
 
@@ -406,9 +422,19 @@ def _fill_row(row: dict[str, Any]) -> dict[str, Any]:
406
422
  return {
407
423
  **row,
408
424
  "outcome": _outcome(row["outcome"]),
425
+ "metadata": _json_object(row.get("metadata")),
409
426
  }
410
427
 
411
428
 
429
+ def _json_object(value: Any) -> Any:
430
+ if not isinstance(value, str):
431
+ return value
432
+ try:
433
+ return json.loads(value)
434
+ except json.JSONDecodeError:
435
+ return value
436
+
437
+
412
438
  def _outcome(value: Any) -> dict[str, Any]:
413
439
  if isinstance(value, dict):
414
440
  return {
@@ -174,6 +174,8 @@ class ProbalyticsClient:
174
174
  market: Market | str | UUID | None = None,
175
175
  market_id: IDFilter = None,
176
176
  market_platform_id: StringFilter = None,
177
+ state: StringFilter = None,
178
+ continuity: StringFilter = None,
177
179
  frame: FrameKind | None = None,
178
180
  **filters: Any,
179
181
  ) -> Any:
@@ -184,6 +186,8 @@ class ProbalyticsClient:
184
186
  market=market,
185
187
  market_id=market_id,
186
188
  market_platform_id=market_platform_id,
189
+ state=state,
190
+ continuity=continuity,
187
191
  frame=validate_frame(frame or self.frame),
188
192
  **filters,
189
193
  )
@@ -14,8 +14,6 @@ from probalytics._frames import FrameKind
14
14
  class Platform(StrEnum):
15
15
  POLYMARKET = "POLYMARKET"
16
16
  KALSHI = "KALSHI"
17
- PREDICTIT = "PREDICTIT"
18
- UNKNOWN = "UNKNOWN"
19
17
 
20
18
 
21
19
  class MarketStatus(StrEnum):
@@ -32,7 +30,6 @@ class MarketType(StrEnum):
32
30
  SCALAR = "SCALAR"
33
31
  PARLAY = "PARLAY"
34
32
  PERPETUAL = "PERPETUAL"
35
- UNKNOWN = "UNKNOWN"
36
33
 
37
34
 
38
35
  class ResolutionType(StrEnum):
@@ -46,6 +43,16 @@ class OrderSide(StrEnum):
46
43
  SELL = "SELL"
47
44
 
48
45
 
46
+ class OrderbookState(StrEnum):
47
+ VERIFIED = "VERIFIED"
48
+ INTERMEDIATE = "INTERMEDIATE"
49
+
50
+
51
+ class OrderbookContinuity(StrEnum):
52
+ CONTIGUOUS = "CONTIGUOUS"
53
+ RESET = "RESET"
54
+
55
+
49
56
  class ProbalyticsModel(BaseModel):
50
57
  model_config = ConfigDict(use_enum_values=True)
51
58
 
@@ -68,6 +75,8 @@ class Resolution(ProbalyticsModel):
68
75
  outcome_payouts: list[OutcomePayout] = Field(default_factory=list)
69
76
  resolved_by: str = ""
70
77
  resolved_at: datetime | None = None
78
+ source_block_number: int | None = None
79
+ source_tx_hash: str | None = None
71
80
 
72
81
 
73
82
  class Market(ProbalyticsModel):
@@ -82,7 +91,7 @@ class Market(ProbalyticsModel):
82
91
  description: str = ""
83
92
  category: str = ""
84
93
  tags: list[str] = Field(default_factory=list)
85
- market_type: MarketType = MarketType.UNKNOWN
94
+ market_type: MarketType
86
95
  outcomes: list[Outcome] = Field(default_factory=list)
87
96
  status: MarketStatus
88
97
  created_at: datetime
@@ -92,6 +101,9 @@ class Market(ProbalyticsModel):
92
101
  end_date: datetime | None = None
93
102
  reset_at: datetime | None = None
94
103
  resolution: Resolution | None = None
104
+ source_block_number: int = 0
105
+ source_tx_hash: str = ""
106
+ indexed_at: datetime
95
107
 
96
108
  def fills(
97
109
  self,
@@ -143,6 +155,8 @@ class Market(ProbalyticsModel):
143
155
  start_time: datetime | str,
144
156
  end_time: datetime | str,
145
157
  client: Any | None = None,
158
+ state: str | list[str] | None = None,
159
+ continuity: str | list[str] | None = None,
146
160
  limit: int = 1000,
147
161
  frame: FrameKind | None = None,
148
162
  ) -> Any:
@@ -150,6 +164,8 @@ class Market(ProbalyticsModel):
150
164
  market=self,
151
165
  start_time=start_time,
152
166
  end_time=end_time,
167
+ state=state,
168
+ continuity=continuity,
153
169
  limit=limit,
154
170
  frame=frame,
155
171
  )
@@ -183,7 +199,12 @@ class Fill(ProbalyticsModel):
183
199
  taker_id: str | None = None
184
200
  maker_id: str | None = None
185
201
  fee: Decimal
202
+ source_block_number: int | None = None
203
+ source_tx_hash: str | None = None
204
+ source_log_index: int | None = None
186
205
  timestamp: datetime
206
+ indexed_at: datetime
207
+ metadata: dict[str, Any] = Field(default_factory=dict)
187
208
 
188
209
 
189
210
  class BookLevel(ProbalyticsModel):
@@ -199,6 +220,11 @@ class OrderbookSnapshot(ProbalyticsModel):
199
220
  bids: list[BookLevel] = Field(default_factory=list)
200
221
  asks: list[BookLevel] = Field(default_factory=list)
201
222
  timestamp: datetime
223
+ indexed_at: datetime
224
+ hash: int
225
+ state: OrderbookState
226
+ continuity: OrderbookContinuity
227
+ path_index: int
202
228
 
203
229
 
204
230
  def model_records(models: list[ProbalyticsModel]) -> list[dict[str, Any]]:
@@ -82,6 +82,10 @@ ORDERBOOK_SNAPSHOT_COLUMNS = (
82
82
  "asks",
83
83
  "timestamp",
84
84
  "indexed_at",
85
+ "hash",
86
+ "state",
87
+ "continuity",
88
+ "path_index",
85
89
  )
86
90
 
87
91
  MARKET_ROWS = [
@@ -159,7 +163,7 @@ FILL_ROWS = [
159
163
  Decimal("0.45"),
160
164
  Decimal("0.45"),
161
165
  "BUY",
162
- Decimal("-4.5"),
166
+ Decimal("4.5"),
163
167
  Decimal("4.5"),
164
168
  "trader-a",
165
169
  "maker-a",
@@ -182,7 +186,7 @@ FILL_ROWS = [
182
186
  Decimal("0.60"),
183
187
  "SELL",
184
188
  Decimal("3"),
185
- Decimal("-3"),
189
+ Decimal("3"),
186
190
  "trader-b",
187
191
  "maker-b",
188
192
  Decimal("0"),
@@ -204,6 +208,10 @@ ORDERBOOK_SNAPSHOT_ROWS = [
204
208
  [(Decimal("0.46"), Decimal("8"))],
205
209
  SAMPLE_TIME.replace(second=1),
206
210
  SAMPLE_TIME.replace(second=1),
211
+ 101,
212
+ "VERIFIED",
213
+ "RESET",
214
+ 0,
207
215
  ),
208
216
  (
209
217
  MARKET_B,
@@ -214,6 +222,10 @@ ORDERBOOK_SNAPSHOT_ROWS = [
214
222
  [(Decimal("0.61"), Decimal("6"))],
215
223
  SAMPLE_TIME.replace(second=2),
216
224
  SAMPLE_TIME.replace(second=2),
225
+ 202,
226
+ "VERIFIED",
227
+ "CONTIGUOUS",
228
+ 0,
217
229
  ),
218
230
  ]
219
231
 
@@ -237,7 +249,7 @@ def insert_rows(client: DriverClient, table: str, columns: tuple[str, ...], rows
237
249
  CREATE_MARKETS = """
238
250
  CREATE TABLE markets (
239
251
  id UUID,
240
- platform Enum('POLYMARKET', 'KALSHI', 'PREDICTIT', 'UNKNOWN'),
252
+ platform Enum('POLYMARKET', 'KALSHI'),
241
253
  platform_id String,
242
254
  slug String,
243
255
  url String,
@@ -245,7 +257,7 @@ CREATE TABLE markets (
245
257
  description String,
246
258
  category LowCardinality(String),
247
259
  tags Array(LowCardinality(String)),
248
- market_type Enum('UNKNOWN', 'BINARY', 'MULTIPLE', 'SCALAR', 'PARLAY', 'PERPETUAL'),
260
+ market_type Enum('BINARY', 'MULTIPLE', 'SCALAR', 'PARLAY', 'PERPETUAL'),
249
261
  outcomes Array(Tuple(id UUID, platform_id String, name String, index UInt8)),
250
262
  created_at DateTime64(3),
251
263
  opened_at Nullable(DateTime64(3)),
@@ -273,7 +285,7 @@ CREATE TABLE fills (
273
285
  id UUID,
274
286
  market_id UUID,
275
287
  market_platform_id String,
276
- platform Enum('POLYMARKET', 'KALSHI', 'PREDICTIT', 'UNKNOWN'),
288
+ platform Enum('POLYMARKET', 'KALSHI'),
277
289
  platform_id String,
278
290
  outcome Tuple(id UUID, platform_id String, name String, index UInt8),
279
291
  size Decimal128(18),
@@ -289,7 +301,8 @@ CREATE TABLE fills (
289
301
  source_tx_hash Nullable(String),
290
302
  source_log_index Nullable(UInt32),
291
303
  timestamp DateTime64(6),
292
- indexed_at DateTime64(3)
304
+ indexed_at DateTime64(9),
305
+ metadata JSON DEFAULT '{}'
293
306
  ) ENGINE = ReplacingMergeTree(indexed_at)
294
307
  ORDER BY (platform, market_id, timestamp, id)
295
308
  PARTITION BY toYYYYMM(timestamp)
@@ -299,13 +312,17 @@ CREATE_ORDERBOOK_SNAPSHOTS = """
299
312
  CREATE TABLE orderbook_snapshots (
300
313
  market_id UUID,
301
314
  market_platform_id String,
302
- platform Enum('POLYMARKET', 'KALSHI', 'PREDICTIT', 'UNKNOWN'),
315
+ platform Enum('POLYMARKET', 'KALSHI'),
303
316
  outcome Tuple(id UUID, platform_id String, name String, index UInt8),
304
317
  bids Array(Tuple(price Decimal64(6), size Decimal64(6))),
305
318
  asks Array(Tuple(price Decimal64(6), size Decimal64(6))),
306
- timestamp DateTime64(3),
307
- indexed_at DateTime64(3)
319
+ timestamp DateTime64(9),
320
+ indexed_at DateTime64(9),
321
+ hash UInt64,
322
+ state Enum8('VERIFIED' = 1, 'INTERMEDIATE' = 2),
323
+ continuity Enum8('CONTIGUOUS' = 1, 'RESET' = 2),
324
+ path_index UInt32
308
325
  ) ENGINE = ReplacingMergeTree(indexed_at)
309
- ORDER BY (market_id, outcome.id, timestamp)
326
+ ORDER BY (market_id, outcome.id, timestamp, hash, state, continuity, path_index)
310
327
  PARTITION BY toYYYYMM(timestamp)
311
328
  """
@@ -133,6 +133,7 @@ def test_fills_dataframe_uses_global_frame_and_market_id_arrays(client: Probalyt
133
133
  assert isinstance(fills, pd.DataFrame)
134
134
  assert set(fills["market_platform_id"]) == {"0xmarket", "KXBTC-26JUN-T50000"}
135
135
  assert set(fills["platform"]) == {"POLYMARKET", "KALSHI"}
136
+ assert all(value == {} for value in fills["metadata"])
136
137
  assert isinstance(fills.loc[0, "outcome"], dict)
137
138
  assert set(fills.loc[0, "outcome"]) == {"id", "platform_id", "name", "index"}
138
139
  assert isinstance(fills.loc[0, "outcome"]["id"], str)
@@ -169,6 +170,8 @@ def test_orderbook_snapshots_accept_market_platform_id_arrays(client: Probalytic
169
170
  end_time="2026-03-15T00:02:00Z",
170
171
  platform=["POLYMARKET", "KALSHI"],
171
172
  market_platform_id=["0xmarket", "KXBTC-26JUN-T50000"],
173
+ state="VERIFIED",
174
+ continuity=["RESET", "CONTIGUOUS"],
172
175
  limit=10,
173
176
  )
174
177
 
@@ -178,6 +181,9 @@ def test_orderbook_snapshots_accept_market_platform_id_arrays(client: Probalytic
178
181
  assert isinstance(snapshots.loc[0, "outcome"]["id"], str)
179
182
  assert isinstance(snapshots.loc[0, "bids"], list)
180
183
  assert isinstance(snapshots.loc[0, "bids"][0], dict)
184
+ assert set(snapshots["state"]) == {"VERIFIED"}
185
+ assert set(snapshots["continuity"]) == {"RESET", "CONTIGUOUS"}
186
+ assert set(snapshots.columns) >= {"indexed_at", "hash", "state", "continuity", "path_index"}
181
187
 
182
188
 
183
189
  def wait_for_clickhouse(host: str, port: int) -> DriverClient:
@@ -5,9 +5,17 @@ from uuid import UUID
5
5
 
6
6
  import pytest
7
7
 
8
- from probalytics import ClickHouseClient, Market, ProbalyticsClient
8
+ from probalytics import (
9
+ ClickHouseClient,
10
+ Market,
11
+ MarketType,
12
+ OrderbookSnapshot,
13
+ Platform,
14
+ ProbalyticsClient,
15
+ )
9
16
  from probalytics._frames import dataframe_to_frame, normalize_time
10
17
  from probalytics._filters import market_filter
18
+ from probalytics.clickhouse import _fill_row
11
19
 
12
20
 
13
21
  def market_payload() -> dict:
@@ -38,6 +46,9 @@ def market_payload() -> dict:
38
46
  "end_date": None,
39
47
  "reset_at": None,
40
48
  "resolution": None,
49
+ "source_block_number": 0,
50
+ "source_tx_hash": "",
51
+ "indexed_at": "2026-03-15T00:00:01Z",
41
52
  }
42
53
 
43
54
 
@@ -51,6 +62,45 @@ def test_market_is_first_class_filter() -> None:
51
62
  assert platform == "POLYMARKET"
52
63
 
53
64
 
65
+ def test_public_enums_only_include_current_values() -> None:
66
+ assert set(Platform) == {Platform.POLYMARKET, Platform.KALSHI}
67
+ assert MarketType.PERPETUAL == "PERPETUAL"
68
+
69
+ with pytest.raises(ValueError):
70
+ Platform("PREDICTIT")
71
+ with pytest.raises(ValueError):
72
+ MarketType("UNKNOWN")
73
+
74
+
75
+ def test_orderbook_snapshot_models_current_quality_fields() -> None:
76
+ snapshot = OrderbookSnapshot.model_validate(
77
+ {
78
+ "market_id": "550e8400-e29b-41d4-a716-446655440000",
79
+ "market_platform_id": "0xmarket",
80
+ "platform": "POLYMARKET",
81
+ "outcome": {
82
+ "id": "550e8400-e29b-41d4-a716-446655440001",
83
+ "platform_id": "yes",
84
+ "name": "Yes",
85
+ "index": 0,
86
+ },
87
+ "bids": [{"price": "0.44", "size": "10"}],
88
+ "asks": [{"price": "0.46", "size": "8"}],
89
+ "timestamp": "2026-03-15T00:00:00Z",
90
+ "indexed_at": "2026-03-15T00:00:00.001Z",
91
+ "hash": 101,
92
+ "state": "VERIFIED",
93
+ "continuity": "RESET",
94
+ "path_index": 0,
95
+ }
96
+ )
97
+
98
+ assert snapshot.state == "VERIFIED"
99
+ assert snapshot.continuity == "RESET"
100
+ assert snapshot.hash == 101
101
+ assert snapshot.path_index == 0
102
+
103
+
54
104
  def test_dataframe_to_polars_preserves_struct() -> None:
55
105
  df = dataframe_to_frame(
56
106
  {
@@ -150,6 +200,8 @@ def test_market_can_query_orderbook_snapshots_with_bound_client() -> None:
150
200
  snapshots = market.orderbook_snapshots(
151
201
  start_time="2026-03-15T00:00:00Z",
152
202
  end_time="2026-03-15T00:01:00Z",
203
+ state="VERIFIED",
204
+ continuity="RESET",
153
205
  frame="pandas",
154
206
  )
155
207
 
@@ -157,6 +209,8 @@ def test_market_can_query_orderbook_snapshots_with_bound_client() -> None:
157
209
  assert backend.orderbook_kwargs["market"] is market
158
210
  assert backend.orderbook_kwargs["start_time"] == "2026-03-15T00:00:00Z"
159
211
  assert backend.orderbook_kwargs["end_time"] == "2026-03-15T00:01:00Z"
212
+ assert backend.orderbook_kwargs["state"] == "VERIFIED"
213
+ assert backend.orderbook_kwargs["continuity"] == "RESET"
160
214
  assert backend.orderbook_kwargs["frame"] == "pandas"
161
215
 
162
216
 
@@ -330,6 +384,48 @@ def test_fills_keeps_scalar_filters_as_equals() -> None:
330
384
  assert client.client.params["platform"] == "POLYMARKET"
331
385
 
332
386
 
387
+ def test_fills_selects_provenance_and_metadata() -> None:
388
+ client = clickhouse_with_recorder()
389
+
390
+ client.fills(limit=10)
391
+
392
+ assert "source_block_number, source_tx_hash, source_log_index" in client.client.query
393
+ assert "timestamp, indexed_at, toJSONString(metadata) AS metadata" in client.client.query
394
+
395
+
396
+ def test_fill_row_decodes_json_metadata() -> None:
397
+ row = {
398
+ "outcome": {
399
+ "id": "550e8400-e29b-41d4-a716-446655440001",
400
+ "platform_id": "yes",
401
+ "name": "Yes",
402
+ "index": 0,
403
+ },
404
+ "metadata": '{"instrument":"BTC","liquidation":false}',
405
+ }
406
+
407
+ assert _fill_row(row)["metadata"] == {"instrument": "BTC", "liquidation": False}
408
+
409
+
410
+ def test_orderbook_snapshots_select_quality_fields_and_filters() -> None:
411
+ client = clickhouse_with_recorder()
412
+
413
+ client.orderbook_snapshots(
414
+ start_time="2026-03-15T00:00:00Z",
415
+ end_time="2026-03-15T00:01:00Z",
416
+ state=["VERIFIED", "INTERMEDIATE"],
417
+ continuity="RESET",
418
+ limit=10,
419
+ )
420
+
421
+ assert "indexed_at, hash, state, continuity, path_index" in client.client.query
422
+ assert "state IN %(state)s" in client.client.query
423
+ assert "continuity = %(continuity)s" in client.client.query
424
+ assert "ORDER BY timestamp ASC, path_index ASC, indexed_at ASC" in client.client.query
425
+ assert client.client.params["state"] == ("VERIFIED", "INTERMEDIATE")
426
+ assert client.client.params["continuity"] == "RESET"
427
+
428
+
333
429
  def test_clickhouse_rejects_invalid_limits() -> None:
334
430
  client = clickhouse_with_recorder()
335
431
 
File without changes
File without changes