1
1
import datetime
2
2
import math
3
3
import time
4
- from typing import Optional
4
+ from typing import Optional , Dict
5
5
6
6
import dateparser
7
7
from binance .client import Client
8
+ from binance .exceptions import BinanceAPIException
8
9
from tqdm import tqdm
9
10
10
11
from BinanceWatch .storage import tables
12
+ from BinanceWatch .utils .LoggerGenerator import LoggerGenerator
11
13
from BinanceWatch .utils .time_utils import datetime_to_millistamp
12
14
from BinanceWatch .storage .BinanceDataBase import BinanceDataBase
13
15
@@ -16,10 +18,24 @@ class BinanceManager:
16
18
"""
17
19
This class is in charge of filling the database by calling the binance API
18
20
"""
21
+ API_MAX_RETRY = 3
19
22
20
- def __init__ (self , api_key : str , api_secret : str ):
21
- self .db = BinanceDataBase ()
23
+ def __init__ (self , api_key : str , api_secret : str , account_name : str = 'default' ):
24
+ """
25
+ initialise the binance manager.
26
+
27
+ :param api_key: key for the Binance api
28
+ :type api_key: str
29
+ :param api_secret: secret for the Binance api
30
+ :type api_secret: str
31
+ :param account_name: if you have several accounts to monitor, you need to give them different names or the
32
+ database will collide
33
+ :type account_name: str
34
+ """
35
+ self .account_name = account_name
36
+ self .db = BinanceDataBase (name = f"{ self .account_name } _db" )
22
37
self .client = Client (api_key = api_key , api_secret = api_secret )
38
+ self .logger = LoggerGenerator .get_logger (f"BinanceManager_{ self .account_name } " )
23
39
24
40
def update_spot (self ):
25
41
"""
@@ -87,10 +103,14 @@ def update_universal_transfers(self, transfer_filter: Optional[str] = None):
87
103
latest_time = self .db .get_last_universal_transfer_time (transfer_type = transfer_type ) + 1
88
104
current = 1
89
105
while True :
90
- universal_transfers = self .client .query_universal_transfer_history (type = transfer_type ,
91
- startTime = latest_time ,
92
- current = current ,
93
- size = 100 )
106
+ client_params = {
107
+ 'type' : transfer_type ,
108
+ 'startTime' : latest_time ,
109
+ 'current' : current ,
110
+ 'size' : 100
111
+ }
112
+ universal_transfers = self ._call_binance_client ('query_universal_transfer_history' , client_params )
113
+
94
114
try :
95
115
universal_transfers = universal_transfers ['rows' ]
96
116
except KeyError :
@@ -134,8 +154,15 @@ def update_cross_margin_interests(self):
134
154
'size' : 100 ,
135
155
'archived' : archived
136
156
}
157
+
137
158
# no built-in method yet in python-binance for margin/interestHistory
138
- interests = self .client ._request_margin_api ('get' , 'margin/interestHistory' , signed = True , data = params )
159
+ client_params = {
160
+ 'method' : 'get' ,
161
+ 'path' : 'margin/interestHistory' ,
162
+ 'signed' : True ,
163
+ 'data' : params
164
+ }
165
+ interests = self ._call_binance_client ('_request_margin_api' , client_params )
139
166
140
167
for interest in interests ['rows' ]:
141
168
self .db .add_margin_interest (margin_type = margin_type ,
@@ -164,7 +191,12 @@ def update_cross_margin_repays(self):
164
191
:return: None
165
192
:rtype: None
166
193
"""
167
- symbols_info = self .client ._request_margin_api ('get' , 'margin/allPairs' , data = {}) # not built-in yet
194
+ client_params = {
195
+ 'method' : 'get' ,
196
+ 'path' : 'margin/allPairs' ,
197
+ 'data' : {}
198
+ }
199
+ symbols_info = self ._call_binance_client ('_request_margin_api' , client_params ) # not built-in yet
168
200
assets = set ()
169
201
for symbol_info in symbols_info :
170
202
assets .add (symbol_info ['base' ])
@@ -197,12 +229,16 @@ def update_margin_asset_repay(self, asset: str, isolated_symbol=''):
197
229
archived = 1000 * time .time () - latest_time > 1000 * 3600 * 24 * 30 * 3
198
230
current = 1
199
231
while True :
200
- repays = self .client .get_margin_repay_details (asset = asset ,
201
- current = current ,
202
- startTime = latest_time + 1000 ,
203
- archived = archived ,
204
- isolatedSymbol = isolated_symbol ,
205
- size = 100 )
232
+ client_params = {
233
+ 'asset' : asset ,
234
+ 'current' :current ,
235
+ 'startTime' : latest_time + 1000 ,
236
+ 'archived' : archived ,
237
+ 'isolatedSymbol' : isolated_symbol ,
238
+ 'size' : 100
239
+ }
240
+ repays = self ._call_binance_client ('get_margin_repay_details' , client_params )
241
+
206
242
for repay in repays ['rows' ]:
207
243
if repay ['status' ] == 'CONFIRMED' :
208
244
self .db .add_repay (margin_type = margin_type ,
@@ -230,7 +266,12 @@ def update_cross_margin_loans(self):
230
266
:return: None
231
267
:rtype: None
232
268
"""
233
- symbols_info = self .client ._request_margin_api ('get' , 'margin/allPairs' , data = {}) # not built-in yet
269
+ client_params = {
270
+ 'method' : 'get' ,
271
+ 'path' : 'margin/allPairs' ,
272
+ 'data' : {}
273
+ }
274
+ symbols_info = self ._call_binance_client ('_request_margin_api' , client_params ) # not built-in yet
234
275
assets = set ()
235
276
for symbol_info in symbols_info :
236
277
assets .add (symbol_info ['base' ])
@@ -263,12 +304,16 @@ def update_margin_asset_loans(self, asset: str, isolated_symbol=''):
263
304
archived = 1000 * time .time () - latest_time > 1000 * 3600 * 24 * 30 * 3
264
305
current = 1
265
306
while True :
266
- loans = self .client .get_margin_loan_details (asset = asset ,
267
- current = current ,
268
- startTime = latest_time + 1000 ,
269
- archived = archived ,
270
- isolatedSymbol = isolated_symbol ,
271
- size = 100 )
307
+ client_params = {
308
+ 'asset' : asset ,
309
+ 'current' : current ,
310
+ 'startTime' : latest_time + 1000 ,
311
+ 'archived' : archived ,
312
+ 'isolatedSymbol' : isolated_symbol ,
313
+ 'size' : 100
314
+ }
315
+ loans = self ._call_binance_client ('get_margin_loan_details' , client_params )
316
+
272
317
for loan in loans ['rows' ]:
273
318
if loan ['status' ] == 'CONFIRMED' :
274
319
self .db .add_loan (margin_type = margin_type ,
@@ -310,7 +355,13 @@ def update_cross_margin_symbol_trades(self, asset: str, ref_asset: str, limit: i
310
355
symbol = asset + ref_asset
311
356
last_trade_id = self .db .get_max_trade_id (asset , ref_asset , 'cross_margin' )
312
357
while True :
313
- new_trades = self .client .get_margin_trades (symbol = symbol , fromId = last_trade_id + 1 , limit = limit )
358
+ client_params = {
359
+ 'symbol' : symbol ,
360
+ 'fromId' : last_trade_id + 1 ,
361
+ 'limit' : limit
362
+ }
363
+ new_trades = self ._call_binance_client ('get_margin_trades' , client_params )
364
+
314
365
for trade in new_trades :
315
366
self .db .add_trade (trade_type = 'cross_margin' ,
316
367
trade_id = int (trade ['id' ]),
@@ -339,7 +390,13 @@ def update_all_cross_margin_trades(self, limit: int = 1000):
339
390
:return: None
340
391
:rtype: None
341
392
"""
342
- symbols_info = self .client ._request_margin_api ('get' , 'margin/allPairs' , data = {}) # not built-in yet
393
+ client_params = {
394
+ 'method' : 'get' ,
395
+ 'path' : 'margin/allPairs' ,
396
+ 'data' : {}
397
+ }
398
+ symbols_info = self ._call_binance_client ('_request_margin_api' , client_params ) # not built-in yet
399
+
343
400
pbar = tqdm (total = len (symbols_info ))
344
401
for symbol_info in symbols_info :
345
402
pbar .set_description (f"fetching { symbol_info ['symbol' ]} cross margin trades" )
@@ -367,10 +424,14 @@ def update_lending_redemptions(self):
367
424
latest_time = self .db .get_last_lending_redemption_time (lending_type = lending_type ) + 1
368
425
current = 1
369
426
while True :
370
- lending_redemptions = self .client .get_lending_redemption_history (lendingType = lending_type ,
371
- startTime = latest_time ,
372
- current = current ,
373
- size = 100 )
427
+ client_params = {
428
+ 'lendingType' : lending_type ,
429
+ 'startTime' : latest_time ,
430
+ 'current' : current ,
431
+ 'size' : 100
432
+ }
433
+ lending_redemptions = self ._call_binance_client ('get_lending_redemption_history' , client_params )
434
+
374
435
for li in lending_redemptions :
375
436
if li ['status' ] == 'PAID' :
376
437
self .db .add_lending_redemption (redemption_time = li ['createTime' ],
@@ -405,10 +466,14 @@ def update_lending_purchases(self):
405
466
latest_time = self .db .get_last_lending_purchase_time (lending_type = lending_type ) + 1
406
467
current = 1
407
468
while True :
408
- lending_purchases = self .client .get_lending_purchase_history (lendingType = lending_type ,
409
- startTime = latest_time ,
410
- current = current ,
411
- size = 100 )
469
+ client_params = {
470
+ 'lendingType' : lending_type ,
471
+ 'startTime' : latest_time ,
472
+ 'current' : current ,
473
+ 'size' : 100
474
+ }
475
+ lending_purchases = self ._call_binance_client ('get_lending_purchase_history' , client_params )
476
+
412
477
for li in lending_purchases :
413
478
if li ['status' ] == 'SUCCESS' :
414
479
self .db .add_lending_purchase (purchase_id = li ['purchaseId' ],
@@ -444,10 +509,14 @@ def update_lending_interests(self):
444
509
latest_time = self .db .get_last_lending_interest_time (lending_type = lending_type ) + 3600 * 1000 # add 1 hour
445
510
current = 1
446
511
while True :
447
- lending_interests = self .client .get_lending_interest_history (lendingType = lending_type ,
448
- startTime = latest_time ,
449
- current = current ,
450
- size = 100 )
512
+ client_params = {
513
+ 'lendingType' : lending_type ,
514
+ 'startTime' : latest_time ,
515
+ 'current' : current ,
516
+ 'size' : 100
517
+ }
518
+ lending_interests = self ._call_binance_client ('get_lending_interest_history' , client_params )
519
+
451
520
for li in lending_interests :
452
521
self .db .add_lending_interest (time = li ['time' ],
453
522
lending_type = li ['lendingType' ],
@@ -477,7 +546,7 @@ def update_spot_dusts(self):
477
546
"""
478
547
self .db .drop_table (tables .SPOT_DUST_TABLE )
479
548
480
- result = self .client . get_dust_log ( )
549
+ result = self ._call_binance_client ( 'get_dust_log' )
481
550
dusts = result ['results' ]
482
551
pbar = tqdm (total = dusts ['total' ])
483
552
pbar .set_description ("fetching spot dusts" )
@@ -517,18 +586,21 @@ def update_spot_dividends(self, day_jump: float = 90, limit: int = 500):
517
586
pbar = tqdm (total = math .ceil ((now_millistamp - start_time ) / delta_jump ))
518
587
pbar .set_description ("fetching spot dividends" )
519
588
while start_time < now_millistamp :
589
+ # the stable working version of client.get_asset_dividend_history is not released yet,
590
+ # for now it has a post error, so this protected member is used in the meantime
520
591
params = {
521
592
'startTime' : start_time ,
522
593
'endTime' : start_time + delta_jump ,
523
594
'limit' : limit
524
595
}
525
- # the stable working version of client.get_asset_dividend_history is not released yet,
526
- # for now it has a post error, so this protected member is used in the meantime
527
- result = self .client ._request_margin_api ('get' ,
528
- 'asset/assetDividend' ,
529
- True ,
530
- data = params
531
- )
596
+ client_params = {
597
+ 'method' : 'get' ,
598
+ 'path' : 'asset/assetDividend' ,
599
+ 'signed' : True ,
600
+ 'data' : params
601
+ }
602
+ result = self ._call_binance_client ('_request_margin_api' , client_params )
603
+
532
604
dividends = result ['rows' ]
533
605
for div in dividends :
534
606
self .db .add_dividend (div_id = int (div ['tranId' ]),
@@ -568,7 +640,13 @@ def update_spot_withdraws(self, day_jump: float = 90):
568
640
pbar = tqdm (total = math .ceil ((now_millistamp - start_time ) / delta_jump ))
569
641
pbar .set_description ("fetching spot withdraws" )
570
642
while start_time < now_millistamp :
571
- result = self .client .get_withdraw_history (startTime = start_time , endTime = start_time + delta_jump , status = 6 )
643
+ client_params = {
644
+ 'startTime' : start_time ,
645
+ 'endTime' : start_time + delta_jump ,
646
+ 'status' : 6
647
+ }
648
+ result = self ._call_binance_client ('get_withdraw_history' , client_params )
649
+
572
650
withdraws = result ['withdrawList' ]
573
651
for withdraw in withdraws :
574
652
self .db .add_withdraw (withdraw_id = withdraw ['id' ],
@@ -607,7 +685,13 @@ def update_spot_deposits(self, day_jump: float = 90):
607
685
pbar = tqdm (total = math .ceil ((now_millistamp - start_time ) / delta_jump ))
608
686
pbar .set_description ("fetching spot deposits" )
609
687
while start_time < now_millistamp :
610
- result = self .client .get_deposit_history (startTime = start_time , endTime = start_time + delta_jump , status = 1 )
688
+ client_params = {
689
+ 'startTime' : start_time ,
690
+ 'endTime' : start_time + delta_jump ,
691
+ 'status' : 1
692
+ }
693
+ result = self ._call_binance_client ('get_deposit_history' , client_params )
694
+
611
695
deposits = result ['depositList' ]
612
696
for deposit in deposits :
613
697
self .db .add_deposit (tx_id = deposit ['txId' ],
@@ -643,7 +727,13 @@ def update_spot_symbol_trades(self, asset: str, ref_asset: str, limit: int = 100
643
727
symbol = asset + ref_asset
644
728
last_trade_id = self .db .get_max_trade_id (asset , ref_asset , 'spot' )
645
729
while True :
646
- new_trades = self .client .get_my_trades (symbol = symbol , fromId = last_trade_id + 1 , limit = limit )
730
+ client_params = {
731
+ 'symbol' : symbol ,
732
+ 'fromId' : last_trade_id + 1 ,
733
+ 'limit' : limit
734
+ }
735
+ new_trades = self ._call_binance_client ('get_my_trades' , client_params )
736
+
647
737
for trade in new_trades :
648
738
self .db .add_trade (trade_type = 'spot' ,
649
739
trade_id = int (trade ['id' ]),
@@ -681,3 +771,35 @@ def update_all_spot_trades(self, limit: int = 1000):
681
771
limit = limit )
682
772
pbar .update ()
683
773
pbar .close ()
774
+
775
+ def _call_binance_client (self , method_name : str , params : Optional [Dict ] = None , retry_count : int = 0 ):
776
+ """
777
+ This method is used to handle rate limits: if a rate limits is breached, it will wait the necessary time
778
+ to call again the API.
779
+
780
+ :param method_name: name of the method binance.Client to call
781
+ :type method_name: str
782
+ :param params: parameters to pass to the above method
783
+ :type params: Dict
784
+ :param retry_count: internal use only to count the number of retry if rate limits are breached
785
+ :type retry_count: int
786
+ :return: response of binance.Client method
787
+ :rtype: Dict
788
+ """
789
+ if params is None :
790
+ params = dict ()
791
+ if retry_count >= BinanceManager .API_MAX_RETRY :
792
+ raise RuntimeError (f"The API rate limits has been breached { retry_count } times" )
793
+
794
+ try :
795
+ return getattr (self .client , method_name )(** params )
796
+ except BinanceAPIException as err :
797
+ if err .code == - 1003 : # API rate Limits
798
+ wait_time = float (err .response .headers ['Retry-After' ])
799
+ if err .response .status_code == 418 : # ban
800
+ self .logger .error (f"API calls resulted in a ban, retry in { wait_time } seconds" )
801
+ raise err
802
+ self .logger .info (f"API calls resulted in a breach of rate limits, will retry after { wait_time } seconds" )
803
+ time .sleep (wait_time + 1 )
804
+ return self ._call_binance_client (method_name , params , retry_count + 1 )
805
+ raise err
0 commit comments