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.
- {probalytics-0.1.0 → probalytics-0.2.0}/PKG-INFO +11 -3
- {probalytics-0.1.0 → probalytics-0.2.0}/README.md +9 -1
- {probalytics-0.1.0 → probalytics-0.2.0}/pyproject.toml +1 -1
- {probalytics-0.1.0 → probalytics-0.2.0}/src/probalytics/__init__.py +4 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/src/probalytics/clickhouse.py +31 -5
- {probalytics-0.1.0 → probalytics-0.2.0}/src/probalytics/client.py +4 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/src/probalytics/models.py +30 -4
- {probalytics-0.1.0 → probalytics-0.2.0}/tests/sample_clickhouse_data.py +27 -10
- {probalytics-0.1.0 → probalytics-0.2.0}/tests/test_clickhouse_integration.py +6 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/tests/test_models.py +97 -1
- {probalytics-0.1.0 → probalytics-0.2.0}/.gitignore +0 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/LICENSE +0 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/src/probalytics/_filters.py +0 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/src/probalytics/_frames.py +0 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/src/probalytics/py.typed +0 -0
- {probalytics-0.1.0 → probalytics-0.2.0}/tests/conftest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: probalytics
|
|
3
|
-
Version: 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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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,
|
|
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,
|
|
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
|
|
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
|
|
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("
|
|
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("
|
|
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'
|
|
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('
|
|
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'
|
|
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(
|
|
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'
|
|
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(
|
|
307
|
-
indexed_at DateTime64(
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|