Skip to content

Commit

Permalink
Merge pull request hummingbot#6989 from hummingbot/development
Browse files Browse the repository at this point in the history
fix/ adding binance API update to staging 1.27.0
  • Loading branch information
rapcmia authored Apr 25, 2024
2 parents 5dd661b + 119e630 commit 92b7a0d
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 68 deletions.
12 changes: 12 additions & 0 deletions hummingbot/client/config/client_config_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,17 @@ class Config:
title = "binance"


class BinanceUSRateSourceMode(ExchangeRateSourceModeBase):
name: str = Field(
default="binance_us",
const=True,
client_data=None,
)

class Config:
title = "binance_us"


class CubeRateSourceMode(ExchangeRateSourceModeBase):
name: str = Field(
default="cube",
Expand Down Expand Up @@ -935,6 +946,7 @@ class Config:
RATE_SOURCE_MODES = {
AscendExRateSourceMode.Config.title: AscendExRateSourceMode,
BinanceRateSourceMode.Config.title: BinanceRateSourceMode,
BinanceUSRateSourceMode.Config.title: BinanceUSRateSourceMode,
CoinGeckoRateSourceMode.Config.title: CoinGeckoRateSourceMode,
CoinCapRateSourceMode.Config.title: CoinCapRateSourceMode,
KuCoinRateSourceMode.Config.title: KuCoinRateSourceMode,
Expand Down
15 changes: 14 additions & 1 deletion hummingbot/connector/exchange/binance/binance_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,20 @@ def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool:
:param exchange_info: the exchange information for a trading pair
:return: True if the trading pair is enabled, False otherwise
"""
return exchange_info.get("status", None) == "TRADING" and "SPOT" in exchange_info.get("permissions", list())
is_spot = False
is_trading = False

if exchange_info.get("status", None) == "TRADING":
is_trading = True

permissions_sets = exchange_info.get("permissionSets", list())
for permission_set in permissions_sets:
# PermissionSet is a list, find if in this list we have "SPOT" value or not
if "SPOT" in permission_set:
is_spot = True
break

return is_trading and is_spot


class BinanceConfigMap(BaseConnectorConfigMap):
Expand Down
2 changes: 2 additions & 0 deletions hummingbot/core/rate_oracle/rate_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from hummingbot.core.network_iterator import NetworkStatus
from hummingbot.core.rate_oracle.sources.ascend_ex_rate_source import AscendExRateSource
from hummingbot.core.rate_oracle.sources.binance_rate_source import BinanceRateSource
from hummingbot.core.rate_oracle.sources.binance_us_rate_source import BinanceUSRateSource
from hummingbot.core.rate_oracle.sources.coin_cap_rate_source import CoinCapRateSource
from hummingbot.core.rate_oracle.sources.coin_gecko_rate_source import CoinGeckoRateSource
from hummingbot.core.rate_oracle.sources.coinbase_advanced_trade_rate_source import CoinbaseAdvancedTradeRateSource
Expand All @@ -22,6 +23,7 @@

RATE_ORACLE_SOURCES = {
"binance": BinanceRateSource,
"binance_us": BinanceUSRateSource,
"coin_gecko": CoinGeckoRateSource,
"coin_cap": CoinCapRateSource,
"kucoin": KucoinRateSource,
Expand Down
3 changes: 0 additions & 3 deletions hummingbot/core/rate_oracle/sources/binance_rate_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class BinanceRateSource(RateSourceBase):
def __init__(self):
super().__init__()
self._binance_exchange: Optional[BinanceExchange] = None # delayed because of circular reference
self._binance_us_exchange: Optional[BinanceExchange] = None # delayed because of circular reference

@property
def name(self) -> str:
Expand All @@ -26,7 +25,6 @@ async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decim
results = {}
tasks = [
self._get_binance_prices(exchange=self._binance_exchange),
self._get_binance_prices(exchange=self._binance_us_exchange, quote_token="USD"),
]
task_results = await safe_gather(*tasks, return_exceptions=True)
for task_result in task_results:
Expand All @@ -43,7 +41,6 @@ async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decim
def _ensure_exchanges(self):
if self._binance_exchange is None:
self._binance_exchange = self._build_binance_connector_without_private_keys(domain="com")
self._binance_us_exchange = self._build_binance_connector_without_private_keys(domain="us")

@staticmethod
async def _get_binance_prices(exchange: 'BinanceExchange', quote_token: str = None) -> Dict[str, Decimal]:
Expand Down
87 changes: 87 additions & 0 deletions hummingbot/core/rate_oracle/sources/binance_us_rate_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from decimal import Decimal
from typing import TYPE_CHECKING, Dict, Optional

from hummingbot.connector.utils import split_hb_trading_pair
from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase
from hummingbot.core.utils import async_ttl_cache
from hummingbot.core.utils.async_utils import safe_gather

if TYPE_CHECKING:
from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange


class BinanceUSRateSource(RateSourceBase):
def __init__(self):
super().__init__()
self._binance_us_exchange: Optional[BinanceExchange] = None # delayed because of circular reference

@property
def name(self) -> str:
return "binance_us"

@async_ttl_cache(ttl=30, maxsize=1)
async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]:
self._ensure_exchanges()
results = {}
tasks = [
self._get_binance_prices(exchange=self._binance_us_exchange, quote_token="USD"),
]
task_results = await safe_gather(*tasks, return_exceptions=True)
for task_result in task_results:
if isinstance(task_result, Exception):
self.logger().error(
msg="Unexpected error while retrieving rates from Binance. Check the log file for more info.",
exc_info=task_result,
)
break
else:
results.update(task_result)
return results

def _ensure_exchanges(self):
if self._binance_us_exchange is None:
self._binance_us_exchange = self._build_binance_connector_without_private_keys(domain="us")

@staticmethod
async def _get_binance_prices(exchange: 'BinanceExchange', quote_token: str = None) -> Dict[str, Decimal]:
"""
Fetches binance prices
:param exchange: The exchange instance from which to query prices.
:param quote_token: A quote symbol, if specified only pairs with the quote symbol are included for prices
:return: A dictionary of trading pairs and prices
"""
pairs_prices = await exchange.get_all_pairs_prices()
results = {}
for pair_price in pairs_prices:
try:
trading_pair = await exchange.trading_pair_associated_to_exchange_symbol(symbol=pair_price["symbol"])
except KeyError:
continue # skip pairs that we don't track
if quote_token is not None:
base, quote = split_hb_trading_pair(trading_pair=trading_pair)
if quote != quote_token:
continue
bid_price = pair_price.get("bidPrice")
ask_price = pair_price.get("askPrice")
if bid_price is not None and ask_price is not None and 0 < Decimal(bid_price) <= Decimal(ask_price):
results[trading_pair] = (Decimal(bid_price) + Decimal(ask_price)) / Decimal("2")

return results

@staticmethod
def _build_binance_connector_without_private_keys(domain: str) -> 'BinanceExchange':
from hummingbot.client.hummingbot_application import HummingbotApplication
from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange

app = HummingbotApplication.main_application()
client_config_map = app.client_config_map

return BinanceExchange(
client_config_map=client_config_map,
binance_api_key="",
binance_api_secret="",
trading_pairs=[],
trading_required=False,
domain=domain,
)
34 changes: 17 additions & 17 deletions test/hummingbot/connector/exchange/binance/test_binance_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ def all_symbols_request_mock_response(self):
"isSpotTradingAllowed": True,
"isMarginTradingAllowed": True,
"filters": [],
"permissions": [
"permissionSets": [[
"SPOT",
"MARGIN"
]
]]
},
]
}
Expand Down Expand Up @@ -149,9 +149,9 @@ def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]:
"isSpotTradingAllowed": True,
"isMarginTradingAllowed": True,
"filters": [],
"permissions": [
"permissionSets": [[
"MARGIN"
]
]]
},
{
"symbol": self.exchange_symbol_for_tokens("INVALID", "PAIR"),
Expand All @@ -176,9 +176,9 @@ def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]:
"isSpotTradingAllowed": True,
"isMarginTradingAllowed": True,
"filters": [],
"permissions": [
"permissionSets": [[
"MARGIN"
]
]]
},
]
}
Expand Down Expand Up @@ -226,10 +226,10 @@ def trading_rules_request_mock_response(self):
"minNotional": "0.00100000"
}
],
"permissions": [
"permissionSets": [[
"SPOT",
"MARGIN"
]
]]
}
]
}
Expand All @@ -255,10 +255,10 @@ def trading_rules_request_erroneous_mock_response(self):
"ocoAllowed": True,
"isSpotTradingAllowed": True,
"isMarginTradingAllowed": True,
"permissions": [
"permissionSets": [[
"SPOT",
"MARGIN"
]
]]
}
]
}
Expand Down Expand Up @@ -297,9 +297,9 @@ def balance_request_mock_response_for_base_and_quote(self):
"locked": "0.00000000"
}
],
"permissions": [
"permissionSets": [[
"SPOT"
]
]]
}

@property
Expand All @@ -315,7 +315,7 @@ def balance_request_mock_response_only_base(self):
"updateTime": 123456789,
"accountType": "SPOT",
"balances": [{"asset": self.base_asset, "free": "10.0", "locked": "5.0"}],
"permissions": ["SPOT"],
"permissionSets": [["SPOT"]],
}

@property
Expand Down Expand Up @@ -1180,9 +1180,9 @@ def test_format_trading_rules__min_notional_present(self):
"minNotional": "0.00100000"
}
],
"permissions": [
"permissionSets": [[
"SPOT"
]
]]
}]
exchange_info = {"symbols": trading_rules}

Expand Down Expand Up @@ -1217,9 +1217,9 @@ def test_format_trading_rules__notional_but_no_min_notional_present(self):
"avgPriceMins": 5
}
],
"permissions": [
"permissionSets": [[
"SPOT"
]
]]
}]
exchange_info = {"symbols": trading_rules}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,28 @@ def setUpClass(cls) -> None:
def test_is_exchange_information_valid(self):
invalid_info_1 = {
"status": "BREAK",
"permissions": ["MARGIN"],
"permissionSets": [["MARGIN"]],
}

self.assertFalse(utils.is_exchange_information_valid(invalid_info_1))

invalid_info_2 = {
"status": "BREAK",
"permissions": ["SPOT"],
"permissionSets": [["SPOT"]],
}

self.assertFalse(utils.is_exchange_information_valid(invalid_info_2))

invalid_info_3 = {
"status": "TRADING",
"permissions": ["MARGIN"],
"permissionSets": [["MARGIN"]],
}

self.assertFalse(utils.is_exchange_information_valid(invalid_info_3))

invalid_info_4 = {
"status": "TRADING",
"permissions": ["SPOT"],
"permissionSets": [["SPOT"]],
}

self.assertTrue(utils.is_exchange_information_valid(invalid_info_4))
Loading

0 comments on commit 92b7a0d

Please sign in to comment.