veloxyz-scraper 0.1.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,28 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something isn't working as expected
4
+ labels: bug
5
+ ---
6
+
7
+ **What happened**
8
+ A clear description of the bug.
9
+
10
+ **To reproduce**
11
+ The exact call or CLI command, e.g.:
12
+
13
+ ```python
14
+ scraper.get_metric("BTCUSDT", "aggregated_open_interest", resolution=60, last=100)
15
+ ```
16
+
17
+ **Expected vs actual**
18
+ What you expected, and what you got (paste the error / traceback if any).
19
+
20
+ **Environment**
21
+ - veloxyz-scraper version:
22
+ - Python version:
23
+ - OS:
24
+
25
+ **Note**
26
+ Because this reads velo.xyz's undocumented chart endpoints, some issues are caused by an
27
+ upstream change or rate-limit rather than the library. If you can, include the failing
28
+ `/api/m/range?...` URL.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a metric or capability
4
+ labels: enhancement
5
+ ---
6
+
7
+ **What would you like?**
8
+ A clear description of the metric or feature.
9
+
10
+ **New metric?**
11
+ If you're requesting a metric, capture its request from a velo chart (browser dev tools →
12
+ Network → `/api/m/range?...`) and paste the full URL — that's what lets it be added and
13
+ verified.
14
+
15
+ **Why**
16
+ What you'd use it for.
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+ env:
16
+ UV_PYTHON: ${{ matrix.python-version }}
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: astral-sh/setup-uv@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ enable-cache: true
23
+ - run: uv sync
24
+ - run: uv run ruff check
25
+ - run: uv run ruff format --check
26
+ - run: uvx basedpyright src/
27
+ - run: uv run pytest
28
+ - run: uv build
29
+
30
+ # Verify the declared dependency floors (pandas>=2.2, etc.) actually work.
31
+ lowest-deps:
32
+ runs-on: ubuntu-latest
33
+ env:
34
+ UV_PYTHON: "3.11"
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v5
38
+ with:
39
+ python-version: "3.11"
40
+ - run: uv sync --resolution lowest-direct
41
+ - run: uv run pytest
@@ -0,0 +1,17 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Generated data / checkpoints
13
+ *.csv
14
+ *.meta.json
15
+ *.tmp.*
16
+
17
+ HANDOVER.md
@@ -0,0 +1,14 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.9.6
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: https://github.com/pre-commit/pre-commit-hooks
9
+ rev: v5.0.0
10
+ hooks:
11
+ - id: end-of-file-fixer
12
+ - id: trailing-whitespace
13
+ - id: check-toml
14
+ - id: check-yaml
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-07-09
9
+
10
+ Initial release.
11
+
12
+ ### Added
13
+
14
+ - `VeloScraper` client for velo.xyz free chart endpoints.
15
+ - Metrics: `price`, `aggregated_funding_rate`, `aggregated_premium`,
16
+ `aggregated_open_interest`, `aggregated_spot_volume_delta`, `aggregated_volume_delta`,
17
+ `aggregated_liquidations`, and the computed `coinbase_premium`.
18
+ - Chunked fetching (450-candle server cap), retry with exponential backoff and `Retry-After`
19
+ handling on 429/5xx, coins/dollars unit toggle, and `last` N-candle ranges.
20
+ - Checkpoint/resume as a bidirectional cache: a resume backfills a missing head range as well
21
+ as extending the tail, and the returned frame is always exactly the requested window.
22
+ - Checkpoint writes are atomic (temp file + `os.replace`); a sidecar `.meta.json` refuses a
23
+ checkpoint written for a different metric / resolution / exchange.
24
+ - `api_base` override on `VeloScraper` (proxies / tests); the HTTP session is per-thread so
25
+ one client is safe across threads.
26
+ - `METRICS` and `resolve_metric` are exported; a custom `MetricSpec` is the documented
27
+ extension path.
28
+ - `veloscraper` CLI (`fetch` command) and discovery helpers
29
+ (`list_coins`, `list_products`).
30
+
31
+ ### Notes
32
+
33
+ - Naive dates/datetimes are consistently interpreted as UTC.
34
+ - `resolution <= 0` is rejected; the `datetime` column stays `datetime64` after a resume;
35
+ `coinbase_premium` rejects unsupported args; `premium_pct` guards against a zero price; and
36
+ an unexpected non-`arr` response surfaces a warning instead of an empty frame.
@@ -0,0 +1,45 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in improving veloxyz-scraper.
4
+
5
+ ## Development setup
6
+
7
+ The project uses [uv](https://docs.astral.sh/uv/).
8
+
9
+ ```bash
10
+ uv sync # create the venv and install runtime + dev dependencies
11
+ ```
12
+
13
+ ## Checks
14
+
15
+ All of these must pass (CI runs them on every push/PR):
16
+
17
+ ```bash
18
+ uv run pytest # tests — the network is mocked, so no internet is needed
19
+ uv run ruff check # lint
20
+ uv run ruff format --check
21
+ uvx basedpyright src/ # type check
22
+ uv build # sdist + wheel build
23
+ ```
24
+
25
+ Please add or update tests for any behavior change, and keep tests network-free
26
+ (`requests-mock` is available; a live call in the suite is a bug).
27
+
28
+ ## Adding a metric
29
+
30
+ velo exposes metrics via a `#`-joined `symbol` payload. Capture the exact request from a
31
+ velo chart (browser dev tools → Network → `/api/m/range?...`), then either:
32
+
33
+ - pass a `MetricSpec(name, tail)` to `get_metric` (no code change needed), or
34
+ - to contribute it upstream, add a **verified** `MetricSpec` to the `METRICS` dict in
35
+ `src/veloxyz_scraper/metrics.py` with a test.
36
+
37
+ ## Commit / PR
38
+
39
+ - Keep changes focused; update `CHANGELOG.md` under `## [Unreleased]`.
40
+ - Describe what you verified (which check passed, any live check you ran).
41
+
42
+ ## Note
43
+
44
+ This is an unofficial client for velo.xyz's free chart endpoints (undocumented, may change).
45
+ Please keep it key-free and respect Velo's terms of service.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 0xAtasoy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,398 @@
1
+ Metadata-Version: 2.4
2
+ Name: veloxyz-scraper
3
+ Version: 0.1.0
4
+ Summary: velo.xyz market-data scraper (price, funding, open interest, premium, spot CVD)
5
+ Project-URL: Homepage, https://github.com/0xAtasoy/veloxyz-scraper
6
+ Project-URL: Repository, https://github.com/0xAtasoy/veloxyz-scraper
7
+ Project-URL: Issues, https://github.com/0xAtasoy/veloxyz-scraper/issues
8
+ Project-URL: Changelog, https://github.com/0xAtasoy/veloxyz-scraper/blob/main/CHANGELOG.md
9
+ Author: 0xAtasoy
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: crypto,funding-rate,market-data,open-interest,scraper,velo
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Office/Business :: Financial :: Investment
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: pandas>=2.2
25
+ Requires-Dist: requests>=2.32
26
+ Requires-Dist: tenacity>=9.0.0
27
+ Requires-Dist: tqdm>=4.66.0
28
+ Requires-Dist: typer>=0.15.0
29
+ Description-Content-Type: text/markdown
30
+
31
+ # veloxyz-scraper
32
+
33
+ A **cross-exchange aggregate time-series** scraper for [velo.xyz](https://velo.xyz) market
34
+ data (aggregated funding rate, aggregated premium, aggregated open interest, and more). It
35
+ automatically splits long ranges into chunks, retries on transient errors, and resumes
36
+ interrupted fetches where they stopped.
37
+
38
+ > **Unofficial.** Not affiliated with Velo. It reads velo.xyz's free, undocumented chart
39
+ > endpoints (the ones the [charts](https://velo.xyz/chart) use) — these can change or
40
+ > rate-limit without notice. Please respect Velo's terms of service. Velo's official,
41
+ > supported, authenticated API is documented at [docs.velo.xyz](https://docs.velo.xyz/) and
42
+ > wrapped by the `velodata` package — a **different** API from the one used here.
43
+
44
+ ## Features
45
+
46
+ - **Robust fetching:** shared `requests.Session`, timeout, and retry with exponential
47
+ backoff (429 / 5xx / timeout / connection error). Long ranges are split into chunks of
48
+ 450 candles (the endpoint's hard per-request cap).
49
+ - **Discovery:** `list_coins()` and `list_products()` to see what velo supports.
50
+ - **Thread-safe:** one `VeloScraper` can be shared across threads (per-thread HTTP session).
51
+ - **Checkpoint + resume:** each chunk is written to disk; if a fetch is interrupted it
52
+ continues from where it stopped.
53
+ - **Generic metric support:** `price`, `aggregated_funding_rate`, `aggregated_premium`,
54
+ `aggregated_open_interest`, `aggregated_spot_volume_delta`, `aggregated_volume_delta`,
55
+ `aggregated_liquidations`, and the computed `coinbase_premium` (scalar / OHLC / OHLCV
56
+ columns detected automatically); extensible via a custom `MetricSpec`.
57
+ - **Coins or dollars:** `unit="dollars"` for `open_interest` / `*_volume_delta` /
58
+ `liquidations`.
59
+ - **Flexible resolution:** minutes (`1, 5, 15, 60, 240 ...`) and daily/weekly (`1D`, `1W`).
60
+ - **CLI + library:** both a `veloscraper` command and `import veloxyz_scraper`.
61
+ - Returns a `pandas.DataFrame`, with an optional `tqdm` progress bar.
62
+
63
+ ## Installation
64
+
65
+ With [uv](https://docs.astral.sh/uv/):
66
+
67
+ ```bash
68
+ uv sync # development environment (includes dev dependencies)
69
+ # or as a dependency in a project:
70
+ uv add veloxyz-scraper
71
+ ```
72
+
73
+ With pip:
74
+
75
+ ```bash
76
+ pip install .
77
+ ```
78
+
79
+ ## Library usage
80
+
81
+ ```python
82
+ from veloxyz_scraper import VeloScraper
83
+
84
+ scraper = VeloScraper()
85
+
86
+ # OI-weighted, cross-exchange aggregate funding rate (hourly):
87
+ df = scraper.get_metric(
88
+ "BTCUSDT",
89
+ "aggregated_funding_rate",
90
+ resolution=60, # minutes; 60 = hourly
91
+ begin="2024-01-01",
92
+ end="2024-02-01",
93
+ )
94
+ print(df.head())
95
+ # datetime timestamp aggregated_funding_rate
96
+ # 0 2024-01-01 00:00:00 1704067200 0.000080
97
+
98
+ # Generic API — any metric:
99
+ oi = scraper.get_metric("ETHUSDT", "aggregated_open_interest", resolution=240,
100
+ begin="2024-06-01", end="2024-06-10")
101
+ # returns OHLC: datetime, timestamp, open, high, low, close
102
+
103
+ # Raw price candles (daily) — the exchange param picks which exchange's candles:
104
+ px = scraper.get_metric("BTCUSDT", "price", resolution="1D",
105
+ begin="2024-01-01", end="2024-06-01", exchange="binance-futures")
106
+ # returns price OHLCV: datetime, timestamp, open, high, low, close, volume
107
+ ```
108
+
109
+ `begin`/`end` accept a date `str` (`"2024-01-01"`), an epoch `int` (seconds or ms), or a
110
+ `datetime`. If omitted, `end=now` and `begin=end-30 days`. **Naive dates/datetimes are
111
+ interpreted as UTC** (so the same input gives the same result on any machine).
112
+
113
+ **Last N candles** — use `last` to compute `begin` automatically
114
+ (`begin = end - last*resolution`):
115
+
116
+ ```python
117
+ # The last 1000 hourly candles:
118
+ df = scraper.get_metric("BTCUSDT", "aggregated_funding_rate", resolution=60, last=1000)
119
+
120
+ # Scales with resolution: the last 500 four-hour candles
121
+ oi = scraper.get_metric("BTCUSDT", "aggregated_open_interest", resolution=240, last=500)
122
+ ```
123
+
124
+ `last` is a **candle count** (int) and works together with `resolution`; it cannot be
125
+ combined with `begin`. velo data starts at **2021-01-01 00:00:00 UTC**; if `begin` (explicit,
126
+ derived from `last`, or the default) is earlier than that, it is clamped there automatically
127
+ (`DATA_START_MS`).
128
+
129
+ `resolution`: minutes as an `int` (`1, 5, 10, 15, 30, 60, 120, 240, 360, 720`) or `"1D"` /
130
+ `"1W"` for daily/weekly. `begin`/`end` are aligned to the resolution grid (begin floored,
131
+ end ceiled) so the bars containing them are included.
132
+
133
+ ### Discovery
134
+
135
+ Look up what velo supports without hardcoding symbols:
136
+
137
+ ```python
138
+ "BTC" in scraper.list_coins() # coins available for aggregation (list[str])
139
+
140
+ products = scraper.list_products() # futures products per exchange (DataFrame)
141
+ products[products["coin"] == "BTC"] # every BTC perp: exchange, product, coin, min
142
+ ```
143
+
144
+ ### Checkpoint / resume
145
+
146
+ ```python
147
+ df = scraper.get_metric(
148
+ "BTCUSDT", "aggregated_funding_rate", 60, "2021-01-01", "2026-01-01",
149
+ checkpoint="btc_funding.csv", # each chunk is written here
150
+ )
151
+ ```
152
+
153
+ If the fetch is interrupted, calling it again with the same `checkpoint` path resumes from
154
+ where it stopped. The last bar of the previous run is **re-fetched and overwritten**, so a
155
+ still-forming / partial bar gets finalized on the next run; only the current (still-open) bar
156
+ is ever provisional.
157
+
158
+ ## CLI
159
+
160
+ Every `fetch` command requires `--out` and `--metric`.
161
+
162
+ ```bash
163
+ veloscraper fetch --metric <METRIC> --out <FILE.csv>
164
+ ```
165
+
166
+ `<METRIC>`: `price` · `aggregated_funding_rate` · `aggregated_premium` ·
167
+ `aggregated_open_interest` · `aggregated_spot_volume_delta` · `aggregated_volume_delta` ·
168
+ `aggregated_liquidations` · `coinbase_premium`
169
+
170
+ `--unit dollars` switches `open_interest` / `*_volume_delta` / `liquidations` to USD. See all
171
+ flags with `veloscraper fetch --help`.
172
+
173
+ ### Basics
174
+
175
+ ```bash
176
+ # BTCUSDT hourly funding rate, last 30 days (default range)
177
+ veloscraper fetch --metric aggregated_funding_rate --out btc.csv
178
+
179
+ # A different symbol
180
+ veloscraper fetch --ticker ETHUSDT --metric aggregated_funding_rate --out eth.csv
181
+ ```
182
+
183
+ ### Metrics
184
+
185
+ ```bash
186
+ veloscraper fetch --metric price --out btc_price.csv # OHLCV
187
+ veloscraper fetch --metric aggregated_open_interest --out btc_oi.csv # OHLC
188
+ veloscraper fetch --metric aggregated_premium --out btc_prem.csv
189
+ veloscraper fetch --metric aggregated_spot_volume_delta --out btc_spotdelta.csv
190
+ veloscraper fetch --metric aggregated_volume_delta --out btc_voldelta.csv
191
+ veloscraper fetch --metric aggregated_liquidations --out btc_liq.csv # short + long
192
+ veloscraper fetch --metric coinbase_premium --out btc_cbp.csv # computed
193
+
194
+ # Dollar-denominated (open_interest / *_volume_delta / liquidations):
195
+ veloscraper fetch --metric aggregated_open_interest --unit dollars --out btc_oi_usd.csv
196
+ ```
197
+
198
+ ### Time range
199
+
200
+ ```bash
201
+ # Explicit start/end
202
+ veloscraper fetch --metric aggregated_funding_rate --begin 2024-01-01 --end 2024-02-01 --out btc_jan.csv
203
+
204
+ # Only a start (end = now)
205
+ veloscraper fetch --metric aggregated_funding_rate --begin 2024-06-01 --out btc_since_jun.csv
206
+
207
+ # A begin that is too early is clamped to 2021-01-01 (where the data starts)
208
+ veloscraper fetch --metric aggregated_funding_rate --begin 2015-01-01 --out btc_all.csv
209
+ ```
210
+
211
+ ### Last N candles (`--last`, works with `--resolution`)
212
+
213
+ ```bash
214
+ # The last 1000 hourly candles
215
+ veloscraper fetch --metric aggregated_funding_rate --resolution 60 --last 1000 --out btc_1000h.csv
216
+
217
+ # The last 500 four-hour candles
218
+ veloscraper fetch --metric price --resolution 240 --last 500 --out btc_500x4h.csv
219
+
220
+ # The last 90 daily candles
221
+ veloscraper fetch --metric aggregated_open_interest --resolution 1D --last 90 --out btc_90d.csv
222
+ ```
223
+
224
+ ### Resolution
225
+
226
+ ```bash
227
+ # Minutes: 1, 5, 10, 15, 30, 60, 120, 240, 360, 720
228
+ veloscraper fetch --metric price --resolution 15 --last 200 --out btc_15m.csv
229
+
230
+ # Daily / weekly
231
+ veloscraper fetch --metric aggregated_funding_rate --resolution 1D --begin 2021-01-01 --out btc_daily.csv
232
+ veloscraper fetch --metric aggregated_funding_rate --resolution 1W --begin 2021-01-01 --out btc_weekly.csv
233
+ ```
234
+
235
+ ### `price` + exchange (`--exchange` is only for `price`)
236
+
237
+ ```bash
238
+ veloscraper fetch --metric price --exchange bybit --last 168 --out bybit_price.csv
239
+ veloscraper fetch --metric price --exchange coinbase --ticker BTC-USD --last 168 --out cb_price.csv
240
+ ```
241
+
242
+ ### Override the aggregation exchanges
243
+
244
+ ```bash
245
+ veloscraper fetch --metric aggregated_funding_rate \
246
+ --exchanges binance-futures,bybit,okex-swap --last 168 --out btc_3exch.csv
247
+ ```
248
+
249
+ ### Checkpoint / resume
250
+
251
+ `--out` acts as a checkpoint by default (disable with `--no-resume`).
252
+
253
+ ```bash
254
+ # A long historical fetch — if it is interrupted, the SAME command resumes it
255
+ veloscraper fetch --metric aggregated_funding_rate --begin 2021-01-01 --resolution 60 --out btc_full.csv
256
+
257
+ # Disable resume (start over each time)
258
+ veloscraper fetch --metric price --last 500 --no-resume --out btc_fresh.csv
259
+ ```
260
+
261
+ ### Other
262
+
263
+ ```bash
264
+ veloscraper fetch --metric price --last 100 --out btc.csv --quiet # silence progress/logs
265
+ veloscraper --version
266
+ veloscraper fetch --help
267
+ ```
268
+
269
+ ### Common mistakes
270
+
271
+ ```bash
272
+ # ✗ --metric is required
273
+ veloscraper fetch --out btc.csv
274
+ # -> Missing option '--metric'.
275
+
276
+ # ✗ begin and last cannot be used together
277
+ veloscraper fetch --metric price --begin 2024-01-01 --last 100 --out x.csv
278
+ # -> ValueError: 'begin' and 'last' cannot be used together.
279
+ ```
280
+
281
+ ## Examples
282
+
283
+ - [`examples/quickstart.py`](examples/quickstart.py) — fetch every metric and write a
284
+ separate CSV for each.
285
+
286
+ ## Development
287
+
288
+ ```bash
289
+ uv run pytest # tests (network is mocked, no internet needed)
290
+ uv run ruff check # lint
291
+ ```
292
+
293
+ ## Supported metrics
294
+
295
+ | Metric | Output shape | Columns | Exchanges |
296
+ |----------------------------|--------------|--------------------------------------------|-----------|
297
+ | `price` | OHLCV | `open`, `high`, `low`, `close`, `volume` | single (exchange param) |
298
+ | `aggregated_funding_rate` | scalar | `aggregated_funding_rate` | futures |
299
+ | `aggregated_premium` | scalar | `aggregated_premium` (OI-weighted) | futures |
300
+ | `aggregated_open_interest` | OHLC | `open`, `high`, `low`, `close` | futures |
301
+ | `aggregated_spot_volume_delta` | scalar | `aggregated_spot_volume_delta` (spot buy−sell) | spot |
302
+ | `aggregated_volume_delta` | scalar | `aggregated_volume_delta` (futures buy−sell) | futures |
303
+ | `aggregated_liquidations` | 2 values | `short_liquidations`, `long_liquidations` | futures |
304
+ | `coinbase_premium` | computed | `binance_close`, `coinbase_close`, `premium`, `premium_pct` | binance + coinbase spot |
305
+
306
+ Metric names follow the velo.xyz interface (`aggregated_*`). All are verified against the
307
+ real API.
308
+
309
+ **Coins vs dollars.** `open_interest`, `spot_volume_delta`, `volume_delta` and
310
+ `liquidations` accept `unit="coins"` (default) or `unit="dollars"`:
311
+
312
+ ```python
313
+ oi_usd = scraper.get_metric("BTCUSDT", "aggregated_open_interest", "1D", unit="dollars")
314
+ liq = scraper.get_metric("BTCUSDT", "aggregated_liquidations", 60, last=168)
315
+ # liq columns: short_liquidations (green), long_liquidations (red)
316
+ ```
317
+
318
+ The unit is not encoded in the column names or file name (like resolution), so track which
319
+ one you fetched. Rates (`funding_rate` / `premium`) and `price` have no coins/dollars unit.
320
+
321
+ - **`aggregated_premium`**: of velo.xyz's three premiums, this is the *aggregated Premium*
322
+ (OI-weighted, cross-exchange). The single-exchange premium is a separate metric (not added
323
+ yet).
324
+ - **`aggregated_open_interest`**: since OI is a *level* metric, the velo API returns
325
+ degenerate candles at low resolutions (e.g. hourly): `open == close`, with `high`/`low`
326
+ from the intra-hour range. To match velo's own chart, `open` is derived from the
327
+ **previous close** on degenerate rows. This fix applies only to rows where `open == close`
328
+ and only when `resolution < 120` (120+ is already real OHLC); the first row of the series
329
+ keeps the API value. `close`/`high`/`low` are never touched.
330
+ - **`aggregated_spot_volume_delta` / `aggregated_volume_delta`**: **per-bar** volume delta
331
+ (buy − sell), not a running total — velo's "Delta" and "Cumulative Delta" return the same
332
+ values; the cumulative line is a UI-only view. Take `.cumsum()` yourself for a running CVD.
333
+ - **`aggregated_liquidations`**: two values per row — `short_liquidations` (velo's green
334
+ series) then `long_liquidations` (red). Use `unit="dollars"` for USD notional.
335
+ - **`coinbase_premium`**: not a single API metric but a **computed** one — the spot price
336
+ difference between coinbase (`BTC-USD`) and binance (`BTCUSDT`). Two requests are made and
337
+ aligned on timestamp:
338
+
339
+ ```python
340
+ cp = scraper.get_coinbase_premium("BTCUSDT", resolution=60,
341
+ begin="2024-01-01", end="2024-02-01")
342
+ # datetime, timestamp, binance_close, coinbase_close, premium, premium_pct
343
+ ```
344
+
345
+ `get_metric("BTCUSDT", "coinbase_premium", ...)` routes to the same result.
346
+
347
+ ### Custom metrics
348
+
349
+ You don't need to fork the library to add a metric — pass a `MetricSpec` (the symbol tail,
350
+ verified from a velo chart request) straight to `get_metric`:
351
+
352
+ ```python
353
+ from veloxyz_scraper import MetricSpec, VeloScraper
354
+
355
+ spec = MetricSpec("my_metric", "some#velo#tail") # tail after {ticker}#{exchanges}#
356
+ df = VeloScraper().get_metric("BTCUSDT", spec, resolution=60, last=100)
357
+ ```
358
+
359
+ The built-in presets are exported read-only as `METRICS` (`from veloxyz_scraper import
360
+ METRICS`) for discovery. To contribute a verified preset upstream, add it to the `METRICS`
361
+ dict in `src/veloxyz_scraper/metrics.py`.
362
+
363
+ ## Per-exchange data availability
364
+
365
+ An aggregate combines several exchanges, but **each exchange only contributes from the date
366
+ its own data begins** — so the composition of the aggregate changes over time. The dates
367
+ below were measured (BTCUSDT) by requesting each exchange on its own
368
+ (`symbol=BTCUSDT#<exchange>#<metric-tail>`) and binary-searching the first day that returns
369
+ data.
370
+
371
+ **Futures exchanges** — same for `aggregated_funding_rate`, `aggregated_premium`,
372
+ `aggregated_open_interest`:
373
+
374
+ | Exchange | Included from |
375
+ |-----------------------|---------------|
376
+ | binance-futures | ≤ 2021-01-01 (data start) |
377
+ | bybit | ≤ 2021-01-01 |
378
+ | okex-swap | ≤ 2021-01-01 |
379
+ | deribit | ≤ 2021-01-01 |
380
+ | binance-coin-margin | ≤ 2021-01-01 |
381
+ | bybit-coin-margin | ≤ 2021-01-01 |
382
+ | okex-coin-margin | ≤ 2021-01-01 |
383
+ | **hyperliquid** | **2024-06-11** |
384
+
385
+ **Spot exchanges** — `aggregated_spot_volume_delta`:
386
+
387
+ | Exchange | Included from |
388
+ |-------------|---------------|
389
+ | binance | ≤ 2021-01-01 |
390
+ | coinbase | ≤ 2021-01-01 |
391
+ | **bybit-spot** | **2024-09-25** |
392
+ | **okex** | **2024-09-26** |
393
+
394
+ > **Heads-up for modeling:** because a new venue joins mid-series, the aggregate can show a
395
+ > **step change** when it is added (e.g. aggregated open interest jumps up around 2024-06-11
396
+ > when hyperliquid enters; spot CVD around 2024-09-25 when bybit-spot/okex enter). This is
397
+ > not a data error — it is a composition change. If a constant-composition series matters for
398
+ > your model, restrict `--exchanges` to venues present over your whole window.