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.
- bitvavo_api_upgraded/__init__.py +3 -0
- bitvavo_api_upgraded/bitvavo.py +80 -34
- bitvavo_api_upgraded/helper_funcs.py +1 -1
- bitvavo_api_upgraded/type_aliases.py +3 -1
- bitvavo_api_upgraded-1.17.0.dist-info/METADATA +319 -0
- bitvavo_api_upgraded-1.17.0.dist-info/RECORD +10 -0
- {bitvavo_api_upgraded-1.16.0.dist-info → bitvavo_api_upgraded-1.17.0.dist-info}/licenses/LICENSE.txt +2 -2
- bitvavo_api_upgraded-1.16.0.dist-info/METADATA +0 -2429
- bitvavo_api_upgraded-1.16.0.dist-info/RECORD +0 -10
- {bitvavo_api_upgraded-1.16.0.dist-info → bitvavo_api_upgraded-1.17.0.dist-info}/WHEEL +0 -0
bitvavo_api_upgraded/__init__.py
CHANGED
bitvavo_api_upgraded/bitvavo.py
CHANGED
@@ -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())
|
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 "
|
287
|
-
self.rateLimitRemaining = int(response["
|
288
|
-
if "
|
289
|
-
self.rateLimitResetAt = int(response["
|
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, ""),
|
348
|
+
sig = createSignature(now, "GET", url.replace(self.base, ""), None, self.APISECRET)
|
328
349
|
headers = {
|
329
|
-
"
|
330
|
-
"
|
331
|
-
"
|
332
|
-
"
|
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
|
-
"
|
381
|
-
"
|
382
|
-
"
|
383
|
-
"
|
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:
|
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:
|
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
|
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,,
|