convexlake 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.
@@ -0,0 +1,544 @@
1
+ Metadata-Version: 2.4
2
+ Name: convexlake
3
+ Version: 0.2.0
4
+ Summary: Python SDK for the Convex Lake market data API
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://convexlake.com
7
+ Project-URL: Repository, https://github.com/convexlake/convexlake-sdk
8
+ Project-URL: Documentation, https://github.com/convexlake/convexlake-sdk#readme
9
+ Keywords: polymarket,kalshi,deribit,market-data,prediction-markets,options
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: requests>=2.31
22
+ Requires-Dist: pandas>=2.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: responses; extra == "dev"
26
+
27
+ # convexlake
28
+
29
+ Python SDK for the [Convex Lake](https://convexlake.com) market data API.
30
+
31
+ Convex Lake provides historical and live orderbook, trade, candle, volatility surface, and leaderboard data from prediction markets (Polymarket, Kalshi, Limitless, Predict.fun) and derivatives exchanges (Deribit, Hyperliquid).
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install convexlake
39
+ ```
40
+
41
+ Or from source:
42
+
43
+ ```bash
44
+ git clone https://github.com/convexlake/convexlake-sdk.git
45
+ cd convexlake-sdk
46
+ pip install -e .
47
+ ```
48
+
49
+ **Requirements:** Python 3.10+, `requests>=2.31`, `pandas>=2.0`
50
+
51
+ ---
52
+
53
+ ## Authentication
54
+
55
+ All requests require an API key passed at construction time. Keys start with `do_`.
56
+
57
+ ```python
58
+ from convexlake import ConvexLake
59
+
60
+ client = ConvexLake(api_key="do_XXXX...")
61
+ ```
62
+
63
+ The key is sent as the `x-api-key` request header on every call.
64
+
65
+ ---
66
+
67
+ ## Quick start
68
+
69
+ ```python
70
+ from convexlake import ConvexLake
71
+
72
+ client = ConvexLake(api_key="do_XXXX...")
73
+
74
+ # 1. Discover what's available
75
+ print(client.info()) # list exchanges
76
+ print(client.info(exchange="kalshi")) # list tickers
77
+ print(client.info(exchange="kalshi", ticker="btc")) # list dates
78
+ print(client.info(exchange="kalshi", ticker="btc", date="2026-07-30")) # list files
79
+
80
+ # 2. Download data
81
+ df = client.candles(exchange="kalshi", ticker="btc", date="2026-07-30",
82
+ slug="KXBTC-2601-prices.csv")
83
+
84
+ df = client.trades(exchange="deribit", ticker="BTC", date="2026-08-07",
85
+ slug="BTC-28AUG26-80000-C-trades.csv")
86
+
87
+ msgs = client.orderbook(exchange="polymarket", ticker="btc", timeframe="15m",
88
+ date="2026-04-23", slug="btc-updown-15m-1776943800-bbo.gz")
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Navigation: `info()`
94
+
95
+ Use `info()` to browse the data catalog. Call it progressively to drill down to individual files, then pass the `slug` you find to a data method.
96
+
97
+ ```python
98
+ # Top level — list exchanges and top-level folders
99
+ client.info()
100
+ # {"tier": "pro", "allowed_exchanges": ["kalshi", "polymarket", ...],
101
+ # "folders": ["candles/", "orderbook/", "trades/", "surface/"]}
102
+
103
+ # List tickers available for an exchange
104
+ client.info(exchange="polymarket")
105
+
106
+ # List dates that have data for a ticker
107
+ client.info(exchange="polymarket", ticker="btc")
108
+
109
+ # List individual files for a date
110
+ client.info(exchange="polymarket", ticker="btc", date="2026-07-30")
111
+ # {"files": ["btc-updown-5m-1785343200-bbo.gz", "btc-updown-5m-1785343200-book.gz", ...]}
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Methods
117
+
118
+ ### `candles()`
119
+
120
+ Download OHLCV or price data as a `pd.DataFrame`.
121
+
122
+ ```python
123
+ df = client.candles(
124
+ exchange="kalshi",
125
+ ticker="btc",
126
+ date="2026-07-30",
127
+ slug="KXBTC-2601-prices.csv",
128
+ )
129
+ ```
130
+
131
+ **Column schemas by exchange:**
132
+
133
+ | Exchange | Columns |
134
+ |---|---|
135
+ | `kalshi` | `timestamp`, `yes_bid`, `yes_ask`, `price`, `volume` |
136
+ | `polymarket` | `timestamp`, `Yes`, `No` |
137
+ | `predict` | `slug`, `starts_at`, `ends_at`, `market_variant`, `title`, `winning_outcome`, `open`, `close`, `feed_symbol`, `outcomes` |
138
+ | `deribit` | `timestamp_ms`, `open`, `high`, `low`, `close`, `volume`, `cost` |
139
+ | `hyperliquid` | `timestamp_ms`, `open`, `high`, `low`, `close`, `volume`, `cost` |
140
+
141
+ **Deribit and Hyperliquid** use rolling per-instrument files (not partitioned by date). Pass `timeframe` and `slug` only — `ticker` and `date` are not used:
142
+
143
+ ```python
144
+ df = client.candles(exchange="deribit", timeframe="15m", slug="BTC-PERPETUAL.csv")
145
+ df = client.candles(exchange="deribit", timeframe="1m", slug="ETH-PERPETUAL.csv")
146
+ ```
147
+
148
+ Available timeframes: `1m`, `5m`, `15m`, `30m`, `1h`
149
+
150
+ ---
151
+
152
+ ### `trades()`
153
+
154
+ Download trade history as a `pd.DataFrame`.
155
+
156
+ ```python
157
+ df = client.trades(
158
+ exchange="deribit",
159
+ ticker="BTC",
160
+ date="2026-08-07",
161
+ slug="BTC-28AUG26-80000-C-trades.csv",
162
+ )
163
+ ```
164
+
165
+ **Column schemas by exchange:**
166
+
167
+ | Exchange | Columns |
168
+ |---|---|
169
+ | `kalshi` | `created_time`, `yes_price`, `no_price`, `count`, `taker_side`, `taker_outcome_side`, `is_block_trade` |
170
+ | `polymarket` | `timestamp`, `price`, `size`, `side`, `outcome`, `transaction_hash` |
171
+ | `deribit` | `timestamp_ms`, `trade_id`, `trade_seq`, `instrument_name`, `direction`, `amount`, `price`, `mark_price`, `index_price`, `iv`, `liquidation` |
172
+
173
+ ---
174
+
175
+ ### `orderbook()`
176
+
177
+ Download orderbook messages as a `list[dict]`. Each dict is one message or snapshot event.
178
+
179
+ ```python
180
+ msgs = client.orderbook(
181
+ exchange="polymarket",
182
+ ticker="btc",
183
+ timeframe="15m",
184
+ date="2026-04-23",
185
+ slug="btc-updown-15m-1776943800-bbo.gz",
186
+ )
187
+
188
+ # msgs[0] → {"event_type": "best_bid_ask", "best_bid": "0.52", "best_ask": "0.54",
189
+ # "timestamp": 1745416812000, "direction": "up", ...}
190
+ ```
191
+
192
+ **Message formats by exchange:**
193
+
194
+ | Exchange | Format | Message types |
195
+ |---|---|---|
196
+ | `kalshi` | Concatenated Python dict literals | `orderbook_snapshot`, `orderbook_delta` |
197
+ | `polymarket` | Newline-delimited Python dict literals | `best_bid_ask`, `book`, `last_trade_price` |
198
+ | `predict` | Newline-delimited Python dict literals | Full book snapshots with `bids`/`asks` arrays |
199
+ | `limitless` | Newline-delimited JSON | Full book snapshots |
200
+
201
+ Format is auto-detected by default. You can override with `fmt="json"` or `fmt="python"`.
202
+
203
+ > **Deribit** orderbooks are CSV, not message-based. Use `orderbook_df()` instead — calling `orderbook()` with `exchange="deribit"` raises a helpful error.
204
+
205
+ ---
206
+
207
+ ### `orderbook_df()`
208
+
209
+ Download orderbook data as a `pd.DataFrame`. Use this for Deribit, which stores orderbook snapshots in CSV format.
210
+
211
+ ```python
212
+ df = client.orderbook_df(
213
+ exchange="deribit",
214
+ ticker="BTC",
215
+ date="2026-08-07",
216
+ slug="BTC-28AUG26-80000-C-book.csv",
217
+ )
218
+ ```
219
+
220
+ ---
221
+
222
+ ### `surface()`
223
+
224
+ Download a volatility surface snapshot as a `pd.DataFrame`. Currently supported for Deribit.
225
+
226
+ ```python
227
+ df = client.surface(
228
+ exchange="deribit",
229
+ date="2026-09-14",
230
+ slug="BTC-2026-09-14",
231
+ )
232
+ ```
233
+
234
+ `slug` format: `"{CURRENCY}-{YYYY-MM-DD}"` — e.g. `"BTC-2026-09-14"`, `"ETH-2026-09-14"`.
235
+
236
+ **Columns:** `snapshot_ts`, `instrument_name`, `expiry`, `strike`, `option_type`, `underlying_price`, `mark_price`, `mark_iv`, `bid_price`, `ask_price`, `mid_price`, `volume`, `volume_usd`, `open_interest`, `estimated_delivery_price`
237
+
238
+ ---
239
+
240
+ ### `leaderboard()`
241
+
242
+ Download a daily leaderboard snapshot as a `pd.DataFrame`.
243
+
244
+ ```python
245
+ df = client.leaderboard(exchange="polymarket", date="2026-09-14")
246
+ df = client.leaderboard(exchange="limitless", date="2026-09-14")
247
+ ```
248
+
249
+ **Column schemas by exchange:**
250
+
251
+ | Exchange | Columns |
252
+ |---|---|
253
+ | `polymarket` | `fetched_at`, `time_period`, `order_by`, `category`, `rank`, `proxyWallet`, `userName`, `xUsername`, `verifiedBadge`, `vol`, `pnl` |
254
+ | `limitless` | `fetched_at`, `metric`, `rank`, `account`, `username`, `displayName`, `positionSize_usd`, `unrealizedPnl_usd`, `heldShares`, `toWin_usd`, `mark_price`, `mark_source`, `side`, `outcomeLabel`, `market_id`, `market_title`, `market_category` |
255
+
256
+ ---
257
+
258
+ ### `leaderboard_trades()`
259
+
260
+ Two-step access to per-trader fill history.
261
+
262
+ **Step 1** — list available trader addresses:
263
+
264
+ ```python
265
+ result = client.leaderboard_trades(exchange="polymarket")
266
+ # {"addresses": ["0xabc...", "0xdef...", ...]} (up to 1000 addresses)
267
+ addresses = result["addresses"]
268
+ ```
269
+
270
+ **Step 2** — fetch one trader's fills:
271
+
272
+ ```python
273
+ df = client.leaderboard_trades(exchange="polymarket", address="0xabc...")
274
+ ```
275
+
276
+ **Columns (Polymarket):** `proxyWallet`, `side`, `asset`, `conditionId`, `size`, `price`, `timestamp`, `title`, `slug`, `outcome`, `outcomeIndex`, `name`, `transactionHash`
277
+
278
+ Supported exchanges: `polymarket`, `limitless`
279
+
280
+ ---
281
+
282
+ ## NautilusTrader integration
283
+
284
+ The SDK ships a complete conversion pipeline for [NautilusTrader](https://nautilustrader.io)
285
+ backtesting. Data flows through three stages:
286
+
287
+ ```
288
+ orderbook() → list[dict] raw messages from S3
289
+ to_nautilus() → pd.DataFrame NautilusTrader schema, int64 fixed-point encoding
290
+ to_orderbook_deltas() → list typed OrderBookDelta Cython objects for BacktestEngine
291
+ ```
292
+
293
+ ### All-in-one: `download_orderbook_deltas()`
294
+
295
+ The simplest path — one call streams the file, converts every message, and returns
296
+ `list[OrderBookDelta]` ready for `BacktestEngine.add_data()`:
297
+
298
+ ```python
299
+ from convexlake import ConvexLake
300
+
301
+ client = ConvexLake(api_key="do_XXXX...")
302
+
303
+ deltas = client.download_orderbook_deltas(
304
+ exchange="binance",
305
+ instrument_id="BTCPERP.BINANCE",
306
+ slug="BTCPERP-book",
307
+ ticker="BTCPERP",
308
+ date="2026-11-04",
309
+ price_precision=1,
310
+ size_precision=3,
311
+ )
312
+
313
+ engine.add_data(deltas)
314
+ engine.run(start=START, end=END)
315
+ ```
316
+
317
+ Internally this runs `orderbook()` → `to_nautilus()` → `to_orderbook_deltas()`.
318
+
319
+ ---
320
+
321
+ ### Efficient path: filter at the DataFrame stage
322
+
323
+ For large files (Binance full-day L2 book can produce 30 M+ delta objects), converting
324
+ everything before filtering is expensive. Stay in DataFrame land as long as possible —
325
+ pandas vectorized operations on int64 columns take milliseconds regardless of row count:
326
+
327
+ ```python
328
+ from convexlake import ConvexLake
329
+ from datetime import datetime, timezone
330
+
331
+ WIN_START_NS = int(datetime(2026, 11, 4, 10, 0, tzinfo=timezone.utc).timestamp() * 1e9)
332
+ WIN_END_NS = int(datetime(2026, 11, 4, 14, 0, tzinfo=timezone.utc).timestamp() * 1e9)
333
+
334
+ # Step 1 — download as DataFrame (raw messages → int64 schema)
335
+ df = client.download_nautilus(
336
+ exchange="binance",
337
+ instrument_id="BTCPERP.BINANCE",
338
+ slug="BTCPERP-book",
339
+ ticker="BTCPERP",
340
+ date="2026-11-04",
341
+ price_precision=1,
342
+ size_precision=3,
343
+ )
344
+
345
+ # Step 2 — filter at the DataFrame stage (vectorized, instant)
346
+ df = df[df["ts_event"].between(WIN_START_NS, WIN_END_NS)]
347
+
348
+ # Step 3 — convert only the rows you need
349
+ deltas = ConvexLake.to_orderbook_deltas(
350
+ df,
351
+ instrument_id="BTCPERP.BINANCE",
352
+ price_precision=1,
353
+ size_precision=3,
354
+ )
355
+
356
+ engine.add_data(deltas)
357
+ ```
358
+
359
+ This produces the same result as `download_orderbook_deltas()` followed by a list
360
+ comprehension filter, but avoids creating the Cython objects you will immediately discard.
361
+
362
+ ---
363
+
364
+ ### DataFrame schema
365
+
366
+ `to_nautilus()` and `download_nautilus()` return a DataFrame with these columns:
367
+
368
+ | Column | dtype | Description |
369
+ |---|---|---|
370
+ | `instrument_id` | `object` | NautilusTrader instrument identifier |
371
+ | `action` | `uint8` | 1=ADD, 2=UPDATE, 3=DELETE, 4=CLEAR |
372
+ | `side` | `uint8` | 0=NONE, 1=BUY, 2=SELL |
373
+ | `price` | `int64` | price × 10^price_precision |
374
+ | `size` | `uint64` | size × 10^size_precision |
375
+ | `order_id` | `uint64` | always 0 (not tracked) |
376
+ | `flags` | `uint8` | 1 = F_LAST (last delta in a snapshot sequence) |
377
+ | `sequence` | `uint64` | monotonically increasing row counter |
378
+ | `ts_event` | `uint64` | event timestamp, nanoseconds since epoch |
379
+ | `ts_init` | `uint64` | same as ts_event |
380
+
381
+ ---
382
+
383
+ ### Persistent storage: `ParquetDataCatalog`
384
+
385
+ For repeated backtests on the same data, write once to a NautilusTrader catalog instead
386
+ of re-downloading or loading large pickle files:
387
+
388
+ ```python
389
+ from nautilus_trader.persistence.catalog import ParquetDataCatalog
390
+
391
+ catalog = ParquetDataCatalog("./nautilus_catalog")
392
+ catalog.write_data(deltas) # Arrow columnar — smaller and faster to reload than pickle
393
+ ```
394
+
395
+ Read back for a later backtest:
396
+
397
+ ```python
398
+ from nautilus_trader.backtest.config import BacktestDataConfig
399
+ from nautilus_trader.model.data import OrderBookDelta
400
+
401
+ data_config = BacktestDataConfig(
402
+ catalog_path="./nautilus_catalog",
403
+ data_cls=OrderBookDelta,
404
+ instrument_id=InstrumentId.from_str("BTCPERP.BINANCE"),
405
+ start_time=START,
406
+ end_time=END,
407
+ )
408
+ ```
409
+
410
+ ---
411
+
412
+ ### L1/BBO: `download_quote_ticks()`
413
+
414
+ For BBO files or when `BookType.L1_MBP` is sufficient, use `to_quote_ticks()` /
415
+ `download_quote_ticks()`. This returns a lightweight DataFrame (`ts_ns`, `bid`, `ask`)
416
+ that converts to `QuoteTick` objects — one object per BBO update instead of a CLEAR + two
417
+ ADDs per snapshot:
418
+
419
+ ```python
420
+ df = client.download_quote_ticks(
421
+ exchange="hyperliquid",
422
+ slug="BTCPERP-bbo",
423
+ ticker="BTCPERP",
424
+ date="2026-11-04",
425
+ price_precision=1,
426
+ )
427
+
428
+ from nautilus_trader.model.data import QuoteTick
429
+ from nautilus_trader.model.objects import Price, Quantity
430
+
431
+ ticks = [
432
+ QuoteTick(
433
+ instrument_id=iid,
434
+ bid_price=Price(row.bid, 1),
435
+ ask_price=Price(row.ask, 1),
436
+ bid_size=Quantity(1, 3),
437
+ ask_size=Quantity(1, 3),
438
+ ts_event=int(row.ts_ns),
439
+ ts_init=int(row.ts_ns),
440
+ )
441
+ for row in df.itertuples()
442
+ ]
443
+ engine.add_data(ticks)
444
+ ```
445
+
446
+ ---
447
+
448
+ ### Choosing between `OrderBookDelta` and `QuoteTick`
449
+
450
+ | | `OrderBookDelta` (L2_MBP) | `QuoteTick` (L1_MBP) |
451
+ |---|---|---|
452
+ | Data depth | Full depth, all levels | Top of book only |
453
+ | Fill simulation | Passive limit fills at quoted price | Passive limit fills when market crosses |
454
+ | Objects per snapshot | CLEAR + N×ADD (N levels) | 1 |
455
+ | Use for | Market-making, depth-sensitive strategies | Directional, simple spread strategies |
456
+ | SDK method | `download_orderbook_deltas()` | `download_quote_ticks()` |
457
+
458
+ ---
459
+
460
+ ## Error handling
461
+
462
+ All exceptions inherit from `ConvexLakeError`.
463
+
464
+ ```python
465
+ from convexlake import (
466
+ ConvexLakeError,
467
+ AuthError, # 401 — invalid or missing API key
468
+ PlanError, # 403 — endpoint requires a higher subscription tier
469
+ NotFoundError, # 404 — no data for the requested parameters
470
+ RateLimitError, # 429 — rate limit exceeded after all retries
471
+ )
472
+
473
+ try:
474
+ df = client.candles(exchange="kalshi", ticker="btc", date="2026-07-30",
475
+ slug="KXBTC-2601-prices.csv")
476
+ except NotFoundError:
477
+ print("No data for that date/slug")
478
+ except PlanError:
479
+ print("Upgrade your plan to access this endpoint")
480
+ except AuthError:
481
+ print("Check your API key")
482
+ except RateLimitError:
483
+ print("Slow down — rate limit hit")
484
+ except ConvexLakeError as e:
485
+ print(f"API error: {e}")
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Configuration
491
+
492
+ ```python
493
+ client = ConvexLake(
494
+ api_key="do_XXXX...",
495
+ timeout=60, # request timeout in seconds (default: 60)
496
+ max_retries=3, # number of retries on 429 rate-limit responses (default: 3)
497
+ )
498
+ ```
499
+
500
+ On a `429` response the client waits for the `Retry-After` header value (defaulting to 2 s) and retries with exponential backoff. If all retries are exhausted, `RateLimitError` is raised.
501
+
502
+ ---
503
+
504
+ ## Supported exchanges
505
+
506
+ | Exchange | candles | trades | orderbook | surface | leaderboard |
507
+ |---|:---:|:---:|:---:|:---:|:---:|
508
+ | `kalshi` | ✓ | ✓ | ✓ | — | — |
509
+ | `polymarket` | ✓ | ✓ | ✓ | — | ✓ |
510
+ | `deribit` | ✓ | ✓ | ✓ | ✓ | — |
511
+ | `predict` | ✓ | — | ✓ | — | — |
512
+ | `limitless` | — | — | ✓ | — | ✓ |
513
+ | `hyperliquid` | ✓ | ✓ | ✓ | — | — |
514
+ | `binance` | — | — | ✓ | — | — |
515
+ | `okx` | — | — | ✓ | — | — |
516
+ | `bitfinex` | — | — | ✓ | — | — |
517
+
518
+ > **Deribit options** orderbooks are CSV format — use `orderbook_df()` instead of `orderbook()`.
519
+ > **Bybit** stores perpetual options as individual CSV files; there is no single perp orderbook file.
520
+
521
+ ---
522
+
523
+ ## Development
524
+
525
+ ```bash
526
+ git clone https://github.com/convexlake/convexlake-sdk.git
527
+ cd convexlake-sdk
528
+ pip install -e ".[dev]"
529
+ ```
530
+
531
+ **Run unit tests** (no API key required — HTTP layer is mocked):
532
+
533
+ ```bash
534
+ pytest tests/ -v -k "not (TestInfo or TestCandles or TestTrades or TestOrderbook or TestSurface or TestLeaderboard or TestDeribit)"
535
+ ```
536
+
537
+ **Run integration tests** (requires a real API key in the environment):
538
+
539
+ ```bash
540
+ export CONVEXLAKE_API_KEY="do_XXXX..."
541
+ pytest tests/ -v
542
+ ```
543
+
544
+ The test suite covers decompression logic, orderbook format auto-detection, HTTP error mapping, retry behavior, and live API responses for each exchange and data type.