bitvavo-api-upgraded 1.15.8__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 +271 -184
- bitvavo_api_upgraded/helper_funcs.py +11 -5
- bitvavo_api_upgraded/py.typed +0 -0
- bitvavo_api_upgraded/settings.py +8 -4
- bitvavo_api_upgraded/type_aliases.py +10 -7
- 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.15.8.dist-info → bitvavo_api_upgraded-1.17.0.dist-info}/WHEEL +1 -2
- {bitvavo_api_upgraded-1.15.8.dist-info → bitvavo_api_upgraded-1.17.0.dist-info/licenses}/LICENSE.txt +2 -2
- bitvavo_api_upgraded-1.15.8.dist-info/METADATA +0 -77
- bitvavo_api_upgraded-1.15.8.dist-info/RECORD +0 -10
- bitvavo_api_upgraded-1.15.8.dist-info/top_level.txt +0 -1
bitvavo_api_upgraded/bitvavo.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime as dt
|
1
4
|
import hashlib
|
2
5
|
import hmac
|
3
6
|
import json
|
4
7
|
import time
|
5
|
-
import datetime as dt
|
6
8
|
from threading import Thread
|
7
|
-
from typing import Any, Callable
|
9
|
+
from typing import Any, Callable
|
8
10
|
|
9
11
|
import websocket as ws_lib
|
10
12
|
from requests import delete, get, post, put
|
@@ -13,22 +15,22 @@ from websocket import WebSocketApp # missing stubs for WebSocketApp
|
|
13
15
|
|
14
16
|
from bitvavo_api_upgraded.helper_funcs import configure_loggers, time_ms, time_to_wait
|
15
17
|
from bitvavo_api_upgraded.settings import BITVAVO_API_UPGRADED
|
16
|
-
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
|
17
19
|
|
18
20
|
configure_loggers()
|
19
21
|
|
20
22
|
logger = get_logger(__name__)
|
21
23
|
|
22
24
|
|
23
|
-
def createSignature(timestamp: ms, method: str, url: str, body: anydict,
|
25
|
+
def createSignature(timestamp: ms, method: str, url: str, body: anydict | None, api_secret: str) -> str:
|
24
26
|
string = f"{timestamp}{method}/v2{url}"
|
25
|
-
if len(body.keys())
|
27
|
+
if body is not None and len(body.keys()) > 0:
|
26
28
|
string += json.dumps(body, separators=(",", ":"))
|
27
|
-
signature = hmac.new(
|
29
|
+
signature = hmac.new(api_secret.encode("utf-8"), string.encode("utf-8"), hashlib.sha256).hexdigest()
|
28
30
|
return signature
|
29
31
|
|
30
32
|
|
31
|
-
def createPostfix(options: anydict) -> str:
|
33
|
+
def createPostfix(options: anydict | None) -> str:
|
32
34
|
"""Generate a URL postfix, based on the `options` dict.
|
33
35
|
|
34
36
|
---
|
@@ -39,11 +41,29 @@ def createPostfix(options: anydict) -> str:
|
|
39
41
|
Returns:
|
40
42
|
str: [description]
|
41
43
|
"""
|
44
|
+
options = _default(options, {})
|
42
45
|
params = [f"{key}={options[key]}" for key in options]
|
43
46
|
postfix = "&".join(params) # intersperse
|
44
47
|
return f"?{postfix}" if len(options) > 0 else postfix
|
45
48
|
|
46
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
|
+
|
47
67
|
def asksCompare(a: float, b: float) -> bool:
|
48
68
|
return a < b
|
49
69
|
|
@@ -53,10 +73,10 @@ def bidsCompare(a: float, b: float) -> bool:
|
|
53
73
|
|
54
74
|
|
55
75
|
def sortAndInsert(
|
56
|
-
asks_or_bids:
|
57
|
-
update:
|
76
|
+
asks_or_bids: list[list[str]],
|
77
|
+
update: list[list[str]],
|
58
78
|
compareFunc: Callable[[float, float], bool],
|
59
|
-
) ->
|
79
|
+
) -> list[list[str]] | errordict:
|
60
80
|
for updateEntry in update:
|
61
81
|
entrySet: bool = False
|
62
82
|
for j in range(len(asks_or_bids)):
|
@@ -70,16 +90,15 @@ def sortAndInsert(
|
|
70
90
|
asks_or_bids[j] = updateEntry
|
71
91
|
entrySet = True
|
72
92
|
break
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
break
|
93
|
+
asks_or_bids.pop(j)
|
94
|
+
entrySet = True
|
95
|
+
break
|
77
96
|
if not entrySet:
|
78
97
|
asks_or_bids.append(updateEntry)
|
79
98
|
return asks_or_bids
|
80
99
|
|
81
100
|
|
82
|
-
def processLocalBook(ws:
|
101
|
+
def processLocalBook(ws: Bitvavo.WebSocketAppFacade, message: anydict) -> None:
|
83
102
|
market: str = ""
|
84
103
|
if "action" in message:
|
85
104
|
if message["action"] == "getBook":
|
@@ -88,29 +107,31 @@ def processLocalBook(ws: "Bitvavo.WebSocketAppFacade", message: anydict) -> None
|
|
88
107
|
ws.localBook[market]["asks"] = message["response"]["asks"]
|
89
108
|
ws.localBook[market]["nonce"] = message["response"]["nonce"]
|
90
109
|
ws.localBook[market]["market"] = market
|
91
|
-
elif "event" in message:
|
92
|
-
|
93
|
-
market = message["market"]
|
110
|
+
elif "event" in message and message["event"] == "book":
|
111
|
+
market = message["market"]
|
94
112
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
113
|
+
if message["nonce"] != ws.localBook[market]["nonce"] + 1:
|
114
|
+
# I think I've fixed this, by looking at the other Bitvavo repos (search for 'nonce' or '!=' 😆)
|
115
|
+
ws.subscriptionBook(market, ws.callbacks[market])
|
116
|
+
return
|
117
|
+
ws.localBook[market]["bids"] = sortAndInsert(ws.localBook[market]["bids"], message["bids"], bidsCompare)
|
118
|
+
ws.localBook[market]["asks"] = sortAndInsert(ws.localBook[market]["asks"], message["asks"], asksCompare)
|
119
|
+
ws.localBook[market]["nonce"] = message["nonce"]
|
102
120
|
|
103
121
|
if market != "":
|
104
122
|
ws.callbacks["subscriptionBookUser"][market](ws.localBook[market])
|
105
123
|
|
106
124
|
|
107
125
|
class ReceiveThread(Thread):
|
108
|
-
|
126
|
+
"""This used to be `class rateLimitThread`."""
|
127
|
+
|
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()
|
@@ -118,9 +139,8 @@ class ReceiveThread(Thread):
|
|
118
139
|
self.ws_facade.authenticated = False
|
119
140
|
time.sleep(self.ws_facade.reconnectTimer)
|
120
141
|
if self.ws_facade.bitvavo.debugging:
|
121
|
-
|
122
|
-
|
123
|
-
)
|
142
|
+
msg = f"we have just set reconnect to true and have waited for {self.ws_facade.reconnectTimer}"
|
143
|
+
logger.debug(msg)
|
124
144
|
self.ws_facade.reconnectTimer = self.ws_facade.reconnectTimer * 2
|
125
145
|
except KeyboardInterrupt:
|
126
146
|
if self.ws_facade.bitvavo.debugging:
|
@@ -136,17 +156,18 @@ def callback_example(response: Any) -> None:
|
|
136
156
|
|
137
157
|
I made this so you can see what kind of function you'll need to stick into the websocket functions.
|
138
158
|
"""
|
139
|
-
if isinstance(response,
|
159
|
+
if isinstance(response, dict):
|
140
160
|
# instead of printing, you could save the object to a file:
|
141
161
|
import json
|
142
162
|
from pathlib import Path
|
143
163
|
|
144
164
|
HERE = Path.cwd() # root of your project folder
|
145
165
|
filepath = HERE / "your_output.json"
|
146
|
-
# a = append; figure out yourself to create multiple callback functions, probably one for each type of call that
|
166
|
+
# a = append; figure out yourself to create multiple callback functions, probably one for each type of call that
|
167
|
+
# you want to make
|
147
168
|
with filepath.open("a") as file:
|
148
169
|
file.write(json.dumps(response))
|
149
|
-
elif isinstance(response,
|
170
|
+
elif isinstance(response, list):
|
150
171
|
# Whether `item` is a list or a dict doesn't matter to print
|
151
172
|
for item in response:
|
152
173
|
print(item)
|
@@ -187,9 +208,9 @@ def error_callback_example(msg: errordict) -> None:
|
|
187
208
|
content=f"{msg}",
|
188
209
|
).execute()
|
189
210
|
```
|
190
|
-
"""
|
211
|
+
""" # noqa: E501
|
191
212
|
# easiest thing is to use the logger, but there's a good chance this message gets silently eaten.
|
192
|
-
logger.error(msg)
|
213
|
+
logger.error("error", msg=msg)
|
193
214
|
|
194
215
|
|
195
216
|
class Bitvavo:
|
@@ -211,7 +232,9 @@ class Bitvavo:
|
|
211
232
|
```
|
212
233
|
"""
|
213
234
|
|
214
|
-
def __init__(self, options:
|
235
|
+
def __init__(self, options: dict[str, str | int] | None = None) -> None:
|
236
|
+
if options is None:
|
237
|
+
options = {}
|
215
238
|
_options = {k.upper(): v for k, v in options.items()}
|
216
239
|
self.base: str = str(_options.get("RESTURL", "https://api.bitvavo.com/v2"))
|
217
240
|
self.wsUrl: str = str(_options.get("WSURL", "wss://ws.bitvavo.com/v2/"))
|
@@ -220,7 +243,7 @@ class Bitvavo:
|
|
220
243
|
self.APISECRET = str(_options.get("APISECRET", ""))
|
221
244
|
self.rateLimitRemaining: int = 1000
|
222
245
|
self.rateLimitResetAt: ms = 0
|
223
|
-
# TODO(NostraDavid) for v2: remove this functionality - logger.debug is a level that can be set
|
246
|
+
# TODO(NostraDavid): for v2: remove this functionality - logger.debug is a level that can be set
|
224
247
|
self.debugging = bool(_options.get("DEBUGGING", False))
|
225
248
|
|
226
249
|
def calcLag(self) -> ms:
|
@@ -256,7 +279,7 @@ class Bitvavo:
|
|
256
279
|
"""
|
257
280
|
return self.rateLimitRemaining
|
258
281
|
|
259
|
-
def updateRateLimit(self, response:
|
282
|
+
def updateRateLimit(self, response: anydict | errordict) -> None:
|
260
283
|
"""
|
261
284
|
Update the rate limited
|
262
285
|
|
@@ -264,34 +287,33 @@ class Bitvavo:
|
|
264
287
|
|
265
288
|
If you're not banned, then use the received headers to update the variables.
|
266
289
|
"""
|
267
|
-
if "errorCode" in response:
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
self.rateLimitResetAt = int(response["Bitvavo-Ratelimit-ResetAt"])
|
290
|
+
if "errorCode" in response and response["errorCode"] == 105: # noqa: PLR2004
|
291
|
+
self.rateLimitRemaining = 0
|
292
|
+
# rateLimitResetAt is a value that's stripped from a string.
|
293
|
+
# Kind of a terrible way to pass that information, but eh, whatever, I guess...
|
294
|
+
# Anyway, here is the string that's being pulled apart:
|
295
|
+
# "Your IP or API key has been banned for not respecting the rate limit. The ban expires at ${expiryInMs}""
|
296
|
+
self.rateLimitResetAt = ms(response["error"].split(" at ")[1].split(".")[0])
|
297
|
+
timeToWait = time_to_wait(self.rateLimitResetAt)
|
298
|
+
logger.warning(
|
299
|
+
"banned",
|
300
|
+
info={
|
301
|
+
"wait_time_seconds": timeToWait + 1,
|
302
|
+
"until": (dt.datetime.now(tz=dt.timezone.utc) + dt.timedelta(seconds=timeToWait + 1)).isoformat(),
|
303
|
+
},
|
304
|
+
)
|
305
|
+
logger.info("napping-until-ban-lifted")
|
306
|
+
time.sleep(timeToWait + 1) # plus one second to ENSURE we're able to run again.
|
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"])
|
289
311
|
|
290
312
|
def publicRequest(
|
291
313
|
self,
|
292
314
|
url: str,
|
293
315
|
rateLimitingWeight: int = 1,
|
294
|
-
) ->
|
316
|
+
) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | errordict:
|
295
317
|
"""Execute a request to the public part of the API; no API key and/or SECRET necessary.
|
296
318
|
Will return the reponse as one of three types.
|
297
319
|
|
@@ -305,9 +327,9 @@ class Bitvavo:
|
|
305
327
|
Returns:
|
306
328
|
```python
|
307
329
|
# either of one:
|
308
|
-
|
309
|
-
|
310
|
-
|
330
|
+
dict[str, Any]
|
331
|
+
list[dict[str, Any]]
|
332
|
+
list[list[str]]
|
311
333
|
```
|
312
334
|
"""
|
313
335
|
if (self.rateLimitRemaining - rateLimitingWeight) <= BITVAVO_API_UPGRADED.RATE_LIMITING_BUFFER:
|
@@ -323,12 +345,12 @@ class Bitvavo:
|
|
323
345
|
)
|
324
346
|
if self.APIKEY != "":
|
325
347
|
now = time_ms() + BITVAVO_API_UPGRADED.LAG
|
326
|
-
sig = createSignature(now, "GET", url.replace(self.base, ""),
|
348
|
+
sig = createSignature(now, "GET", url.replace(self.base, ""), None, self.APISECRET)
|
327
349
|
headers = {
|
328
|
-
"
|
329
|
-
"
|
330
|
-
"
|
331
|
-
"
|
350
|
+
"bitvavo-access-key": self.APIKEY,
|
351
|
+
"bitvavo-access-signature": sig,
|
352
|
+
"bitvavo-access-timestamp": str(now),
|
353
|
+
"bitvavo-access-window": str(self.ACCESSWINDOW),
|
332
354
|
}
|
333
355
|
r = get(url, headers=headers, timeout=(self.ACCESSWINDOW / 1000))
|
334
356
|
else:
|
@@ -337,16 +359,16 @@ class Bitvavo:
|
|
337
359
|
self.updateRateLimit(r.json())
|
338
360
|
else:
|
339
361
|
self.updateRateLimit(dict(r.headers))
|
340
|
-
return r.json() # type:ignore
|
362
|
+
return r.json() # type:ignore[no-any-return]
|
341
363
|
|
342
364
|
def privateRequest(
|
343
365
|
self,
|
344
366
|
endpoint: str,
|
345
367
|
postfix: str,
|
346
|
-
body: anydict,
|
368
|
+
body: anydict | None = None,
|
347
369
|
method: str = "GET",
|
348
370
|
rateLimitingWeight: int = 1,
|
349
|
-
) ->
|
371
|
+
) -> list[anydict] | list[list[str]] | intdict | strdict | anydict | Any | errordict:
|
350
372
|
"""Execute a request to the private part of the API. API key and SECRET are required.
|
351
373
|
Will return the reponse as one of three types.
|
352
374
|
|
@@ -364,22 +386,22 @@ class Bitvavo:
|
|
364
386
|
Returns:
|
365
387
|
```python
|
366
388
|
# either of one:
|
367
|
-
|
368
|
-
|
369
|
-
|
389
|
+
dict[str, Any]
|
390
|
+
list[dict[str, Any]]
|
391
|
+
list[list[str]]
|
370
392
|
```
|
371
393
|
"""
|
372
394
|
if (self.rateLimitRemaining - rateLimitingWeight) <= BITVAVO_API_UPGRADED.RATE_LIMITING_BUFFER:
|
373
395
|
self.sleep_until_can_continue()
|
374
|
-
# if this method breaks: add `= {}` after `body:
|
396
|
+
# if this method breaks: add `= {}` after `body: dict`
|
375
397
|
now = time_ms() + BITVAVO_API_UPGRADED.LAG
|
376
398
|
sig = createSignature(now, method, (endpoint + postfix), body, self.APISECRET)
|
377
399
|
url = self.base + endpoint + postfix
|
378
400
|
headers = {
|
379
|
-
"
|
380
|
-
"
|
381
|
-
"
|
382
|
-
"
|
401
|
+
"bitvavo-access-key": self.APIKEY,
|
402
|
+
"bitvavo-access-signature": sig,
|
403
|
+
"bitvavo-access-timestamp": str(now),
|
404
|
+
"bitvavo-access-window": str(self.ACCESSWINDOW),
|
383
405
|
}
|
384
406
|
if self.debugging:
|
385
407
|
logger.debug(
|
@@ -405,12 +427,15 @@ class Bitvavo:
|
|
405
427
|
self.updateRateLimit(dict(r.headers))
|
406
428
|
return r.json()
|
407
429
|
|
408
|
-
def sleep_until_can_continue(self):
|
430
|
+
def sleep_until_can_continue(self) -> None:
|
409
431
|
napTime = time_to_wait(self.rateLimitResetAt)
|
410
432
|
logger.warning("rate-limit-reached", rateLimitRemaining=self.rateLimitRemaining)
|
411
|
-
logger.info(
|
412
|
-
|
413
|
-
|
433
|
+
logger.info(
|
434
|
+
"napping-until-reset",
|
435
|
+
napTime=napTime,
|
436
|
+
currentTime=dt.datetime.now(tz=dt.timezone.utc).isoformat(),
|
437
|
+
targetDatetime=dt.datetime.fromtimestamp(self.rateLimitResetAt / 1000.0, tz=dt.timezone.utc).isoformat(),
|
438
|
+
)
|
414
439
|
time.sleep(napTime + 1) # +1 to add a tiny bit of buffer time
|
415
440
|
|
416
441
|
def time(self) -> intdict:
|
@@ -432,9 +457,9 @@ class Bitvavo:
|
|
432
457
|
{"time": 1539180275424 }
|
433
458
|
```
|
434
459
|
"""
|
435
|
-
return self.publicRequest(f"{self.base}/time")
|
460
|
+
return self.publicRequest(f"{self.base}/time") # type: ignore[return-value]
|
436
461
|
|
437
|
-
def markets(self, options: strdict) ->
|
462
|
+
def markets(self, options: strdict | None = None) -> list[anydict] | anydict | errordict:
|
438
463
|
"""Get all available markets with some meta-information, unless options is given a `market` key.
|
439
464
|
Then you will get a single market, instead of a list of markets.
|
440
465
|
|
@@ -484,9 +509,9 @@ class Bitvavo:
|
|
484
509
|
```
|
485
510
|
"""
|
486
511
|
postfix = createPostfix(options)
|
487
|
-
return self.publicRequest(f"{self.base}/markets{postfix}")
|
512
|
+
return self.publicRequest(f"{self.base}/markets{postfix}") # type: ignore[return-value]
|
488
513
|
|
489
|
-
def assets(self, options: strdict) ->
|
514
|
+
def assets(self, options: strdict | None = None) -> list[anydict] | anydict:
|
490
515
|
"""Get all available assets, unless `options` is given a `symbol` key.
|
491
516
|
Then you will get a single asset, instead of a list of assets.
|
492
517
|
|
@@ -533,9 +558,9 @@ class Bitvavo:
|
|
533
558
|
```
|
534
559
|
"""
|
535
560
|
postfix = createPostfix(options)
|
536
|
-
return self.publicRequest(f"{self.base}/assets{postfix}")
|
561
|
+
return self.publicRequest(f"{self.base}/assets{postfix}") # type: ignore[return-value]
|
537
562
|
|
538
|
-
def book(self, market: str, options: intdict) ->
|
563
|
+
def book(self, market: str, options: intdict | None = None) -> dict[str, str | int | list[str]] | errordict:
|
539
564
|
"""Get a book (with two lists: asks and bids, as they're called)
|
540
565
|
|
541
566
|
---
|
@@ -577,9 +602,9 @@ class Bitvavo:
|
|
577
602
|
```
|
578
603
|
"""
|
579
604
|
postfix = createPostfix(options)
|
580
|
-
return self.publicRequest(f"{self.base}/{market}/book{postfix}")
|
605
|
+
return self.publicRequest(f"{self.base}/{market}/book{postfix}") # type: ignore[return-value]
|
581
606
|
|
582
|
-
def publicTrades(self, market: str, options:
|
607
|
+
def publicTrades(self, market: str, options: strintdict | None = None) -> list[anydict] | errordict:
|
583
608
|
"""Publically available trades
|
584
609
|
|
585
610
|
---
|
@@ -599,7 +624,8 @@ class Bitvavo:
|
|
599
624
|
options={
|
600
625
|
"limit": [ 1 .. 1000 ], default 500
|
601
626
|
"start": int timestamp in ms >= 0
|
602
|
-
|
627
|
+
# (that's somewhere in the year 2243, or near the number 2^52)
|
628
|
+
"end": int timestamp in ms <= 8_640_000_000_000_000
|
603
629
|
"tradeIdFrom": "" # if you get a list and want everything AFTER a certain id, put that id here
|
604
630
|
"tradeIdTo": "" # if you get a list and want everything BEFORE a certain id, put that id here
|
605
631
|
}
|
@@ -626,14 +652,17 @@ class Bitvavo:
|
|
626
652
|
```
|
627
653
|
"""
|
628
654
|
postfix = createPostfix(options)
|
629
|
-
return self.publicRequest(f"{self.base}/{market}/trades{postfix}", 5)
|
655
|
+
return self.publicRequest(f"{self.base}/{market}/trades{postfix}", 5) # type: ignore[return-value]
|
630
656
|
|
631
|
-
def candles(
|
657
|
+
def candles( # noqa: PLR0913
|
632
658
|
self,
|
633
659
|
market: str,
|
634
660
|
interval: str,
|
635
|
-
options:
|
636
|
-
|
661
|
+
options: strintdict | None = None,
|
662
|
+
limit: int | None = None,
|
663
|
+
start: dt.datetime | None = None,
|
664
|
+
end: dt.datetime | None = None,
|
665
|
+
) -> list[list[str]] | errordict:
|
637
666
|
"""Get up to 1440 candles for a market, with a specific interval (candle size)
|
638
667
|
|
639
668
|
Extra reading material: https://en.wikipedia.org/wiki/Candlestick_chart
|
@@ -679,11 +708,18 @@ class Bitvavo:
|
|
679
708
|
]
|
680
709
|
```
|
681
710
|
"""
|
711
|
+
options = _default(options, {})
|
682
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)
|
683
719
|
postfix = createPostfix(options)
|
684
|
-
return self.publicRequest(f"{self.base}/{market}/candles{postfix}")
|
720
|
+
return self.publicRequest(f"{self.base}/{market}/candles{postfix}") # type: ignore[return-value]
|
685
721
|
|
686
|
-
def tickerPrice(self, options: strdict) ->
|
722
|
+
def tickerPrice(self, options: strdict | None = None) -> list[strdict] | strdict:
|
687
723
|
"""Get the current price for each market
|
688
724
|
|
689
725
|
---
|
@@ -729,9 +765,9 @@ class Bitvavo:
|
|
729
765
|
```
|
730
766
|
"""
|
731
767
|
postfix = createPostfix(options)
|
732
|
-
return self.publicRequest(f"{self.base}/ticker/price{postfix}")
|
768
|
+
return self.publicRequest(f"{self.base}/ticker/price{postfix}") # type: ignore[return-value]
|
733
769
|
|
734
|
-
def tickerBook(self, options: strdict) ->
|
770
|
+
def tickerBook(self, options: strdict | None = None) -> list[strdict] | strdict:
|
735
771
|
"""Get current bid/ask, bidsize/asksize per market
|
736
772
|
|
737
773
|
---
|
@@ -770,9 +806,9 @@ class Bitvavo:
|
|
770
806
|
```
|
771
807
|
"""
|
772
808
|
postfix = createPostfix(options)
|
773
|
-
return self.publicRequest(f"{self.base}/ticker/book{postfix}")
|
809
|
+
return self.publicRequest(f"{self.base}/ticker/book{postfix}") # type: ignore[return-value]
|
774
810
|
|
775
|
-
def ticker24h(self, options: strdict) ->
|
811
|
+
def ticker24h(self, options: strdict | None = None) -> list[anydict] | anydict | errordict:
|
776
812
|
"""Get current bid/ask, bidsize/asksize per market
|
777
813
|
|
778
814
|
---
|
@@ -834,11 +870,12 @@ class Bitvavo:
|
|
834
870
|
]
|
835
871
|
```
|
836
872
|
"""
|
873
|
+
options = _default(options, {})
|
837
874
|
rateLimitingWeight = 25
|
838
875
|
if "market" in options:
|
839
876
|
rateLimitingWeight = 1
|
840
877
|
postfix = createPostfix(options)
|
841
|
-
return self.publicRequest(f"{self.base}/ticker/24h{postfix}", rateLimitingWeight)
|
878
|
+
return self.publicRequest(f"{self.base}/ticker/24h{postfix}", rateLimitingWeight) # type: ignore[return-value]
|
842
879
|
|
843
880
|
def placeOrder(self, market: str, side: str, orderType: str, body: anydict) -> anydict:
|
844
881
|
"""Place a new order on the exchange
|
@@ -959,15 +996,14 @@ class Bitvavo:
|
|
959
996
|
"disableMarketProtection": true
|
960
997
|
}
|
961
998
|
```
|
962
|
-
"""
|
999
|
+
""" # noqa: E501
|
963
1000
|
body["market"] = market
|
964
1001
|
body["side"] = side
|
965
1002
|
body["orderType"] = orderType
|
966
|
-
return self.privateRequest("/order", "", body, "POST")
|
1003
|
+
return self.privateRequest("/order", "", body, "POST") # type: ignore[return-value]
|
967
1004
|
|
968
1005
|
def updateOrder(self, market: str, orderId: str, body: anydict) -> anydict:
|
969
|
-
"""Update an existing order for a specific market. Make sure that at least one of the optional parameters is set,
|
970
|
-
otherwise nothing will be updated.
|
1006
|
+
"""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.
|
971
1007
|
|
972
1008
|
---
|
973
1009
|
Args:
|
@@ -1044,10 +1080,10 @@ class Bitvavo:
|
|
1044
1080
|
"disableMarketProtection": true
|
1045
1081
|
}
|
1046
1082
|
```
|
1047
|
-
"""
|
1083
|
+
""" # noqa: E501
|
1048
1084
|
body["market"] = market
|
1049
1085
|
body["orderId"] = orderId
|
1050
|
-
return self.privateRequest("/order", "", body, "PUT")
|
1086
|
+
return self.privateRequest("/order", "", body, "PUT") # type: ignore[return-value]
|
1051
1087
|
|
1052
1088
|
def cancelOrder(self, market: str, orderId: str) -> strdict:
|
1053
1089
|
"""Cancel an existing order for a specific market
|
@@ -1072,9 +1108,9 @@ class Bitvavo:
|
|
1072
1108
|
```
|
1073
1109
|
"""
|
1074
1110
|
postfix = createPostfix({"market": market, "orderId": orderId})
|
1075
|
-
return self.privateRequest("/order", postfix, {}, "DELETE")
|
1111
|
+
return self.privateRequest("/order", postfix, {}, "DELETE") # type: ignore[return-value]
|
1076
1112
|
|
1077
|
-
def getOrder(self, market: str, orderId: str) ->
|
1113
|
+
def getOrder(self, market: str, orderId: str) -> list[anydict] | errordict:
|
1078
1114
|
"""Get an existing order for a specific market
|
1079
1115
|
|
1080
1116
|
---
|
@@ -1137,9 +1173,9 @@ class Bitvavo:
|
|
1137
1173
|
```
|
1138
1174
|
"""
|
1139
1175
|
postfix = createPostfix({"market": market, "orderId": orderId})
|
1140
|
-
return self.privateRequest("/order", postfix, {}, "GET")
|
1176
|
+
return self.privateRequest("/order", postfix, {}, "GET") # type: ignore[return-value]
|
1141
1177
|
|
1142
|
-
def getOrders(self, market: str, options: anydict) ->
|
1178
|
+
def getOrders(self, market: str, options: anydict | None = None) -> list[anydict] | errordict:
|
1143
1179
|
"""Get multiple existing orders for a specific market
|
1144
1180
|
|
1145
1181
|
---
|
@@ -1209,12 +1245,13 @@ class Bitvavo:
|
|
1209
1245
|
}
|
1210
1246
|
]
|
1211
1247
|
```
|
1212
|
-
"""
|
1248
|
+
""" # noqa: E501
|
1249
|
+
options = _default(options, {})
|
1213
1250
|
options["market"] = market
|
1214
1251
|
postfix = createPostfix(options)
|
1215
|
-
return self.privateRequest("/orders", postfix, {}, "GET", 5)
|
1252
|
+
return self.privateRequest("/orders", postfix, {}, "GET", 5) # type: ignore[return-value]
|
1216
1253
|
|
1217
|
-
def cancelOrders(self, options: anydict) ->
|
1254
|
+
def cancelOrders(self, options: anydict | None = None) -> list[strdict] | errordict:
|
1218
1255
|
"""Cancel all existing orders for a specific market (or account)
|
1219
1256
|
|
1220
1257
|
---
|
@@ -1240,9 +1277,9 @@ class Bitvavo:
|
|
1240
1277
|
```
|
1241
1278
|
"""
|
1242
1279
|
postfix = createPostfix(options)
|
1243
|
-
return self.privateRequest("/orders", postfix, {}, "DELETE")
|
1280
|
+
return self.privateRequest("/orders", postfix, {}, "DELETE") # type: ignore[return-value]
|
1244
1281
|
|
1245
|
-
def ordersOpen(self, options: anydict) ->
|
1282
|
+
def ordersOpen(self, options: anydict | None = None) -> list[anydict] | errordict:
|
1246
1283
|
"""Get all open orders, either for all markets, or a single market
|
1247
1284
|
|
1248
1285
|
---
|
@@ -1307,13 +1344,14 @@ class Bitvavo:
|
|
1307
1344
|
]
|
1308
1345
|
```
|
1309
1346
|
"""
|
1347
|
+
options = _default(options, {})
|
1310
1348
|
rateLimitingWeight = 25
|
1311
1349
|
if "market" in options:
|
1312
1350
|
rateLimitingWeight = 1
|
1313
1351
|
postfix = createPostfix(options)
|
1314
|
-
return self.privateRequest("/ordersOpen", postfix, {}, "GET", rateLimitingWeight)
|
1352
|
+
return self.privateRequest("/ordersOpen", postfix, {}, "GET", rateLimitingWeight) # type: ignore[return-value]
|
1315
1353
|
|
1316
|
-
def trades(self, market: str, options: anydict) ->
|
1354
|
+
def trades(self, market: str, options: anydict | None = None) -> list[anydict] | errordict:
|
1317
1355
|
"""Get all historic trades from this account
|
1318
1356
|
|
1319
1357
|
---
|
@@ -1354,12 +1392,13 @@ class Bitvavo:
|
|
1354
1392
|
}
|
1355
1393
|
]
|
1356
1394
|
```
|
1357
|
-
"""
|
1395
|
+
""" # noqa: E501
|
1396
|
+
options = _default(options, {})
|
1358
1397
|
options["market"] = market
|
1359
1398
|
postfix = createPostfix(options)
|
1360
|
-
return self.privateRequest("/trades", postfix, {}, "GET", 5)
|
1399
|
+
return self.privateRequest("/trades", postfix, {}, "GET", 5) # type: ignore[return-value]
|
1361
1400
|
|
1362
|
-
def account(self) ->
|
1401
|
+
def account(self) -> dict[str, strdict]:
|
1363
1402
|
"""Get all fees for this account
|
1364
1403
|
|
1365
1404
|
---
|
@@ -1380,9 +1419,18 @@ class Bitvavo:
|
|
1380
1419
|
}
|
1381
1420
|
```
|
1382
1421
|
"""
|
1383
|
-
return self.privateRequest("/account", "", {}, "GET")
|
1422
|
+
return self.privateRequest("/account", "", {}, "GET") # type: ignore[return-value]
|
1423
|
+
|
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]
|
1384
1432
|
|
1385
|
-
def balance(self, options: strdict) ->
|
1433
|
+
def balance(self, options: strdict | None = None) -> list[strdict] | errordict:
|
1386
1434
|
"""Get the balance for this account
|
1387
1435
|
|
1388
1436
|
---
|
@@ -1411,7 +1459,7 @@ class Bitvavo:
|
|
1411
1459
|
```
|
1412
1460
|
"""
|
1413
1461
|
postfix = createPostfix(options)
|
1414
|
-
return self.privateRequest("/balance", postfix, {}, "GET", 5)
|
1462
|
+
return self.privateRequest("/balance", postfix, {}, "GET", 5) # type: ignore[return-value]
|
1415
1463
|
|
1416
1464
|
def depositAssets(self, symbol: str) -> strdict:
|
1417
1465
|
"""Get the deposit address (with paymentId for some assets) or bank account information to increase your balance
|
@@ -1446,9 +1494,9 @@ class Bitvavo:
|
|
1446
1494
|
```
|
1447
1495
|
"""
|
1448
1496
|
postfix = createPostfix({"symbol": symbol})
|
1449
|
-
return self.privateRequest("/deposit", postfix, {}, "GET")
|
1497
|
+
return self.privateRequest("/deposit", postfix, {}, "GET") # type: ignore[return-value]
|
1450
1498
|
|
1451
|
-
def depositHistory(self, options: anydict) ->
|
1499
|
+
def depositHistory(self, options: anydict | None = None) -> list[anydict] | errordict:
|
1452
1500
|
"""Get the deposit history of the account
|
1453
1501
|
|
1454
1502
|
Even when you want something from a single `symbol`, you'll still receive a list with multiple deposits.
|
@@ -1495,9 +1543,9 @@ class Bitvavo:
|
|
1495
1543
|
}
|
1496
1544
|
]
|
1497
1545
|
```
|
1498
|
-
"""
|
1546
|
+
""" # noqa: E501
|
1499
1547
|
postfix = createPostfix(options)
|
1500
|
-
return self.privateRequest("/depositHistory", postfix, {}, "GET", 5)
|
1548
|
+
return self.privateRequest("/depositHistory", postfix, {}, "GET", 5) # type: ignore[return-value]
|
1501
1549
|
|
1502
1550
|
def withdrawAssets(self, symbol: str, amount: str, address: str, body: anydict) -> anydict:
|
1503
1551
|
"""Withdraw a coin/token to an external crypto address or bank account.
|
@@ -1530,13 +1578,13 @@ class Bitvavo:
|
|
1530
1578
|
"amount": "1.5"
|
1531
1579
|
}
|
1532
1580
|
```
|
1533
|
-
"""
|
1581
|
+
""" # noqa: E501
|
1534
1582
|
body["symbol"] = symbol
|
1535
1583
|
body["amount"] = amount
|
1536
1584
|
body["address"] = address
|
1537
|
-
return self.privateRequest("/withdrawal", "", body, "POST")
|
1585
|
+
return self.privateRequest("/withdrawal", "", body, "POST") # type: ignore[return-value]
|
1538
1586
|
|
1539
|
-
def withdrawalHistory(self, options: anydict) ->
|
1587
|
+
def withdrawalHistory(self, options: anydict | None = None) -> list[anydict] | errordict:
|
1540
1588
|
"""Get the withdrawal history
|
1541
1589
|
|
1542
1590
|
---
|
@@ -1572,11 +1620,11 @@ class Bitvavo:
|
|
1572
1620
|
}
|
1573
1621
|
}
|
1574
1622
|
```
|
1575
|
-
"""
|
1623
|
+
""" # noqa: E501
|
1576
1624
|
postfix = createPostfix(options)
|
1577
|
-
return self.privateRequest("/withdrawalHistory", postfix, {}, "GET", 5)
|
1625
|
+
return self.privateRequest("/withdrawalHistory", postfix, {}, "GET", 5) # type: ignore[return-value]
|
1578
1626
|
|
1579
|
-
def newWebsocket(self) ->
|
1627
|
+
def newWebsocket(self) -> Bitvavo.WebSocketAppFacade:
|
1580
1628
|
return Bitvavo.WebSocketAppFacade(self.APIKEY, self.APISECRET, self.ACCESSWINDOW, self.wsUrl, self)
|
1581
1629
|
|
1582
1630
|
class WebSocketAppFacade:
|
@@ -1586,7 +1634,14 @@ class Bitvavo:
|
|
1586
1634
|
It's a facade for the WebSocketApp class, with its own implementation for the on_* methods
|
1587
1635
|
"""
|
1588
1636
|
|
1589
|
-
def __init__(
|
1637
|
+
def __init__(
|
1638
|
+
self,
|
1639
|
+
APIKEY: str,
|
1640
|
+
APISECRET: str,
|
1641
|
+
ACCESSWINDOW: int,
|
1642
|
+
WSURL: str,
|
1643
|
+
bitvavo: Bitvavo,
|
1644
|
+
) -> None:
|
1590
1645
|
self.APIKEY = APIKEY
|
1591
1646
|
self.APISECRET = APISECRET
|
1592
1647
|
self.ACCESSWINDOW = ACCESSWINDOW
|
@@ -1623,35 +1678,38 @@ class Bitvavo:
|
|
1623
1678
|
self.keepAlive = False
|
1624
1679
|
self.receiveThread.join()
|
1625
1680
|
|
1626
|
-
def waitForSocket(self, ws: WebSocketApp, message: str, private: bool) -> None:
|
1681
|
+
def waitForSocket(self, ws: WebSocketApp, message: str, private: bool) -> None: # noqa: ARG002, FBT001
|
1627
1682
|
while True:
|
1628
1683
|
if (not private and self.open) or (private and self.authenticated and self.open):
|
1629
1684
|
return
|
1630
1685
|
time.sleep(0.1)
|
1631
1686
|
|
1632
|
-
def doSend(self, ws: WebSocketApp, message: str, private: bool = False) -> None:
|
1633
|
-
# TODO(NostraDavid) add nap-time to the websocket, or do it here; I don't know yet.
|
1687
|
+
def doSend(self, ws: WebSocketApp, message: str, private: bool = False) -> None: # noqa: FBT001, FBT002
|
1688
|
+
# TODO(NostraDavid): add nap-time to the websocket, or do it here; I don't know yet.
|
1634
1689
|
if private and self.APIKEY == "":
|
1635
|
-
logger.error(
|
1690
|
+
logger.error(
|
1691
|
+
"no-apikey",
|
1692
|
+
tip="set the API key to be able to make private API calls",
|
1693
|
+
)
|
1636
1694
|
return
|
1637
1695
|
self.waitForSocket(ws, message, private)
|
1638
1696
|
ws.send(message)
|
1639
1697
|
if self.bitvavo.debugging:
|
1640
1698
|
logger.debug("message-sent", message=message)
|
1641
1699
|
|
1642
|
-
def on_message(self, ws, msg: str) -> None: # noqa: C901 (too-complex)
|
1700
|
+
def on_message(self, ws: Any, msg: str) -> None: # noqa: C901, PLR0912, PLR0915, ARG002 (too-complex)
|
1643
1701
|
if self.bitvavo.debugging:
|
1644
1702
|
logger.debug("message-received", message=msg)
|
1645
1703
|
msg_dict: anydict = json.loads(msg)
|
1646
1704
|
callbacks = self.callbacks
|
1647
1705
|
|
1648
1706
|
if "error" in msg_dict:
|
1649
|
-
if msg_dict["errorCode"] == 105:
|
1707
|
+
if msg_dict["errorCode"] == 105: # noqa: PLR2004
|
1650
1708
|
self.bitvavo.updateRateLimit(msg_dict)
|
1651
1709
|
if "error" in callbacks:
|
1652
1710
|
callbacks["error"](msg_dict)
|
1653
1711
|
else:
|
1654
|
-
logger.error(msg_dict)
|
1712
|
+
logger.error("error", msg_dict=msg_dict)
|
1655
1713
|
|
1656
1714
|
if "action" in msg_dict:
|
1657
1715
|
if msg_dict["action"] == "getTime":
|
@@ -1686,6 +1744,8 @@ class Bitvavo:
|
|
1686
1744
|
callbacks["trades"](msg_dict["response"])
|
1687
1745
|
elif msg_dict["action"] == "privateGetAccount":
|
1688
1746
|
callbacks["account"](msg_dict["response"])
|
1747
|
+
elif msg_dict["action"] == "privateGetFees":
|
1748
|
+
callbacks["fees"](msg_dict["response"])
|
1689
1749
|
elif msg_dict["action"] == "privateGetBalance":
|
1690
1750
|
callbacks["balance"](msg_dict["response"])
|
1691
1751
|
elif msg_dict["action"] == "privateDepositAssets":
|
@@ -1702,17 +1762,13 @@ class Bitvavo:
|
|
1702
1762
|
market = msg_dict["response"]["market"]
|
1703
1763
|
if "book" in callbacks:
|
1704
1764
|
callbacks["book"](msg_dict["response"])
|
1705
|
-
if self.keepBookCopy:
|
1706
|
-
|
1707
|
-
callbacks["subscriptionBook"][market](self, msg_dict)
|
1765
|
+
if self.keepBookCopy and market in callbacks["subscriptionBook"]:
|
1766
|
+
callbacks["subscriptionBook"][market](self, msg_dict)
|
1708
1767
|
|
1709
1768
|
elif "event" in msg_dict:
|
1710
1769
|
if msg_dict["event"] == "authenticate":
|
1711
1770
|
self.authenticated = True
|
1712
|
-
elif msg_dict["event"] == "fill":
|
1713
|
-
market = msg_dict["market"]
|
1714
|
-
callbacks["subscriptionAccount"][market](msg_dict)
|
1715
|
-
elif msg_dict["event"] == "order":
|
1771
|
+
elif msg_dict["event"] == "fill" or msg_dict["event"] == "order":
|
1716
1772
|
market = msg_dict["market"]
|
1717
1773
|
callbacks["subscriptionAccount"][market](msg_dict)
|
1718
1774
|
elif msg_dict["event"] == "ticker":
|
@@ -1727,29 +1783,27 @@ class Bitvavo:
|
|
1727
1783
|
callbacks["subscriptionCandles"][market][interval](msg_dict)
|
1728
1784
|
elif msg_dict["event"] == "book":
|
1729
1785
|
market = msg_dict["market"]
|
1730
|
-
if "subscriptionBookUpdate" in callbacks:
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
if market in callbacks["subscriptionBook"]:
|
1735
|
-
callbacks["subscriptionBook"][market](self, msg_dict)
|
1786
|
+
if "subscriptionBookUpdate" in callbacks and market in callbacks["subscriptionBookUpdate"]:
|
1787
|
+
callbacks["subscriptionBookUpdate"][market](msg_dict)
|
1788
|
+
if self.keepBookCopy and market in callbacks["subscriptionBook"]:
|
1789
|
+
callbacks["subscriptionBook"][market](self, msg_dict)
|
1736
1790
|
elif msg_dict["event"] == "trade":
|
1737
1791
|
market = msg_dict["market"]
|
1738
1792
|
if "subscriptionTrades" in callbacks:
|
1739
1793
|
callbacks["subscriptionTrades"][market](msg_dict)
|
1740
1794
|
|
1741
|
-
def on_error(self, ws, error: Any) -> None:
|
1795
|
+
def on_error(self, ws: Any, error: Any) -> None: # noqa: ARG002
|
1742
1796
|
if "error" in self.callbacks:
|
1743
1797
|
self.callbacks["error"](error)
|
1744
1798
|
else:
|
1745
1799
|
logger.error(error)
|
1746
1800
|
|
1747
|
-
def on_close(self, ws) -> None:
|
1801
|
+
def on_close(self, ws: Any) -> None: # noqa: ARG002
|
1748
1802
|
self.receiveThread.stop()
|
1749
1803
|
if self.bitvavo.debugging:
|
1750
1804
|
logger.debug("websocket-closed")
|
1751
1805
|
|
1752
|
-
def checkReconnect(self) -> None: # noqa: C901 (too-complex)
|
1806
|
+
def checkReconnect(self) -> None: # noqa: C901, PLR0912 (too-complex)
|
1753
1807
|
if "subscriptionTicker" in self.callbacks:
|
1754
1808
|
for market in self.callbacks["subscriptionTicker"]:
|
1755
1809
|
self.subscriptionTicker(market, self.callbacks["subscriptionTicker"][market])
|
@@ -1777,7 +1831,7 @@ class Bitvavo:
|
|
1777
1831
|
for market in self.callbacks["subscriptionBookUser"]:
|
1778
1832
|
self.subscriptionBook(market, self.callbacks["subscriptionBookUser"][market])
|
1779
1833
|
|
1780
|
-
def on_open(self, ws) -> None:
|
1834
|
+
def on_open(self, ws: Any) -> None: # noqa: ARG002
|
1781
1835
|
now = time_ms() + BITVAVO_API_UPGRADED.LAG
|
1782
1836
|
self.open = True
|
1783
1837
|
self.reconnectTimer = 0.5
|
@@ -2028,13 +2082,19 @@ class Bitvavo:
|
|
2028
2082
|
}
|
2029
2083
|
]
|
2030
2084
|
```
|
2031
|
-
"""
|
2085
|
+
""" # noqa: E501
|
2032
2086
|
self.callbacks["publicTrades"] = callback
|
2033
2087
|
options["market"] = market
|
2034
2088
|
options["action"] = "getTrades"
|
2035
2089
|
self.doSend(self.ws, json.dumps(options))
|
2036
2090
|
|
2037
|
-
def candles(
|
2091
|
+
def candles(
|
2092
|
+
self,
|
2093
|
+
market: str,
|
2094
|
+
interval: str,
|
2095
|
+
options: anydict,
|
2096
|
+
callback: Callable[[Any], None],
|
2097
|
+
) -> None:
|
2038
2098
|
"""Get up to 1440 candles for a market, with a specific interval (candle size)
|
2039
2099
|
|
2040
2100
|
Extra reading material: https://en.wikipedia.org/wiki/Candlestick_chart
|
@@ -2175,7 +2235,7 @@ class Bitvavo:
|
|
2175
2235
|
# and another 215 markets below this point
|
2176
2236
|
]
|
2177
2237
|
```
|
2178
|
-
"""
|
2238
|
+
""" # noqa: E501
|
2179
2239
|
self.callbacks["tickerBook"] = callback
|
2180
2240
|
options["action"] = "getTickerBook"
|
2181
2241
|
self.doSend(self.ws, json.dumps(options))
|
@@ -2374,7 +2434,7 @@ class Bitvavo:
|
|
2374
2434
|
"disableMarketProtection": true
|
2375
2435
|
}
|
2376
2436
|
```
|
2377
|
-
"""
|
2437
|
+
""" # noqa: E501
|
2378
2438
|
self.callbacks["placeOrder"] = callback
|
2379
2439
|
body["market"] = market
|
2380
2440
|
body["side"] = side
|
@@ -2382,9 +2442,16 @@ class Bitvavo:
|
|
2382
2442
|
body["action"] = "privateCreateOrder"
|
2383
2443
|
self.doSend(self.ws, json.dumps(body), True)
|
2384
2444
|
|
2385
|
-
def updateOrder(
|
2386
|
-
|
2387
|
-
|
2445
|
+
def updateOrder(
|
2446
|
+
self,
|
2447
|
+
market: str,
|
2448
|
+
orderId: str,
|
2449
|
+
body: anydict,
|
2450
|
+
callback: Callable[[Any], None],
|
2451
|
+
) -> None:
|
2452
|
+
"""
|
2453
|
+
Update an existing order for a specific market. Make sure that at least one of the optional parameters
|
2454
|
+
is set, otherwise nothing will be updated.
|
2388
2455
|
|
2389
2456
|
---
|
2390
2457
|
Args:
|
@@ -2462,7 +2529,7 @@ class Bitvavo:
|
|
2462
2529
|
"disableMarketProtection": true
|
2463
2530
|
}
|
2464
2531
|
```
|
2465
|
-
"""
|
2532
|
+
""" # noqa: E501
|
2466
2533
|
self.callbacks["updateOrder"] = callback
|
2467
2534
|
body["market"] = market
|
2468
2535
|
body["orderId"] = orderId
|
@@ -2581,9 +2648,12 @@ class Bitvavo:
|
|
2581
2648
|
options={
|
2582
2649
|
"limit": [ 1 .. 1000 ], default 500
|
2583
2650
|
"start": int timestamp in ms >= 0
|
2584
|
-
|
2585
|
-
"
|
2586
|
-
|
2651
|
+
# (that's somewhere in the year 2243, or near the number 2^52)
|
2652
|
+
"end": int timestamp in ms <= 8_640_000_000_000_000
|
2653
|
+
# if you get a list and want everything AFTER a certain id, put that id here
|
2654
|
+
"tradeIdFrom": ""
|
2655
|
+
# if you get a list and want everything BEFORE a certain id, put that id here
|
2656
|
+
"tradeIdTo": ""
|
2587
2657
|
}
|
2588
2658
|
callback=callback_example
|
2589
2659
|
```
|
@@ -2758,7 +2828,8 @@ class Bitvavo:
|
|
2758
2828
|
options={
|
2759
2829
|
"limit": [ 1 .. 1000 ], default 500
|
2760
2830
|
"start": int timestamp in ms >= 0
|
2761
|
-
|
2831
|
+
# (that's somewhere in the year 2243, or near the number 2^52)
|
2832
|
+
"end": int timestamp in ms <= 8_640_000_000_000_000
|
2762
2833
|
"tradeIdFrom": "" # if you get a list and want everything AFTER a certain id, put that id here
|
2763
2834
|
"tradeIdTo": "" # if you get a list and want everything BEFORE a certain id, put that id here
|
2764
2835
|
}
|
@@ -2860,7 +2931,9 @@ class Bitvavo:
|
|
2860
2931
|
self.doSend(self.ws, json.dumps(options), True)
|
2861
2932
|
|
2862
2933
|
def depositAssets(self, symbol: str, callback: Callable[[Any], None]) -> None:
|
2863
|
-
"""
|
2934
|
+
"""
|
2935
|
+
Get the deposit address (with paymentId for some assets) or bank account information to increase your
|
2936
|
+
balance.
|
2864
2937
|
|
2865
2938
|
---
|
2866
2939
|
Args:
|
@@ -2911,7 +2984,8 @@ class Bitvavo:
|
|
2911
2984
|
"symbol":"EUR"
|
2912
2985
|
"limit": [ 1 .. 1000 ], default 500
|
2913
2986
|
"start": int timestamp in ms >= 0
|
2914
|
-
|
2987
|
+
# (that's somewhere in the year 2243, or near the number 2^52)
|
2988
|
+
"end": int timestamp in ms <= 8_640_000_000_000_000
|
2915
2989
|
}
|
2916
2990
|
callback=callback_example
|
2917
2991
|
```
|
@@ -2969,9 +3043,15 @@ class Bitvavo:
|
|
2969
3043
|
amount=10
|
2970
3044
|
address="BitcoinAddress", # Wallet address or IBAN
|
2971
3045
|
options={
|
2972
|
-
|
2973
|
-
|
2974
|
-
"
|
3046
|
+
# For digital assets only. Should be set when withdrawing straight to another exchange or merchants that
|
3047
|
+
# require payment id's.
|
3048
|
+
"paymentId": "10002653",
|
3049
|
+
# For digital assets only. Should be set to true if the withdrawal must be sent to another Bitvavo user
|
3050
|
+
# internally
|
3051
|
+
"internal": false,
|
3052
|
+
# If set to true, the fee will be added on top of the requested amount, otherwise the fee is part of the
|
3053
|
+
# requested amount and subtracted from the withdrawal.
|
3054
|
+
"addWithdrawalFee": false
|
2975
3055
|
}
|
2976
3056
|
callback=callback_example
|
2977
3057
|
```
|
@@ -3009,7 +3089,8 @@ class Bitvavo:
|
|
3009
3089
|
"symbol":"SHIB"
|
3010
3090
|
"limit": [ 1 .. 1000 ], default 500
|
3011
3091
|
"start": int timestamp in ms >= 0
|
3012
|
-
|
3092
|
+
# (that's somewhere in the year 2243, or near the number 2^52)
|
3093
|
+
"end": int timestamp in ms <= 8_640_000_000_000_000
|
3013
3094
|
}
|
3014
3095
|
callback=callback_example
|
3015
3096
|
```
|
@@ -3042,8 +3123,11 @@ class Bitvavo:
|
|
3042
3123
|
self.doSend(self.ws, json.dumps(options), True)
|
3043
3124
|
|
3044
3125
|
def subscriptionTicker(self, market: str, callback: Callable[[Any], None]) -> None:
|
3045
|
-
# TODO(NostraDavid) one possible improvement here is to turn `market` into a list of markets, so we can sub
|
3046
|
-
|
3126
|
+
# TODO(NostraDavid): one possible improvement here is to turn `market` into a list of markets, so we can sub
|
3127
|
+
# to all of them at once. Same goes for other `subscription*()`
|
3128
|
+
"""
|
3129
|
+
Subscribe to the ticker channel, which means `callback` gets passed the new best bid or ask whenever they
|
3130
|
+
change (server-side).
|
3047
3131
|
|
3048
3132
|
|
3049
3133
|
---
|
@@ -3091,7 +3175,9 @@ class Bitvavo:
|
|
3091
3175
|
)
|
3092
3176
|
|
3093
3177
|
def subscriptionTicker24h(self, market: str, callback: Callable[[Any], None]) -> None:
|
3094
|
-
"""
|
3178
|
+
"""
|
3179
|
+
Subscribe to the ticker-24-hour channel, which means `callback` gets passed the new object every second, if
|
3180
|
+
values have changed.
|
3095
3181
|
|
3096
3182
|
---
|
3097
3183
|
Args:
|
@@ -3146,8 +3232,9 @@ class Bitvavo:
|
|
3146
3232
|
)
|
3147
3233
|
|
3148
3234
|
def subscriptionAccount(self, market: str, callback: Callable[[Any], None]) -> None:
|
3149
|
-
"""
|
3150
|
-
|
3235
|
+
"""
|
3236
|
+
Subscribes to the account channel, which sends an update whenever an event happens which is related to
|
3237
|
+
the account. These are 'order' events (create, update, cancel) or 'fill' events (a trade occurred).
|
3151
3238
|
|
3152
3239
|
---
|
3153
3240
|
Args:
|