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.
- convexlake-0.2.0/PKG-INFO +544 -0
- convexlake-0.2.0/README.md +518 -0
- convexlake-0.2.0/convexlake/__init__.py +19 -0
- convexlake-0.2.0/convexlake/client.py +1646 -0
- convexlake-0.2.0/convexlake/exceptions.py +21 -0
- convexlake-0.2.0/convexlake.egg-info/PKG-INFO +544 -0
- convexlake-0.2.0/convexlake.egg-info/SOURCES.txt +11 -0
- convexlake-0.2.0/convexlake.egg-info/dependency_links.txt +1 -0
- convexlake-0.2.0/convexlake.egg-info/requires.txt +6 -0
- convexlake-0.2.0/convexlake.egg-info/top_level.txt +1 -0
- convexlake-0.2.0/pyproject.toml +39 -0
- convexlake-0.2.0/setup.cfg +4 -0
- convexlake-0.2.0/tests/test_client.py +1074 -0
|
@@ -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.
|