Incorporate fetch_bids_asks to allow binance spread filter to work
closes #6474
This commit is contained in:
parent
fdce055061
commit
208a139d2b
@ -12,6 +12,7 @@ from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.exchange.common import retrier
|
||||
from freqtrade.misc import deep_merge_dicts
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -55,6 +56,15 @@ class Binance(Exchange):
|
||||
(side == "buy" and stop_loss < float(order['info']['stopPrice']))
|
||||
)
|
||||
|
||||
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
||||
tickers = super().get_tickers(symbols=symbols, cached=cached)
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
# Binance's future result has no bid/ask values.
|
||||
# Therefore we must fetch that from fetch_bids_asks and combine the two results.
|
||||
bidsasks = self.fetch_bids_asks(symbols, cached)
|
||||
tickers = deep_merge_dicts(bidsasks, tickers, allow_null_overrides=False)
|
||||
return tickers
|
||||
|
||||
@retrier
|
||||
def _set_leverage(
|
||||
self,
|
||||
|
@ -104,7 +104,7 @@ class Exchange:
|
||||
self._last_markets_refresh: int = 0
|
||||
|
||||
# Cache for 10 minutes ...
|
||||
self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=1, ttl=60 * 10)
|
||||
self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=2, ttl=60 * 10)
|
||||
# Cache values for 1800 to avoid frequent polling of the exchange for prices
|
||||
# Caching only applies to RPC methods, so prices for open trades are still
|
||||
# refreshed once every iteration.
|
||||
@ -1289,6 +1289,34 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@retrier
|
||||
def fetch_bids_asks(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
||||
"""
|
||||
:param cached: Allow cached result
|
||||
:return: fetch_tickers result
|
||||
"""
|
||||
if not self.exchange_has('fetchBidsAsks'):
|
||||
return {}
|
||||
if cached:
|
||||
tickers = self._fetch_tickers_cache.get('fetch_bids_asks')
|
||||
if tickers:
|
||||
return tickers
|
||||
try:
|
||||
tickers = self._api.fetch_bids_asks(symbols)
|
||||
self._fetch_tickers_cache['fetch_bids_asks'] = tickers
|
||||
return tickers
|
||||
except ccxt.NotSupported as e:
|
||||
raise OperationalException(
|
||||
f'Exchange {self._api.name} does not support fetching bids/asks in batch. '
|
||||
f'Message: {e}') from e
|
||||
except ccxt.DDoSProtection as e:
|
||||
raise DDosProtection(e) from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not load bids/asks due to {e.__class__.__name__}. Message: {e}') from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@retrier
|
||||
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
||||
"""
|
||||
|
@ -1572,6 +1572,59 @@ def test_fetch_positions(default_conf, mocker, exchange_name):
|
||||
"fetch_positions", "fetch_positions")
|
||||
|
||||
|
||||
def test_fetch_bids_asks(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
tick = {'ETH/BTC': {
|
||||
'symbol': 'ETH/BTC',
|
||||
'bid': 0.5,
|
||||
'ask': 1,
|
||||
'last': 42,
|
||||
}, 'BCH/BTC': {
|
||||
'symbol': 'BCH/BTC',
|
||||
'bid': 0.6,
|
||||
'ask': 0.5,
|
||||
'last': 41,
|
||||
}
|
||||
}
|
||||
exchange_name = 'binance'
|
||||
api_mock.fetch_bids_asks = MagicMock(return_value=tick)
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
# retrieve original ticker
|
||||
bidsasks = exchange.fetch_bids_asks()
|
||||
|
||||
assert 'ETH/BTC' in bidsasks
|
||||
assert 'BCH/BTC' in bidsasks
|
||||
assert bidsasks['ETH/BTC']['bid'] == 0.5
|
||||
assert bidsasks['ETH/BTC']['ask'] == 1
|
||||
assert bidsasks['BCH/BTC']['bid'] == 0.6
|
||||
assert bidsasks['BCH/BTC']['ask'] == 0.5
|
||||
assert api_mock.fetch_bids_asks.call_count == 1
|
||||
|
||||
api_mock.fetch_bids_asks.reset_mock()
|
||||
|
||||
# Cached ticker should not call api again
|
||||
tickers2 = exchange.fetch_bids_asks(cached=True)
|
||||
assert tickers2 == bidsasks
|
||||
assert api_mock.fetch_bids_asks.call_count == 0
|
||||
tickers2 = exchange.fetch_bids_asks(cached=False)
|
||||
assert api_mock.fetch_bids_asks.call_count == 1
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||
"fetch_bids_asks", "fetch_bids_asks")
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.fetch_bids_asks = MagicMock(side_effect=ccxt.NotSupported("DeadBeef"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.fetch_bids_asks()
|
||||
|
||||
api_mock.fetch_bids_asks = MagicMock(return_value={})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.fetch_bids_asks()
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
||||
assert exchange.fetch_bids_asks() == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_tickers(default_conf, mocker, exchange_name):
|
||||
api_mock = MagicMock()
|
||||
@ -1588,6 +1641,7 @@ def test_get_tickers(default_conf, mocker, exchange_name):
|
||||
}
|
||||
}
|
||||
api_mock.fetch_tickers = MagicMock(return_value=tick)
|
||||
api_mock.fetch_bids_asks = MagicMock(return_value={})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
# retrieve original ticker
|
||||
tickers = exchange.get_tickers()
|
||||
@ -1599,6 +1653,7 @@ def test_get_tickers(default_conf, mocker, exchange_name):
|
||||
assert tickers['BCH/BTC']['bid'] == 0.6
|
||||
assert tickers['BCH/BTC']['ask'] == 0.5
|
||||
assert api_mock.fetch_tickers.call_count == 1
|
||||
assert api_mock.fetch_bids_asks.call_count == 0
|
||||
|
||||
api_mock.fetch_tickers.reset_mock()
|
||||
|
||||
@ -1606,8 +1661,10 @@ def test_get_tickers(default_conf, mocker, exchange_name):
|
||||
tickers2 = exchange.get_tickers(cached=True)
|
||||
assert tickers2 == tickers
|
||||
assert api_mock.fetch_tickers.call_count == 0
|
||||
assert api_mock.fetch_bids_asks.call_count == 0
|
||||
tickers2 = exchange.get_tickers(cached=False)
|
||||
assert api_mock.fetch_tickers.call_count == 1
|
||||
assert api_mock.fetch_bids_asks.call_count == 0
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||
"get_tickers", "fetch_tickers")
|
||||
@ -1621,6 +1678,17 @@ def test_get_tickers(default_conf, mocker, exchange_name):
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.get_tickers()
|
||||
|
||||
api_mock.fetch_tickers.reset_mock()
|
||||
api_mock.fetch_bids_asks.reset_mock()
|
||||
default_conf['trading_mode'] = TradingMode.FUTURES
|
||||
default_conf['margin_mode'] = MarginMode.ISOLATED
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
|
||||
exchange.get_tickers()
|
||||
assert api_mock.fetch_tickers.call_count == 1
|
||||
assert api_mock.fetch_bids_asks.call_count == (1 if exchange_name == 'binance' else 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_fetch_ticker(default_conf, mocker, exchange_name):
|
||||
|
Loading…
Reference in New Issue
Block a user