Merge pull request #3868 from freqtrade/fix/3865
bittrex fetch_orderbook API change.
This commit is contained in:
commit
8ae193f638
@ -5,15 +5,15 @@
|
|||||||
"tradable_balance_ratio": 0.99,
|
"tradable_balance_ratio": 0.99,
|
||||||
"fiat_display_currency": "USD",
|
"fiat_display_currency": "USD",
|
||||||
"timeframe": "5m",
|
"timeframe": "5m",
|
||||||
"dry_run": false,
|
"dry_run": true,
|
||||||
"cancel_open_orders_on_exit": false,
|
"cancel_open_orders_on_exit": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"buy": 10,
|
||||||
"sell": 30
|
"sell": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
|
"ask_last_balance": 0.0,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"amount_reserve_percent": 0.05,
|
"amount_reserve_percent": 0.05,
|
||||||
"amend_last_stake_amount": false,
|
"amend_last_stake_amount": false,
|
||||||
"last_stake_amount_min_ratio": 0.5,
|
"last_stake_amount_min_ratio": 0.5,
|
||||||
"dry_run": false,
|
"dry_run": true,
|
||||||
"cancel_open_orders_on_exit": false,
|
"cancel_open_orders_on_exit": false,
|
||||||
"timeframe": "5m",
|
"timeframe": "5m",
|
||||||
"trailing_stop": false,
|
"trailing_stop": false,
|
||||||
|
@ -27,12 +27,11 @@
|
|||||||
"use_sell_signal": true,
|
"use_sell_signal": true,
|
||||||
"sell_profit_only": false,
|
"sell_profit_only": false,
|
||||||
"ignore_roi_if_buy_signal": false
|
"ignore_roi_if_buy_signal": false
|
||||||
|
|
||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "kraken",
|
"name": "kraken",
|
||||||
"key": "",
|
"key": "your_exchange_key",
|
||||||
"secret": "",
|
"secret": "your_exchange_key",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": true,
|
"enableRateLimit": true,
|
||||||
|
@ -5,6 +5,7 @@ from freqtrade.exchange.exchange import Exchange
|
|||||||
# isort: on
|
# isort: on
|
||||||
from freqtrade.exchange.bibox import Bibox
|
from freqtrade.exchange.bibox import Bibox
|
||||||
from freqtrade.exchange.binance import Binance
|
from freqtrade.exchange.binance import Binance
|
||||||
|
from freqtrade.exchange.bittrex import Bittrex
|
||||||
from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
||||||
get_exchange_bad_reason, is_exchange_bad,
|
get_exchange_bad_reason, is_exchange_bad,
|
||||||
is_exchange_known_ccxt, is_exchange_officially_supported,
|
is_exchange_known_ccxt, is_exchange_officially_supported,
|
||||||
|
@ -20,20 +20,9 @@ class Binance(Exchange):
|
|||||||
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
||||||
"trades_pagination": "id",
|
"trades_pagination": "id",
|
||||||
"trades_pagination_arg": "fromId",
|
"trades_pagination_arg": "fromId",
|
||||||
|
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
|
||||||
}
|
}
|
||||||
|
|
||||||
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
|
||||||
"""
|
|
||||||
get order book level 2 from exchange
|
|
||||||
|
|
||||||
20180619: binance support limits but only on specific range
|
|
||||||
"""
|
|
||||||
limit_range = [5, 10, 20, 50, 100, 500, 1000]
|
|
||||||
# get next-higher step in the limit_range list
|
|
||||||
limit = min(list(filter(lambda x: limit <= x, limit_range)))
|
|
||||||
|
|
||||||
return super().fetch_l2_order_book(pair, limit)
|
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify stop_loss against stoploss-order value (limit or price)
|
Verify stop_loss against stoploss-order value (limit or price)
|
||||||
|
23
freqtrade/exchange/bittrex.py
Normal file
23
freqtrade/exchange/bittrex.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
""" Bittrex exchange subclass """
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Bittrex(Exchange):
|
||||||
|
"""
|
||||||
|
Bittrex exchange class. Contains adjustments needed for Freqtrade to work
|
||||||
|
with this exchange.
|
||||||
|
|
||||||
|
Please note that this exchange is not included in the list of exchanges
|
||||||
|
officially supported by the Freqtrade development team. So some features
|
||||||
|
may still not work as expected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_ft_has: Dict = {
|
||||||
|
"l2_limit_range": [1, 25, 500],
|
||||||
|
}
|
@ -53,7 +53,7 @@ class Exchange:
|
|||||||
"ohlcv_partial_candle": True,
|
"ohlcv_partial_candle": True,
|
||||||
"trades_pagination": "time", # Possible are "time" or "id"
|
"trades_pagination": "time", # Possible are "time" or "id"
|
||||||
"trades_pagination_arg": "since",
|
"trades_pagination_arg": "since",
|
||||||
|
"l2_limit_range": None,
|
||||||
}
|
}
|
||||||
_ft_has: Dict = {}
|
_ft_has: Dict = {}
|
||||||
|
|
||||||
@ -1069,6 +1069,16 @@ class Exchange:
|
|||||||
return self.fetch_stoploss_order(order_id, pair)
|
return self.fetch_stoploss_order(order_id, pair)
|
||||||
return self.fetch_order(order_id, pair)
|
return self.fetch_order(order_id, pair)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_next_limit_in_list(limit: int, limit_range: Optional[List[int]]):
|
||||||
|
"""
|
||||||
|
Get next greater value in the list.
|
||||||
|
Used by fetch_l2_order_book if the api only supports a limited range
|
||||||
|
"""
|
||||||
|
if not limit_range:
|
||||||
|
return limit
|
||||||
|
return min([x for x in limit_range if limit <= x] + [max(limit_range)])
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
||||||
"""
|
"""
|
||||||
@ -1077,9 +1087,10 @@ class Exchange:
|
|||||||
Returns a dict in the format
|
Returns a dict in the format
|
||||||
{'asks': [price, volume], 'bids': [price, volume]}
|
{'asks': [price, volume], 'bids': [price, volume]}
|
||||||
"""
|
"""
|
||||||
|
limit1 = self.get_next_limit_in_list(limit, self._ft_has['l2_limit_range'])
|
||||||
try:
|
try:
|
||||||
|
|
||||||
return self._api.fetch_l2_order_book(pair, limit)
|
return self._api.fetch_l2_order_book(pair, limit1)
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Exchange {self._api.name} does not support fetching order book.'
|
f'Exchange {self._api.name} does not support fetching order book.'
|
||||||
|
@ -132,7 +132,7 @@ def test_orderbook(mocker, default_conf, order_book_l2):
|
|||||||
res = dp.orderbook('ETH/BTC', 5)
|
res = dp.orderbook('ETH/BTC', 5)
|
||||||
assert order_book_l2.call_count == 1
|
assert order_book_l2.call_count == 1
|
||||||
assert order_book_l2.call_args_list[0][0][0] == 'ETH/BTC'
|
assert order_book_l2.call_args_list[0][0][0] == 'ETH/BTC'
|
||||||
assert order_book_l2.call_args_list[0][0][1] == 5
|
assert order_book_l2.call_args_list[0][0][1] >= 5
|
||||||
|
|
||||||
assert type(res) is dict
|
assert type(res) is dict
|
||||||
assert 'bids' in res
|
assert 'bids' in res
|
||||||
|
@ -11,7 +11,7 @@ from pandas import DataFrame
|
|||||||
|
|
||||||
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
|
||||||
OperationalException, TemporaryError)
|
OperationalException, TemporaryError)
|
||||||
from freqtrade.exchange import Binance, Exchange, Kraken
|
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
|
||||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
||||||
calculate_backoff)
|
calculate_backoff)
|
||||||
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
|
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
|
||||||
@ -148,11 +148,19 @@ def test_exchange_resolver(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
|
|
||||||
|
exchange = ExchangeResolver.load_exchange('huobi', default_conf)
|
||||||
assert isinstance(exchange, Exchange)
|
assert isinstance(exchange, Exchange)
|
||||||
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
|
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
|
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
|
||||||
|
assert isinstance(exchange, Exchange)
|
||||||
|
assert isinstance(exchange, Bittrex)
|
||||||
|
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
|
||||||
|
caplog)
|
||||||
|
caplog.clear()
|
||||||
|
|
||||||
exchange = ExchangeResolver.load_exchange('kraken', default_conf)
|
exchange = ExchangeResolver.load_exchange('kraken', default_conf)
|
||||||
assert isinstance(exchange, Exchange)
|
assert isinstance(exchange, Exchange)
|
||||||
assert isinstance(exchange, Kraken)
|
assert isinstance(exchange, Kraken)
|
||||||
@ -1438,6 +1446,27 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
|
|||||||
assert log_has("Async code raised an exception: TypeError", caplog)
|
assert log_has("Async code raised an exception: TypeError", caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_next_limit_in_list():
|
||||||
|
limit_range = [5, 10, 20, 50, 100, 500, 1000]
|
||||||
|
assert Exchange.get_next_limit_in_list(1, limit_range) == 5
|
||||||
|
assert Exchange.get_next_limit_in_list(5, limit_range) == 5
|
||||||
|
assert Exchange.get_next_limit_in_list(6, limit_range) == 10
|
||||||
|
assert Exchange.get_next_limit_in_list(9, limit_range) == 10
|
||||||
|
assert Exchange.get_next_limit_in_list(10, limit_range) == 10
|
||||||
|
assert Exchange.get_next_limit_in_list(11, limit_range) == 20
|
||||||
|
assert Exchange.get_next_limit_in_list(19, limit_range) == 20
|
||||||
|
assert Exchange.get_next_limit_in_list(21, limit_range) == 50
|
||||||
|
assert Exchange.get_next_limit_in_list(51, limit_range) == 100
|
||||||
|
assert Exchange.get_next_limit_in_list(1000, limit_range) == 1000
|
||||||
|
# Going over the limit ...
|
||||||
|
assert Exchange.get_next_limit_in_list(1001, limit_range) == 1000
|
||||||
|
assert Exchange.get_next_limit_in_list(2000, limit_range) == 1000
|
||||||
|
|
||||||
|
assert Exchange.get_next_limit_in_list(21, None) == 21
|
||||||
|
assert Exchange.get_next_limit_in_list(100, None) == 100
|
||||||
|
assert Exchange.get_next_limit_in_list(1000, None) == 1000
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name):
|
def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name):
|
||||||
default_conf['exchange']['name'] = exchange_name
|
default_conf['exchange']['name'] = exchange_name
|
||||||
@ -1450,6 +1479,19 @@ def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name)
|
|||||||
assert 'asks' in order_book
|
assert 'asks' in order_book
|
||||||
assert len(order_book['bids']) == 10
|
assert len(order_book['bids']) == 10
|
||||||
assert len(order_book['asks']) == 10
|
assert len(order_book['asks']) == 10
|
||||||
|
assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC'
|
||||||
|
|
||||||
|
for val in [1, 5, 10, 12, 20, 50, 100]:
|
||||||
|
api_mock.fetch_l2_order_book.reset_mock()
|
||||||
|
|
||||||
|
order_book = exchange.fetch_l2_order_book(pair='ETH/BTC', limit=val)
|
||||||
|
assert api_mock.fetch_l2_order_book.call_args_list[0][0][0] == 'ETH/BTC'
|
||||||
|
# Not all exchanges support all limits for orderbook
|
||||||
|
if not exchange._ft_has['l2_limit_range'] or val in exchange._ft_has['l2_limit_range']:
|
||||||
|
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == val
|
||||||
|
else:
|
||||||
|
next_limit = exchange.get_next_limit_in_list(val, exchange._ft_has['l2_limit_range'])
|
||||||
|
assert api_mock.fetch_l2_order_book.call_args_list[0][0][1] == next_limit
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
|
Loading…
Reference in New Issue
Block a user