forked from hummingbot/hummingbot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split binance_us rate oracle from binance
- Loading branch information
mlguys
committed
Apr 25, 2024
1 parent
50be513
commit 1074ecd
Showing
5 changed files
with
186 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
hummingbot/core/rate_oracle/sources/binance_us_rate_source.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
test/hummingbot/core/rate_oracle/sources/test_binance_us_rate_source.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import asyncio | ||
import json | ||
import unittest | ||
from decimal import Decimal | ||
from typing import Awaitable | ||
|
||
from aioresponses import aioresponses | ||
|
||
from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils as web_utils | ||
from hummingbot.connector.utils import combine_to_hb_trading_pair | ||
from hummingbot.core.rate_oracle.sources.binance_us_rate_source import BinanceUSRateSource | ||
|
||
|
||
class BinanceUSRateSourceTest(unittest.TestCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
super().setUpClass() | ||
cls.ev_loop = asyncio.get_event_loop() | ||
cls.target_token = "COINALPHA" | ||
cls.binance_us_pair = f"{cls.target_token}USD" | ||
cls.us_trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote="USD") | ||
cls.binance_ignored_pair = "SOMEPAIR" | ||
cls.ignored_trading_pair = combine_to_hb_trading_pair(base="SOME", quote="PAIR") | ||
|
||
def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): | ||
ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) | ||
return ret | ||
|
||
def setup_binance_responses(self, mock_api, expected_rate: Decimal): | ||
pairs_us_url = web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain="us") | ||
symbols_response = { # truncated | ||
"symbols": [ | ||
{ | ||
"symbol": self.binance_us_pair, | ||
"status": "TRADING", | ||
"baseAsset": self.target_token, | ||
"quoteAsset": "USD", | ||
"permissionSets": [[ | ||
"SPOT", | ||
]], | ||
}, | ||
{ | ||
"symbol": self.binance_ignored_pair, | ||
"status": "PAUSED", | ||
"baseAsset": "SOME", | ||
"quoteAsset": "PAIR", | ||
"permissionSets": [[ | ||
"SPOT", | ||
]], | ||
}, | ||
] | ||
} | ||
binance_prices_us_url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_BOOK_PATH_URL, domain="us") | ||
binance_prices_us_response = [ | ||
{ | ||
"symbol": self.binance_us_pair, | ||
"bidPrice": str(expected_rate - Decimal("0.1")), | ||
"bidQty": "0.50000000", | ||
"askPrice": str(expected_rate + Decimal("0.1")), | ||
"askQty": "0.14500000", | ||
}, | ||
{ | ||
"symbol": self.binance_ignored_pair, | ||
"bidPrice": "0", | ||
"bidQty": "0", | ||
"askPrice": "0", | ||
"askQty": "0", | ||
} | ||
] | ||
mock_api.get(pairs_us_url, body=json.dumps(symbols_response)) | ||
mock_api.get(binance_prices_us_url, body=json.dumps(binance_prices_us_response)) | ||
|
||
@aioresponses() | ||
def test_get_binance_prices(self, mock_api): | ||
expected_rate = Decimal("10") | ||
self.setup_binance_responses(mock_api=mock_api, expected_rate=expected_rate) | ||
|
||
rate_source = BinanceUSRateSource() | ||
prices = self.async_run_with_timeout(rate_source.get_prices()) | ||
|
||
self.assertIn(self.us_trading_pair, prices) | ||
self.assertEqual(expected_rate, prices[self.us_trading_pair]) | ||
# self.assertIn(self.us_trading_pair, prices) | ||
self.assertNotIn(self.ignored_trading_pair, prices) |