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.exceptions import DDosProtection, OperationalException, TemporaryError
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.exchange.common import retrier
|
from freqtrade.exchange.common import retrier
|
||||||
|
from freqtrade.misc import deep_merge_dicts
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -55,6 +56,15 @@ class Binance(Exchange):
|
|||||||
(side == "buy" and stop_loss < float(order['info']['stopPrice']))
|
(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
|
@retrier
|
||||||
def _set_leverage(
|
def _set_leverage(
|
||||||
self,
|
self,
|
||||||
|
@ -104,7 +104,7 @@ class Exchange:
|
|||||||
self._last_markets_refresh: int = 0
|
self._last_markets_refresh: int = 0
|
||||||
|
|
||||||
# Cache for 10 minutes ...
|
# 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
|
# 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
|
# Caching only applies to RPC methods, so prices for open trades are still
|
||||||
# refreshed once every iteration.
|
# refreshed once every iteration.
|
||||||
@ -1289,6 +1289,34 @@ class Exchange:
|
|||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from 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
|
@retrier
|
||||||
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
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")
|
"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)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
def test_get_tickers(default_conf, mocker, exchange_name):
|
def test_get_tickers(default_conf, mocker, exchange_name):
|
||||||
api_mock = MagicMock()
|
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_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)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
# retrieve original ticker
|
# retrieve original ticker
|
||||||
tickers = exchange.get_tickers()
|
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']['bid'] == 0.6
|
||||||
assert tickers['BCH/BTC']['ask'] == 0.5
|
assert tickers['BCH/BTC']['ask'] == 0.5
|
||||||
assert api_mock.fetch_tickers.call_count == 1
|
assert api_mock.fetch_tickers.call_count == 1
|
||||||
|
assert api_mock.fetch_bids_asks.call_count == 0
|
||||||
|
|
||||||
api_mock.fetch_tickers.reset_mock()
|
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)
|
tickers2 = exchange.get_tickers(cached=True)
|
||||||
assert tickers2 == tickers
|
assert tickers2 == tickers
|
||||||
assert api_mock.fetch_tickers.call_count == 0
|
assert api_mock.fetch_tickers.call_count == 0
|
||||||
|
assert api_mock.fetch_bids_asks.call_count == 0
|
||||||
tickers2 = exchange.get_tickers(cached=False)
|
tickers2 = exchange.get_tickers(cached=False)
|
||||||
assert api_mock.fetch_tickers.call_count == 1
|
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,
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||||
"get_tickers", "fetch_tickers")
|
"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_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.get_tickers()
|
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)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
def test_fetch_ticker(default_conf, mocker, exchange_name):
|
def test_fetch_ticker(default_conf, mocker, exchange_name):
|
||||||
|
Loading…
Reference in New Issue
Block a user