bitvavo-api-upgraded 1.16.0__py3-none-any.whl → 1.17.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.
@@ -0,0 +1,3 @@
1
+ from bitvavo_api_upgraded.bitvavo import Bitvavo
2
+
3
+ __all__ = ["Bitvavo"]
@@ -15,22 +15,22 @@ from websocket import WebSocketApp # missing stubs for WebSocketApp
15
15
 
16
16
  from bitvavo_api_upgraded.helper_funcs import configure_loggers, time_ms, time_to_wait
17
17
  from bitvavo_api_upgraded.settings import BITVAVO_API_UPGRADED
18
- from bitvavo_api_upgraded.type_aliases import anydict, errordict, intdict, ms, s_f, strdict
18
+ from bitvavo_api_upgraded.type_aliases import anydict, errordict, intdict, ms, s_f, strdict, strintdict
19
19
 
20
20
  configure_loggers()
21
21
 
22
22
  logger = get_logger(__name__)
23
23
 
24
24
 
25
- def createSignature(timestamp: ms, method: str, url: str, body: anydict, api_secret: str) -> str:
25
+ def createSignature(timestamp: ms, method: str, url: str, body: anydict | None, api_secret: str) -> str:
26
26
  string = f"{timestamp}{method}/v2{url}"
27
- if len(body.keys()) != 0:
27
+ if body is not None and len(body.keys()) > 0:
28
28
  string += json.dumps(body, separators=(",", ":"))
29
29
  signature = hmac.new(api_secret.encode("utf-8"), string.encode("utf-8"), hashlib.sha256).hexdigest()
30
30
  return signature
31
31
 
32
32
 
33
- def createPostfix(options: anydict) -> str:
33
+ def createPostfix(options: anydict | None) -> str:
34
34
  """Generate a URL postfix, based on the `options` dict.
35
35
 
36
36
  ---
@@ -41,11 +41,29 @@ def createPostfix(options: anydict) -> str:
41
41
  Returns:
42
42
  str: [description]
43
43
  """
44
+ options = _default(options, {})
44
45
  params = [f"{key}={options[key]}" for key in options]
45
46
  postfix = "&".join(params) # intersperse
46
47
  return f"?{postfix}" if len(options) > 0 else postfix
47
48
 
48
49
 
50
+ def _default(value: anydict | None, fallback: anydict) -> anydict:
51
+ """
52
+ Note that is close, but not actually equal to:
53
+
54
+ `return value or fallback`
55
+
56
+ I checked this with a temporary hypothesis test.
57
+
58
+ This note is all you will get out of me.
59
+ """
60
+ return value if value is not None else fallback
61
+
62
+
63
+ def _epoch_millis(dt: dt.datetime) -> int:
64
+ return int(dt.timestamp() * 1000)
65
+
66
+
49
67
  def asksCompare(a: float, b: float) -> bool:
50
68
  return a < b
51
69
 
@@ -105,12 +123,15 @@ def processLocalBook(ws: Bitvavo.WebSocketAppFacade, message: anydict) -> None:
105
123
 
106
124
 
107
125
  class ReceiveThread(Thread):
126
+ """This used to be `class rateLimitThread`."""
127
+
108
128
  def __init__(self, ws: WebSocketApp, ws_facade: Bitvavo.WebSocketAppFacade) -> None:
109
129
  self.ws = ws
110
130
  self.ws_facade = ws_facade
111
131
  Thread.__init__(self)
112
132
 
113
133
  def run(self) -> None:
134
+ """This used to be `self.waitForReset`."""
114
135
  try:
115
136
  while self.ws_facade.keepAlive:
116
137
  self.ws.run_forever()
@@ -283,10 +304,10 @@ class Bitvavo:
283
304
  )
284
305
  logger.info("napping-until-ban-lifted")
285
306
  time.sleep(timeToWait + 1) # plus one second to ENSURE we're able to run again.
286
- if "Bitvavo-Ratelimit-Remaining" in response:
287
- self.rateLimitRemaining = int(response["Bitvavo-Ratelimit-Remaining"])
288
- if "Bitvavo-Ratelimit-ResetAt" in response:
289
- self.rateLimitResetAt = int(response["Bitvavo-Ratelimit-ResetAt"])
307
+ if "bitvavo-ratelimit-remaining" in response:
308
+ self.rateLimitRemaining = int(response["bitvavo-ratelimit-remaining"])
309
+ if "bitvavo-ratelimit-resetat" in response:
310
+ self.rateLimitResetAt = int(response["bitvavo-ratelimit-resetat"])
290
311
 
291
312
  def publicRequest(
292
313
  self,
@@ -324,12 +345,12 @@ class Bitvavo:
324
345
  )
325
346
  if self.APIKEY != "":
326
347
  now = time_ms() + BITVAVO_API_UPGRADED.LAG
327
- sig = createSignature(now, "GET", url.replace(self.base, ""), {}, self.APISECRET)
348
+ sig = createSignature(now, "GET", url.replace(self.base, ""), None, self.APISECRET)
328
349
  headers = {
329
- "Bitvavo-Access-Key": self.APIKEY,
330
- "Bitvavo-Access-Signature": sig,
331
- "Bitvavo-Access-Timestamp": str(now),
332
- "Bitvavo-Access-Window": str(self.ACCESSWINDOW),
350
+ "bitvavo-access-key": self.APIKEY,
351
+ "bitvavo-access-signature": sig,
352
+ "bitvavo-access-timestamp": str(now),
353
+ "bitvavo-access-window": str(self.ACCESSWINDOW),
333
354
  }
334
355
  r = get(url, headers=headers, timeout=(self.ACCESSWINDOW / 1000))
335
356
  else:
@@ -344,7 +365,7 @@ class Bitvavo:
344
365
  self,
345
366
  endpoint: str,
346
367
  postfix: str,
347
- body: anydict,
368
+ body: anydict | None = None,
348
369
  method: str = "GET",
349
370
  rateLimitingWeight: int = 1,
350
371
  ) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | Any | errordict:
@@ -377,10 +398,10 @@ class Bitvavo:
377
398
  sig = createSignature(now, method, (endpoint + postfix), body, self.APISECRET)
378
399
  url = self.base + endpoint + postfix
379
400
  headers = {
380
- "Bitvavo-Access-Key": self.APIKEY,
381
- "Bitvavo-Access-Signature": sig,
382
- "Bitvavo-Access-Timestamp": str(now),
383
- "Bitvavo-Access-Window": str(self.ACCESSWINDOW),
401
+ "bitvavo-access-key": self.APIKEY,
402
+ "bitvavo-access-signature": sig,
403
+ "bitvavo-access-timestamp": str(now),
404
+ "bitvavo-access-window": str(self.ACCESSWINDOW),
384
405
  }
385
406
  if self.debugging:
386
407
  logger.debug(
@@ -438,7 +459,7 @@ class Bitvavo:
438
459
  """
439
460
  return self.publicRequest(f"{self.base}/time") # type: ignore[return-value]
440
461
 
441
- def markets(self, options: strdict) -> list[anydict] | anydict | errordict:
462
+ def markets(self, options: strdict | None = None) -> list[anydict] | anydict | errordict:
442
463
  """Get all available markets with some meta-information, unless options is given a `market` key.
443
464
  Then you will get a single market, instead of a list of markets.
444
465
 
@@ -490,7 +511,7 @@ class Bitvavo:
490
511
  postfix = createPostfix(options)
491
512
  return self.publicRequest(f"{self.base}/markets{postfix}") # type: ignore[return-value]
492
513
 
493
- def assets(self, options: strdict) -> list[anydict] | anydict:
514
+ def assets(self, options: strdict | None = None) -> list[anydict] | anydict:
494
515
  """Get all available assets, unless `options` is given a `symbol` key.
495
516
  Then you will get a single asset, instead of a list of assets.
496
517
 
@@ -539,7 +560,7 @@ class Bitvavo:
539
560
  postfix = createPostfix(options)
540
561
  return self.publicRequest(f"{self.base}/assets{postfix}") # type: ignore[return-value]
541
562
 
542
- def book(self, market: str, options: intdict) -> dict[str, str | int | list[str]] | errordict:
563
+ def book(self, market: str, options: intdict | None = None) -> dict[str, str | int | list[str]] | errordict:
543
564
  """Get a book (with two lists: asks and bids, as they're called)
544
565
 
545
566
  ---
@@ -583,7 +604,7 @@ class Bitvavo:
583
604
  postfix = createPostfix(options)
584
605
  return self.publicRequest(f"{self.base}/{market}/book{postfix}") # type: ignore[return-value]
585
606
 
586
- def publicTrades(self, market: str, options: dict[str, str | int]) -> list[anydict] | errordict:
607
+ def publicTrades(self, market: str, options: strintdict | None = None) -> list[anydict] | errordict:
587
608
  """Publically available trades
588
609
 
589
610
  ---
@@ -633,11 +654,14 @@ class Bitvavo:
633
654
  postfix = createPostfix(options)
634
655
  return self.publicRequest(f"{self.base}/{market}/trades{postfix}", 5) # type: ignore[return-value]
635
656
 
636
- def candles(
657
+ def candles( # noqa: PLR0913
637
658
  self,
638
659
  market: str,
639
660
  interval: str,
640
- options: dict[str, str | int],
661
+ options: strintdict | None = None,
662
+ limit: int | None = None,
663
+ start: dt.datetime | None = None,
664
+ end: dt.datetime | None = None,
641
665
  ) -> list[list[str]] | errordict:
642
666
  """Get up to 1440 candles for a market, with a specific interval (candle size)
643
667
 
@@ -684,11 +708,18 @@ class Bitvavo:
684
708
  ]
685
709
  ```
686
710
  """
711
+ options = _default(options, {})
687
712
  options["interval"] = interval
713
+ if limit is not None:
714
+ options["limit"] = limit
715
+ if start is not None:
716
+ options["start"] = _epoch_millis(start)
717
+ if end is not None:
718
+ options["end"] = _epoch_millis(end)
688
719
  postfix = createPostfix(options)
689
720
  return self.publicRequest(f"{self.base}/{market}/candles{postfix}") # type: ignore[return-value]
690
721
 
691
- def tickerPrice(self, options: strdict) -> list[strdict] | strdict:
722
+ def tickerPrice(self, options: strdict | None = None) -> list[strdict] | strdict:
692
723
  """Get the current price for each market
693
724
 
694
725
  ---
@@ -736,7 +767,7 @@ class Bitvavo:
736
767
  postfix = createPostfix(options)
737
768
  return self.publicRequest(f"{self.base}/ticker/price{postfix}") # type: ignore[return-value]
738
769
 
739
- def tickerBook(self, options: strdict) -> list[strdict] | strdict:
770
+ def tickerBook(self, options: strdict | None = None) -> list[strdict] | strdict:
740
771
  """Get current bid/ask, bidsize/asksize per market
741
772
 
742
773
  ---
@@ -777,7 +808,7 @@ class Bitvavo:
777
808
  postfix = createPostfix(options)
778
809
  return self.publicRequest(f"{self.base}/ticker/book{postfix}") # type: ignore[return-value]
779
810
 
780
- def ticker24h(self, options: strdict) -> list[anydict] | anydict | errordict:
811
+ def ticker24h(self, options: strdict | None = None) -> list[anydict] | anydict | errordict:
781
812
  """Get current bid/ask, bidsize/asksize per market
782
813
 
783
814
  ---
@@ -839,6 +870,7 @@ class Bitvavo:
839
870
  ]
840
871
  ```
841
872
  """
873
+ options = _default(options, {})
842
874
  rateLimitingWeight = 25
843
875
  if "market" in options:
844
876
  rateLimitingWeight = 1
@@ -1143,7 +1175,7 @@ class Bitvavo:
1143
1175
  postfix = createPostfix({"market": market, "orderId": orderId})
1144
1176
  return self.privateRequest("/order", postfix, {}, "GET") # type: ignore[return-value]
1145
1177
 
1146
- def getOrders(self, market: str, options: anydict) -> list[anydict] | errordict:
1178
+ def getOrders(self, market: str, options: anydict | None = None) -> list[anydict] | errordict:
1147
1179
  """Get multiple existing orders for a specific market
1148
1180
 
1149
1181
  ---
@@ -1214,11 +1246,12 @@ class Bitvavo:
1214
1246
  ]
1215
1247
  ```
1216
1248
  """ # noqa: E501
1249
+ options = _default(options, {})
1217
1250
  options["market"] = market
1218
1251
  postfix = createPostfix(options)
1219
1252
  return self.privateRequest("/orders", postfix, {}, "GET", 5) # type: ignore[return-value]
1220
1253
 
1221
- def cancelOrders(self, options: anydict) -> list[strdict] | errordict:
1254
+ def cancelOrders(self, options: anydict | None = None) -> list[strdict] | errordict:
1222
1255
  """Cancel all existing orders for a specific market (or account)
1223
1256
 
1224
1257
  ---
@@ -1246,7 +1279,7 @@ class Bitvavo:
1246
1279
  postfix = createPostfix(options)
1247
1280
  return self.privateRequest("/orders", postfix, {}, "DELETE") # type: ignore[return-value]
1248
1281
 
1249
- def ordersOpen(self, options: anydict) -> list[anydict] | errordict:
1282
+ def ordersOpen(self, options: anydict | None = None) -> list[anydict] | errordict:
1250
1283
  """Get all open orders, either for all markets, or a single market
1251
1284
 
1252
1285
  ---
@@ -1311,13 +1344,14 @@ class Bitvavo:
1311
1344
  ]
1312
1345
  ```
1313
1346
  """
1347
+ options = _default(options, {})
1314
1348
  rateLimitingWeight = 25
1315
1349
  if "market" in options:
1316
1350
  rateLimitingWeight = 1
1317
1351
  postfix = createPostfix(options)
1318
1352
  return self.privateRequest("/ordersOpen", postfix, {}, "GET", rateLimitingWeight) # type: ignore[return-value]
1319
1353
 
1320
- def trades(self, market: str, options: anydict) -> list[anydict] | errordict:
1354
+ def trades(self, market: str, options: anydict | None = None) -> list[anydict] | errordict:
1321
1355
  """Get all historic trades from this account
1322
1356
 
1323
1357
  ---
@@ -1359,6 +1393,7 @@ class Bitvavo:
1359
1393
  ]
1360
1394
  ```
1361
1395
  """ # noqa: E501
1396
+ options = _default(options, {})
1362
1397
  options["market"] = market
1363
1398
  postfix = createPostfix(options)
1364
1399
  return self.privateRequest("/trades", postfix, {}, "GET", 5) # type: ignore[return-value]
@@ -1386,7 +1421,16 @@ class Bitvavo:
1386
1421
  """
1387
1422
  return self.privateRequest("/account", "", {}, "GET") # type: ignore[return-value]
1388
1423
 
1389
- def balance(self, options: strdict) -> list[strdict] | errordict:
1424
+ def fees(self, market: str | None = None, quote: str | None = None) -> list[strdict] | errordict:
1425
+ options = {}
1426
+ if market is not None:
1427
+ options["market"] = market
1428
+ if quote is not None:
1429
+ options["quote"] = quote
1430
+ postfix = createPostfix(options)
1431
+ return self.privateRequest("/account/fees", postfix, {}, "GET") # type: ignore[return-value]
1432
+
1433
+ def balance(self, options: strdict | None = None) -> list[strdict] | errordict:
1390
1434
  """Get the balance for this account
1391
1435
 
1392
1436
  ---
@@ -1452,7 +1496,7 @@ class Bitvavo:
1452
1496
  postfix = createPostfix({"symbol": symbol})
1453
1497
  return self.privateRequest("/deposit", postfix, {}, "GET") # type: ignore[return-value]
1454
1498
 
1455
- def depositHistory(self, options: anydict) -> list[anydict] | errordict:
1499
+ def depositHistory(self, options: anydict | None = None) -> list[anydict] | errordict:
1456
1500
  """Get the deposit history of the account
1457
1501
 
1458
1502
  Even when you want something from a single `symbol`, you'll still receive a list with multiple deposits.
@@ -1540,7 +1584,7 @@ class Bitvavo:
1540
1584
  body["address"] = address
1541
1585
  return self.privateRequest("/withdrawal", "", body, "POST") # type: ignore[return-value]
1542
1586
 
1543
- def withdrawalHistory(self, options: anydict) -> list[anydict] | errordict:
1587
+ def withdrawalHistory(self, options: anydict | None = None) -> list[anydict] | errordict:
1544
1588
  """Get the withdrawal history
1545
1589
 
1546
1590
  ---
@@ -1700,6 +1744,8 @@ class Bitvavo:
1700
1744
  callbacks["trades"](msg_dict["response"])
1701
1745
  elif msg_dict["action"] == "privateGetAccount":
1702
1746
  callbacks["account"](msg_dict["response"])
1747
+ elif msg_dict["action"] == "privateGetFees":
1748
+ callbacks["fees"](msg_dict["response"])
1703
1749
  elif msg_dict["action"] == "privateGetBalance":
1704
1750
  callbacks["balance"](msg_dict["response"])
1705
1751
  elif msg_dict["action"] == "privateDepositAssets":
@@ -34,7 +34,7 @@ def configure_loggers() -> None:
34
34
  source: https://docs.python.org/3.9/library/logging.config.html#dictionary-schema-details
35
35
  """
36
36
  shared_pre_chain: list[Callable[[WrappedLogger, str, EventDict], EventDict]] = [
37
- structlog.threadlocal.merge_threadlocal,
37
+ # structlog.threadlocal.merge_threadlocal,
38
38
  structlog.stdlib.add_logger_name, # show which named logger made the message!
39
39
  structlog.processors.add_log_level, # info, warning, error, etc
40
40
  structlog.processors.TimeStamper(fmt="%Y-%m-%dT%H:%M:%S", utc=False), # add an ISO formatted string
@@ -3,12 +3,14 @@ This file contains all type aliases that I use within the lib,
3
3
  to clearify the intention or semantics/meaning/unit of a variable
4
4
  """
5
5
 
6
- from typing import Any
6
+ from typing import Any, Union
7
7
 
8
8
  # type simplification
9
9
  anydict = dict[str, Any]
10
10
  strdict = dict[str, str]
11
11
  intdict = dict[str, int]
12
+ # can't use | here, with __future__. Not sure why.
13
+ strintdict = dict[str, Union[str, int]]
12
14
  errordict = dict[str, Any] # same type as anydict, but the semantics/meaning is different
13
15
 
14
16
  # note: You can also use these for type conversion, so instead of int(some_float / 1000), you can just do ms(some_float
@@ -0,0 +1,319 @@
1
+ Metadata-Version: 2.3
2
+ Name: bitvavo-api-upgraded
3
+ Version: 1.17.0
4
+ Summary: A unit-tested fork of the Bitvavo API
5
+ Project-URL: homepage, https://github.com/Thaumatorium/bitvavo-api-upgraded
6
+ Project-URL: repository, https://github.com/Thaumatorium/bitvavo-api-upgraded
7
+ Project-URL: changelog, https://github.com/Thaumatorium/bitvavo-api-upgraded/blob/master/CHANGELOG.md
8
+ Author: Bitvavo BV (original code)
9
+ Author-email: NostraDavid <55331731+NostraDavid@users.noreply.github.com>
10
+ Maintainer-email: NostraDavid <55331731+NostraDavid@users.noreply.github.com>
11
+ License: ISC License
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Framework :: tox
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Financial and Insurance Industry
18
+ Classifier: License :: OSI Approved :: ISC License (ISCL)
19
+ Classifier: Operating System :: MacOS :: MacOS X
20
+ Classifier: Operating System :: Microsoft :: Windows
21
+ Classifier: Operating System :: POSIX
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Programming Language :: Python :: 3.9
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.9
30
+ Requires-Dist: python-decouple==3.*,>=3.5
31
+ Requires-Dist: requests==2.*,>=2.26
32
+ Requires-Dist: structlog==24.*,>=21.5
33
+ Requires-Dist: websocket-client==1.*,>=1.2
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Bitvavo API (upgraded)
37
+
38
+ ## Userguide
39
+
40
+ `pip install bitvavo_api_upgraded`
41
+
42
+ Works the same as the official API lib, but I have:
43
+
44
+ - typing for _all_ functions and classes
45
+ - unit tests (I already found three bugs that I fixed, because the original code
46
+ wasn't tested, at all)
47
+ - a changelog, so you can track of the changes that I make
48
+ - compatible with Python 3.7 and newer ([3.6 isn't supported as of
49
+ 2021-12-23](https://endoflife.date/python))
50
+
51
+ ## Devguide
52
+
53
+ ```shell
54
+ echo "install development requirements"
55
+ uv sync
56
+ echo "run tox, a program that creates separate environments for different python versions, for testing purposes (among other things)"
57
+ uv run tox
58
+ ```
59
+
60
+ ### Semantic Versioning (SemVer)
61
+
62
+ I'm using semantic versioning, which means that changes mean this:
63
+
64
+ 1. MAJOR version when you make incompatible API changes,
65
+ 1. MINOR version when you add functionality in a backwards compatible manner,
66
+ and
67
+ 1. PATCH version when you make backwards compatible bug fixes.
68
+
69
+ ### Versioning
70
+
71
+ Copy the following block to CHANGELOG.md and add all information since last
72
+ version bump
73
+
74
+ ```markdown
75
+ ## $UNRELEASED
76
+
77
+ ### Added
78
+
79
+ ...
80
+
81
+ ### Changed
82
+
83
+ ...
84
+
85
+ ### Removed
86
+
87
+ ...
88
+ ```
89
+
90
+ Commit those changes.
91
+
92
+ After that, run `bump-my-version bump (major|minor|patch)` to automatically
93
+ replace `$UNRELEASED` with the new version number, and also automatically tag
94
+ and commit (with tag) to release a new version via the Github workflow.
95
+
96
+ ## py.typed
97
+
98
+ Perhaps a curious file, but it simply exists to let `mypy` know that the code is
99
+ typed: [Don't forget `py.typed` for your typed Python package
100
+ ](https://blog.whtsky.me/tech/2021/dont-forget-py.typed-for-your-typed-python-package/)
101
+
102
+ ## Last note
103
+
104
+ _below this line is the old README.md_
105
+
106
+ ---
107
+
108
+ # Bitvavo SDK for Python
109
+
110
+ Crypto starts with Bitvavo. You use Bitvavo SDK for Python to buy, sell, and
111
+ store over 200 digital assets on Bitvavo from inside your app.
112
+
113
+ To trade and execute your advanced trading strategies, Bitvavo SDK for Python is
114
+ a wrapper that enables you to easily call every endpoint in [Bitvavo
115
+ API](https://docs.bitvavo.com/).
116
+
117
+ - [Prerequisites](#prerequisites) - what you need to start developing with
118
+ Bitvavo SDK for Python
119
+ - [Get started](#get-started) - rapidly create an app and start trading with
120
+ Bitvavo
121
+ - [About the SDK](#about-the-sdk) - general information about Bitvavo SDK for
122
+ Python
123
+ - [API reference](https://docs.bitvavo.com/) - information on the specifics of
124
+ every parameter
125
+
126
+ This page shows you how to use Bitvavo SDK for Python with WebSockets. For REST,
127
+ see the [REST readme](docs/rest.md).
128
+
129
+ ## Prerequisites
130
+
131
+ To start programming with Bitvavo SDK for Python you need:
132
+
133
+ - [Python3](https://www.python.org/downloads/) installed on your development
134
+ environment
135
+
136
+ If you are working on macOS, ensure that you have installed SSH certificates:
137
+
138
+ ```terminal
139
+ open /Applications/Python\ 3.12/Install\ Certificates.command
140
+ open /Applications/Python\ 3.12/Update\ Shell\ Profile.command
141
+ ```
142
+
143
+ - A Python app. Use your favorite IDE, or run from the command line
144
+ - An [API key and
145
+ secret](https://support.bitvavo.com/hc/en-us/articles/4405059841809)
146
+ associated with your Bitvavo account
147
+
148
+ You control the actions your app can do using the rights you assign to the API
149
+ key. Possible rights are:
150
+
151
+ - **View**: retrieve information about your balance, account, deposit and
152
+ withdrawals
153
+ - **Trade**: place, update, view and cancel orders
154
+ - **Withdraw**: withdraw funds
155
+
156
+ Best practice is to not grant this privilege, withdrawals using the API do
157
+ not require 2FA and e-mail confirmation.
158
+
159
+ ## Get started
160
+
161
+ Want to quickly make a trading app? Here you go:
162
+
163
+ 1. **Install Bitvavo SDK for Python**
164
+
165
+ In your Python app, add [Bitvavo SDK for
166
+ Python](https://github.com/bitvavo/python-bitvavo-api) from
167
+ [pypi.org](https://pypi.org/project/python-bitvavo-api/):
168
+
169
+ ```shell
170
+ python -m pip install python_bitvavo_api
171
+ ```
172
+
173
+ If you installed from `test.pypi.com`, update the requests library: `pip
174
+ install --upgrade requests`.
175
+
176
+ 1. **Create a simple Bitvavo implementation**
177
+
178
+ Add the following code to a new file in your app:
179
+
180
+ ```python
181
+ from python_bitvavo_api.bitvavo import Bitvavo
182
+ import json
183
+ import time
184
+
185
+ # Use this class to connect to Bitvavo and make your first calls.
186
+ # Add trading strategies to implement your business logic.
187
+ class BitvavoImplementation:
188
+ api_key = "<Replace with your your API key from Bitvavo Dashboard>"
189
+ api_secret = "<Replace with your API secret from Bitvavo Dashboard>"
190
+ bitvavo_engine = None
191
+ bitvavo_socket = None
192
+
193
+ # Connect securely to Bitvavo, create the WebSocket and error callbacks.
194
+ def __init__(self):
195
+ self.bitvavo_engine = Bitvavo({
196
+ 'APIKEY': self.api_key,
197
+ 'APISECRET': self.api_secret
198
+ })
199
+ self.bitvavo_socket = self.bitvavo_engine.newWebsocket()
200
+ self.bitvavo_socket.setErrorCallback(self.error_callback)
201
+
202
+ # Handle errors.
203
+ def error_callback(self, error):
204
+ print("Add your error message.")
205
+ #print("Errors:", json.dumps(error, indent=2))
206
+
207
+ # Retrieve the data you need from Bitvavo in order to implement your
208
+ # trading logic. Use multiple workflows to return data to your
209
+ # callbacks.
210
+ def a_trading_strategy(self):
211
+ self.bitvavo_socket.ticker24h({}, self.a_trading_strategy_callback)
212
+
213
+ # In your app you analyse data returned by the trading strategy, then make
214
+ # calls to Bitvavo to respond to market conditions.
215
+ def a_trading_strategy_callback(self, response):
216
+ # Iterate through the markets
217
+ for market in response:
218
+
219
+ match market["market"]:
220
+ case "ZRX-EUR":
221
+ print("Eureka, the latest bid for ZRX-EUR is: ", market["bid"] )
222
+ # Implement calculations for your trading logic.
223
+ # If they are positive, place an order: For example:
224
+ # self.bitvavo_socket.placeOrder("ZRX-EUR",
225
+ # 'buy',
226
+ # 'limit',
227
+ # { 'amount': '1', 'price': '00001' },
228
+ # self.order_placed_callback)
229
+ case "a different market":
230
+ print("do something else")
231
+ case _:
232
+ print("Not this one: ", market["market"])
233
+
234
+
235
+
236
+ def order_placed_callback(self, response):
237
+ # The order return parameters explain the quote and the fees for this trade.
238
+ print("Order placed:", json.dumps(response, indent=2))
239
+ # Add your business logic.
240
+
241
+
242
+ # Sockets are fast, but asynchronous. Keep the socket open while you are
243
+ # trading.
244
+ def wait_and_close(self):
245
+ # Bitvavo uses a weight based rate limiting system. Your app is limited to 1000 weight points per IP or
246
+ # API key per minute. The rate weighting for each endpoint is supplied in Bitvavo API documentation.
247
+ # This call returns the amount of points left. If you make more requests than permitted by the weight limit,
248
+ # your IP or API key is banned.
249
+ limit = self.bitvavo_engine.getRemainingLimit()
250
+ try:
251
+ while (limit > 0):
252
+ time.sleep(0.5)
253
+ limit = self.bitvavo_engine.getRemainingLimit()
254
+ except KeyboardInterrupt:
255
+ self.bitvavo_socket.closeSocket()
256
+
257
+
258
+ # Shall I re-explain main? Naaaaaaaaaa.
259
+ if __name__ == '__main__':
260
+ bvavo = BitvavoImplementation()
261
+ bvavo.a_trading_strategy()
262
+ bvavo.wait_and_close()
263
+ ```
264
+
265
+ 1. **Add security information**
266
+
267
+ You must supply your security information to trade on Bitvavo and see your
268
+ account information using the authenticate methods. Replace the values of
269
+ `api_key` and `api_secret` with your credentials from [Bitvavo
270
+ Dashboard](https://account.bitvavo.com/user/api).
271
+
272
+ You can retrieve public information such as available markets, assets and
273
+ current market without supplying your key and secret. However,
274
+ unauthenticated calls have lower rate limits based on your IP address, and
275
+ your account is blocked for longer if you exceed your limit.
276
+
277
+ 1. **Run your app**
278
+
279
+ - Command line warriors: `python3 <filename>`.
280
+ - IDE heroes: press the big green button.
281
+
282
+ Your app connects to Bitvavo and returns a list the latest trade price for each
283
+ market. You use this data to implement your trading logic.
284
+
285
+ ## About the SDK
286
+
287
+ This section explains global concepts about Bitvavo SDK for Python.
288
+
289
+ ### Rate limit
290
+
291
+ Bitvavo uses a weight based rate limiting system. Your app is limited to 1000
292
+ weight points per IP or API key per minute. When you make a call to Bitvavo API,
293
+ your remaining weight points are returned in the header of each REST request.
294
+
295
+ Websocket methods do not return your returning weight points, you track your
296
+ remaining weight points with a call to:
297
+
298
+ ```python
299
+ limit = bitvavo.getRemainingLimit()
300
+ ```
301
+
302
+ If you make more requests than permitted by the weight limit, your IP or API key
303
+ is banned.
304
+
305
+ The rate weighting for each endpoint is supplied in the [Bitvavo API
306
+ documentation](https://docs.bitvavo.com/).
307
+
308
+ ### Requests
309
+
310
+ For all methods, required parameters are passed as separate values, optional
311
+ parameters are passed as a dictionary. Return parameters are in dictionary
312
+ format: `response['<key>'] = '<value>'`. However, as a limit order requires more
313
+ information than a market order, some optional parameters are required when you
314
+ place an order.
315
+
316
+ ### Security
317
+
318
+ You must set your API key and secret for authenticated endpoints, public
319
+ endpoints do not require authentication.
@@ -0,0 +1,10 @@
1
+ bitvavo_api_upgraded/__init__.py,sha256=IS9Ci2orRtYKHyFNXq_9qk9bB6zt4KU4tPbQAxkkCUM,72
2
+ bitvavo_api_upgraded/bitvavo.py,sha256=-AgVq4hXHEsuQhCLEweyjJ_DhAPwbwtCeCPSNeweisA,125942
3
+ bitvavo_api_upgraded/helper_funcs.py,sha256=4oBdQ1xB-C2XkQTmN-refzIzWfO-IUowDSWhOSFdCRU,3212
4
+ bitvavo_api_upgraded/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ bitvavo_api_upgraded/settings.py,sha256=MalcO2buJso4NZktDRGGSZ7poF593ceb66zrmX-8dc4,1903
6
+ bitvavo_api_upgraded/type_aliases.py,sha256=NAnMSk5n6SaEIvHFeSMKnXOxfOwnbFuEnRKaAXlcmYw,932
7
+ bitvavo_api_upgraded-1.17.0.dist-info/METADATA,sha256=Csu9ntgiu7tVaEvKXJmBjrFEMAxlVfHhIHRlvYwflIo,11519
8
+ bitvavo_api_upgraded-1.17.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
9
+ bitvavo_api_upgraded-1.17.0.dist-info/licenses/LICENSE.txt,sha256=hiFyor_njVlzVblnb-78mzx1Um3CGvuFxEH3YR735rc,744
10
+ bitvavo_api_upgraded-1.17.0.dist-info/RECORD,,