bitvavo-api-upgraded 4.0.0__py3-none-any.whl → 4.1.0__py3-none-any.whl
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.
- bitvavo_api_upgraded/bitvavo.py +124 -109
- bitvavo_api_upgraded/dataframe_utils.py +3 -1
- bitvavo_api_upgraded/settings.py +1 -1
- bitvavo_api_upgraded/type_aliases.py +2 -2
- {bitvavo_api_upgraded-4.0.0.dist-info → bitvavo_api_upgraded-4.1.0.dist-info}/METADATA +404 -84
- bitvavo_api_upgraded-4.1.0.dist-info/RECORD +10 -0
- {bitvavo_api_upgraded-4.0.0.dist-info → bitvavo_api_upgraded-4.1.0.dist-info}/WHEEL +1 -1
- bitvavo_api_upgraded-4.0.0.dist-info/RECORD +0 -10
bitvavo_api_upgraded/bitvavo.py
CHANGED
@@ -2,16 +2,16 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import contextlib
|
4
4
|
import datetime as dt
|
5
|
-
import hashlib
|
6
|
-
import hmac
|
7
5
|
import json
|
6
|
+
import statistics
|
8
7
|
import time
|
8
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
9
9
|
from pathlib import Path
|
10
10
|
from threading import Thread
|
11
|
-
from typing import
|
11
|
+
from typing import TYPE_CHECKING, Any
|
12
12
|
|
13
13
|
import websocket as ws_lib
|
14
|
-
from
|
14
|
+
from httpx import delete, get, post, put
|
15
15
|
from structlog.stdlib import get_logger
|
16
16
|
from websocket import WebSocketApp # missing stubs for WebSocketApp
|
17
17
|
|
@@ -19,88 +19,24 @@ from bitvavo_api_upgraded.dataframe_utils import convert_candles_to_dataframe, c
|
|
19
19
|
from bitvavo_api_upgraded.helper_funcs import configure_loggers, time_ms, time_to_wait
|
20
20
|
from bitvavo_api_upgraded.settings import bitvavo_settings, bitvavo_upgraded_settings
|
21
21
|
from bitvavo_api_upgraded.type_aliases import OutputFormat, anydict, errordict, intdict, ms, s_f, strdict, strintdict
|
22
|
+
from bitvavo_client.auth.signing import create_signature
|
23
|
+
from bitvavo_client.endpoints.common import (
|
24
|
+
asks_compare,
|
25
|
+
bids_compare,
|
26
|
+
create_postfix,
|
27
|
+
default,
|
28
|
+
epoch_millis,
|
29
|
+
sort_and_insert,
|
30
|
+
)
|
31
|
+
|
32
|
+
if TYPE_CHECKING:
|
33
|
+
from collections.abc import Callable
|
22
34
|
|
23
35
|
configure_loggers()
|
24
36
|
|
25
37
|
logger = get_logger(__name__)
|
26
38
|
|
27
39
|
|
28
|
-
def create_signature(timestamp: ms, method: str, url: str, body: anydict | None, api_secret: str) -> str:
|
29
|
-
string = f"{timestamp}{method}/v2{url}"
|
30
|
-
if body is not None and len(body.keys()) > 0:
|
31
|
-
string += json.dumps(body, separators=(",", ":"))
|
32
|
-
signature = hmac.new(api_secret.encode("utf-8"), string.encode("utf-8"), hashlib.sha256).hexdigest()
|
33
|
-
return signature
|
34
|
-
|
35
|
-
|
36
|
-
def create_postfix(options: anydict | None) -> str:
|
37
|
-
"""Generate a URL postfix, based on the `options` dict.
|
38
|
-
|
39
|
-
---
|
40
|
-
Args:
|
41
|
-
options (anydict): [description]
|
42
|
-
|
43
|
-
---
|
44
|
-
Returns:
|
45
|
-
str: [description]
|
46
|
-
"""
|
47
|
-
options = _default(options, {})
|
48
|
-
params = [f"{key}={options[key]}" for key in options]
|
49
|
-
postfix = "&".join(params) # intersperse
|
50
|
-
return f"?{postfix}" if len(options) > 0 else postfix
|
51
|
-
|
52
|
-
|
53
|
-
def _default(value: anydict | None, fallback: anydict) -> anydict:
|
54
|
-
"""
|
55
|
-
Note that is close, but not actually equal to:
|
56
|
-
|
57
|
-
`return value or fallback`
|
58
|
-
|
59
|
-
I checked this with a temporary hypothesis test.
|
60
|
-
|
61
|
-
This note is all you will get out of me.
|
62
|
-
"""
|
63
|
-
return value if value is not None else fallback
|
64
|
-
|
65
|
-
|
66
|
-
def _epoch_millis(dt: dt.datetime) -> int:
|
67
|
-
return int(dt.timestamp() * 1000)
|
68
|
-
|
69
|
-
|
70
|
-
def asks_compare(a: float, b: float) -> bool:
|
71
|
-
return a < b
|
72
|
-
|
73
|
-
|
74
|
-
def bids_compare(a: float, b: float) -> bool:
|
75
|
-
return a > b
|
76
|
-
|
77
|
-
|
78
|
-
def sort_and_insert(
|
79
|
-
asks_or_bids: list[list[str]],
|
80
|
-
update: list[list[str]],
|
81
|
-
compareFunc: Callable[[float, float], bool],
|
82
|
-
) -> list[list[str]] | errordict:
|
83
|
-
for updateEntry in update:
|
84
|
-
entrySet: bool = False
|
85
|
-
for j in range(len(asks_or_bids)):
|
86
|
-
bookItem = asks_or_bids[j]
|
87
|
-
if compareFunc(float(updateEntry[0]), float(bookItem[0])):
|
88
|
-
asks_or_bids.insert(j, updateEntry)
|
89
|
-
entrySet = True
|
90
|
-
break
|
91
|
-
if float(updateEntry[0]) == float(bookItem[0]):
|
92
|
-
if float(updateEntry[1]) > 0.0:
|
93
|
-
asks_or_bids[j] = updateEntry
|
94
|
-
entrySet = True
|
95
|
-
break
|
96
|
-
asks_or_bids.pop(j)
|
97
|
-
entrySet = True
|
98
|
-
break
|
99
|
-
if not entrySet:
|
100
|
-
asks_or_bids.append(updateEntry)
|
101
|
-
return asks_or_bids
|
102
|
-
|
103
|
-
|
104
40
|
def process_local_book(ws: Bitvavo.WebSocketAppFacade, message: anydict) -> None:
|
105
41
|
market: str = ""
|
106
42
|
if "action" in message:
|
@@ -412,7 +348,9 @@ class Bitvavo:
|
|
412
348
|
key_name = f"API_KEY_{key_index}" if key_index >= 0 else "KEYLESS"
|
413
349
|
|
414
350
|
logger.warning(
|
415
|
-
"rate-limit-reached",
|
351
|
+
"rate-limit-reached",
|
352
|
+
key_name=key_name,
|
353
|
+
rateLimitRemaining=self.rate_limits[key_index]["remaining"],
|
416
354
|
)
|
417
355
|
logger.info(
|
418
356
|
"napping-until-reset",
|
@@ -423,27 +361,108 @@ class Bitvavo:
|
|
423
361
|
)
|
424
362
|
time.sleep(napTime + 1) # +1 to add a tiny bit of buffer time
|
425
363
|
|
426
|
-
def calc_lag(self) -> ms:
|
364
|
+
def calc_lag(self, samples: int = 5, timeout_seconds: float = 5.0) -> ms: # noqa: C901
|
427
365
|
"""
|
428
|
-
Calculate the time difference between the client and server
|
429
|
-
|
366
|
+
Calculate the time difference between the client and server using statistical analysis.
|
367
|
+
|
368
|
+
Uses multiple samples with outlier detection to get a more accurate lag measurement.
|
369
|
+
|
370
|
+
Args:
|
371
|
+
samples: Number of time samples to collect (default: 5)
|
372
|
+
timeout_seconds: Maximum time to spend collecting samples (default: 5.0)
|
373
|
+
|
374
|
+
Returns:
|
375
|
+
Average lag in milliseconds
|
430
376
|
|
431
|
-
Raises
|
377
|
+
Raises:
|
378
|
+
ValueError: If unable to collect sufficient valid samples
|
379
|
+
RuntimeError: If all API calls fail
|
432
380
|
"""
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
381
|
+
ARBITRARY = 3
|
382
|
+
if samples < ARBITRARY:
|
383
|
+
msg = f"Need at least {ARBITRARY} samples for statistical analysis"
|
384
|
+
raise ValueError(msg)
|
385
|
+
|
386
|
+
def measure_single_lag() -> ms | None:
|
387
|
+
"""Measure lag for a single request with error handling."""
|
388
|
+
try:
|
389
|
+
client_time_before = time_ms()
|
390
|
+
server_response = self.time()
|
391
|
+
client_time_after = time_ms()
|
392
|
+
|
393
|
+
if isinstance(server_response, dict) and "time" in server_response:
|
394
|
+
# Use midpoint of request duration for better accuracy
|
395
|
+
client_time_avg = (client_time_before + client_time_after) // 2
|
396
|
+
server_time = server_response["time"]
|
397
|
+
if isinstance(server_time, int):
|
398
|
+
return ms(server_time - client_time_avg)
|
399
|
+
return None
|
400
|
+
except (ValueError, TypeError, KeyError):
|
401
|
+
return None
|
402
|
+
else:
|
403
|
+
# If error or unexpected response
|
404
|
+
return None
|
405
|
+
|
406
|
+
lag_measurements: list[ms] = []
|
407
|
+
|
408
|
+
# Collect samples concurrently for better performance
|
409
|
+
with ThreadPoolExecutor(max_workers=min(samples, 5)) as executor:
|
410
|
+
try:
|
411
|
+
# Submit all measurement tasks
|
412
|
+
future_to_sample = {executor.submit(measure_single_lag): i for i in range(samples)}
|
413
|
+
|
414
|
+
# Collect results with timeout
|
415
|
+
for future in as_completed(future_to_sample, timeout=timeout_seconds):
|
416
|
+
lag = future.result()
|
417
|
+
if lag is not None:
|
418
|
+
lag_measurements.append(lag)
|
419
|
+
|
420
|
+
except TimeoutError:
|
421
|
+
if self.debugging:
|
422
|
+
logger.warning(
|
423
|
+
"lag-calculation-timeout",
|
424
|
+
collected_samples=len(lag_measurements),
|
425
|
+
requested_samples=samples,
|
426
|
+
)
|
427
|
+
|
428
|
+
if len(lag_measurements) < max(2, samples // 2):
|
429
|
+
msg = f"Insufficient valid samples: got {len(lag_measurements)}, need at least {max(2, samples // 2)}"
|
430
|
+
raise RuntimeError(msg)
|
431
|
+
|
432
|
+
# Remove outliers using interquartile range method
|
433
|
+
QUARTILES = 4
|
434
|
+
if len(lag_measurements) >= QUARTILES:
|
435
|
+
try:
|
436
|
+
q1 = statistics.quantiles(lag_measurements, n=QUARTILES)[0]
|
437
|
+
q3 = statistics.quantiles(lag_measurements, n=QUARTILES)[2]
|
438
|
+
iqr = q3 - q1
|
439
|
+
lower_bound = q1 - 1.5 * iqr
|
440
|
+
upper_bound = q3 + 1.5 * iqr
|
441
|
+
|
442
|
+
filtered_measurements = [lag for lag in lag_measurements if lower_bound <= lag <= upper_bound]
|
443
|
+
|
444
|
+
# Use filtered data if we still have enough samples
|
445
|
+
if len(filtered_measurements) >= 2: # noqa: PLR2004
|
446
|
+
lag_measurements = filtered_measurements
|
447
|
+
|
448
|
+
except statistics.StatisticsError:
|
449
|
+
# Fall back to original measurements if filtering fails
|
450
|
+
pass
|
451
|
+
|
452
|
+
# Calculate final lag using median for robustness
|
453
|
+
final_lag = ms(statistics.median(lag_measurements))
|
454
|
+
|
455
|
+
if self.debugging:
|
456
|
+
logger.debug(
|
457
|
+
"lag-calculated",
|
458
|
+
samples_collected=len(lag_measurements),
|
459
|
+
lag_ms=final_lag,
|
460
|
+
min_lag=min(lag_measurements),
|
461
|
+
max_lag=max(lag_measurements),
|
462
|
+
std_dev=statistics.stdev(lag_measurements) if len(lag_measurements) > 1 else 0,
|
463
|
+
)
|
445
464
|
|
446
|
-
return
|
465
|
+
return final_lag
|
447
466
|
|
448
467
|
def get_remaining_limit(self) -> int:
|
449
468
|
"""Get the remaining rate limit
|
@@ -576,7 +595,6 @@ class Bitvavo:
|
|
576
595
|
|
577
596
|
---
|
578
597
|
Args:
|
579
|
-
# TODO(NostraDavid) fill these in
|
580
598
|
```python
|
581
599
|
endpoint: str = "/order"
|
582
600
|
postfix: str = "" # ?key=value&key2=another_value&...
|
@@ -1045,14 +1063,14 @@ class Bitvavo:
|
|
1045
1063
|
# timestamp is converted to datetime, numeric columns to float
|
1046
1064
|
```
|
1047
1065
|
"""
|
1048
|
-
options =
|
1066
|
+
options = default(options, {})
|
1049
1067
|
options["interval"] = interval
|
1050
1068
|
if limit is not None:
|
1051
1069
|
options["limit"] = limit
|
1052
1070
|
if start is not None:
|
1053
|
-
options["start"] =
|
1071
|
+
options["start"] = epoch_millis(start)
|
1054
1072
|
if end is not None:
|
1055
|
-
options["end"] =
|
1073
|
+
options["end"] = epoch_millis(end)
|
1056
1074
|
postfix = create_postfix(options)
|
1057
1075
|
result = self.public_request(f"{self.base}/{market}/candles{postfix}") # type: ignore[return-value]
|
1058
1076
|
return convert_candles_to_dataframe(result, output_format)
|
@@ -1279,7 +1297,7 @@ class Bitvavo:
|
|
1279
1297
|
]
|
1280
1298
|
```
|
1281
1299
|
"""
|
1282
|
-
options =
|
1300
|
+
options = default(options, {})
|
1283
1301
|
rateLimitingWeight = 25
|
1284
1302
|
if "market" in options:
|
1285
1303
|
rateLimitingWeight = 1
|
@@ -1796,7 +1814,7 @@ class Bitvavo:
|
|
1796
1814
|
]
|
1797
1815
|
```
|
1798
1816
|
""" # noqa: E501
|
1799
|
-
options =
|
1817
|
+
options = default(options, {})
|
1800
1818
|
options["market"] = market
|
1801
1819
|
postfix = create_postfix(options)
|
1802
1820
|
return self.private_request("/orders", postfix, {}, "GET", 5) # type: ignore[return-value]
|
@@ -1894,7 +1912,7 @@ class Bitvavo:
|
|
1894
1912
|
]
|
1895
1913
|
```
|
1896
1914
|
"""
|
1897
|
-
options =
|
1915
|
+
options = default(options, {})
|
1898
1916
|
rateLimitingWeight = 25
|
1899
1917
|
if "market" in options:
|
1900
1918
|
rateLimitingWeight = 1
|
@@ -1960,7 +1978,7 @@ class Bitvavo:
|
|
1960
1978
|
]
|
1961
1979
|
```
|
1962
1980
|
""" # noqa: E501
|
1963
|
-
options =
|
1981
|
+
options = default(options, {})
|
1964
1982
|
options["market"] = market
|
1965
1983
|
postfix = create_postfix(options)
|
1966
1984
|
result = self.private_request("/trades", postfix, {}, "GET", 5) # type: ignore[return-value]
|
@@ -2489,7 +2507,6 @@ class Bitvavo:
|
|
2489
2507
|
time.sleep(0.1)
|
2490
2508
|
|
2491
2509
|
def do_send(self, ws: WebSocketApp, message: str, private: bool = False) -> None: # noqa: FBT001, FBT002
|
2492
|
-
# TODO(NostraDavid): add nap-time to the websocket, or do it here; I don't know yet.
|
2493
2510
|
if private and self.APIKEY == "":
|
2494
2511
|
logger.error(
|
2495
2512
|
"no-apikey",
|
@@ -3961,8 +3978,6 @@ class Bitvavo:
|
|
3961
3978
|
self.do_send(self.ws, json.dumps(options), True)
|
3962
3979
|
|
3963
3980
|
def subscription_ticker(self, market: str, callback: Callable[[Any], None]) -> None:
|
3964
|
-
# TODO(NostraDavid): one possible improvement here is to turn `market` into a list of markets, so we can sub
|
3965
|
-
# to all of them at once. Same goes for other `subscription*()`
|
3966
3981
|
"""
|
3967
3982
|
Subscribe to the ticker channel, which means `callback` gets passed the new best bid or ask whenever they
|
3968
3983
|
change (server-side).
|
@@ -165,7 +165,9 @@ def convert_candles_to_dataframe(data: Any, output_format: str | OutputFormat) -
|
|
165
165
|
# Convert list of lists to list of dicts first
|
166
166
|
columns = ["timestamp", "open", "high", "low", "close", "volume"]
|
167
167
|
dict_data = [
|
168
|
-
dict(zip(columns, candle
|
168
|
+
dict(zip(columns, candle, strict=True))
|
169
|
+
for candle in data
|
170
|
+
if isinstance(candle, list) and len(candle) >= len(columns)
|
169
171
|
]
|
170
172
|
|
171
173
|
if not dict_data:
|
bitvavo_api_upgraded/settings.py
CHANGED
@@ -105,7 +105,7 @@ class BitvavoSettings(BaseSettings):
|
|
105
105
|
PREFER_KEYLESS: bool = Field(default=True)
|
106
106
|
|
107
107
|
# Configuration for Pydantic Settings
|
108
|
-
model_config
|
108
|
+
model_config = SettingsConfigDict(
|
109
109
|
env_file=Path.cwd() / ".env",
|
110
110
|
env_file_encoding="utf-8",
|
111
111
|
env_prefix="BITVAVO_",
|
@@ -4,7 +4,7 @@ to clearify the intention or semantics/meaning/unit of a variable
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import sys
|
7
|
-
from typing import Any
|
7
|
+
from typing import Any
|
8
8
|
|
9
9
|
if sys.version_info >= (3, 11):
|
10
10
|
from enum import StrEnum
|
@@ -26,7 +26,7 @@ anydict = dict[str, Any]
|
|
26
26
|
strdict = dict[str, str]
|
27
27
|
intdict = dict[str, int]
|
28
28
|
# can't use | here, with __future__. Not sure why.
|
29
|
-
strintdict = dict[str,
|
29
|
+
strintdict = dict[str, str | int]
|
30
30
|
errordict = dict[str, Any] # same type as anydict, but the semantics/meaning is different
|
31
31
|
|
32
32
|
# note: You can also use these for type conversion, so instead of int(some_float / 1000), you can just do ms(some_float
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: bitvavo-api-upgraded
|
3
|
-
Version: 4.
|
3
|
+
Version: 4.1.0
|
4
4
|
Summary: A unit-tested fork of the Bitvavo API
|
5
5
|
Author: Bitvavo BV (original code), NostraDavid
|
6
6
|
Author-email: NostraDavid <55331731+NostraDavid@users.noreply.github.com>
|
@@ -21,11 +21,12 @@ Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
22
22
|
Classifier: Programming Language :: Python
|
23
23
|
Classifier: Typing :: Typed
|
24
|
-
Requires-Dist:
|
25
|
-
Requires-Dist:
|
24
|
+
Requires-Dist: httpx>=0.28.1
|
25
|
+
Requires-Dist: pydantic-settings>=2.6
|
26
|
+
Requires-Dist: requests>=2.26
|
26
27
|
Requires-Dist: returns>=0.23.0
|
27
|
-
Requires-Dist: structlog>=21.5
|
28
|
-
Requires-Dist: websocket-client
|
28
|
+
Requires-Dist: structlog>=21.5
|
29
|
+
Requires-Dist: websocket-client>=1.2
|
29
30
|
Requires-Dist: cudf-cu12>=24.0.0 ; extra == 'cudf'
|
30
31
|
Requires-Dist: narwhals>=2.0.0 ; extra == 'cudf'
|
31
32
|
Requires-Dist: narwhals[dask]>=2.0.0 ; extra == 'dask'
|
@@ -34,6 +35,9 @@ Requires-Dist: narwhals[ibis]>=2.0.0 ; extra == 'ibis'
|
|
34
35
|
Requires-Dist: narwhals[modin]>=2.0.0 ; extra == 'modin'
|
35
36
|
Requires-Dist: narwhals[pandas]>=2.0.0 ; extra == 'pandas'
|
36
37
|
Requires-Dist: narwhals[polars]>=2.0.0 ; extra == 'polars'
|
38
|
+
Requires-Dist: cudf-polars-cu12>=24.0.0 ; extra == 'polars-gpu'
|
39
|
+
Requires-Dist: polars ; extra == 'polars-gpu'
|
40
|
+
Requires-Dist: narwhals>=2.0.0 ; extra == 'polars-gpu'
|
37
41
|
Requires-Dist: narwhals[pyarrow]>=2.0.0 ; extra == 'pyarrow'
|
38
42
|
Requires-Dist: narwhals[pyspark]>=2.0.0 ; extra == 'pyspark'
|
39
43
|
Requires-Dist: narwhals[pyspark-connect]>=2.0.0 ; extra == 'pyspark-connect'
|
@@ -51,6 +55,7 @@ Provides-Extra: ibis
|
|
51
55
|
Provides-Extra: modin
|
52
56
|
Provides-Extra: pandas
|
53
57
|
Provides-Extra: polars
|
58
|
+
Provides-Extra: polars-gpu
|
54
59
|
Provides-Extra: pyarrow
|
55
60
|
Provides-Extra: pyspark
|
56
61
|
Provides-Extra: pyspark-connect
|
@@ -59,7 +64,7 @@ Description-Content-Type: text/markdown
|
|
59
64
|
|
60
65
|
# Bitvavo API (upgraded)
|
61
66
|
|
62
|
-
A **typed, tested, and enhanced** Python wrapper for the Bitvavo cryptocurrency exchange API. This is an "upgraded" fork of the official Bitvavo SDK with comprehensive type hints, unit tests, and improved developer experience.
|
67
|
+
A **typed, tested, and enhanced** Python wrapper for the Bitvavo cryptocurrency exchange API. This is an "upgraded" fork of the official Bitvavo SDK with comprehensive type hints, unit tests, modern architecture, and improved developer experience.
|
63
68
|
|
64
69
|
## Quick Start
|
65
70
|
|
@@ -67,6 +72,23 @@ A **typed, tested, and enhanced** Python wrapper for the Bitvavo cryptocurrency
|
|
67
72
|
pip install bitvavo_api_upgraded
|
68
73
|
```
|
69
74
|
|
75
|
+
### Basic Usage
|
76
|
+
|
77
|
+
```python
|
78
|
+
# Option 1: Original Bitvavo interface (legacy)
|
79
|
+
from bitvavo_api_upgraded import Bitvavo
|
80
|
+
|
81
|
+
bitvavo = Bitvavo({'APIKEY': 'your-key', 'APISECRET': 'your-secret'})
|
82
|
+
balance = bitvavo.balance({})
|
83
|
+
|
84
|
+
# Option 2: New modular BitvavoClient interface (recommended)
|
85
|
+
from bitvavo_client import BitvavoClient, BitvavoSettings
|
86
|
+
|
87
|
+
client = BitvavoClient()
|
88
|
+
result = client.public.time() # No authentication needed
|
89
|
+
result = client.private.balance() # Authentication required
|
90
|
+
```
|
91
|
+
|
70
92
|
### Optional Dataframe Support
|
71
93
|
|
72
94
|
This package supports multiple dataframe libraries via [Narwhals](https://narwhals-dev.github.io/narwhals/), providing a unified interface across:
|
@@ -113,66 +135,127 @@ Scroll down for detailed usage examples and configuration instructions.
|
|
113
135
|
|
114
136
|
This wrapper improves upon the official Bitvavo SDK with:
|
115
137
|
|
116
|
-
|
117
|
-
|
118
|
-
-
|
119
|
-
-
|
120
|
-
-
|
121
|
-
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
-
|
138
|
+
### Modern Architecture
|
139
|
+
|
140
|
+
- **Modular design**: Clean separation between public/private APIs, transport, and authentication
|
141
|
+
- **Two interfaces**: Legacy `Bitvavo` class for backward compatibility + new `BitvavoClient` for modern development
|
142
|
+
- **Dependency injection**: Testable, maintainable, and extensible codebase
|
143
|
+
- **Type safety**: Comprehensive type annotations with generics and precise return types
|
144
|
+
|
145
|
+
### Quality & Reliability
|
146
|
+
|
147
|
+
- **Comprehensive test suite** (found and fixed multiple bugs in the original)
|
148
|
+
- **100% type coverage** with mypy strict mode
|
149
|
+
- **Enhanced error handling** with detailed validation messages
|
150
|
+
- **Rate limiting** with automatic throttling and multi-key support
|
151
|
+
|
152
|
+
### Data Format Flexibility
|
153
|
+
|
154
|
+
- **Unified dataframe support** via Narwhals (pandas, polars, cuDF, modin, PyArrow, Dask, DuckDB, Ibis, PySpark, SQLFrame)
|
155
|
+
- **Pydantic models** for validated, structured data
|
156
|
+
- **Raw dictionary access** for backward compatibility
|
157
|
+
- **Result types** for functional error handling
|
158
|
+
|
159
|
+
### Enhanced Performance
|
160
|
+
|
161
|
+
- **Multi-key support** for better rate limiting and load distribution
|
162
|
+
- **Keyless access** for public endpoints (doesn't count against your API limits)
|
163
|
+
- **Connection pooling** and retry logic
|
164
|
+
- **Async-ready architecture** (async support coming in future release)
|
165
|
+
|
166
|
+
### Developer Experience
|
167
|
+
|
168
|
+
- **Modern Python support** (3.9+, dropped EOL versions)
|
169
|
+
- **Configuration via environment variables** or Pydantic settings
|
170
|
+
- **Detailed changelog** tracking all changes and improvements
|
171
|
+
- **Enhanced documentation** with examples and clear usage patterns
|
172
|
+
- **Developer-friendly tooling** (ruff, mypy, pre-commit hooks)
|
126
173
|
|
127
174
|
## Features
|
128
175
|
|
129
176
|
### Full API Coverage
|
130
177
|
|
131
|
-
-
|
132
|
-
-
|
133
|
-
-
|
134
|
-
-
|
135
|
-
-
|
136
|
-
-
|
137
|
-
-
|
178
|
+
- All REST endpoints (public and private)
|
179
|
+
- Multiple API key support with automatic load balancing
|
180
|
+
- Keyless access for public endpoints without authentication
|
181
|
+
- Comprehensive dataframe support via Narwhals (pandas, polars, cuDF, modin, PyArrow, Dask, DuckDB, Ibis, PySpark, and more)
|
182
|
+
- WebSocket support with reconnection logic
|
183
|
+
- Rate limiting with automatic throttling
|
184
|
+
- MiCA compliance reporting endpoints
|
138
185
|
|
139
186
|
### Developer Experience
|
140
187
|
|
141
|
-
-
|
142
|
-
-
|
143
|
-
-
|
144
|
-
-
|
145
|
-
-
|
188
|
+
- Type hints for better IDE support
|
189
|
+
- Comprehensive error handling
|
190
|
+
- Detailed logging with `structlog`
|
191
|
+
- Configuration via `.env` files
|
192
|
+
- Extensive test coverage
|
146
193
|
|
147
194
|
### Production Ready
|
148
195
|
|
149
|
-
-
|
150
|
-
-
|
151
|
-
-
|
152
|
-
-
|
153
|
-
-
|
196
|
+
- Automatic rate limit management
|
197
|
+
- Multi-key failover support
|
198
|
+
- Connection retry logic
|
199
|
+
- Proper error responses
|
200
|
+
- Memory efficient WebSocket handling
|
154
201
|
|
155
202
|
## Configuration
|
156
203
|
|
204
|
+
### Environment Variables
|
205
|
+
|
157
206
|
Create a `.env` file in your project root:
|
158
207
|
|
159
208
|
```env
|
160
|
-
#
|
161
|
-
|
162
|
-
|
209
|
+
# API authentication
|
210
|
+
BITVAVO_API_KEY=your-api-key-here
|
211
|
+
BITVAVO_API_SECRET=your-api-secret-here
|
212
|
+
|
213
|
+
# Multi-key support (JSON array as string)
|
214
|
+
# BITVAVO_API_KEYS='[{"key": "key1", "secret": "secret1"}, {"key": "key2", "secret": "secret2"}]'
|
215
|
+
|
216
|
+
# Client behavior
|
217
|
+
BITVAVO_PREFER_KEYLESS=true # Use keyless for public endpoints
|
218
|
+
BITVAVO_DEFAULT_RATE_LIMIT=1000 # Rate limit per key
|
219
|
+
BITVAVO_RATE_LIMIT_BUFFER=50 # Buffer to avoid hitting limits
|
220
|
+
BITVAVO_DEBUGGING=false # Enable debug logging
|
221
|
+
|
222
|
+
# API endpoints (usually not needed to change)
|
223
|
+
BITVAVO_REST_URL=https://api.bitvavo.com/v2
|
224
|
+
BITVAVO_WS_URL=wss://ws.bitvavo.com/v2/
|
225
|
+
```
|
226
|
+
|
227
|
+
### Usage Examples
|
228
|
+
|
229
|
+
#### New BitvavoClient (Recommended)
|
230
|
+
|
231
|
+
```python
|
232
|
+
from bitvavo_client import BitvavoClient, BitvavoSettings
|
233
|
+
|
234
|
+
# Option 1: Auto-load from .env file
|
235
|
+
client = BitvavoClient()
|
236
|
+
|
237
|
+
# Option 2: Custom settings
|
238
|
+
settings = BitvavoSettings(
|
239
|
+
api_key="your-key",
|
240
|
+
api_secret="your-secret",
|
241
|
+
prefer_keyless=True,
|
242
|
+
debugging=True
|
243
|
+
)
|
244
|
+
client = BitvavoClient(settings)
|
163
245
|
|
164
|
-
#
|
165
|
-
|
246
|
+
# Option 3: Manual settings override
|
247
|
+
client = BitvavoClient(BitvavoSettings(default_rate_limit=750))
|
166
248
|
|
167
|
-
#
|
168
|
-
|
249
|
+
# Access public endpoints (no auth needed)
|
250
|
+
time_result = client.public.time()
|
251
|
+
markets_result = client.public.markets()
|
169
252
|
|
170
|
-
#
|
171
|
-
|
172
|
-
|
253
|
+
# Access private endpoints (auth required)
|
254
|
+
balance_result = client.private.balance()
|
255
|
+
account_result = client.private.account()
|
173
256
|
```
|
174
257
|
|
175
|
-
|
258
|
+
#### Legacy Bitvavo Class (Backward Compatibility)
|
176
259
|
|
177
260
|
```python
|
178
261
|
from bitvavo_api_upgraded import Bitvavo, BitvavoSettings
|
@@ -199,6 +282,68 @@ bitvavo = Bitvavo({
|
|
199
282
|
bitvavo = Bitvavo({'PREFER_KEYLESS': True})
|
200
283
|
```
|
201
284
|
|
285
|
+
## Data Format Flexibility
|
286
|
+
|
287
|
+
The new BitvavoClient supports multiple output formats to match your workflow:
|
288
|
+
|
289
|
+
### Model Preferences
|
290
|
+
|
291
|
+
```python
|
292
|
+
from bitvavo_client import BitvavoClient
|
293
|
+
from bitvavo_client.core.model_preferences import ModelPreference
|
294
|
+
|
295
|
+
# Option 1: Raw dictionaries (default, backward compatible)
|
296
|
+
client = BitvavoClient(preferred_model=ModelPreference.RAW)
|
297
|
+
result = client.public.time() # Returns: {"time": 1609459200000}
|
298
|
+
|
299
|
+
# Option 2: Validated Pydantic models
|
300
|
+
client = BitvavoClient(preferred_model=ModelPreference.PYDANTIC)
|
301
|
+
result = client.public.time() # Returns: ServerTime(time=1609459200000)
|
302
|
+
|
303
|
+
# Option 3: DataFrame format (pandas, polars, etc.)
|
304
|
+
client = BitvavoClient(preferred_model=ModelPreference.DATAFRAME)
|
305
|
+
result = client.public.markets() # Returns: polars.DataFrame with market data
|
306
|
+
```
|
307
|
+
|
308
|
+
### Per-Request Format Override
|
309
|
+
|
310
|
+
```python
|
311
|
+
# Set a default preference but override per request
|
312
|
+
client = BitvavoClient(preferred_model=ModelPreference.RAW)
|
313
|
+
|
314
|
+
# Get raw dict (uses default)
|
315
|
+
raw_data = client.public.markets()
|
316
|
+
|
317
|
+
# Override to get DataFrame for this request
|
318
|
+
import polars as pl
|
319
|
+
df_data = client.public.markets(model=pl.DataFrame)
|
320
|
+
|
321
|
+
# Override to get Pydantic model
|
322
|
+
from bitvavo_client.core.public_models import Markets
|
323
|
+
validated_data = client.public.markets(model=Markets)
|
324
|
+
```
|
325
|
+
|
326
|
+
### Result Types for Error Handling
|
327
|
+
|
328
|
+
```python
|
329
|
+
from returns.result import Success, Failure
|
330
|
+
|
331
|
+
# Use result types for functional error handling
|
332
|
+
result = client.public.time()
|
333
|
+
|
334
|
+
if isinstance(result, Success):
|
335
|
+
print(f"Server time: {result.unwrap()}")
|
336
|
+
elif isinstance(result, Failure):
|
337
|
+
print(f"Error: {result.failure()}")
|
338
|
+
|
339
|
+
# Or use match-case (Python 3.10+)
|
340
|
+
match result:
|
341
|
+
case Success(value):
|
342
|
+
print(f"Success: {value}")
|
343
|
+
case Failure(error):
|
344
|
+
print(f"Error: {error}")
|
345
|
+
```
|
346
|
+
|
202
347
|
## WebSocket Usage
|
203
348
|
|
204
349
|
```python
|
@@ -288,9 +433,44 @@ balance = bitvavo.balance({})
|
|
288
433
|
|
289
434
|
## API Examples
|
290
435
|
|
291
|
-
### Public Endpoints (No Authentication)
|
436
|
+
### Public Endpoints (No Authentication Required)
|
437
|
+
|
438
|
+
#### New BitvavoClient Interface
|
292
439
|
|
293
440
|
```python
|
441
|
+
from bitvavo_client import BitvavoClient
|
442
|
+
|
443
|
+
client = BitvavoClient()
|
444
|
+
|
445
|
+
# Get server time
|
446
|
+
time_result = client.public.time()
|
447
|
+
|
448
|
+
# Get all markets
|
449
|
+
markets_result = client.public.markets()
|
450
|
+
|
451
|
+
# Get specific market
|
452
|
+
btc_market = client.public.markets(market='BTC-EUR')
|
453
|
+
|
454
|
+
# Get order book
|
455
|
+
book_result = client.public.book('BTC-EUR')
|
456
|
+
|
457
|
+
# Get recent trades
|
458
|
+
trades_result = client.public.trades('BTC-EUR')
|
459
|
+
|
460
|
+
# Get 24h ticker
|
461
|
+
ticker_result = client.public.ticker_24h(market='BTC-EUR')
|
462
|
+
|
463
|
+
# Get candlestick data
|
464
|
+
candles_result = client.public.candles('BTC-EUR', '1h')
|
465
|
+
```
|
466
|
+
|
467
|
+
#### Legacy Bitvavo Interface
|
468
|
+
|
469
|
+
```python
|
470
|
+
from bitvavo_api_upgraded import Bitvavo
|
471
|
+
|
472
|
+
bitvavo = Bitvavo({'PREFER_KEYLESS': True}) # For public endpoints
|
473
|
+
|
294
474
|
# Get server time
|
295
475
|
time_resp = bitvavo.time()
|
296
476
|
|
@@ -312,7 +492,50 @@ ticker = bitvavo.ticker24h({'market': 'BTC-EUR'})
|
|
312
492
|
|
313
493
|
### Private Endpoints (Authentication Required)
|
314
494
|
|
495
|
+
#### New BitvavoClient Interface
|
496
|
+
|
497
|
+
```python
|
498
|
+
from bitvavo_client import BitvavoClient, BitvavoSettings
|
499
|
+
|
500
|
+
# Configure with API credentials
|
501
|
+
settings = BitvavoSettings(api_key="your-key", api_secret="your-secret")
|
502
|
+
client = BitvavoClient(settings)
|
503
|
+
|
504
|
+
# Get account info
|
505
|
+
account_result = client.private.account()
|
506
|
+
|
507
|
+
# Get balance
|
508
|
+
balance_result = client.private.balance()
|
509
|
+
|
510
|
+
# Place order
|
511
|
+
order_result = client.private.place_order(
|
512
|
+
market="BTC-EUR",
|
513
|
+
side="buy",
|
514
|
+
order_type="limit",
|
515
|
+
amount="0.01",
|
516
|
+
price="45000"
|
517
|
+
)
|
518
|
+
|
519
|
+
# Get order history
|
520
|
+
orders_result = client.private.orders('BTC-EUR')
|
521
|
+
|
522
|
+
# Cancel order
|
523
|
+
cancel_result = client.private.cancel_order(
|
524
|
+
market="BTC-EUR",
|
525
|
+
order_id="order-id-here"
|
526
|
+
)
|
527
|
+
|
528
|
+
# Get trades
|
529
|
+
trades_result = client.private.trades('BTC-EUR')
|
530
|
+
```
|
531
|
+
|
532
|
+
#### Legacy Bitvavo Interface
|
533
|
+
|
315
534
|
```python
|
535
|
+
from bitvavo_api_upgraded import Bitvavo
|
536
|
+
|
537
|
+
bitvavo = Bitvavo({'APIKEY': 'your-key', 'APISECRET': 'your-secret'})
|
538
|
+
|
316
539
|
# Get account info
|
317
540
|
account = bitvavo.account()
|
318
541
|
|
@@ -524,15 +747,49 @@ uv run ruff format
|
|
524
747
|
### Project Structure
|
525
748
|
|
526
749
|
```text
|
527
|
-
src/
|
528
|
-
├──
|
529
|
-
├──
|
530
|
-
├──
|
531
|
-
├──
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
750
|
+
src/
|
751
|
+
├── bitvavo_api_upgraded/ # Legacy interface (backward compatibility)
|
752
|
+
│ ├── __init__.py # Main exports
|
753
|
+
│ ├── bitvavo.py # Original monolithic API class
|
754
|
+
│ ├── settings.py # Pydantic settings
|
755
|
+
│ ├── helper_funcs.py # Utility functions
|
756
|
+
│ └── type_aliases.py # Type definitions
|
757
|
+
└── bitvavo_client/ # Modern modular interface
|
758
|
+
├── __init__.py # New client exports
|
759
|
+
├── facade.py # Main BitvavoClient class
|
760
|
+
├── core/ # Core functionality
|
761
|
+
│ ├── settings.py # Settings management
|
762
|
+
│ ├── models.py # Pydantic data models
|
763
|
+
│ ├── validation_helpers.py # Enhanced error handling
|
764
|
+
│ └── types.py # Type definitions
|
765
|
+
├── endpoints/ # API endpoint handlers
|
766
|
+
│ ├── public.py # Public API endpoints
|
767
|
+
│ ├── private.py # Private API endpoints
|
768
|
+
│ └── common.py # Shared endpoint utilities
|
769
|
+
├── transport/ # HTTP transport layer
|
770
|
+
│ └── http.py # HTTP client with connection pooling
|
771
|
+
├── auth/ # Authentication & authorization
|
772
|
+
│ ├── signing.py # Request signing
|
773
|
+
│ └── rate_limit.py # Rate limiting management
|
774
|
+
├── adapters/ # External integrations
|
775
|
+
│ └── returns_adapter.py # Result type adapters
|
776
|
+
├── schemas/ # DataFrame schemas
|
777
|
+
│ ├── public_schemas.py # Public endpoint schemas
|
778
|
+
│ └── private_schemas.py # Private endpoint schemas
|
779
|
+
└── df/ # DataFrame conversion
|
780
|
+
└── convert.py # Narwhals-based converters
|
781
|
+
|
782
|
+
tests/ # Comprehensive test suite
|
783
|
+
├── bitvavo_api_upgraded/ # Legacy interface tests
|
784
|
+
└── bitvavo_client/ # Modern interface tests
|
785
|
+
├── core/ # Core functionality tests
|
786
|
+
├── endpoints/ # Endpoint tests
|
787
|
+
├── transport/ # Transport layer tests
|
788
|
+
├── auth/ # Authentication tests
|
789
|
+
├── adapters/ # Adapter tests
|
790
|
+
└── df/ # DataFrame tests
|
791
|
+
|
792
|
+
docs/ # Documentation
|
536
793
|
```
|
537
794
|
|
538
795
|
### Semantic Versioning
|
@@ -547,47 +804,110 @@ This project follows [semantic versioning](https://semver.org/):
|
|
547
804
|
|
548
805
|
This package includes a `py.typed` file to enable type checking. Reference: [Don't forget py.typed for your typed Python package](https://blog.whtsky.me/tech/2021/dont-forget-py.typed-for-your-typed-python-package/)
|
549
806
|
|
550
|
-
## Migration
|
807
|
+
## Migration & Architecture Options
|
808
|
+
|
809
|
+
This package provides **two interfaces** to suit different use cases:
|
810
|
+
|
811
|
+
### 1. Legacy Bitvavo Class (Backward Compatibility)
|
812
|
+
|
813
|
+
For existing users migrating from the official SDK:
|
551
814
|
|
552
|
-
|
815
|
+
```python
|
816
|
+
from bitvavo_api_upgraded import Bitvavo
|
553
817
|
|
554
|
-
-
|
555
|
-
|
556
|
-
|
818
|
+
# Drop-in replacement for python_bitvavo_api.bitvavo
|
819
|
+
bitvavo = Bitvavo({'APIKEY': 'key', 'APISECRET': 'secret'})
|
820
|
+
balance = bitvavo.balance({})
|
821
|
+
```
|
822
|
+
|
823
|
+
### 2. New BitvavoClient (Modern Architecture)
|
824
|
+
|
825
|
+
For new projects or those wanting better architecture:
|
826
|
+
|
827
|
+
```python
|
828
|
+
from bitvavo_client import BitvavoClient, BitvavoSettings
|
829
|
+
|
830
|
+
# Modern, typed, modular interface
|
831
|
+
client = BitvavoClient()
|
832
|
+
result = client.public.time()
|
833
|
+
result = client.private.balance()
|
834
|
+
```
|
835
|
+
|
836
|
+
### Migration from Official SDK
|
837
|
+
|
838
|
+
#### Key Changes
|
839
|
+
|
840
|
+
- **Import**: `from bitvavo_api_upgraded import Bitvavo` (instead of `from python_bitvavo_api.bitvavo import Bitvavo`)
|
841
|
+
- **Breaking**: Trading operations require `operatorId` parameter
|
842
|
+
- **Enhanced**: Better error handling and type safety
|
843
|
+
- **New**: Modern `BitvavoClient` interface available
|
844
|
+
- **New**: Multiple API key support for rate limiting
|
557
845
|
- **New**: Keyless access for public endpoints
|
558
846
|
- **New**: Comprehensive dataframe support
|
559
|
-
-
|
560
|
-
- Better configuration management with `.env` support
|
847
|
+
- **New**: Configuration via `.env` files
|
561
848
|
|
562
|
-
|
849
|
+
#### Migration Steps
|
563
850
|
|
564
|
-
1. Update import statements
|
565
|
-
2. Add `operatorId` to trading method calls
|
566
|
-
3. Optional: Migrate to `.env` configuration
|
567
|
-
4. Optional: Configure multiple API keys for better rate limits
|
568
|
-
5. Optional: Enable keyless mode for public endpoint efficiency
|
569
|
-
6. Enjoy improved type hints and error handling!
|
851
|
+
1. **Update import statements**
|
570
852
|
|
571
|
-
|
853
|
+
```python
|
854
|
+
# Old
|
855
|
+
from python_bitvavo_api.bitvavo import Bitvavo
|
572
856
|
|
573
|
-
|
574
|
-
|
575
|
-
bitvavo = Bitvavo({'APIKEY': 'key', 'APISECRET': 'secret'})
|
857
|
+
# New (legacy interface)
|
858
|
+
from bitvavo_api_upgraded import Bitvavo
|
576
859
|
|
577
|
-
# New
|
578
|
-
|
579
|
-
|
580
|
-
{'key': 'key1', 'secret': 'secret1'},
|
581
|
-
{'key': 'key2', 'secret': 'secret2'}
|
582
|
-
]
|
583
|
-
})
|
860
|
+
# New (modern interface)
|
861
|
+
from bitvavo_client import BitvavoClient
|
862
|
+
```
|
584
863
|
|
585
|
-
|
586
|
-
bitvavo = Bitvavo({'PREFER_KEYLESS': True})
|
864
|
+
2. **Add operatorId to trading operations**
|
587
865
|
|
588
|
-
|
589
|
-
|
590
|
-
|
866
|
+
```python
|
867
|
+
# Add operatorId parameter to placeOrder, cancelOrder, etc.
|
868
|
+
order = bitvavo.placeOrder("BTC-EUR", "buy", "limit", {...}, operatorId=12345)
|
869
|
+
```
|
870
|
+
|
871
|
+
3. **Optional: Migrate to modern interface**
|
872
|
+
|
873
|
+
```python
|
874
|
+
# Legacy style
|
875
|
+
bitvavo = Bitvavo({'APIKEY': 'key', 'APISECRET': 'secret'})
|
876
|
+
|
877
|
+
# Modern style
|
878
|
+
client = BitvavoClient(BitvavoSettings(api_key='key', api_secret='secret'))
|
879
|
+
```
|
880
|
+
|
881
|
+
4. **Optional: Use new features**
|
882
|
+
|
883
|
+
```python
|
884
|
+
# Multi-key support
|
885
|
+
bitvavo = Bitvavo({'APIKEYS': [{'key': 'k1', 'secret': 's1'}, {'key': 'k2', 'secret': 's2'}]})
|
886
|
+
|
887
|
+
# Keyless for public endpoints
|
888
|
+
bitvavo = Bitvavo({'PREFER_KEYLESS': True})
|
889
|
+
|
890
|
+
# DataFrame support
|
891
|
+
markets_df = bitvavo.markets({}, output_format='pandas')
|
892
|
+
```
|
893
|
+
|
894
|
+
### Choosing an Interface
|
895
|
+
|
896
|
+
| Feature | Legacy `Bitvavo` | Modern `BitvavoClient` |
|
897
|
+
| -------------------------- | ---------------------- | -------------------------- |
|
898
|
+
| **Backward compatibility** | ✅ Drop-in replacement | ❌ New interface |
|
899
|
+
| **Type safety** | ✅ Typed responses | ✅ Full generics support |
|
900
|
+
| **Error handling** | ✅ Enhanced errors | ✅ Result types + enhanced |
|
901
|
+
| **Modular design** | ❌ Monolithic | ✅ Separated concerns |
|
902
|
+
| **Testing** | ✅ Testable | ✅ Highly testable |
|
903
|
+
| **DataFrame support** | ✅ Via output_format | ✅ Via model preferences |
|
904
|
+
| **Result types** | ❌ Exceptions only | ✅ Success/Failure pattern |
|
905
|
+
| **WebSocket support** | ✅ Full support | 🚧 Coming soon |
|
906
|
+
|
907
|
+
**Recommendation**:
|
908
|
+
|
909
|
+
- Use **Legacy `Bitvavo`** for quick migrations and WebSocket usage
|
910
|
+
- Use **Modern `BitvavoClient`** for new projects requiring clean architecture
|
591
911
|
|
592
912
|
---
|
593
913
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
bitvavo_api_upgraded/__init__.py,sha256=J_HdGBmZOfb1eOydaxsPmXfOIZ58hVa1qAfE6QErUHs,301
|
2
|
+
bitvavo_api_upgraded/bitvavo.py,sha256=_3FRVVPg7_1HrALyGPjcuokCsHF5oz6itN_GKx7yTMo,166155
|
3
|
+
bitvavo_api_upgraded/dataframe_utils.py,sha256=UvcDM0HeE-thUvsm9EjCmddmGBzZ9Puu40UVa0fR_p8,5821
|
4
|
+
bitvavo_api_upgraded/helper_funcs.py,sha256=4oBdQ1xB-C2XkQTmN-refzIzWfO-IUowDSWhOSFdCRU,3212
|
5
|
+
bitvavo_api_upgraded/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
bitvavo_api_upgraded/settings.py,sha256=I1fogU6_kb1hOe_0YDzOgDhzKfnnYFoIR2OXbwtyD4E,5291
|
7
|
+
bitvavo_api_upgraded/type_aliases.py,sha256=SbPBcuKWJZPZ8DSDK-Uycu5O-TUO6ejVaTt_7oyGyIU,1979
|
8
|
+
bitvavo_api_upgraded-4.1.0.dist-info/WHEEL,sha256=Jb20R3Ili4n9P1fcwuLup21eQ5r9WXhs4_qy7VTrgPI,79
|
9
|
+
bitvavo_api_upgraded-4.1.0.dist-info/METADATA,sha256=Go7SrHcYNBc3aZKxf09IBC8nudujCyN_Ze7tZST3mZI,35857
|
10
|
+
bitvavo_api_upgraded-4.1.0.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
bitvavo_api_upgraded/__init__.py,sha256=J_HdGBmZOfb1eOydaxsPmXfOIZ58hVa1qAfE6QErUHs,301
|
2
|
-
bitvavo_api_upgraded/bitvavo.py,sha256=PHN4lPlA2wWB5e6LAOewQLGqN--x_N_BLg1A25z5TxA,164948
|
3
|
-
bitvavo_api_upgraded/dataframe_utils.py,sha256=Jf-2zkYuK5Zs9kiMFJlCmO-OykjZyFIvW2aaMJYPUpo,5792
|
4
|
-
bitvavo_api_upgraded/helper_funcs.py,sha256=4oBdQ1xB-C2XkQTmN-refzIzWfO-IUowDSWhOSFdCRU,3212
|
5
|
-
bitvavo_api_upgraded/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
bitvavo_api_upgraded/settings.py,sha256=br1oqN3K8OoimmKa1JlvzMgpYNHfr2kK2f14tC72dl8,5311
|
7
|
-
bitvavo_api_upgraded/type_aliases.py,sha256=EOd3LhruyM1aZYn4xyKYhdoSql8mZH88acN0qGzMMck,1992
|
8
|
-
bitvavo_api_upgraded-4.0.0.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
|
9
|
-
bitvavo_api_upgraded-4.0.0.dist-info/METADATA,sha256=v5Xi6jH4qRBhuZVS_TtNv23Jd6HnmRsXMXKZvV-_15s,25510
|
10
|
-
bitvavo_api_upgraded-4.0.0.dist-info/RECORD,,
|