bitvavo-api-upgraded 2.0.0__py3-none-any.whl → 2.3.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.
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import datetime as dt
4
5
  import hashlib
5
6
  import hmac
@@ -10,20 +11,27 @@ from threading import Thread
10
11
  from typing import Any, Callable
11
12
 
12
13
  import websocket as ws_lib
14
+ from deprecated import deprecated
13
15
  from requests import delete, get, post, put
14
16
  from structlog.stdlib import get_logger
15
17
  from websocket import WebSocketApp # missing stubs for WebSocketApp
16
18
 
19
+ from bitvavo_api_upgraded.dataframe_utils import convert_candles_to_dataframe, convert_to_dataframe
17
20
  from bitvavo_api_upgraded.helper_funcs import configure_loggers, time_ms, time_to_wait
18
- from bitvavo_api_upgraded.settings import bitvavo_upgraded_settings
19
- from bitvavo_api_upgraded.type_aliases import anydict, errordict, intdict, ms, s_f, strdict, strintdict
21
+ from bitvavo_api_upgraded.settings import bitvavo_settings, bitvavo_upgraded_settings
22
+ from bitvavo_api_upgraded.type_aliases import OutputFormat, anydict, errordict, intdict, ms, s_f, strdict, strintdict
20
23
 
21
24
  configure_loggers()
22
25
 
23
26
  logger = get_logger(__name__)
24
27
 
25
28
 
29
+ @deprecated(version="2.2.0", reason="Use create_signature instead")
26
30
  def createSignature(timestamp: ms, method: str, url: str, body: anydict | None, api_secret: str) -> str:
31
+ return create_signature(timestamp, method, url, body, api_secret)
32
+
33
+
34
+ def create_signature(timestamp: ms, method: str, url: str, body: anydict | None, api_secret: str) -> str:
27
35
  string = f"{timestamp}{method}/v2{url}"
28
36
  if body is not None and len(body.keys()) > 0:
29
37
  string += json.dumps(body, separators=(",", ":"))
@@ -31,7 +39,12 @@ def createSignature(timestamp: ms, method: str, url: str, body: anydict | None,
31
39
  return signature
32
40
 
33
41
 
42
+ @deprecated(version="2.2.0", reason="Use create_postfix instead")
34
43
  def createPostfix(options: anydict | None) -> str:
44
+ return create_postfix(options)
45
+
46
+
47
+ def create_postfix(options: anydict | None) -> str:
35
48
  """Generate a URL postfix, based on the `options` dict.
36
49
 
37
50
  ---
@@ -65,18 +78,37 @@ def _epoch_millis(dt: dt.datetime) -> int:
65
78
  return int(dt.timestamp() * 1000)
66
79
 
67
80
 
81
+ @deprecated(version="2.2.0", reason="Use asks_compare instead")
68
82
  def asksCompare(a: float, b: float) -> bool:
83
+ return asks_compare(a, b)
84
+
85
+
86
+ def asks_compare(a: float, b: float) -> bool:
69
87
  return a < b
70
88
 
71
89
 
90
+ @deprecated(version="2.2.0", reason="Use bids_compare instead")
72
91
  def bidsCompare(a: float, b: float) -> bool:
92
+ return bids_compare(a, b)
93
+
94
+
95
+ def bids_compare(a: float, b: float) -> bool:
73
96
  return a > b
74
97
 
75
98
 
99
+ @deprecated(version="2.2.0", reason="Use sort_and_insert instead")
76
100
  def sortAndInsert(
77
101
  asks_or_bids: list[list[str]],
78
102
  update: list[list[str]],
79
103
  compareFunc: Callable[[float, float], bool],
104
+ ) -> list[list[str]] | errordict:
105
+ return sort_and_insert(asks_or_bids, update, compareFunc)
106
+
107
+
108
+ def sort_and_insert(
109
+ asks_or_bids: list[list[str]],
110
+ update: list[list[str]],
111
+ compareFunc: Callable[[float, float], bool],
80
112
  ) -> list[list[str]] | errordict:
81
113
  for updateEntry in update:
82
114
  entrySet: bool = False
@@ -99,7 +131,12 @@ def sortAndInsert(
99
131
  return asks_or_bids
100
132
 
101
133
 
134
+ @deprecated(version="2.2.0", reason="Use process_local_book instead")
102
135
  def processLocalBook(ws: Bitvavo.WebSocketAppFacade, message: anydict) -> None:
136
+ return process_local_book(ws, message)
137
+
138
+
139
+ def process_local_book(ws: Bitvavo.WebSocketAppFacade, message: anydict) -> None:
103
140
  market: str = ""
104
141
  if "action" in message:
105
142
  if message["action"] == "getBook":
@@ -113,10 +150,10 @@ def processLocalBook(ws: Bitvavo.WebSocketAppFacade, message: anydict) -> None:
113
150
 
114
151
  if message["nonce"] != ws.localBook[market]["nonce"] + 1:
115
152
  # I think I've fixed this, by looking at the other Bitvavo repos (search for 'nonce' or '!=' 😆)
116
- ws.subscriptionBook(market, ws.callbacks[market])
153
+ ws.subscription_book(market, ws.callbacks[market])
117
154
  return
118
- ws.localBook[market]["bids"] = sortAndInsert(ws.localBook[market]["bids"], message["bids"], bidsCompare)
119
- ws.localBook[market]["asks"] = sortAndInsert(ws.localBook[market]["asks"], message["asks"], asksCompare)
155
+ ws.localBook[market]["bids"] = sort_and_insert(ws.localBook[market]["bids"], message["bids"], bids_compare)
156
+ ws.localBook[market]["asks"] = sort_and_insert(ws.localBook[market]["asks"], message["asks"], asks_compare)
120
157
  ws.localBook[market]["nonce"] = message["nonce"]
121
158
 
122
159
  if market != "":
@@ -216,6 +253,7 @@ class Bitvavo:
216
253
  Example code to get your started:
217
254
 
218
255
  ```python
256
+ # Single API key (backward compatible)
219
257
  bitvavo = Bitvavo(
220
258
  {
221
259
  "APIKEY": "$YOUR_API_KEY",
@@ -227,24 +265,204 @@ class Bitvavo:
227
265
  },
228
266
  )
229
267
  time_dict = bitvavo.time()
268
+
269
+ # Multiple API keys with keyless preference
270
+ bitvavo = Bitvavo(
271
+ {
272
+ "APIKEYS": [
273
+ {"key": "$YOUR_API_KEY_1", "secret": "$YOUR_API_SECRET_1"},
274
+ {"key": "$YOUR_API_KEY_2", "secret": "$YOUR_API_SECRET_2"},
275
+ {"key": "$YOUR_API_KEY_3", "secret": "$YOUR_API_SECRET_3"},
276
+ ],
277
+ "PREFER_KEYLESS": True, # Use keyless requests first, then API keys
278
+ "RESTURL": "https://api.bitvavo.com/v2",
279
+ "WSURL": "wss://ws.bitvavo.com/v2/",
280
+ "ACCESSWINDOW": 10000,
281
+ "DEBUGGING": True,
282
+ },
283
+ )
284
+ time_dict = bitvavo.time()
285
+
286
+ # Keyless only (no API keys)
287
+ bitvavo = Bitvavo(
288
+ {
289
+ "PREFER_KEYLESS": True,
290
+ "RESTURL": "https://api.bitvavo.com/v2",
291
+ "WSURL": "wss://ws.bitvavo.com/v2/",
292
+ "ACCESSWINDOW": 10000,
293
+ "DEBUGGING": True,
294
+ },
295
+ )
296
+ markets = bitvavo.markets() # Only public endpoints will work
230
297
  ```
231
298
  """
232
299
 
233
- def __init__(self, options: dict[str, str | int] | None = None) -> None:
300
+ def __init__(self, options: dict[str, str | int | list[dict[str, str]]] | None = None) -> None:
234
301
  if options is None:
235
302
  options = {}
236
303
  _options = {k.upper(): v for k, v in options.items()}
237
- self.base: str = str(_options.get("RESTURL", "https://api.bitvavo.com/v2"))
238
- self.wsUrl: str = str(_options.get("WSURL", "wss://ws.bitvavo.com/v2/"))
239
- self.ACCESSWINDOW = ms(_options.get("ACCESSWINDOW", 10000))
240
- self.APIKEY = str(_options.get("APIKEY", ""))
241
- self.APISECRET = str(_options.get("APISECRET", ""))
242
- self.rateLimitRemaining: int = 1000
304
+
305
+ # Options take precedence over settings
306
+ self.base: str = str(_options.get("RESTURL", bitvavo_settings.RESTURL))
307
+ self.wsUrl: str = str(_options.get("WSURL", bitvavo_settings.WSURL))
308
+ self.ACCESSWINDOW: int = int(_options.get("ACCESSWINDOW", bitvavo_settings.ACCESSWINDOW))
309
+
310
+ # Support for multiple API keys - options take absolute precedence
311
+ if "APIKEY" in _options and "APISECRET" in _options:
312
+ # Single API key explicitly provided in options - takes precedence
313
+ single_key = str(_options["APIKEY"])
314
+ single_secret = str(_options["APISECRET"])
315
+ self.api_keys: list[dict[str, str]] = [{"key": single_key, "secret": single_secret}]
316
+ elif "APIKEYS" in _options:
317
+ # Multiple API keys provided in options - takes precedence
318
+ api_keys = _options["APIKEYS"]
319
+ if isinstance(api_keys, list) and api_keys:
320
+ self.api_keys = api_keys
321
+ else:
322
+ self.api_keys = []
323
+ else:
324
+ # Fall back to settings only if no API key options provided
325
+ api_keys = bitvavo_settings.APIKEYS
326
+ if isinstance(api_keys, list) and api_keys:
327
+ self.api_keys = api_keys
328
+ else:
329
+ # Single API key from settings (backward compatibility)
330
+ single_key = str(bitvavo_settings.APIKEY)
331
+ single_secret = str(bitvavo_settings.APISECRET)
332
+ if single_key and single_secret:
333
+ self.api_keys = [{"key": single_key, "secret": single_secret}]
334
+ else:
335
+ self.api_keys = []
336
+
337
+ # Current API key index and keyless preference - options take precedence
338
+ self.current_api_key_index: int = 0
339
+ self.prefer_keyless: bool = bool(_options.get("PREFER_KEYLESS", bitvavo_upgraded_settings.PREFER_KEYLESS))
340
+
341
+ # Rate limiting per API key (keyless has index -1)
342
+ self.rate_limits: dict[int, dict[str, int | ms]] = {}
343
+ # Get default rate limit from options or settings
344
+ default_rate_limit_option = _options.get("DEFAULT_RATE_LIMIT", bitvavo_upgraded_settings.DEFAULT_RATE_LIMIT)
345
+ default_rate_limit = (
346
+ int(default_rate_limit_option)
347
+ if isinstance(default_rate_limit_option, (int, str))
348
+ else bitvavo_upgraded_settings.DEFAULT_RATE_LIMIT
349
+ )
350
+
351
+ self.rate_limits[-1] = {"remaining": default_rate_limit, "resetAt": ms(0)} # keyless
352
+ for i in range(len(self.api_keys)):
353
+ self.rate_limits[i] = {"remaining": default_rate_limit, "resetAt": ms(0)}
354
+
355
+ # Legacy properties for backward compatibility
356
+ self.APIKEY: str = self.api_keys[0]["key"] if self.api_keys else ""
357
+ self.APISECRET: str = self.api_keys[0]["secret"] if self.api_keys else ""
358
+ self._current_api_key: str = self.APIKEY
359
+ self._current_api_secret: str = self.APISECRET
360
+ self.rateLimitRemaining: int = default_rate_limit
243
361
  self.rateLimitResetAt: ms = 0
244
- # TODO(NostraDavid): for v2: remove this functionality - logger.debug is a level that can be set
245
- self.debugging = bool(_options.get("DEBUGGING", False))
246
362
 
363
+ # Options take precedence over settings for debugging
364
+ self.debugging: bool = bool(_options.get("DEBUGGING", bitvavo_settings.DEBUGGING))
365
+
366
+ def get_best_api_key_config(self, rateLimitingWeight: int = 1) -> tuple[str, str, int]:
367
+ """
368
+ Get the best API key configuration to use for a request.
369
+
370
+ Returns:
371
+ tuple: (api_key, api_secret, key_index) where key_index is -1 for keyless
372
+ """
373
+ # If prefer keyless and keyless has enough rate limit, use keyless
374
+ if self.prefer_keyless and self._has_rate_limit_available(-1, rateLimitingWeight):
375
+ return "", "", -1
376
+
377
+ # Try to find an API key with enough rate limit
378
+ for i in range(len(self.api_keys)):
379
+ if self._has_rate_limit_available(i, rateLimitingWeight):
380
+ return self.api_keys[i]["key"], self.api_keys[i]["secret"], i
381
+
382
+ # If keyless is available, use it as fallback
383
+ if self._has_rate_limit_available(-1, rateLimitingWeight):
384
+ return "", "", -1
385
+
386
+ # No keys available, use current key and let rate limiting handle the wait
387
+ if self.api_keys:
388
+ return (
389
+ self.api_keys[self.current_api_key_index]["key"],
390
+ self.api_keys[self.current_api_key_index]["secret"],
391
+ self.current_api_key_index,
392
+ )
393
+ return "", "", -1
394
+
395
+ def _has_rate_limit_available(self, key_index: int, weight: int) -> bool:
396
+ """Check if a specific API key (or keyless) has enough rate limit."""
397
+ if key_index not in self.rate_limits:
398
+ return False
399
+ remaining = self.rate_limits[key_index]["remaining"]
400
+ return (remaining - weight) > bitvavo_upgraded_settings.RATE_LIMITING_BUFFER
401
+
402
+ def _update_rate_limit_for_key(self, key_index: int, response: anydict | errordict) -> None:
403
+ """Update rate limit for a specific API key index."""
404
+ if key_index not in self.rate_limits:
405
+ self.rate_limits[key_index] = {"remaining": 1000, "resetAt": ms(0)}
406
+
407
+ if "errorCode" in response and response["errorCode"] == 105: # noqa: PLR2004
408
+ self.rate_limits[key_index]["remaining"] = 0
409
+ # rateLimitResetAt is a value that's stripped from a string.
410
+ reset_time_str = str(response.get("error", "")).split(" at ")
411
+ if len(reset_time_str) > 1:
412
+ try:
413
+ reset_time = ms(int(reset_time_str[1].split(".")[0]))
414
+ self.rate_limits[key_index]["resetAt"] = reset_time
415
+ except (ValueError, IndexError):
416
+ # Fallback to current time + 60 seconds if parsing fails
417
+ self.rate_limits[key_index]["resetAt"] = ms(time_ms() + 60000)
418
+ else:
419
+ self.rate_limits[key_index]["resetAt"] = ms(time_ms() + 60000)
420
+
421
+ timeToWait = time_to_wait(ms(self.rate_limits[key_index]["resetAt"]))
422
+ key_name = f"API_KEY_{key_index}" if key_index >= 0 else "KEYLESS"
423
+ logger.warning(
424
+ "api-key-banned",
425
+ info={
426
+ "key_name": key_name,
427
+ "wait_time_seconds": timeToWait + 1,
428
+ "until": (dt.datetime.now(tz=dt.timezone.utc) + dt.timedelta(seconds=timeToWait + 1)).isoformat(),
429
+ },
430
+ )
431
+
432
+ if "bitvavo-ratelimit-remaining" in response:
433
+ with contextlib.suppress(ValueError, TypeError):
434
+ self.rate_limits[key_index]["remaining"] = int(response["bitvavo-ratelimit-remaining"])
435
+
436
+ if "bitvavo-ratelimit-resetat" in response:
437
+ with contextlib.suppress(ValueError, TypeError):
438
+ self.rate_limits[key_index]["resetAt"] = ms(int(response["bitvavo-ratelimit-resetat"]))
439
+
440
+ def _sleep_for_key(self, key_index: int) -> None:
441
+ """Sleep until the specified API key's rate limit resets."""
442
+ if key_index not in self.rate_limits:
443
+ return
444
+
445
+ reset_at = ms(self.rate_limits[key_index]["resetAt"])
446
+ napTime = time_to_wait(reset_at)
447
+ key_name = f"API_KEY_{key_index}" if key_index >= 0 else "KEYLESS"
448
+
449
+ logger.warning(
450
+ "rate-limit-reached", key_name=key_name, rateLimitRemaining=self.rate_limits[key_index]["remaining"]
451
+ )
452
+ logger.info(
453
+ "napping-until-reset",
454
+ key_name=key_name,
455
+ napTime=napTime,
456
+ currentTime=dt.datetime.now(tz=dt.timezone.utc).isoformat(),
457
+ targetDatetime=dt.datetime.fromtimestamp(reset_at / 1000.0, tz=dt.timezone.utc).isoformat(),
458
+ )
459
+ time.sleep(napTime + 1) # +1 to add a tiny bit of buffer time
460
+
461
+ @deprecated(version="2.2.0", reason="Use calc_lag instead")
247
462
  def calcLag(self) -> ms:
463
+ return self.calc_lag()
464
+
465
+ def calc_lag(self) -> ms:
248
466
  """
249
467
  Calculate the time difference between the client and server; use this value with BITVAVO_API_UPGRADED_LAG,
250
468
  when you make an api call, to precent 304 errors.
@@ -266,8 +484,12 @@ class Bitvavo:
266
484
 
267
485
  return ms(sum(lag_list) / len(lag_list))
268
486
 
487
+ @deprecated(version="2.2.0", reason="Use get_remaining_limit instead")
269
488
  def getRemainingLimit(self) -> int:
270
- """Get the remaing rate limit
489
+ return self.get_remaining_limit()
490
+
491
+ def get_remaining_limit(self) -> int:
492
+ """Get the remaining rate limit
271
493
 
272
494
  ---
273
495
  Returns:
@@ -277,21 +499,31 @@ class Bitvavo:
277
499
  """
278
500
  return self.rateLimitRemaining
279
501
 
502
+ @deprecated(version="2.2.0", reason="Use get_remaining_limit instead")
280
503
  def updateRateLimit(self, response: anydict | errordict) -> None:
504
+ return self.update_rate_limit(response)
505
+
506
+ def update_rate_limit(self, response: anydict | errordict) -> None:
281
507
  """
282
508
  Update the rate limited
283
509
 
284
510
  If you're banned, use the errordict to sleep until you're not banned
285
511
 
286
512
  If you're not banned, then use the received headers to update the variables.
513
+
514
+ This method maintains backward compatibility by updating the legacy properties.
287
515
  """
516
+ # Update rate limit for the current API key being used
517
+ current_key = self.current_api_key_index if self.APIKEY else -1
518
+ self._update_rate_limit_for_key(current_key, response)
519
+
520
+ # Update legacy properties for backward compatibility
521
+ if current_key in self.rate_limits:
522
+ self.rateLimitRemaining = int(self.rate_limits[current_key]["remaining"])
523
+ self.rateLimitResetAt = ms(self.rate_limits[current_key]["resetAt"])
524
+
525
+ # Handle ban with sleep (legacy behavior)
288
526
  if "errorCode" in response and response["errorCode"] == 105: # noqa: PLR2004
289
- self.rateLimitRemaining = 0
290
- # rateLimitResetAt is a value that's stripped from a string.
291
- # Kind of a terrible way to pass that information, but eh, whatever, I guess...
292
- # Anyway, here is the string that's being pulled apart:
293
- # "Your IP or API key has been banned for not respecting the rate limit. The ban expires at ${expiryInMs}""
294
- self.rateLimitResetAt = ms(response["error"].split(" at ")[1].split(".")[0])
295
527
  timeToWait = time_to_wait(self.rateLimitResetAt)
296
528
  logger.warning(
297
529
  "banned",
@@ -302,15 +534,19 @@ class Bitvavo:
302
534
  )
303
535
  logger.info("napping-until-ban-lifted")
304
536
  time.sleep(timeToWait + 1) # plus one second to ENSURE we're able to run again.
305
- if "bitvavo-ratelimit-remaining" in response:
306
- self.rateLimitRemaining = int(response["bitvavo-ratelimit-remaining"])
307
- if "bitvavo-ratelimit-resetat" in response:
308
- self.rateLimitResetAt = int(response["bitvavo-ratelimit-resetat"])
309
537
 
538
+ @deprecated(version="2.2.0", reason="Use public_request instead")
310
539
  def publicRequest(
311
540
  self,
312
541
  url: str,
313
542
  rateLimitingWeight: int = 1,
543
+ ) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | errordict:
544
+ return self.public_request(url, rateLimitingWeight)
545
+
546
+ def public_request(
547
+ self,
548
+ url: str,
549
+ rateLimitingWeight: int = 1,
314
550
  ) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | errordict:
315
551
  """Execute a request to the public part of the API; no API key and/or SECRET necessary.
316
552
  Will return the reponse as one of three types.
@@ -330,22 +566,39 @@ class Bitvavo:
330
566
  list[list[str]]
331
567
  ```
332
568
  """
333
- if (self.rateLimitRemaining - rateLimitingWeight) <= bitvavo_upgraded_settings.RATE_LIMITING_BUFFER:
334
- self.sleep_until_can_continue()
569
+ # Get the best API key configuration (keyless preferred, then available keys)
570
+ api_key, api_secret, key_index = self.get_best_api_key_config(rateLimitingWeight)
571
+
572
+ # Check if we need to wait for rate limit
573
+ if not self._has_rate_limit_available(key_index, rateLimitingWeight):
574
+ self._sleep_for_key(key_index)
575
+
576
+ # Update current API key for legacy compatibility
577
+ if api_key:
578
+ self._current_api_key = api_key
579
+ self._current_api_secret = api_secret
580
+ self.current_api_key_index = key_index
581
+ else:
582
+ # Using keyless
583
+ self._current_api_key = ""
584
+ self._current_api_secret = ""
585
+
335
586
  if self.debugging:
336
587
  logger.debug(
337
588
  "api-request",
338
589
  info={
339
590
  "url": url,
340
- "with_api_key": bool(self.APIKEY != ""),
591
+ "with_api_key": bool(api_key != ""),
341
592
  "public_or_private": "public",
593
+ "key_index": key_index,
342
594
  },
343
595
  )
344
- if self.APIKEY != "":
596
+
597
+ if api_key:
345
598
  now = time_ms() + bitvavo_upgraded_settings.LAG
346
- sig = createSignature(now, "GET", url.replace(self.base, ""), None, self.APISECRET)
599
+ sig = create_signature(now, "GET", url.replace(self.base, ""), None, api_secret)
347
600
  headers = {
348
- "bitvavo-access-key": self.APIKEY,
601
+ "bitvavo-access-key": api_key,
349
602
  "bitvavo-access-signature": sig,
350
603
  "bitvavo-access-timestamp": str(now),
351
604
  "bitvavo-access-window": str(self.ACCESSWINDOW),
@@ -353,12 +606,19 @@ class Bitvavo:
353
606
  r = get(url, headers=headers, timeout=(self.ACCESSWINDOW / 1000))
354
607
  else:
355
608
  r = get(url, timeout=(self.ACCESSWINDOW / 1000))
609
+
610
+ # Update rate limit for the specific key used
356
611
  if "error" in r.json():
357
- self.updateRateLimit(r.json())
612
+ self._update_rate_limit_for_key(key_index, r.json())
358
613
  else:
359
- self.updateRateLimit(dict(r.headers))
614
+ self._update_rate_limit_for_key(key_index, dict(r.headers))
615
+
616
+ # Also update legacy rate limit tracking
617
+ self.update_rate_limit(r.json() if "error" in r.json() else dict(r.headers))
618
+
360
619
  return r.json() # type:ignore[no-any-return]
361
620
 
621
+ @deprecated(version="2.2.0", reason="Use private_request instead")
362
622
  def privateRequest(
363
623
  self,
364
624
  endpoint: str,
@@ -366,7 +626,17 @@ class Bitvavo:
366
626
  body: anydict | None = None,
367
627
  method: str = "GET",
368
628
  rateLimitingWeight: int = 1,
369
- ) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | Any | errordict:
629
+ ) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | errordict:
630
+ return self.private_request(endpoint, postfix, body, method, rateLimitingWeight)
631
+
632
+ def private_request(
633
+ self,
634
+ endpoint: str,
635
+ postfix: str,
636
+ body: anydict | None = None,
637
+ method: str = "GET",
638
+ rateLimitingWeight: int = 1,
639
+ ) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | errordict:
370
640
  """Execute a request to the private part of the API. API key and SECRET are required.
371
641
  Will return the reponse as one of three types.
372
642
 
@@ -389,14 +659,33 @@ class Bitvavo:
389
659
  list[list[str]]
390
660
  ```
391
661
  """
392
- if (self.rateLimitRemaining - rateLimitingWeight) <= bitvavo_upgraded_settings.RATE_LIMITING_BUFFER:
393
- self.sleep_until_can_continue()
394
- # if this method breaks: add `= {}` after `body: dict`
662
+ # Private requests require an API key, so get the best available one
663
+ api_key, api_secret, key_index = self.get_best_api_key_config(rateLimitingWeight)
664
+
665
+ # If no API keys available, use the configured one (may fail)
666
+ if not api_key and self.api_keys:
667
+ api_key = self.api_keys[self.current_api_key_index]["key"]
668
+ api_secret = self.api_keys[self.current_api_key_index]["secret"]
669
+ key_index = self.current_api_key_index
670
+ elif not api_key:
671
+ # No API keys configured at all
672
+ api_key = self.APIKEY
673
+ api_secret = self.APISECRET
674
+ key_index = 0 if api_key else -1
675
+
676
+ # Check if we need to wait for rate limit
677
+ if not self._has_rate_limit_available(key_index, rateLimitingWeight):
678
+ self._sleep_for_key(key_index)
679
+
680
+ # Update current API key for legacy compatibility
681
+ self._current_api_key = api_key
682
+ self._current_api_secret = api_secret
683
+
395
684
  now = time_ms() + bitvavo_upgraded_settings.LAG
396
- sig = createSignature(now, method, (endpoint + postfix), body, self.APISECRET)
685
+ sig = create_signature(now, method, (endpoint + postfix), body, api_secret)
397
686
  url = self.base + endpoint + postfix
398
687
  headers = {
399
- "bitvavo-access-key": self.APIKEY,
688
+ "bitvavo-access-key": api_key,
400
689
  "bitvavo-access-signature": sig,
401
690
  "bitvavo-access-timestamp": str(now),
402
691
  "bitvavo-access-window": str(self.ACCESSWINDOW),
@@ -406,9 +695,10 @@ class Bitvavo:
406
695
  "api-request",
407
696
  info={
408
697
  "url": url,
409
- "with_api_key": bool(self.APIKEY != ""),
698
+ "with_api_key": bool(api_key != ""),
410
699
  "public_or_private": "private",
411
700
  "method": method,
701
+ "key_index": key_index,
412
702
  },
413
703
  )
414
704
  if method == "DELETE":
@@ -419,10 +709,16 @@ class Bitvavo:
419
709
  r = put(url, headers=headers, json=body, timeout=(self.ACCESSWINDOW / 1000))
420
710
  else: # method == "GET"
421
711
  r = get(url, headers=headers, timeout=(self.ACCESSWINDOW / 1000))
712
+
713
+ # Update rate limit for the specific key used
422
714
  if "error" in r.json():
423
- self.updateRateLimit(r.json())
715
+ self._update_rate_limit_for_key(key_index, r.json())
424
716
  else:
425
- self.updateRateLimit(dict(r.headers))
717
+ self._update_rate_limit_for_key(key_index, dict(r.headers))
718
+
719
+ # Also update legacy rate limit tracking
720
+ self.update_rate_limit(r.json() if "error" in r.json() else dict(r.headers))
721
+
426
722
  return r.json()
427
723
 
428
724
  def sleep_until_can_continue(self) -> None:
@@ -455,9 +751,13 @@ class Bitvavo:
455
751
  {"time": 1539180275424 }
456
752
  ```
457
753
  """
458
- return self.publicRequest(f"{self.base}/time") # type: ignore[return-value]
754
+ return self.public_request(f"{self.base}/time") # type: ignore[return-value]
459
755
 
460
- def markets(self, options: strdict | None = None) -> list[anydict] | anydict | errordict:
756
+ def markets(
757
+ self,
758
+ options: strdict | None = None,
759
+ output_format: OutputFormat = OutputFormat.DICT,
760
+ ) -> list[anydict] | anydict | errordict | Any:
461
761
  """Get all available markets with some meta-information, unless options is given a `market` key.
462
762
  Then you will get a single market, instead of a list of markets.
463
763
 
@@ -474,6 +774,23 @@ class Bitvavo:
474
774
  options={} # returns all markets
475
775
  options={"market": "BTC-EUR"} # returns only the BTC-EUR market
476
776
  # If you want multiple markets, but not all, make multiple calls
777
+
778
+ # Output format selection:
779
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
780
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
781
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
782
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
783
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
784
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
785
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
786
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
787
+ output_format=OutputFormat.IBIS # Returns Ibis expression
788
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
789
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
790
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
791
+
792
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
793
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
477
794
  ```
478
795
 
479
796
  ---
@@ -485,6 +802,7 @@ class Bitvavo:
485
802
  ---
486
803
  Returns:
487
804
  ```python
805
+ # When output_format=OutputFormat.DICT (default):
488
806
  [
489
807
  {
490
808
  "market": "BTC-EUR",
@@ -504,12 +822,22 @@ class Bitvavo:
504
822
  ]
505
823
  }
506
824
  ]
825
+
826
+ # When output_format is any DataFrame format (pandas, polars, cudf, etc.):
827
+ # Returns a DataFrame with columns: market, status, base, quote, pricePrecision,
828
+ # minOrderInQuoteAsset, minOrderInBaseAsset, orderTypes
829
+ # The specific DataFrame type depends on the selected format.
507
830
  ```
508
831
  """
509
- postfix = createPostfix(options)
510
- return self.publicRequest(f"{self.base}/markets{postfix}") # type: ignore[return-value]
832
+ postfix = create_postfix(options)
833
+ result = self.public_request(f"{self.base}/markets{postfix}") # type: ignore[return-value]
834
+ return convert_to_dataframe(result, output_format)
511
835
 
512
- def assets(self, options: strdict | None = None) -> list[anydict] | anydict:
836
+ def assets(
837
+ self,
838
+ options: strdict | None = None,
839
+ output_format: OutputFormat = OutputFormat.DICT,
840
+ ) -> list[anydict] | anydict | Any:
513
841
  """Get all available assets, unless `options` is given a `symbol` key.
514
842
  Then you will get a single asset, instead of a list of assets.
515
843
 
@@ -527,6 +855,23 @@ class Bitvavo:
527
855
  # pick one
528
856
  options={} # returns all assets
529
857
  options={"symbol": "BTC"} # returns a single asset (the one of Bitcoin)
858
+
859
+ # Output format selection:
860
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
861
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
862
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
863
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
864
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
865
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
866
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
867
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
868
+ output_format=OutputFormat.IBIS # Returns Ibis expression
869
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
870
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
871
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
872
+
873
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
874
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
530
875
  ```
531
876
 
532
877
  ---
@@ -538,6 +883,7 @@ class Bitvavo:
538
883
  ---
539
884
  Returns:
540
885
  ```python
886
+ # When output_format=OutputFormat.DICT (default):
541
887
  [
542
888
  {
543
889
  "symbol": "BTC",
@@ -553,10 +899,17 @@ class Bitvavo:
553
899
  "message": ""
554
900
  }
555
901
  ]
902
+
903
+ # When output_format is any DataFrame format (pandas, polars, cudf, etc.):
904
+ # Returns a DataFrame with columns: symbol, name, decimals, depositFee,
905
+ # depositConfirmations, depositStatus, withdrawalFee, withdrawalMinAmount,
906
+ # withdrawalStatus, networks, message
907
+ # The specific DataFrame type depends on the selected format.
556
908
  ```
557
909
  """
558
- postfix = createPostfix(options)
559
- return self.publicRequest(f"{self.base}/assets{postfix}") # type: ignore[return-value]
910
+ postfix = create_postfix(options)
911
+ result = self.public_request(f"{self.base}/assets{postfix}") # type: ignore[return-value]
912
+ return convert_to_dataframe(result, output_format)
560
913
 
561
914
  def book(self, market: str, options: intdict | None = None) -> dict[str, str | int | list[str]] | errordict:
562
915
  """Get a book (with two lists: asks and bids, as they're called)
@@ -600,10 +953,24 @@ class Bitvavo:
600
953
  assert result == 714.48 # EUR can be gained from this bid if it's sold (minus the fee)
601
954
  ```
602
955
  """
603
- postfix = createPostfix(options)
604
- return self.publicRequest(f"{self.base}/{market}/book{postfix}") # type: ignore[return-value]
956
+ postfix = create_postfix(options)
957
+ return self.public_request(f"{self.base}/{market}/book{postfix}") # type: ignore[return-value]
958
+
959
+ @deprecated(version="2.2.0", reason="Use public_trades instead")
960
+ def publicTrades(
961
+ self,
962
+ market: str,
963
+ options: strintdict | None = None,
964
+ output_format: OutputFormat = OutputFormat.DICT,
965
+ ) -> list[anydict] | errordict | Any:
966
+ return self.public_trades(market, options, output_format)
605
967
 
606
- def publicTrades(self, market: str, options: strintdict | None = None) -> list[anydict] | errordict:
968
+ def public_trades(
969
+ self,
970
+ market: str,
971
+ options: strintdict | None = None,
972
+ output_format: OutputFormat = OutputFormat.DICT,
973
+ ) -> list[anydict] | errordict | Any:
607
974
  """Publically available trades
608
975
 
609
976
  ---
@@ -628,6 +995,23 @@ class Bitvavo:
628
995
  "tradeIdFrom": "" # if you get a list and want everything AFTER a certain id, put that id here
629
996
  "tradeIdTo": "" # if you get a list and want everything BEFORE a certain id, put that id here
630
997
  }
998
+
999
+ # Output format selection:
1000
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
1001
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
1002
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
1003
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
1004
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
1005
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
1006
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
1007
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
1008
+ output_format=OutputFormat.IBIS # Returns Ibis expression
1009
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
1010
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
1011
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
1012
+
1013
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
1014
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
631
1015
  ```
632
1016
 
633
1017
  ---
@@ -639,6 +1023,7 @@ class Bitvavo:
639
1023
  ---
640
1024
  Returns:
641
1025
  ```python
1026
+ # When output_format='dict' (default):
642
1027
  [
643
1028
  {
644
1029
  "timestamp": 1542967486256,
@@ -648,10 +1033,14 @@ class Bitvavo:
648
1033
  "side": "sell"
649
1034
  }
650
1035
  ]
1036
+
1037
+ # When output_format is any DataFrame format:
1038
+ # Returns the above data as a DataFrame in the requested format (pandas, polars, etc.)
651
1039
  ```
652
1040
  """
653
- postfix = createPostfix(options)
654
- return self.publicRequest(f"{self.base}/{market}/trades{postfix}", 5) # type: ignore[return-value]
1041
+ postfix = create_postfix(options)
1042
+ result = self.public_request(f"{self.base}/{market}/trades{postfix}", 5) # type: ignore[return-value]
1043
+ return convert_to_dataframe(result, output_format)
655
1044
 
656
1045
  def candles(
657
1046
  self,
@@ -661,7 +1050,8 @@ class Bitvavo:
661
1050
  limit: int | None = None,
662
1051
  start: dt.datetime | None = None,
663
1052
  end: dt.datetime | None = None,
664
- ) -> list[list[str]] | errordict:
1053
+ output_format: OutputFormat = OutputFormat.DICT,
1054
+ ) -> list[list[str]] | errordict | Any:
665
1055
  """Get up to 1440 candles for a market, with a specific interval (candle size)
666
1056
 
667
1057
  Extra reading material: https://en.wikipedia.org/wiki/Candlestick_chart
@@ -684,6 +1074,23 @@ class Bitvavo:
684
1074
  "start": int timestamp in ms >= 0
685
1075
  "end": int timestamp in ms <= 8640000000000000
686
1076
  }
1077
+
1078
+ # Output format selection:
1079
+ output_format=OutputFormat.DICT # Default: returns standard Python list/dict
1080
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
1081
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
1082
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
1083
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
1084
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
1085
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
1086
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
1087
+ output_format=OutputFormat.IBIS # Returns Ibis expression
1088
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
1089
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
1090
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
1091
+
1092
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
1093
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
687
1094
  ```
688
1095
 
689
1096
  ---
@@ -695,6 +1102,7 @@ class Bitvavo:
695
1102
  ---
696
1103
  Returns:
697
1104
  ```python
1105
+ # When output_format='dict' (default):
698
1106
  [
699
1107
  # For whatever reason, you're getting a list of lists; no keys,
700
1108
  # so here is the explanation of what's what.
@@ -705,6 +1113,11 @@ class Bitvavo:
705
1113
  [1640804400000, "41937", "41955", "41449", "41540", "23.64498292"],
706
1114
  [1640800800000, "41955", "42163", "41807", "41939", "10.40093845"],
707
1115
  ]
1116
+
1117
+ # When output_format is any DataFrame format:
1118
+ # Returns the above data as a DataFrame in the requested format (pandas, polars, etc.)
1119
+ # with columns: timestamp, open, high, low, close, volume
1120
+ # timestamp is converted to datetime, numeric columns to float
708
1121
  ```
709
1122
  """
710
1123
  options = _default(options, {})
@@ -715,10 +1128,23 @@ class Bitvavo:
715
1128
  options["start"] = _epoch_millis(start)
716
1129
  if end is not None:
717
1130
  options["end"] = _epoch_millis(end)
718
- postfix = createPostfix(options)
719
- return self.publicRequest(f"{self.base}/{market}/candles{postfix}") # type: ignore[return-value]
1131
+ postfix = create_postfix(options)
1132
+ result = self.public_request(f"{self.base}/{market}/candles{postfix}") # type: ignore[return-value]
1133
+ return convert_candles_to_dataframe(result, output_format)
1134
+
1135
+ @deprecated(version="2.2.0", reason="Use ticker_price instead")
1136
+ def tickerPrice(
1137
+ self,
1138
+ options: strdict | None = None,
1139
+ output_format: OutputFormat = OutputFormat.DICT,
1140
+ ) -> list[strdict] | strdict | Any:
1141
+ return self.ticker_price(options, output_format)
720
1142
 
721
- def tickerPrice(self, options: strdict | None = None) -> list[strdict] | strdict:
1143
+ def ticker_price(
1144
+ self,
1145
+ options: strdict | None = None,
1146
+ output_format: OutputFormat = OutputFormat.DICT,
1147
+ ) -> list[strdict] | strdict | Any:
722
1148
  """Get the current price for each market
723
1149
 
724
1150
  ---
@@ -735,6 +1161,23 @@ class Bitvavo:
735
1161
  ```python
736
1162
  options={}
737
1163
  options={"market": "BTC-EUR"}
1164
+
1165
+ # Output format selection:
1166
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
1167
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
1168
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
1169
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
1170
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
1171
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
1172
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
1173
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
1174
+ output_format=OutputFormat.IBIS # Returns Ibis expression
1175
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
1176
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
1177
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
1178
+
1179
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
1180
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
738
1181
  ```
739
1182
 
740
1183
  ---
@@ -746,6 +1189,7 @@ class Bitvavo:
746
1189
  ---
747
1190
  Returns:
748
1191
  ```python
1192
+ # When output_format=OutputFormat.DICT (default):
749
1193
  # Note that `price` is unconverted
750
1194
  [
751
1195
  {"market": "1INCH-EUR", "price": "2.1594"},
@@ -761,12 +1205,28 @@ class Bitvavo:
761
1205
  {"market": "ALGO-EUR", "price": "1.3942"},
762
1206
  # and another 210 markets below this point
763
1207
  ]
1208
+
1209
+ # When output_format is any DataFrame format (pandas, polars, cudf, etc.):
1210
+ # Returns a DataFrame with columns: market, price
764
1211
  ```
765
1212
  """
766
- postfix = createPostfix(options)
767
- return self.publicRequest(f"{self.base}/ticker/price{postfix}") # type: ignore[return-value]
1213
+ postfix = create_postfix(options)
1214
+ result = self.public_request(f"{self.base}/ticker/price{postfix}") # type: ignore[return-value]
1215
+ return convert_to_dataframe(result, output_format)
1216
+
1217
+ @deprecated(version="2.2.0", reason="Use ticker_book instead")
1218
+ def tickerBook(
1219
+ self,
1220
+ options: strdict | None = None,
1221
+ output_format: OutputFormat = OutputFormat.DICT,
1222
+ ) -> list[strdict] | strdict | Any:
1223
+ return self.ticker_book(options, output_format)
768
1224
 
769
- def tickerBook(self, options: strdict | None = None) -> list[strdict] | strdict:
1225
+ def ticker_book(
1226
+ self,
1227
+ options: strdict | None = None,
1228
+ output_format: OutputFormat = OutputFormat.DICT,
1229
+ ) -> list[strdict] | strdict | Any:
770
1230
  """Get current bid/ask, bidsize/asksize per market
771
1231
 
772
1232
  ---
@@ -783,6 +1243,23 @@ class Bitvavo:
783
1243
  ```python
784
1244
  options={}
785
1245
  options={"market": "BTC-EUR"}
1246
+
1247
+ # Output format selection:
1248
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
1249
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
1250
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
1251
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
1252
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
1253
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
1254
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
1255
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
1256
+ output_format=OutputFormat.IBIS # Returns Ibis expression
1257
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
1258
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
1259
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
1260
+
1261
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
1262
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
786
1263
  ```
787
1264
 
788
1265
  ---
@@ -794,6 +1271,7 @@ class Bitvavo:
794
1271
  ---
795
1272
  Returns:
796
1273
  ```python
1274
+ # When output_format=OutputFormat.DICT (default):
797
1275
  [
798
1276
  {"market": "1INCH-EUR", "bid": "2.1534", "ask": "2.1587", "bidSize": "194.8", "askSize": "194.8"},
799
1277
  {"market": "AAVE-EUR", "bid": "213.7", "ask": "214.05", "bidSize": "212.532", "askSize": "4.77676965"},
@@ -802,12 +1280,20 @@ class Bitvavo:
802
1280
  {"market": "AION-EUR", "bid": "0.12531", "ask": "0.12578", "bidSize": "3345", "askSize": "10958.49228653"},
803
1281
  # and another 215 markets below this point
804
1282
  ]
1283
+
1284
+ # When output_format is any DataFrame format (pandas, polars, cudf, etc.):
1285
+ # Returns a DataFrame with columns: market, bid, ask, bidSize, askSize
805
1286
  ```
806
1287
  """
807
- postfix = createPostfix(options)
808
- return self.publicRequest(f"{self.base}/ticker/book{postfix}") # type: ignore[return-value]
1288
+ postfix = create_postfix(options)
1289
+ result = self.public_request(f"{self.base}/ticker/book{postfix}") # type: ignore[return-value]
1290
+ return convert_to_dataframe(result, output_format)
809
1291
 
810
- def ticker24h(self, options: strdict | None = None) -> list[anydict] | anydict | errordict:
1292
+ def ticker24h(
1293
+ self,
1294
+ options: strdict | None = None,
1295
+ output_format: OutputFormat = OutputFormat.DICT,
1296
+ ) -> list[anydict] | anydict | errordict | Any:
811
1297
  """Get current bid/ask, bidsize/asksize per market
812
1298
 
813
1299
  ---
@@ -824,15 +1310,30 @@ class Bitvavo:
824
1310
  ```python
825
1311
  options={}
826
1312
  options={"market": "BTC-EUR"}
827
- ```
828
1313
 
1314
+ # Output format selection:
1315
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
1316
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
1317
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
1318
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
1319
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
1320
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
1321
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
1322
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
1323
+ output_format=OutputFormat.IBIS # Returns Ibis expression
1324
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
1325
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
1326
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
1327
+
1328
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
1329
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
1330
+ ```
829
1331
  ---
830
1332
  Rate Limit Weight:
831
1333
  ```python
832
1334
  25 # if no market option is used
833
1335
  1 # if a market option is used
834
1336
  ```
835
-
836
1337
  ---
837
1338
  Returns:
838
1339
  ```python
@@ -873,10 +1374,25 @@ class Bitvavo:
873
1374
  rateLimitingWeight = 25
874
1375
  if "market" in options:
875
1376
  rateLimitingWeight = 1
876
- postfix = createPostfix(options)
877
- return self.publicRequest(f"{self.base}/ticker/24h{postfix}", rateLimitingWeight) # type: ignore[return-value]
1377
+ postfix = create_postfix(options)
1378
+ result = self.public_request(f"{self.base}/ticker/24h{postfix}", rateLimitingWeight) # type: ignore[return-value]
1379
+ return convert_to_dataframe(result, output_format)
1380
+
1381
+ @deprecated(version="2.2.0", reason="Use report_trades instead")
1382
+ def reportTrades(
1383
+ self,
1384
+ market: str,
1385
+ options: strintdict | None = None,
1386
+ output_format: OutputFormat = OutputFormat.DICT,
1387
+ ) -> list[anydict] | errordict | Any:
1388
+ return self.report_trades(market, options, output_format)
878
1389
 
879
- def reportTrades(self, market: str, options: strintdict | None = None) -> list[anydict] | errordict:
1390
+ def report_trades(
1391
+ self,
1392
+ market: str,
1393
+ options: strintdict | None = None,
1394
+ output_format: OutputFormat = OutputFormat.DICT,
1395
+ ) -> list[anydict] | errordict | Any:
880
1396
  """Get MiCA-compliant trades report for a specific market
881
1397
 
882
1398
  Returns trades from the specified market and time period made by all Bitvavo users.
@@ -893,14 +1409,27 @@ class Bitvavo:
893
1409
  ```python
894
1410
  market="BTC-EUR"
895
1411
  options={
896
- "limit": [ 1 .. 1000 ], default 500
897
- "start": int timestamp in ms >= 0
898
- "end": int timestamp in ms <= 8_640_000_000_000_000 # Cannot be more than 24 hours after start
899
- "tradeIdFrom": "" # if you get a list and want everything AFTER a certain id, put that id here
900
- "tradeIdTo": "" # if you get a list and want everything BEFORE a certain id, put that id here
1412
+ "limit": [ 1 .. 1000 ], default 500
1413
+ "start": int timestamp in ms >= 0
1414
+ "end": int timestamp in ms <= 8_640_000_000_000_000 # Cannot be more than 24 hours after start
1415
+ "tradeIdFrom": "" # if you get a list and want everything AFTER a certain id, put that id here
1416
+ "tradeIdTo": "" # if you get a list and want everything BEFORE a certain id, put that id here
901
1417
  }
902
- ```
903
1418
 
1419
+ # Output format selection:
1420
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
1421
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
1422
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
1423
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
1424
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
1425
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
1426
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
1427
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
1428
+ output_format=OutputFormat.IBIS # Returns Ibis expression
1429
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
1430
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
1431
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
1432
+ ```
904
1433
  ---
905
1434
  Rate Limit Weight:
906
1435
  ```python
@@ -912,19 +1441,24 @@ class Bitvavo:
912
1441
  ```python
913
1442
  [
914
1443
  {
915
- "timestamp": 1542967486256,
916
- "id": "57b1159b-6bf5-4cde-9e2c-6bd6a5678baf",
917
- "amount": "0.1",
918
- "price": "5012",
919
- "side": "sell"
1444
+ "timestamp": 1542967486256,
1445
+ "id": "57b1159b-6bf5-4cde-9e2c-6bd6a5678baf",
1446
+ "amount": "0.1",
1447
+ "price": "5012",
1448
+ "side": "sell"
920
1449
  }
921
1450
  ]
922
1451
  ```
923
1452
  """
924
- postfix = createPostfix(options)
925
- return self.publicRequest(f"{self.base}/report/{market}/trades{postfix}", 5) # type: ignore[return-value]
1453
+ postfix = create_postfix(options)
1454
+ result = self.public_request(f"{self.base}/report/{market}/trades{postfix}", 5) # type: ignore[return-value]
1455
+ return convert_to_dataframe(result, output_format)
926
1456
 
1457
+ @deprecated(version="2.2.0", reason="Use report_book instead")
927
1458
  def reportBook(self, market: str, options: intdict | None = None) -> dict[str, str | int | list[str]] | errordict:
1459
+ return self.report_book(market, options)
1460
+
1461
+ def report_book(self, market: str, options: intdict | None = None) -> dict[str, str | int | list[str]] | errordict:
928
1462
  """Get MiCA-compliant order book report for a specific market
929
1463
 
930
1464
  Returns the list of all bids and asks for the specified market, sorted by price.
@@ -961,10 +1495,14 @@ class Bitvavo:
961
1495
  }
962
1496
  ```
963
1497
  """
964
- postfix = createPostfix(options)
965
- return self.publicRequest(f"{self.base}/report/{market}/book{postfix}") # type: ignore[return-value]
1498
+ postfix = create_postfix(options)
1499
+ return self.public_request(f"{self.base}/report/{market}/book{postfix}") # type: ignore[return-value]
966
1500
 
1501
+ @deprecated(version="2.2.0", reason="Use place_order instead")
967
1502
  def placeOrder(self, market: str, side: str, orderType: str, operatorId: int, body: anydict) -> anydict:
1503
+ return self.place_order(market, side, orderType, operatorId, body)
1504
+
1505
+ def place_order(self, market: str, side: str, orderType: str, operatorId: int, body: anydict) -> anydict:
968
1506
  """Place a new order on the exchange
969
1507
 
970
1508
  ---
@@ -1093,9 +1631,13 @@ class Bitvavo:
1093
1631
  body["side"] = side
1094
1632
  body["orderType"] = orderType
1095
1633
  body["operatorId"] = operatorId
1096
- return self.privateRequest("/order", "", body, "POST") # type: ignore[return-value]
1634
+ return self.private_request("/order", "", body, "POST") # type: ignore[return-value]
1097
1635
 
1636
+ @deprecated(version="2.2.0", reason="Use update_order instead")
1098
1637
  def updateOrder(self, market: str, orderId: str, operatorId: int, body: anydict) -> anydict:
1638
+ return self.update_order(market, orderId, operatorId, body)
1639
+
1640
+ def update_order(self, market: str, orderId: str, operatorId: int, body: anydict) -> anydict:
1099
1641
  """Update an existing order for a specific market. Make sure that at least one of the optional parameters is set, otherwise nothing will be updated.
1100
1642
 
1101
1643
  ---
@@ -1179,14 +1721,24 @@ class Bitvavo:
1179
1721
  body["market"] = market
1180
1722
  body["orderId"] = orderId
1181
1723
  body["operatorId"] = operatorId
1182
- return self.privateRequest("/order", "", body, "PUT") # type: ignore[return-value]
1724
+ return self.private_request("/order", "", body, "PUT") # type: ignore[return-value]
1183
1725
 
1726
+ @deprecated(version="2.2.0", reason="Use cancel_order instead")
1184
1727
  def cancelOrder(
1185
1728
  self,
1186
1729
  market: str,
1187
1730
  operatorId: int,
1188
1731
  orderId: str | None = None,
1189
1732
  clientOrderId: str | None = None,
1733
+ ) -> strdict:
1734
+ return self.cancel_order(market, operatorId, orderId, clientOrderId)
1735
+
1736
+ def cancel_order(
1737
+ self,
1738
+ market: str,
1739
+ operatorId: int,
1740
+ orderId: str | None = None,
1741
+ clientOrderId: str | None = None,
1190
1742
  ) -> strdict:
1191
1743
  """Cancel an existing order for a specific market
1192
1744
 
@@ -1227,10 +1779,14 @@ class Bitvavo:
1227
1779
  elif orderId is not None:
1228
1780
  params["orderId"] = orderId
1229
1781
 
1230
- postfix = createPostfix(params)
1231
- return self.privateRequest("/order", postfix, {}, "DELETE") # type: ignore[return-value]
1782
+ postfix = create_postfix(params)
1783
+ return self.private_request("/order", postfix, {}, "DELETE") # type: ignore[return-value]
1232
1784
 
1785
+ @deprecated(version="2.2.0", reason="Use get_order instead")
1233
1786
  def getOrder(self, market: str, orderId: str) -> list[anydict] | errordict:
1787
+ return self.get_order(market, orderId)
1788
+
1789
+ def get_order(self, market: str, orderId: str) -> list[anydict] | errordict:
1234
1790
  """Get an existing order for a specific market
1235
1791
 
1236
1792
  ---
@@ -1292,10 +1848,14 @@ class Bitvavo:
1292
1848
  }
1293
1849
  ```
1294
1850
  """
1295
- postfix = createPostfix({"market": market, "orderId": orderId})
1296
- return self.privateRequest("/order", postfix, {}, "GET") # type: ignore[return-value]
1851
+ postfix = create_postfix({"market": market, "orderId": orderId})
1852
+ return self.private_request("/order", postfix, {}, "GET") # type: ignore[return-value]
1297
1853
 
1854
+ @deprecated(version="2.2.0", reason="Use get_orders instead")
1298
1855
  def getOrders(self, market: str, options: anydict | None = None) -> list[anydict] | errordict:
1856
+ return self.get_orders(market, options)
1857
+
1858
+ def get_orders(self, market: str, options: anydict | None = None) -> list[anydict] | errordict:
1299
1859
  """Get multiple existing orders for a specific market
1300
1860
 
1301
1861
  ---
@@ -1368,10 +1928,14 @@ class Bitvavo:
1368
1928
  """ # noqa: E501
1369
1929
  options = _default(options, {})
1370
1930
  options["market"] = market
1371
- postfix = createPostfix(options)
1372
- return self.privateRequest("/orders", postfix, {}, "GET", 5) # type: ignore[return-value]
1931
+ postfix = create_postfix(options)
1932
+ return self.private_request("/orders", postfix, {}, "GET", 5) # type: ignore[return-value]
1373
1933
 
1934
+ @deprecated(version="2.2.0", reason="Use cancel_orders instead")
1374
1935
  def cancelOrders(self, options: anydict | None = None) -> list[strdict] | errordict:
1936
+ return self.cancel_orders(options)
1937
+
1938
+ def cancel_orders(self, options: anydict | None = None) -> list[strdict] | errordict:
1375
1939
  """Cancel all existing orders for a specific market (or account)
1376
1940
 
1377
1941
  ---
@@ -1396,10 +1960,14 @@ class Bitvavo:
1396
1960
  ]
1397
1961
  ```
1398
1962
  """
1399
- postfix = createPostfix(options)
1400
- return self.privateRequest("/orders", postfix, {}, "DELETE") # type: ignore[return-value]
1963
+ postfix = create_postfix(options)
1964
+ return self.private_request("/orders", postfix, {}, "DELETE") # type: ignore[return-value]
1401
1965
 
1966
+ @deprecated(version="2.2.0", reason="Use orders_open instead")
1402
1967
  def ordersOpen(self, options: anydict | None = None) -> list[anydict] | errordict:
1968
+ return self.orders_open(options)
1969
+
1970
+ def orders_open(self, options: anydict | None = None) -> list[anydict] | errordict:
1403
1971
  """Get all open orders, either for all markets, or a single market
1404
1972
 
1405
1973
  ---
@@ -1468,10 +2036,15 @@ class Bitvavo:
1468
2036
  rateLimitingWeight = 25
1469
2037
  if "market" in options:
1470
2038
  rateLimitingWeight = 1
1471
- postfix = createPostfix(options)
1472
- return self.privateRequest("/ordersOpen", postfix, {}, "GET", rateLimitingWeight) # type: ignore[return-value]
2039
+ postfix = create_postfix(options)
2040
+ return self.private_request("/ordersOpen", postfix, {}, "GET", rateLimitingWeight) # type: ignore[return-value]
1473
2041
 
1474
- def trades(self, market: str, options: anydict | None = None) -> list[anydict] | errordict:
2042
+ def trades(
2043
+ self,
2044
+ market: str,
2045
+ options: anydict | None = None,
2046
+ output_format: OutputFormat = OutputFormat.DICT,
2047
+ ) -> list[anydict] | errordict | Any:
1475
2048
  """Get all historic trades from this account
1476
2049
 
1477
2050
  ---
@@ -1479,44 +2052,57 @@ class Bitvavo:
1479
2052
  ```python
1480
2053
  market="BTC-EUR"
1481
2054
  options={
1482
- "limit": [ 1 .. 1000 ], default 500
1483
- "start": int timestamp in ms >= 0
1484
- "end": int timestamp in ms <= 8_640_000_000_000_000 # (that's somewhere in the year 2243, or near the number 2^52)
1485
- "tradeIdFrom": "" # if you get a list and want everything AFTER a certain id, put that id here
1486
- "tradeIdTo": "" # if you get a list and want everything BEFORE a certain id, put that id here
2055
+ "limit": [ 1 .. 1000 ], default 500
2056
+ "start": int timestamp in ms >= 0
2057
+ "end": int timestamp in ms <= 8_640_000_000_000_000 # (that's somewhere in the year 2243, or near the number 2^52)
2058
+ "tradeIdFrom": "" # if you get a list and want everything AFTER a certain id, put that id here
2059
+ "tradeIdTo": "" # if you get a list and want everything BEFORE a certain id, put that id here
1487
2060
  }
1488
- ```
1489
2061
 
2062
+ # Output format selection:
2063
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
2064
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
2065
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
2066
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
2067
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
2068
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
2069
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
2070
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
2071
+ output_format=OutputFormat.IBIS # Returns Ibis expression
2072
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
2073
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
2074
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
2075
+ ```
1490
2076
  ---
1491
2077
  Rate Limit Weight:
1492
2078
  ```python
1493
2079
  5
1494
2080
  ```
1495
-
1496
2081
  ---
1497
2082
  Returns:
1498
2083
  ```python
1499
2084
  [
1500
2085
  {
1501
- "id": "108c3633-0276-4480-a902-17a01829deae",
1502
- "orderId": "1d671998-3d44-4df4-965f-0d48bd129a1b",
1503
- "timestamp": 1542967486256,
1504
- "market": "BTC-EUR",
1505
- "side": "buy",
1506
- "amount": "0.005",
1507
- "price": "5000.1",
1508
- "taker": true,
1509
- "fee": "0.03",
1510
- "feeCurrency": "EUR",
1511
- "settled": true
2086
+ "id": "108c3633-0276-4480-a902-17a01829deae",
2087
+ "orderId": "1d671998-3d44-4df4-965f-0d48bd129a1b",
2088
+ "timestamp": 1542967486256,
2089
+ "market": "BTC-EUR",
2090
+ "side": "buy",
2091
+ "amount": "0.005",
2092
+ "price": "5000.1",
2093
+ "taker": true,
2094
+ "fee": "0.03",
2095
+ "feeCurrency": "EUR",
2096
+ "settled": true
1512
2097
  }
1513
2098
  ]
1514
2099
  ```
1515
2100
  """ # noqa: E501
1516
2101
  options = _default(options, {})
1517
2102
  options["market"] = market
1518
- postfix = createPostfix(options)
1519
- return self.privateRequest("/trades", postfix, {}, "GET", 5) # type: ignore[return-value]
2103
+ postfix = create_postfix(options)
2104
+ result = self.private_request("/trades", postfix, {}, "GET", 5) # type: ignore[return-value]
2105
+ return convert_to_dataframe(result, output_format)
1520
2106
 
1521
2107
  def account(self) -> dict[str, strdict]:
1522
2108
  """Get all fees for this account
@@ -1539,7 +2125,7 @@ class Bitvavo:
1539
2125
  }
1540
2126
  ```
1541
2127
  """
1542
- return self.privateRequest("/account", "", {}, "GET") # type: ignore[return-value]
2128
+ return self.private_request("/account", "", {}, "GET") # type: ignore[return-value]
1543
2129
 
1544
2130
  def fees(self, market: str | None = None, quote: str | None = None) -> list[strdict] | errordict:
1545
2131
  """Get market fees for a specific market or quote currency
@@ -1576,10 +2162,14 @@ class Bitvavo:
1576
2162
  options["market"] = market
1577
2163
  if quote is not None:
1578
2164
  options["quote"] = quote
1579
- postfix = createPostfix(options)
1580
- return self.privateRequest("/account/fees", postfix, {}, "GET") # type: ignore[return-value]
2165
+ postfix = create_postfix(options)
2166
+ return self.private_request("/account/fees", postfix, {}, "GET") # type: ignore[return-value]
1581
2167
 
1582
- def balance(self, options: strdict | None = None) -> list[strdict] | errordict:
2168
+ def balance(
2169
+ self,
2170
+ options: strdict | None = None,
2171
+ output_format: OutputFormat = OutputFormat.DICT,
2172
+ ) -> list[strdict] | errordict | Any:
1583
2173
  """Get the balance for this account
1584
2174
 
1585
2175
  ---
@@ -1587,6 +2177,23 @@ class Bitvavo:
1587
2177
  ```python
1588
2178
  options={} # return all balances
1589
2179
  options={symbol="BTC"} # return a single balance
2180
+
2181
+ # Output format selection:
2182
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
2183
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
2184
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
2185
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
2186
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
2187
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
2188
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
2189
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
2190
+ output_format=OutputFormat.IBIS # Returns Ibis expression
2191
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
2192
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
2193
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
2194
+
2195
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
2196
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
1590
2197
  ```
1591
2198
 
1592
2199
  ---
@@ -1598,6 +2205,7 @@ class Bitvavo:
1598
2205
  ---
1599
2206
  Returns:
1600
2207
  ```python
2208
+ # When output_format='dict' (default):
1601
2209
  [
1602
2210
  {
1603
2211
  "symbol": "BTC",
@@ -1605,12 +2213,21 @@ class Bitvavo:
1605
2213
  "inOrder": "0.74832374"
1606
2214
  }
1607
2215
  ]
2216
+
2217
+ # When output_format is any DataFrame format:
2218
+ # Returns the above data as a DataFrame in the requested format (pandas, polars, etc.)
2219
+ # with columns: symbol, available, inOrder
1608
2220
  ```
1609
2221
  """
1610
- postfix = createPostfix(options)
1611
- return self.privateRequest("/balance", postfix, {}, "GET", 5) # type: ignore[return-value]
2222
+ postfix = create_postfix(options)
2223
+ result = self.private_request("/balance", postfix, {}, "GET", 5) # type: ignore[return-value]
2224
+ return convert_to_dataframe(result, output_format)
1612
2225
 
2226
+ @deprecated(version="2.2.0", reason="Use account_history instead")
1613
2227
  def accountHistory(self, options: strintdict | None = None) -> anydict | errordict:
2228
+ return self.account_history(options)
2229
+
2230
+ def account_history(self, options: strintdict | None = None) -> anydict | errordict:
1614
2231
  """Get all past transactions for your account
1615
2232
 
1616
2233
  ---
@@ -1654,10 +2271,14 @@ class Bitvavo:
1654
2271
  }
1655
2272
  ```
1656
2273
  """
1657
- postfix = createPostfix(options)
1658
- return self.privateRequest("/account/history", postfix, {}, "GET") # type: ignore[return-value]
2274
+ postfix = create_postfix(options)
2275
+ return self.private_request("/account/history", postfix, {}, "GET") # type: ignore[return-value]
1659
2276
 
2277
+ @deprecated(version="2.2.0", reason="Use deposit_assets instead")
1660
2278
  def depositAssets(self, symbol: str) -> strdict:
2279
+ return self.deposit_assets(symbol)
2280
+
2281
+ def deposit_assets(self, symbol: str) -> strdict:
1661
2282
  """Get the deposit address (with paymentId for some assets) or bank account information to increase your balance
1662
2283
 
1663
2284
  ---
@@ -1689,10 +2310,14 @@ class Bitvavo:
1689
2310
  }
1690
2311
  ```
1691
2312
  """
1692
- postfix = createPostfix({"symbol": symbol})
1693
- return self.privateRequest("/deposit", postfix, {}, "GET") # type: ignore[return-value]
2313
+ postfix = create_postfix({"symbol": symbol})
2314
+ return self.private_request("/deposit", postfix, {}, "GET") # type: ignore[return-value]
1694
2315
 
2316
+ @deprecated(version="2.2.0", reason="Use deposit_history instead")
1695
2317
  def depositHistory(self, options: anydict | None = None) -> list[anydict] | errordict:
2318
+ return self.deposit_history(options)
2319
+
2320
+ def deposit_history(self, options: anydict | None = None) -> list[anydict] | errordict:
1696
2321
  """Get the deposit history of the account
1697
2322
 
1698
2323
  Even when you want something from a single `symbol`, you'll still receive a list with multiple deposits.
@@ -1740,10 +2365,14 @@ class Bitvavo:
1740
2365
  ]
1741
2366
  ```
1742
2367
  """ # noqa: E501
1743
- postfix = createPostfix(options)
1744
- return self.privateRequest("/depositHistory", postfix, {}, "GET", 5) # type: ignore[return-value]
2368
+ postfix = create_postfix(options)
2369
+ return self.private_request("/depositHistory", postfix, {}, "GET", 5) # type: ignore[return-value]
1745
2370
 
2371
+ @deprecated(version="2.2.0", reason="Use withdraw_assets instead")
1746
2372
  def withdrawAssets(self, symbol: str, amount: str, address: str, body: anydict) -> anydict:
2373
+ return self.withdraw_assets(symbol, amount, address, body)
2374
+
2375
+ def withdraw_assets(self, symbol: str, amount: str, address: str, body: anydict) -> anydict:
1747
2376
  """Withdraw a coin/token to an external crypto address or bank account.
1748
2377
 
1749
2378
  ---
@@ -1778,9 +2407,21 @@ class Bitvavo:
1778
2407
  body["symbol"] = symbol
1779
2408
  body["amount"] = amount
1780
2409
  body["address"] = address
1781
- return self.privateRequest("/withdrawal", "", body, "POST") # type: ignore[return-value]
2410
+ return self.private_request("/withdrawal", "", body, "POST") # type: ignore[return-value]
2411
+
2412
+ @deprecated(version="2.2.0", reason="Use withdrawal_history instead")
2413
+ def withdrawalHistory(
2414
+ self,
2415
+ options: anydict | None = None,
2416
+ output_format: OutputFormat = OutputFormat.DICT,
2417
+ ) -> list[anydict] | errordict | Any:
2418
+ return self.withdrawal_history(options, output_format)
1782
2419
 
1783
- def withdrawalHistory(self, options: anydict | None = None) -> list[anydict] | errordict:
2420
+ def withdrawal_history(
2421
+ self,
2422
+ options: anydict | None = None,
2423
+ output_format: OutputFormat = OutputFormat.DICT,
2424
+ ) -> list[anydict] | errordict | Any:
1784
2425
  """Get the withdrawal history
1785
2426
 
1786
2427
  ---
@@ -1792,8 +2433,24 @@ class Bitvavo:
1792
2433
  "start": int timestamp in ms >= 0
1793
2434
  "end": int timestamp in ms <= 8_640_000_000_000_000 # (that's somewhere in the year 2243, or near the number 2^52)
1794
2435
  }
1795
- ```
1796
2436
 
2437
+ # Output format selection:
2438
+ output_format=OutputFormat.DICT # Default: returns standard Python dict/list
2439
+ output_format=OutputFormat.PANDAS # Returns pandas DataFrame
2440
+ output_format=OutputFormat.POLARS # Returns polars DataFrame
2441
+ output_format=OutputFormat.CUDF # Returns NVIDIA cuDF (GPU-accelerated)
2442
+ output_format=OutputFormat.MODIN # Returns modin (distributed pandas)
2443
+ output_format=OutputFormat.PYARROW # Returns Apache Arrow Table
2444
+ output_format=OutputFormat.DASK # Returns Dask DataFrame (distributed)
2445
+ output_format=OutputFormat.DUCKDB # Returns DuckDB relation
2446
+ output_format=OutputFormat.IBIS # Returns Ibis expression
2447
+ output_format=OutputFormat.PYSPARK # Returns PySpark DataFrame
2448
+ output_format=OutputFormat.PYSPARK_CONNECT # Returns PySpark Connect DataFrame
2449
+ output_format=OutputFormat.SQLFRAME # Returns SQLFrame DataFrame
2450
+
2451
+ # Note: DataFrame formats require narwhals and the respective library to be installed.
2452
+ # Install with: pip install 'bitvavo-api-upgraded[pandas]' or similar for other formats.
2453
+ ```
1797
2454
  ---
1798
2455
  Rate Limit Weight:
1799
2456
  ```python
@@ -1814,13 +2471,130 @@ class Bitvavo:
1814
2471
  "fee": "0.00006",
1815
2472
  "status": "awaiting_processing"
1816
2473
  }
1817
- }
2474
+ ]
1818
2475
  ```
1819
2476
  """ # noqa: E501
1820
- postfix = createPostfix(options)
1821
- return self.privateRequest("/withdrawalHistory", postfix, {}, "GET", 5) # type: ignore[return-value]
2477
+ postfix = create_postfix(options)
2478
+ result = self.private_request("/withdrawalHistory", postfix, {}, "GET", 5) # type: ignore[return-value]
2479
+ return convert_to_dataframe(result, output_format)
2480
+
2481
+ # API Key Management Helper Methods
2482
+
2483
+ def add_api_key(self, api_key: str, api_secret: str) -> None:
2484
+ """Add a new API key to the available keys.
2485
+
2486
+ Args:
2487
+ api_key: The API key to add
2488
+ api_secret: The corresponding API secret
2489
+ """
2490
+ new_key = {"key": api_key, "secret": api_secret}
2491
+ self.api_keys.append(new_key)
2492
+
2493
+ # Initialize rate limit tracking for this key using settings default
2494
+ key_index = len(self.api_keys) - 1
2495
+ default_rate_limit = bitvavo_upgraded_settings.DEFAULT_RATE_LIMIT
2496
+ self.rate_limits[key_index] = {"remaining": default_rate_limit, "resetAt": ms(0)}
2497
+
2498
+ logger.info("api-key-added", key_index=key_index)
2499
+
2500
+ def remove_api_key(self, api_key: str) -> bool:
2501
+ """Remove an API key from the available keys.
2502
+
2503
+ Args:
2504
+ api_key: The API key to remove
2505
+
2506
+ Returns:
2507
+ bool: True if the key was found and removed, False otherwise
2508
+ """
2509
+ for i, key_data in enumerate(self.api_keys):
2510
+ if key_data["key"] == api_key:
2511
+ _ = self.api_keys.pop(i)
2512
+ # Remove rate limit tracking for this key
2513
+ if i in self.rate_limits:
2514
+ del self.rate_limits[i]
2515
+ # Update rate limit tracking indices (shift them down)
2516
+ new_rate_limits = {}
2517
+ for key_idx, limits in self.rate_limits.items():
2518
+ if key_idx == -1 or key_idx < i: # keyless
2519
+ new_rate_limits[key_idx] = limits
2520
+ elif key_idx > i:
2521
+ new_rate_limits[key_idx - 1] = limits
2522
+ self.rate_limits = new_rate_limits
2523
+
2524
+ # Update current index if needed
2525
+ if self.current_api_key_index >= i:
2526
+ self.current_api_key_index = max(0, self.current_api_key_index - 1)
2527
+
2528
+ logger.info("api-key-removed", key_index=i)
2529
+ return True
2530
+ return False
2531
+
2532
+ def get_api_key_status(self) -> dict[str, dict[str, int | str | bool]]:
2533
+ """Get the current status of all API keys including rate limits.
1822
2534
 
2535
+ Returns:
2536
+ dict: Status information for keyless and all API keys
2537
+ """
2538
+ status = {}
2539
+
2540
+ # Keyless status
2541
+ keyless_limits = self.rate_limits.get(-1, {"remaining": 0, "resetAt": ms(0)})
2542
+ status["keyless"] = {
2543
+ "remaining": int(keyless_limits["remaining"]),
2544
+ "resetAt": int(keyless_limits["resetAt"]),
2545
+ "available": self._has_rate_limit_available(-1, 1),
2546
+ }
2547
+
2548
+ # API key status
2549
+ for i, key_data in enumerate(self.api_keys):
2550
+ key_limits = self.rate_limits.get(i, {"remaining": 0, "resetAt": ms(0)})
2551
+ KEY_LENGTH = 12
2552
+ key_masked = (
2553
+ key_data["key"][:8] + "..." + key_data["key"][-4:]
2554
+ if len(key_data["key"]) > KEY_LENGTH
2555
+ else key_data["key"]
2556
+ )
2557
+ status[f"api_key_{i}"] = {
2558
+ "key": key_masked,
2559
+ "remaining": int(key_limits["remaining"]),
2560
+ "resetAt": int(key_limits["resetAt"]),
2561
+ "available": self._has_rate_limit_available(i, 1),
2562
+ }
2563
+
2564
+ return status
2565
+
2566
+ def set_keyless_preference(self, prefer_keyless: bool) -> None: # noqa: FBT001 (Boolean-typed positional argument in function definition)
2567
+ """Set whether to prefer keyless requests.
2568
+
2569
+ Args:
2570
+ prefer_keyless: If True, use keyless requests first when available
2571
+ """
2572
+ self.prefer_keyless = prefer_keyless
2573
+ logger.info("keyless-preference-changed", prefer_keyless=prefer_keyless)
2574
+
2575
+ def get_current_config(self) -> dict[str, str | bool | int]:
2576
+ """Get the current configuration.
2577
+
2578
+ Returns:
2579
+ dict: Current configuration including key count and preferences
2580
+ """
2581
+ KEY_LENGTH = 12
2582
+ return {
2583
+ "api_key_count": len(self.api_keys),
2584
+ "prefer_keyless": self.prefer_keyless,
2585
+ "current_api_key_index": self.current_api_key_index,
2586
+ "current_api_key": self._current_api_key[:8] + "..." + self._current_api_key[-4:]
2587
+ if len(self._current_api_key) > KEY_LENGTH
2588
+ else self._current_api_key,
2589
+ "rate_limit_remaining": self.rateLimitRemaining,
2590
+ "rate_limit_reset_at": int(self.rateLimitResetAt),
2591
+ }
2592
+
2593
+ @deprecated(version="2.2.0", reason="Use new_websocket instead")
1823
2594
  def newWebsocket(self) -> Bitvavo.WebSocketAppFacade:
2595
+ return self.new_websocket()
2596
+
2597
+ def new_websocket(self) -> Bitvavo.WebSocketAppFacade:
1824
2598
  return Bitvavo.WebSocketAppFacade(self.APIKEY, self.APISECRET, self.ACCESSWINDOW, self.wsUrl, self)
1825
2599
 
1826
2600
  class WebSocketAppFacade:
@@ -1869,18 +2643,30 @@ class Bitvavo:
1869
2643
  self.keepBookCopy = False
1870
2644
  self.localBook: anydict = {}
1871
2645
 
2646
+ @deprecated(version="2.2.0", reason="Use close_socket instead")
1872
2647
  def closeSocket(self) -> None:
2648
+ return self.close_socket()
2649
+
2650
+ def close_socket(self) -> None:
1873
2651
  self.ws.close()
1874
2652
  self.keepAlive = False
1875
2653
  self.receiveThread.join()
1876
2654
 
1877
- def waitForSocket(self, ws: WebSocketApp, message: str, private: bool) -> None: # noqa: ARG002, FBT001
1878
- while True:
2655
+ @deprecated(version="2.2.0", reason="Use wait_for_socket instead")
2656
+ def waitForSocket(self, ws: WebSocketApp, message: str, private: bool) -> None: # noqa: FBT001
2657
+ return self.wait_for_socket(ws, message, private)
2658
+
2659
+ def wait_for_socket(self, ws: WebSocketApp, message: str, private: bool) -> None: # noqa: ARG002, FBT001
2660
+ while self.keepAlive:
1879
2661
  if (not private and self.open) or (private and self.authenticated and self.open):
1880
2662
  return
1881
2663
  time.sleep(0.1)
1882
2664
 
2665
+ @deprecated(version="2.2.0", reason="Use do_send instead")
1883
2666
  def doSend(self, ws: WebSocketApp, message: str, private: bool = False) -> None: # noqa: FBT001, FBT002
2667
+ return self.do_send(ws, message, private)
2668
+
2669
+ def do_send(self, ws: WebSocketApp, message: str, private: bool = False) -> None: # noqa: FBT001, FBT002
1884
2670
  # TODO(NostraDavid): add nap-time to the websocket, or do it here; I don't know yet.
1885
2671
  if private and self.APIKEY == "":
1886
2672
  logger.error(
@@ -1888,7 +2674,7 @@ class Bitvavo:
1888
2674
  tip="set the API key to be able to make private API calls",
1889
2675
  )
1890
2676
  return
1891
- self.waitForSocket(ws, message, private)
2677
+ self.wait_for_socket(ws, message, private)
1892
2678
  ws.send(message)
1893
2679
  if self.bitvavo.debugging:
1894
2680
  logger.debug("message-sent", message=message)
@@ -1901,7 +2687,7 @@ class Bitvavo:
1901
2687
 
1902
2688
  if "error" in msg_dict:
1903
2689
  if msg_dict["errorCode"] == 105: # noqa: PLR2004
1904
- self.bitvavo.updateRateLimit(msg_dict)
2690
+ self.bitvavo.update_rate_limit(msg_dict)
1905
2691
  if "error" in callbacks:
1906
2692
  callbacks["error"](msg_dict)
1907
2693
  else:
@@ -1989,6 +2775,8 @@ class Bitvavo:
1989
2775
  callbacks["subscriptionTrades"][market](msg_dict)
1990
2776
 
1991
2777
  def on_error(self, ws: Any, error: Any) -> None: # noqa: ARG002
2778
+ # Stop the receive thread on error to prevent hanging
2779
+ self.receiveThread.stop()
1992
2780
  if "error" in self.callbacks:
1993
2781
  self.callbacks["error"](error)
1994
2782
  else:
@@ -1999,47 +2787,51 @@ class Bitvavo:
1999
2787
  if self.bitvavo.debugging:
2000
2788
  logger.debug("websocket-closed")
2001
2789
 
2002
- def checkReconnect(self) -> None: # noqa: C901, PLR0912 (too-complex)
2790
+ @deprecated(version="2.2.0", reason="Use check_reconnect instead")
2791
+ def checkReconnect(self) -> None:
2792
+ return self.check_reconnect()
2793
+
2794
+ def check_reconnect(self) -> None: # noqa: C901, PLR0912 (too-complex)
2003
2795
  if "subscriptionTicker" in self.callbacks:
2004
2796
  for market in self.callbacks["subscriptionTicker"]:
2005
- self.subscriptionTicker(market, self.callbacks["subscriptionTicker"][market])
2797
+ self.subscription_ticker(market, self.callbacks["subscriptionTicker"][market])
2006
2798
  if "subscriptionTicker24h" in self.callbacks:
2007
2799
  for market in self.callbacks["subscriptionTicker24h"]:
2008
- self.subscriptionTicker(market, self.callbacks["subscriptionTicker24h"][market])
2800
+ self.subscription_ticker(market, self.callbacks["subscriptionTicker24h"][market])
2009
2801
  if "subscriptionAccount" in self.callbacks:
2010
2802
  for market in self.callbacks["subscriptionAccount"]:
2011
- self.subscriptionAccount(market, self.callbacks["subscriptionAccount"][market])
2803
+ self.subscription_account(market, self.callbacks["subscriptionAccount"][market])
2012
2804
  if "subscriptionCandles" in self.callbacks:
2013
2805
  for market in self.callbacks["subscriptionCandles"]:
2014
2806
  for interval in self.callbacks["subscriptionCandles"][market]:
2015
- self.subscriptionCandles(
2807
+ self.subscription_candles(
2016
2808
  market,
2017
2809
  interval,
2018
2810
  self.callbacks["subscriptionCandles"][market][interval],
2019
2811
  )
2020
2812
  if "subscriptionTrades" in self.callbacks:
2021
2813
  for market in self.callbacks["subscriptionTrades"]:
2022
- self.subscriptionTrades(market, self.callbacks["subscriptionTrades"][market])
2814
+ self.subscription_trades(market, self.callbacks["subscriptionTrades"][market])
2023
2815
  if "subscriptionBookUpdate" in self.callbacks:
2024
2816
  for market in self.callbacks["subscriptionBookUpdate"]:
2025
- self.subscriptionBookUpdate(market, self.callbacks["subscriptionBookUpdate"][market])
2817
+ self.subscription_book_update(market, self.callbacks["subscriptionBookUpdate"][market])
2026
2818
  if "subscriptionBookUser" in self.callbacks:
2027
2819
  for market in self.callbacks["subscriptionBookUser"]:
2028
- self.subscriptionBook(market, self.callbacks["subscriptionBookUser"][market])
2820
+ self.subscription_book(market, self.callbacks["subscriptionBookUser"][market])
2029
2821
 
2030
2822
  def on_open(self, ws: Any) -> None: # noqa: ARG002
2031
2823
  now = time_ms() + bitvavo_upgraded_settings.LAG
2032
2824
  self.open = True
2033
2825
  self.reconnectTimer = 0.5
2034
2826
  if self.APIKEY != "":
2035
- self.doSend(
2827
+ self.do_send(
2036
2828
  self.ws,
2037
2829
  json.dumps(
2038
2830
  {
2039
2831
  "window": str(self.ACCESSWINDOW),
2040
2832
  "action": "authenticate",
2041
2833
  "key": self.APIKEY,
2042
- "signature": createSignature(now, "GET", "/websocket", {}, self.APISECRET),
2834
+ "signature": create_signature(now, "GET", "/websocket", {}, self.APISECRET),
2043
2835
  "timestamp": now,
2044
2836
  },
2045
2837
  ),
@@ -2047,10 +2839,14 @@ class Bitvavo:
2047
2839
  if self.reconnect:
2048
2840
  if self.bitvavo.debugging:
2049
2841
  logger.debug("reconnecting")
2050
- thread = Thread(target=self.checkReconnect)
2842
+ thread = Thread(target=self.check_reconnect)
2051
2843
  thread.start()
2052
2844
 
2845
+ @deprecated(version="2.2.0", reason="Use set_error_callback instead")
2053
2846
  def setErrorCallback(self, callback: Callable[[Any], None]) -> None:
2847
+ return self.set_error_callback(callback)
2848
+
2849
+ def set_error_callback(self, callback: Callable[[Any], None]) -> None:
2054
2850
  self.callbacks["error"] = callback
2055
2851
 
2056
2852
  def time(self, callback: Callable[[Any], None]) -> None:
@@ -2078,7 +2874,7 @@ class Bitvavo:
2078
2874
  ```
2079
2875
  """
2080
2876
  self.callbacks["time"] = callback
2081
- self.doSend(self.ws, json.dumps({"action": "getTime"}))
2877
+ self.do_send(self.ws, json.dumps({"action": "getTime"}))
2082
2878
 
2083
2879
  def markets(self, options: anydict, callback: Callable[[Any], None]) -> None:
2084
2880
  """Get all available markets with some meta-information, unless options is given a `market` key.
@@ -2132,7 +2928,7 @@ class Bitvavo:
2132
2928
  """
2133
2929
  self.callbacks["markets"] = callback
2134
2930
  options["action"] = "getMarkets"
2135
- self.doSend(self.ws, json.dumps(options))
2931
+ self.do_send(self.ws, json.dumps(options))
2136
2932
 
2137
2933
  def assets(self, options: anydict, callback: Callable[[Any], None]) -> None:
2138
2934
  """Get all available assets, unless `options` is given a `symbol` key.
@@ -2183,7 +2979,7 @@ class Bitvavo:
2183
2979
  """
2184
2980
  self.callbacks["assets"] = callback
2185
2981
  options["action"] = "getAssets"
2186
- self.doSend(self.ws, json.dumps(options))
2982
+ self.do_send(self.ws, json.dumps(options))
2187
2983
 
2188
2984
  def book(self, market: str, options: anydict, callback: Callable[[Any], None]) -> None:
2189
2985
  """Get a book (with two lists: asks and bids, as they're called)
@@ -2230,7 +3026,7 @@ class Bitvavo:
2230
3026
  self.callbacks["book"] = callback
2231
3027
  options["market"] = market
2232
3028
  options["action"] = "getBook"
2233
- self.doSend(self.ws, json.dumps(options))
3029
+ self.do_send(self.ws, json.dumps(options))
2234
3030
 
2235
3031
  def publicTrades(self, market: str, options: anydict, callback: Callable[[Any], None]) -> None:
2236
3032
  """Publically available trades
@@ -2282,7 +3078,7 @@ class Bitvavo:
2282
3078
  self.callbacks["publicTrades"] = callback
2283
3079
  options["market"] = market
2284
3080
  options["action"] = "getTrades"
2285
- self.doSend(self.ws, json.dumps(options))
3081
+ self.do_send(self.ws, json.dumps(options))
2286
3082
 
2287
3083
  def candles(
2288
3084
  self,
@@ -2341,9 +3137,13 @@ class Bitvavo:
2341
3137
  options["market"] = market
2342
3138
  options["interval"] = interval
2343
3139
  options["action"] = "getCandles"
2344
- self.doSend(self.ws, json.dumps(options))
3140
+ self.do_send(self.ws, json.dumps(options))
2345
3141
 
3142
+ @deprecated(version="2.2.0", reason="Use ticker_price instead")
2346
3143
  def tickerPrice(self, options: anydict, callback: Callable[[Any], None]) -> None:
3144
+ return self.ticker_price(options, callback)
3145
+
3146
+ def ticker_price(self, options: anydict, callback: Callable[[Any], None]) -> None:
2347
3147
  """Get the current price for each market
2348
3148
 
2349
3149
  ---
@@ -2391,9 +3191,13 @@ class Bitvavo:
2391
3191
  """
2392
3192
  self.callbacks["tickerPrice"] = callback
2393
3193
  options["action"] = "getTickerPrice"
2394
- self.doSend(self.ws, json.dumps(options))
3194
+ self.do_send(self.ws, json.dumps(options))
2395
3195
 
3196
+ @deprecated(version="2.2.0", reason="Use ticker_book instead")
2396
3197
  def tickerBook(self, options: anydict, callback: Callable[[Any], None]) -> None:
3198
+ return self.ticker_book(options, callback)
3199
+
3200
+ def ticker_book(self, options: anydict, callback: Callable[[Any], None]) -> None:
2397
3201
  """Get current bid/ask, bidsize/asksize per market
2398
3202
 
2399
3203
  ---
@@ -2434,7 +3238,7 @@ class Bitvavo:
2434
3238
  """ # noqa: E501
2435
3239
  self.callbacks["tickerBook"] = callback
2436
3240
  options["action"] = "getTickerBook"
2437
- self.doSend(self.ws, json.dumps(options))
3241
+ self.do_send(self.ws, json.dumps(options))
2438
3242
 
2439
3243
  def ticker24h(self, options: anydict, callback: Callable[[Any], None]) -> None:
2440
3244
  """Get current bid/ask, bidsize/asksize per market
@@ -2501,8 +3305,9 @@ class Bitvavo:
2501
3305
  """
2502
3306
  self.callbacks["ticker24h"] = callback
2503
3307
  options["action"] = "getTicker24h"
2504
- self.doSend(self.ws, json.dumps(options))
3308
+ self.do_send(self.ws, json.dumps(options))
2505
3309
 
3310
+ @deprecated(version="2.2.0", reason="Use place_order instead")
2506
3311
  def placeOrder(
2507
3312
  self,
2508
3313
  market: str,
@@ -2511,6 +3316,17 @@ class Bitvavo:
2511
3316
  operatorId: int,
2512
3317
  body: anydict,
2513
3318
  callback: Callable[[Any], None],
3319
+ ) -> None:
3320
+ return self.place_order(market, side, orderType, operatorId, body, callback)
3321
+
3322
+ def place_order(
3323
+ self,
3324
+ market: str,
3325
+ side: str,
3326
+ orderType: str,
3327
+ operatorId: int,
3328
+ body: anydict,
3329
+ callback: Callable[[Any], None],
2514
3330
  ) -> None:
2515
3331
  """Place a new order on the exchange
2516
3332
 
@@ -2643,8 +3459,9 @@ class Bitvavo:
2643
3459
  body["orderType"] = orderType
2644
3460
  body["operatorId"] = operatorId
2645
3461
  body["action"] = "privateCreateOrder"
2646
- self.doSend(self.ws, json.dumps(body), True)
3462
+ self.do_send(self.ws, json.dumps(body), True)
2647
3463
 
3464
+ @deprecated(version="2.2.0", reason="Use update_order instead")
2648
3465
  def updateOrder(
2649
3466
  self,
2650
3467
  market: str,
@@ -2652,6 +3469,16 @@ class Bitvavo:
2652
3469
  operatorId: int,
2653
3470
  body: anydict,
2654
3471
  callback: Callable[[Any], None],
3472
+ ) -> None:
3473
+ return self.update_order(market, orderId, operatorId, body, callback)
3474
+
3475
+ def update_order(
3476
+ self,
3477
+ market: str,
3478
+ orderId: str,
3479
+ operatorId: int,
3480
+ body: anydict,
3481
+ callback: Callable[[Any], None],
2655
3482
  ) -> None:
2656
3483
  """
2657
3484
  Update an existing order for a specific market. Make sure that at least one of the optional parameters
@@ -2741,8 +3568,9 @@ class Bitvavo:
2741
3568
  body["orderId"] = orderId
2742
3569
  body["operatorId"] = operatorId
2743
3570
  body["action"] = "privateUpdateOrder"
2744
- self.doSend(self.ws, json.dumps(body), True)
3571
+ self.do_send(self.ws, json.dumps(body), True)
2745
3572
 
3573
+ @deprecated(version="2.2.0", reason="Use cancel_order instead")
2746
3574
  def cancelOrder(
2747
3575
  self,
2748
3576
  market: str,
@@ -2750,6 +3578,16 @@ class Bitvavo:
2750
3578
  callback: Callable[[Any], None],
2751
3579
  orderId: str | None = None,
2752
3580
  clientOrderId: str | None = None,
3581
+ ) -> None:
3582
+ return self.cancel_order(market, operatorId, callback, orderId, clientOrderId)
3583
+
3584
+ def cancel_order(
3585
+ self,
3586
+ market: str,
3587
+ operatorId: int,
3588
+ callback: Callable[[Any], None],
3589
+ orderId: str | None = None,
3590
+ clientOrderId: str | None = None,
2753
3591
  ) -> None:
2754
3592
  """Cancel an existing order for a specific market
2755
3593
 
@@ -2793,9 +3631,13 @@ class Bitvavo:
2793
3631
  elif orderId is not None:
2794
3632
  options["orderId"] = orderId
2795
3633
 
2796
- self.doSend(self.ws, json.dumps(options), True)
3634
+ self.do_send(self.ws, json.dumps(options), True)
2797
3635
 
3636
+ @deprecated(version="2.2.0", reason="Use get_order instead")
2798
3637
  def getOrder(self, market: str, orderId: str, callback: Callable[[Any], None]) -> None:
3638
+ return self.get_order(market, orderId, callback)
3639
+
3640
+ def get_order(self, market: str, orderId: str, callback: Callable[[Any], None]) -> None:
2799
3641
  """Get an existing order for a specific market
2800
3642
 
2801
3643
  ---
@@ -2864,9 +3706,13 @@ class Bitvavo:
2864
3706
  "market": market,
2865
3707
  "orderId": orderId,
2866
3708
  }
2867
- self.doSend(self.ws, json.dumps(options), True)
3709
+ self.do_send(self.ws, json.dumps(options), True)
2868
3710
 
3711
+ @deprecated(version="2.2.0", reason="Use get_orders instead")
2869
3712
  def getOrders(self, market: str, options: anydict, callback: Callable[[Any], None]) -> None:
3713
+ return self.get_orders(market, options, callback)
3714
+
3715
+ def get_orders(self, market: str, options: anydict, callback: Callable[[Any], None]) -> None:
2870
3716
  """Get multiple existing orders for a specific market
2871
3717
 
2872
3718
  ---
@@ -2944,9 +3790,13 @@ class Bitvavo:
2944
3790
  self.callbacks["getOrders"] = callback
2945
3791
  options["action"] = "privateGetOrders"
2946
3792
  options["market"] = market
2947
- self.doSend(self.ws, json.dumps(options), True)
3793
+ self.do_send(self.ws, json.dumps(options), True)
2948
3794
 
3795
+ @deprecated(version="2.2.0", reason="Use cancel_orders instead")
2949
3796
  def cancelOrders(self, options: anydict, callback: Callable[[Any], None]) -> None:
3797
+ return self.cancel_orders(options, callback)
3798
+
3799
+ def cancel_orders(self, options: anydict, callback: Callable[[Any], None]) -> None:
2950
3800
  """Cancel all existing orders for a specific market (or account)
2951
3801
 
2952
3802
  ---
@@ -2974,9 +3824,13 @@ class Bitvavo:
2974
3824
  """
2975
3825
  self.callbacks["cancelOrders"] = callback
2976
3826
  options["action"] = "privateCancelOrders"
2977
- self.doSend(self.ws, json.dumps(options), True)
3827
+ self.do_send(self.ws, json.dumps(options), True)
2978
3828
 
3829
+ @deprecated(version="2.2.0", reason="Use orders_open instead")
2979
3830
  def ordersOpen(self, options: anydict, callback: Callable[[Any], None]) -> None:
3831
+ return self.orders_open(options, callback)
3832
+
3833
+ def orders_open(self, options: anydict, callback: Callable[[Any], None]) -> None:
2980
3834
  """Get all open orders, either for all markets, or a single market
2981
3835
 
2982
3836
  ---
@@ -3044,7 +3898,7 @@ class Bitvavo:
3044
3898
  """
3045
3899
  self.callbacks["ordersOpen"] = callback
3046
3900
  options["action"] = "privateGetOrdersOpen"
3047
- self.doSend(self.ws, json.dumps(options), True)
3901
+ self.do_send(self.ws, json.dumps(options), True)
3048
3902
 
3049
3903
  def trades(self, market: str, options: anydict, callback: Callable[[Any], None]) -> None:
3050
3904
  """Get all historic trades from this account
@@ -3093,7 +3947,7 @@ class Bitvavo:
3093
3947
  self.callbacks["trades"] = callback
3094
3948
  options["action"] = "privateGetTrades"
3095
3949
  options["market"] = market
3096
- self.doSend(self.ws, json.dumps(options), True)
3950
+ self.do_send(self.ws, json.dumps(options), True)
3097
3951
 
3098
3952
  def account(self, callback: Callable[[Any], None]) -> None:
3099
3953
  """Get all fees for this account
@@ -3123,7 +3977,7 @@ class Bitvavo:
3123
3977
  ```
3124
3978
  """
3125
3979
  self.callbacks["account"] = callback
3126
- self.doSend(self.ws, json.dumps({"action": "privateGetAccount"}), True)
3980
+ self.do_send(self.ws, json.dumps({"action": "privateGetAccount"}), True)
3127
3981
 
3128
3982
  def balance(self, options: anydict, callback: Callable[[Any], None]) -> None:
3129
3983
  """Get the balance for this account
@@ -3156,9 +4010,13 @@ class Bitvavo:
3156
4010
  """
3157
4011
  options["action"] = "privateGetBalance"
3158
4012
  self.callbacks["balance"] = callback
3159
- self.doSend(self.ws, json.dumps(options), True)
4013
+ self.do_send(self.ws, json.dumps(options), True)
3160
4014
 
4015
+ @deprecated(version="2.2.0", reason="Use deposit_assets instead")
3161
4016
  def depositAssets(self, symbol: str, callback: Callable[[Any], None]) -> None:
4017
+ return self.deposit_assets(symbol, callback)
4018
+
4019
+ def deposit_assets(self, symbol: str, callback: Callable[[Any], None]) -> None:
3162
4020
  """
3163
4021
  Get the deposit address (with paymentId for some assets) or bank account information to increase your
3164
4022
  balance.
@@ -3194,13 +4052,17 @@ class Bitvavo:
3194
4052
  ```
3195
4053
  """
3196
4054
  self.callbacks["depositAssets"] = callback
3197
- self.doSend(
4055
+ self.do_send(
3198
4056
  self.ws,
3199
4057
  json.dumps({"action": "privateDepositAssets", "symbol": symbol}),
3200
4058
  True,
3201
4059
  )
3202
4060
 
4061
+ @deprecated(version="2.2.0", reason="Use deposit_history instead")
3203
4062
  def depositHistory(self, options: anydict, callback: Callable[[Any], None]) -> None:
4063
+ return self.deposit_history(options, callback)
4064
+
4065
+ def deposit_history(self, options: anydict, callback: Callable[[Any], None]) -> None:
3204
4066
  """Get the deposit history of the account
3205
4067
 
3206
4068
  Even when you want something from a single `symbol`, you'll still receive a list with multiple deposits.
@@ -3252,8 +4114,9 @@ class Bitvavo:
3252
4114
  """
3253
4115
  self.callbacks["depositHistory"] = callback
3254
4116
  options["action"] = "privateGetDepositHistory"
3255
- self.doSend(self.ws, json.dumps(options), True)
4117
+ self.do_send(self.ws, json.dumps(options), True)
3256
4118
 
4119
+ @deprecated(version="2.2.0", reason="Use withdraw_assets instead")
3257
4120
  def withdrawAssets(
3258
4121
  self,
3259
4122
  symbol: str,
@@ -3261,6 +4124,16 @@ class Bitvavo:
3261
4124
  address: str,
3262
4125
  body: anydict,
3263
4126
  callback: Callable[[Any], None],
4127
+ ) -> None:
4128
+ return self.withdraw_assets(symbol, amount, address, body, callback)
4129
+
4130
+ def withdraw_assets(
4131
+ self,
4132
+ symbol: str,
4133
+ amount: str,
4134
+ address: str,
4135
+ body: anydict,
4136
+ callback: Callable[[Any], None],
3264
4137
  ) -> None:
3265
4138
  """Withdraw a coin/token to an external crypto address or bank account.
3266
4139
 
@@ -3305,9 +4178,13 @@ class Bitvavo:
3305
4178
  body["symbol"] = symbol
3306
4179
  body["amount"] = amount
3307
4180
  body["address"] = address
3308
- self.doSend(self.ws, json.dumps(body), True)
4181
+ self.do_send(self.ws, json.dumps(body), True)
3309
4182
 
4183
+ @deprecated(version="2.2.0", reason="Use withdrawal_history instead")
3310
4184
  def withdrawalHistory(self, options: anydict, callback: Callable[[Any], None]) -> None:
4185
+ return self.withdrawal_history(options, callback)
4186
+
4187
+ def withdrawal_history(self, options: anydict, callback: Callable[[Any], None]) -> None:
3311
4188
  """Get the withdrawal history
3312
4189
 
3313
4190
  ---
@@ -3348,9 +4225,13 @@ class Bitvavo:
3348
4225
  """
3349
4226
  self.callbacks["withdrawalHistory"] = callback
3350
4227
  options["action"] = "privateGetWithdrawalHistory"
3351
- self.doSend(self.ws, json.dumps(options), True)
4228
+ self.do_send(self.ws, json.dumps(options), True)
3352
4229
 
4230
+ @deprecated(version="2.2.0", reason="Use subscription_ticker instead")
3353
4231
  def subscriptionTicker(self, market: str, callback: Callable[[Any], None]) -> None:
4232
+ return self.subscription_ticker(market, callback)
4233
+
4234
+ def subscription_ticker(self, market: str, callback: Callable[[Any], None]) -> None:
3354
4235
  # TODO(NostraDavid): one possible improvement here is to turn `market` into a list of markets, so we can sub
3355
4236
  # to all of them at once. Same goes for other `subscription*()`
3356
4237
  """
@@ -3392,7 +4273,7 @@ class Bitvavo:
3392
4273
  if "subscriptionTicker" not in self.callbacks:
3393
4274
  self.callbacks["subscriptionTicker"] = {}
3394
4275
  self.callbacks["subscriptionTicker"][market] = callback
3395
- self.doSend(
4276
+ self.do_send(
3396
4277
  self.ws,
3397
4278
  json.dumps(
3398
4279
  {
@@ -3402,7 +4283,11 @@ class Bitvavo:
3402
4283
  ),
3403
4284
  )
3404
4285
 
4286
+ @deprecated(version="2.2.0", reason="Use subscription_ticker24h instead")
3405
4287
  def subscriptionTicker24h(self, market: str, callback: Callable[[Any], None]) -> None:
4288
+ return self.subscription_ticker24h(market, callback)
4289
+
4290
+ def subscription_ticker24h(self, market: str, callback: Callable[[Any], None]) -> None:
3406
4291
  """
3407
4292
  Subscribe to the ticker-24-hour channel, which means `callback` gets passed the new object every second, if
3408
4293
  values have changed.
@@ -3449,7 +4334,7 @@ class Bitvavo:
3449
4334
  if "subscriptionTicker24h" not in self.callbacks:
3450
4335
  self.callbacks["subscriptionTicker24h"] = {}
3451
4336
  self.callbacks["subscriptionTicker24h"][market] = callback
3452
- self.doSend(
4337
+ self.do_send(
3453
4338
  self.ws,
3454
4339
  json.dumps(
3455
4340
  {
@@ -3459,7 +4344,11 @@ class Bitvavo:
3459
4344
  ),
3460
4345
  )
3461
4346
 
4347
+ @deprecated(version="2.2.0", reason="Use subscription_account instead")
3462
4348
  def subscriptionAccount(self, market: str, callback: Callable[[Any], None]) -> None:
4349
+ return self.subscription_account(market, callback)
4350
+
4351
+ def subscription_account(self, market: str, callback: Callable[[Any], None]) -> None:
3463
4352
  """
3464
4353
  Subscribes to the account channel, which sends an update whenever an event happens which is related to
3465
4354
  the account. These are 'order' events (create, update, cancel) or 'fill' events (a trade occurred).
@@ -3526,7 +4415,7 @@ class Bitvavo:
3526
4415
  if "subscriptionAccount" not in self.callbacks:
3527
4416
  self.callbacks["subscriptionAccount"] = {}
3528
4417
  self.callbacks["subscriptionAccount"][market] = callback
3529
- self.doSend(
4418
+ self.do_send(
3530
4419
  self.ws,
3531
4420
  json.dumps(
3532
4421
  {
@@ -3537,7 +4426,11 @@ class Bitvavo:
3537
4426
  True,
3538
4427
  )
3539
4428
 
4429
+ @deprecated(version="2.2.0", reason="Use subscription_candles instead")
3540
4430
  def subscriptionCandles(self, market: str, interval: str, callback: Callable[[Any], None]) -> None:
4431
+ return self.subscription_candles(market, interval, callback)
4432
+
4433
+ def subscription_candles(self, market: str, interval: str, callback: Callable[[Any], None]) -> None:
3541
4434
  """Subscribes to candles and returns a candle each time a new one is formed, depending on the interval
3542
4435
 
3543
4436
  ---
@@ -3585,7 +4478,7 @@ class Bitvavo:
3585
4478
  if market not in self.callbacks["subscriptionCandles"]:
3586
4479
  self.callbacks["subscriptionCandles"][market] = {}
3587
4480
  self.callbacks["subscriptionCandles"][market][interval] = callback
3588
- self.doSend(
4481
+ self.do_send(
3589
4482
  self.ws,
3590
4483
  json.dumps(
3591
4484
  {
@@ -3601,7 +4494,11 @@ class Bitvavo:
3601
4494
  ),
3602
4495
  )
3603
4496
 
4497
+ @deprecated(version="2.2.0", reason="Use subscription_trades instead")
3604
4498
  def subscriptionTrades(self, market: str, callback: Callable[[Any], None]) -> None:
4499
+ return self.subscription_trades(market, callback)
4500
+
4501
+ def subscription_trades(self, market: str, callback: Callable[[Any], None]) -> None:
3605
4502
  """Subscribes to trades, which sends an object whenever a trade has occurred.
3606
4503
 
3607
4504
  ---
@@ -3638,7 +4535,7 @@ class Bitvavo:
3638
4535
  if "subscriptionTrades" not in self.callbacks:
3639
4536
  self.callbacks["subscriptionTrades"] = {}
3640
4537
  self.callbacks["subscriptionTrades"][market] = callback
3641
- self.doSend(
4538
+ self.do_send(
3642
4539
  self.ws,
3643
4540
  json.dumps(
3644
4541
  {
@@ -3648,7 +4545,11 @@ class Bitvavo:
3648
4545
  ),
3649
4546
  )
3650
4547
 
4548
+ @deprecated(version="2.2.0", reason="Use subscription_book_update instead")
3651
4549
  def subscriptionBookUpdate(self, market: str, callback: Callable[[Any], None]) -> None:
4550
+ return self.subscription_book_update(market, callback)
4551
+
4552
+ def subscription_book_update(self, market: str, callback: Callable[[Any], None]) -> None:
3652
4553
  """Subscribes to the book and returns a delta on every change to the book.
3653
4554
 
3654
4555
  ---
@@ -3705,7 +4606,7 @@ class Bitvavo:
3705
4606
  if "subscriptionBookUpdate" not in self.callbacks:
3706
4607
  self.callbacks["subscriptionBookUpdate"] = {}
3707
4608
  self.callbacks["subscriptionBookUpdate"][market] = callback
3708
- self.doSend(
4609
+ self.do_send(
3709
4610
  self.ws,
3710
4611
  json.dumps(
3711
4612
  {
@@ -3715,7 +4616,11 @@ class Bitvavo:
3715
4616
  ),
3716
4617
  )
3717
4618
 
4619
+ @deprecated(version="2.2.0", reason="Use subscription_book instead")
3718
4620
  def subscriptionBook(self, market: str, callback: Callable[[Any], None]) -> None:
4621
+ return self.subscription_book(market, callback)
4622
+
4623
+ def subscription_book(self, market: str, callback: Callable[[Any], None]) -> None:
3719
4624
  """Subscribes to the book and returns a delta on every change to the book.
3720
4625
 
3721
4626
  ---
@@ -3775,8 +4680,8 @@ class Bitvavo:
3775
4680
  self.callbacks["subscriptionBookUser"][market] = callback
3776
4681
  if "subscriptionBook" not in self.callbacks:
3777
4682
  self.callbacks["subscriptionBook"] = {}
3778
- self.callbacks["subscriptionBook"][market] = processLocalBook
3779
- self.doSend(
4683
+ self.callbacks["subscriptionBook"][market] = process_local_book
4684
+ self.do_send(
3780
4685
  self.ws,
3781
4686
  json.dumps(
3782
4687
  {
@@ -3787,4 +4692,4 @@ class Bitvavo:
3787
4692
  )
3788
4693
 
3789
4694
  self.localBook[market] = {}
3790
- self.doSend(self.ws, json.dumps({"action": "getBook", "market": market}))
4695
+ self.do_send(self.ws, json.dumps({"action": "getBook", "market": market}))