Skip to content

Commit 8e00813

Browse files
committed
slow down binance client.get_symbol_info() calls
Hitting API limits quite frequently when querying for symbol info on a token that no longer exists. Added a few breakers and a lru cache for repeated calls to the same symbol.
1 parent 8b58021 commit 8e00813

File tree

3 files changed

+87
-39
lines changed

3 files changed

+87
-39
lines changed

lib/bot.py

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from os.path import basename, exists
1010
from time import sleep
1111
from typing import Any, Dict, List, Tuple
12+
from functools import lru_cache
1213

1314
import requests
1415
import udatetime
@@ -162,7 +163,9 @@ def __init__(
162163
# price.log service
163164
self.price_log_service: str = config["PRICE_LOG_SERVICE_URL"]
164165

165-
def extract_order_data(self, order_details, coin) -> Dict[str, Any]:
166+
def extract_order_data(
167+
self, order_details, coin
168+
) -> Tuple[bool, Dict[str, Any]]:
166169
"""calculate average price and volume for a buy order"""
167170

168171
# Each order will be fullfilled by different traders, and made of
@@ -183,13 +186,20 @@ def extract_order_data(self, order_details, coin) -> Dict[str, Any]:
183186

184187
avg: float = total / qty
185188

186-
volume: float = float(self.calculate_volume_size(coin))
187-
logging.debug(f"{coin.symbol} -> volume:{volume} avgPrice:{avg}")
189+
ok, _volume = self.calculate_volume_size(coin)
190+
if ok:
191+
volume: float = float(_volume)
188192

189-
return {
190-
"avgPrice": float(avg),
191-
"volume": float(volume),
192-
}
193+
logging.debug(f"{coin.symbol} -> volume:{volume} avgPrice:{avg}")
194+
195+
return (
196+
True,
197+
{
198+
"avgPrice": float(avg),
199+
"volume": float(volume),
200+
},
201+
)
202+
return (False, {})
193203

194204
def run_strategy(self, coin) -> None:
195205
"""runs a specific strategy against a coin"""
@@ -329,13 +339,17 @@ def place_sell_order(self, coin):
329339
orders = self.client.get_all_orders(symbol=coin.symbol, limit=1)
330340
logging.debug(orders)
331341
# calculate how much we got based on the total lines in our order
332-
coin.price = self.extract_order_data(order_details, coin)[
333-
"avgPrice"
334-
]
342+
ok, _value = self.extract_order_data(order_details, coin)
343+
if not ok:
344+
return False
345+
346+
coin.price = _value["avgPrice"]
335347
# retrieve the total number of units for this coin
336-
coin.volume = self.extract_order_data(order_details, coin)[
337-
"volume"
338-
]
348+
ok, _value = self.extract_order_data(order_details, coin)
349+
if not ok:
350+
return False
351+
352+
coin.volume = _value["volume"]
339353

340354
# and give this coin a new fresh date based on our recent actions
341355
coin.date = float(udatetime.now().timestamp())
@@ -448,13 +462,15 @@ def place_buy_order(self, coin, volume):
448462
logging.debug(orders)
449463
# our order will have been fullfilled by different traders,
450464
# find out the average price we paid accross all these sales.
451-
coin.bought_at = self.extract_order_data(order_details, coin)[
452-
"avgPrice"
453-
]
465+
ok, _value = self.extract_order_data(order_details, coin)
466+
if not ok:
467+
return False
468+
coin.bought_at = float(_value["avgPrice"])
454469
# retrieve the total number of units for this coin
455-
coin.volume = self.extract_order_data(order_details, coin)[
456-
"volume"
457-
]
470+
ok, _volume = self.extract_order_data(order_details, coin)
471+
if not ok:
472+
return False
473+
coin.volume = float(_volume["volume"])
458474
with open("log/binance.place_buy_order.log", "at") as f:
459475
f.write(f"{coin.symbol} {coin.date} {self.order_type} ")
460476
f.write(f"{bid} {coin.volume} {order_details}\n")
@@ -477,7 +493,10 @@ def buy_coin(self, coin) -> bool:
477493

478494
# calculate how many units of this coin we can afford based on our
479495
# investment share.
480-
volume = float(self.calculate_volume_size(coin))
496+
ok, _volume = self.calculate_volume_size(coin)
497+
if not ok:
498+
return False
499+
volume: float = float(_volume)
481500

482501
# we never place binance orders in backtesting mode.
483502
if self.mode in ["testnet", "live"]:
@@ -612,7 +631,8 @@ def sell_coin(self, coin) -> bool:
612631
)
613632
return True
614633

615-
def get_step_size(self, symbol: str) -> str:
634+
@lru_cache(1024)
635+
def get_step_size(self, symbol: str) -> Tuple[bool, str]:
616636
"""retrieves and caches the decimal step size for a coin in binance"""
617637

618638
# each coin in binance uses a number of decimal points, these can vary
@@ -631,29 +651,42 @@ def get_step_size(self, symbol: str) -> str:
631651
else:
632652
try:
633653
info = self.client.get_symbol_info(symbol)
654+
655+
if not info:
656+
return (False, "")
657+
658+
if "filters" not in info:
659+
return (False, "")
634660
except BinanceAPIException as error_msg:
635661
logging.error(error_msg)
636-
return str(-1)
662+
if "Too much request weight used;" in str(error_msg):
663+
sleep(60)
664+
return (False, "")
637665

638666
for d in info["filters"]:
639667
if "filterType" in d.keys():
640668
if d["filterType"] == "LOT_SIZE":
641669
step_size = d["stepSize"]
642670

643-
if self.mode == "backtesting" and not exists(f_path):
644-
with open(f_path, "w") as f:
645-
f.write(json.dumps(info))
671+
if self.mode == "backtesting" and not exists(f_path):
672+
with open(f_path, "w") as f:
673+
f.write(json.dumps(info))
646674

647-
with open("log/binance.step_size.log", "at") as f:
648-
f.write(f"{symbol} {step_size}\n")
649-
return step_size
675+
with open("log/binance.step_size.log", "at") as f:
676+
f.write(f"{symbol} {step_size}\n")
677+
return (True, step_size)
678+
return (False, "")
650679

651-
def calculate_volume_size(self, coin) -> float:
680+
def calculate_volume_size(self, coin) -> Tuple[bool, float]:
652681
"""calculates the amount of coin we are to buy"""
653682

654683
# calculates the number of units we are about to buy based on the number
655684
# of decimal points used, the share of the investment and the price
656-
step_size: str = self.get_step_size(coin.symbol)
685+
ok, _step_size = self.get_step_size(coin.symbol)
686+
if ok:
687+
step_size: str = _step_size
688+
else:
689+
return (False, 0)
657690

658691
investment: float = percent(self.investment, self.re_invest_percentage)
659692

@@ -667,7 +700,7 @@ def calculate_volume_size(self, coin) -> float:
667700
)
668701
with open("log/binance.volume.log", "at") as f:
669702
f.write(f"{coin.symbol} {step_size} {investment} {volume}\n")
670-
return volume
703+
return (True, volume)
671704

672705
@retry(wait=wait_exponential(multiplier=1, max=3))
673706
def get_binance_prices(self) -> List[Dict[str, str]]:

lib/helpers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
import logging
33
import math
44
import pickle # nosec
5+
import re
56
from datetime import datetime
67
from functools import lru_cache
78
from os.path import exists, getctime
9+
from time import time, sleep
810

911
import udatetime
1012
from binance.client import Client
1113
from filelock import SoftFileLock
14+
from tenacity import retry, wait_exponential
1215

1316

1417
def mean(values: list) -> float:
@@ -41,6 +44,7 @@ def c_from_timestamp(date: float) -> datetime:
4144
return datetime.fromtimestamp(date)
4245

4346

47+
@retry(wait=wait_exponential(multiplier=1, max=3))
4448
def cached_binance_client(access_key: str, secret_key: str) -> Client:
4549
"""retry wrapper for binance client first call"""
4650

@@ -64,6 +68,16 @@ def cached_binance_client(access_key: str, secret_key: str) -> Client:
6468
_client = Client(access_key, secret_key)
6569
except Exception as err:
6670
logging.warning(f"API client exception: {err}")
71+
if "much request weight used" in str(err):
72+
timestamp = (
73+
int(re.findall(r"IP banned until (\d+)", str(err))[0])
74+
/ 1000
75+
)
76+
logging.info(
77+
f"Pausing until {datetime.fromtimestamp(timestamp)}"
78+
)
79+
while int(time()) < timestamp:
80+
sleep(1)
6781
raise Exception from err
6882
with open(cachefile, "wb") as f:
6983
pickle.dump(_client, f)

tests/test_bot.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ def test_sell_coin_using_market_order_in_testnet(self, bot, coin):
320320
],
321321
) as _:
322322
with mock.patch.object(
323-
bot, "get_step_size", return_value="0.00001000"
323+
bot, "get_step_size", return_value=(True, "0.00001000")
324324
) as _:
325325
assert bot.sell_coin(coin) is True
326326
assert bot.wallet == []
@@ -357,7 +357,7 @@ def test_sell_coin_using_limit_order_in_testnet(self, bot, coin):
357357
},
358358
) as _:
359359
with mock.patch.object(
360-
bot, "get_step_size", return_value="0.00001000"
360+
bot, "get_step_size", return_value=(True, "0.00001000")
361361
) as _:
362362
with mock.patch.object(
363363
bot.client,
@@ -456,16 +456,17 @@ def test_get_step_size(self, bot):
456456
},
457457
) as _:
458458
result = bot.get_step_size("BTCUSDT")
459-
assert result == "0.00001000"
459+
assert result == (True, "0.00001000")
460460

461461
def test_extract_order_data(self):
462462
pass
463463

464464
def test_calculate_volume_size(self, bot, coin):
465465
with mock.patch.object(
466-
bot, "get_step_size", return_value="0.00001000"
466+
bot, "get_step_size", return_value=(True, "0.00001000")
467467
) as _:
468-
volume = bot.calculate_volume_size(coin)
468+
ok, volume = bot.calculate_volume_size(coin)
469+
assert ok == True
469470
assert volume == 0.5
470471

471472
def test_get_binance_prices(self, bot):
@@ -710,7 +711,7 @@ def test_buy_coin_when_coin_is_naughty(self, bot, coin):
710711
bot.buy_coin(coin)
711712
assert bot.wallet == []
712713

713-
@mock.patch("lib.bot.Bot.get_step_size", return_value="0.00001000")
714+
@mock.patch("lib.bot.Bot.get_step_size", return_value=(True, "0.00001000"))
714715
def test_buy_coin_in_backtesting(self, _, bot, coin):
715716
bot.mode = "backtesting"
716717
coin.price = 100
@@ -767,7 +768,7 @@ def test_buy_coin_using_market_order_in_testnet(self, bot, coin):
767768
],
768769
) as _:
769770
with mock.patch.object(
770-
bot, "get_step_size", return_value="0.00001000"
771+
bot, "get_step_size", return_value=(True, "0.00001000")
771772
) as _:
772773

773774
assert bot.buy_coin(coin) is True
@@ -803,7 +804,7 @@ def test_buy_coin_using_limit_order_in_testnet(self, bot, coin):
803804
},
804805
) as _:
805806
with mock.patch.object(
806-
bot, "get_step_size", return_value="0.00001000"
807+
bot, "get_step_size", return_value=(True, "0.00001000")
807808
) as _:
808809
with mock.patch.object(
809810
bot.client,

0 commit comments

Comments
 (0)