Merge pull request #6550 from freqtrade/short_tickerproblems
Short tickerproblems
This commit is contained in:
commit
7d8ca63752
@ -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__)
|
||||||
@ -22,7 +23,6 @@ class Binance(Exchange):
|
|||||||
_ft_has: Dict = {
|
_ft_has: Dict = {
|
||||||
"stoploss_on_exchange": True,
|
"stoploss_on_exchange": True,
|
||||||
"stoploss_order_types": {"limit": "stop_loss_limit"},
|
"stoploss_order_types": {"limit": "stop_loss_limit"},
|
||||||
"stoploss_order_types_futures": {"limit": "stop"},
|
|
||||||
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
||||||
"time_in_force_parameter": "timeInForce",
|
"time_in_force_parameter": "timeInForce",
|
||||||
"ohlcv_candle_limit": 1000,
|
"ohlcv_candle_limit": 1000,
|
||||||
@ -31,6 +31,10 @@ class Binance(Exchange):
|
|||||||
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
|
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
|
||||||
"ccxt_futures_name": "future"
|
"ccxt_futures_name": "future"
|
||||||
}
|
}
|
||||||
|
_ft_has_futures: Dict = {
|
||||||
|
"stoploss_order_types": {"limit": "stop"},
|
||||||
|
"tickers_have_price": False,
|
||||||
|
}
|
||||||
|
|
||||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||||
# TradingMode.SPOT always supported and not required in this list
|
# TradingMode.SPOT always supported and not required in this list
|
||||||
@ -53,6 +57,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,
|
||||||
|
@ -65,6 +65,8 @@ class Exchange:
|
|||||||
"ohlcv_partial_candle": True,
|
"ohlcv_partial_candle": True,
|
||||||
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
|
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
|
||||||
"ohlcv_volume_currency": "base", # "base" or "quote"
|
"ohlcv_volume_currency": "base", # "base" or "quote"
|
||||||
|
"tickers_have_quoteVolume": True,
|
||||||
|
"tickers_have_price": 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,
|
"l2_limit_range": None,
|
||||||
@ -74,6 +76,7 @@ class Exchange:
|
|||||||
"ccxt_futures_name": "swap",
|
"ccxt_futures_name": "swap",
|
||||||
}
|
}
|
||||||
_ft_has: Dict = {}
|
_ft_has: Dict = {}
|
||||||
|
_ft_has_futures: Dict = {}
|
||||||
|
|
||||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||||
# TradingMode.SPOT always supported and not required in this list
|
# TradingMode.SPOT always supported and not required in this list
|
||||||
@ -101,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.
|
||||||
@ -121,8 +124,19 @@ class Exchange:
|
|||||||
exchange_config = config['exchange']
|
exchange_config = config['exchange']
|
||||||
self.log_responses = exchange_config.get('log_responses', False)
|
self.log_responses = exchange_config.get('log_responses', False)
|
||||||
|
|
||||||
|
# Leverage properties
|
||||||
|
self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT)
|
||||||
|
self.margin_mode: Optional[MarginMode] = (
|
||||||
|
MarginMode(config.get('margin_mode'))
|
||||||
|
if config.get('margin_mode')
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
self.liquidation_buffer = config.get('liquidation_buffer', 0.05)
|
||||||
|
|
||||||
# Deep merge ft_has with default ft_has options
|
# Deep merge ft_has with default ft_has options
|
||||||
self._ft_has = deep_merge_dicts(self._ft_has, deepcopy(self._ft_has_default))
|
self._ft_has = deep_merge_dicts(self._ft_has, deepcopy(self._ft_has_default))
|
||||||
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
|
self._ft_has = deep_merge_dicts(self._ft_has_futures, self._ft_has)
|
||||||
if exchange_config.get('_ft_has_params'):
|
if exchange_config.get('_ft_has_params'):
|
||||||
self._ft_has = deep_merge_dicts(exchange_config.get('_ft_has_params'),
|
self._ft_has = deep_merge_dicts(exchange_config.get('_ft_has_params'),
|
||||||
self._ft_has)
|
self._ft_has)
|
||||||
@ -134,15 +148,6 @@ class Exchange:
|
|||||||
self._trades_pagination = self._ft_has['trades_pagination']
|
self._trades_pagination = self._ft_has['trades_pagination']
|
||||||
self._trades_pagination_arg = self._ft_has['trades_pagination_arg']
|
self._trades_pagination_arg = self._ft_has['trades_pagination_arg']
|
||||||
|
|
||||||
# Leverage properties
|
|
||||||
self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT)
|
|
||||||
self.margin_mode: Optional[MarginMode] = (
|
|
||||||
MarginMode(config.get('margin_mode'))
|
|
||||||
if config.get('margin_mode')
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
self.liquidation_buffer = config.get('liquidation_buffer', 0.05)
|
|
||||||
|
|
||||||
# Initialize ccxt objects
|
# Initialize ccxt objects
|
||||||
ccxt_config = self._ccxt_config
|
ccxt_config = self._ccxt_config
|
||||||
ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config)
|
ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config)
|
||||||
@ -176,6 +181,8 @@ class Exchange:
|
|||||||
self.required_candle_call_count = self.validate_required_startup_candles(
|
self.required_candle_call_count = self.validate_required_startup_candles(
|
||||||
config.get('startup_candle_count', 0), config.get('timeframe', ''))
|
config.get('startup_candle_count', 0), config.get('timeframe', ''))
|
||||||
self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode)
|
self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode)
|
||||||
|
self.validate_pricing(config['ask_strategy'])
|
||||||
|
self.validate_pricing(config['bid_strategy'])
|
||||||
|
|
||||||
# Converts the interval provided in minutes in config to seconds
|
# Converts the interval provided in minutes in config to seconds
|
||||||
self.markets_refresh_interval: int = exchange_config.get(
|
self.markets_refresh_interval: int = exchange_config.get(
|
||||||
@ -569,6 +576,14 @@ class Exchange:
|
|||||||
f'On exchange stoploss is not supported for {self.name}.'
|
f'On exchange stoploss is not supported for {self.name}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_pricing(self, pricing: Dict) -> None:
|
||||||
|
if pricing.get('use_order_book', False) and not self.exchange_has('fetchL2OrderBook'):
|
||||||
|
raise OperationalException(f'Orderbook not available for {self.name}.')
|
||||||
|
if (not pricing.get('use_order_book', False) and (
|
||||||
|
not self.exchange_has('fetchTicker')
|
||||||
|
or not self._ft_has['tickers_have_price'])):
|
||||||
|
raise OperationalException(f'Ticker pricing not available for {self.name}.')
|
||||||
|
|
||||||
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
|
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
Checks if order time in force configured in strategy/config are supported
|
Checks if order time in force configured in strategy/config are supported
|
||||||
@ -1010,10 +1025,6 @@ class Exchange:
|
|||||||
def _get_stop_order_type(self, user_order_type) -> Tuple[str, str]:
|
def _get_stop_order_type(self, user_order_type) -> Tuple[str, str]:
|
||||||
|
|
||||||
available_order_Types: Dict[str, str] = self._ft_has["stoploss_order_types"]
|
available_order_Types: Dict[str, str] = self._ft_has["stoploss_order_types"]
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
|
||||||
# Optionally use different order type for stop order
|
|
||||||
available_order_Types = self._ft_has.get('stoploss_order_types_futures',
|
|
||||||
self._ft_has["stoploss_order_types"])
|
|
||||||
|
|
||||||
if user_order_type in available_order_Types.keys():
|
if user_order_type in available_order_Types.keys():
|
||||||
ordertype = available_order_Types[user_order_type]
|
ordertype = available_order_Types[user_order_type]
|
||||||
@ -1288,6 +1299,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:
|
||||||
"""
|
"""
|
||||||
@ -1397,7 +1436,7 @@ class Exchange:
|
|||||||
|
|
||||||
conf_strategy = self._config.get(strat_name, {})
|
conf_strategy = self._config.get(strat_name, {})
|
||||||
|
|
||||||
if conf_strategy.get('use_order_book', False) and ('use_order_book' in conf_strategy):
|
if conf_strategy.get('use_order_book', False):
|
||||||
|
|
||||||
order_book_top = conf_strategy.get('order_book_top', 1)
|
order_book_top = conf_strategy.get('order_book_top', 1)
|
||||||
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
||||||
|
@ -23,6 +23,9 @@ class Okx(Exchange):
|
|||||||
"mark_ohlcv_timeframe": "4h",
|
"mark_ohlcv_timeframe": "4h",
|
||||||
"funding_fee_timeframe": "8h",
|
"funding_fee_timeframe": "8h",
|
||||||
}
|
}
|
||||||
|
_ft_has_futures: Dict = {
|
||||||
|
"tickers_have_quoteVolume": False,
|
||||||
|
}
|
||||||
|
|
||||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||||
# TradingMode.SPOT always supported and not required in this list
|
# TradingMode.SPOT always supported and not required in this list
|
||||||
|
@ -129,7 +129,7 @@ def format_ms_time(date: int) -> str:
|
|||||||
return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S')
|
return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
def deep_merge_dicts(source, destination):
|
def deep_merge_dicts(source, destination, allow_null_overrides: bool = True):
|
||||||
"""
|
"""
|
||||||
Values from Source override destination, destination is returned (and modified!!)
|
Values from Source override destination, destination is returned (and modified!!)
|
||||||
Sample:
|
Sample:
|
||||||
@ -142,8 +142,8 @@ def deep_merge_dicts(source, destination):
|
|||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
# get node or create one
|
# get node or create one
|
||||||
node = destination.setdefault(key, {})
|
node = destination.setdefault(key, {})
|
||||||
deep_merge_dicts(value, node)
|
deep_merge_dicts(value, node, allow_null_overrides)
|
||||||
else:
|
elif value is not None or allow_null_overrides:
|
||||||
destination[key] = value
|
destination[key] = value
|
||||||
|
|
||||||
return destination
|
return destination
|
||||||
|
@ -71,10 +71,13 @@ class VolumePairList(IPairList):
|
|||||||
f'to at least {self._tf_in_sec} and restart the bot.'
|
f'to at least {self._tf_in_sec} and restart the bot.'
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self._exchange.exchange_has('fetchTickers'):
|
if (not self._use_range and not (
|
||||||
|
self._exchange.exchange_has('fetchTickers')
|
||||||
|
and self._exchange._ft_has["tickers_have_quoteVolume"])):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Exchange does not support dynamic whitelist. '
|
"Exchange does not support dynamic whitelist in this configuration. "
|
||||||
'Please edit your config and restart the bot.'
|
"Please edit your config and either remove Volumepairlist, "
|
||||||
|
"or switch to using candles. and restart the bot."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self._validate_keys(self._sort_key):
|
if not self._validate_keys(self._sort_key):
|
||||||
@ -95,7 +98,7 @@ class VolumePairList(IPairList):
|
|||||||
If no Pairlist requires tickers, an empty Dict is passed
|
If no Pairlist requires tickers, an empty Dict is passed
|
||||||
as tickers argument to filter_pairlist
|
as tickers argument to filter_pairlist
|
||||||
"""
|
"""
|
||||||
return True
|
return not self._use_range
|
||||||
|
|
||||||
def _validate_keys(self, key):
|
def _validate_keys(self, key):
|
||||||
return key in SORT_VALUES
|
return key in SORT_VALUES
|
||||||
@ -126,13 +129,15 @@ class VolumePairList(IPairList):
|
|||||||
tradable_only=True, active_only=True).keys()]
|
tradable_only=True, active_only=True).keys()]
|
||||||
# No point in testing for blacklisted pairs...
|
# No point in testing for blacklisted pairs...
|
||||||
_pairlist = self.verify_blacklist(_pairlist, logger.info)
|
_pairlist = self.verify_blacklist(_pairlist, logger.info)
|
||||||
|
if not self._use_range:
|
||||||
filtered_tickers = [
|
filtered_tickers = [
|
||||||
v for k, v in tickers.items()
|
v for k, v in tickers.items()
|
||||||
if (self._exchange.get_pair_quote_currency(k) == self._stake_currency
|
if (self._exchange.get_pair_quote_currency(k) == self._stake_currency
|
||||||
and (self._use_range or v[self._sort_key] is not None)
|
and (self._use_range or v[self._sort_key] is not None)
|
||||||
and v['symbol'] in _pairlist)]
|
and v['symbol'] in _pairlist)]
|
||||||
pairlist = [s['symbol'] for s in filtered_tickers]
|
pairlist = [s['symbol'] for s in filtered_tickers]
|
||||||
|
else:
|
||||||
|
pairlist = _pairlist
|
||||||
|
|
||||||
pairlist = self.filter_pairlist(pairlist, tickers)
|
pairlist = self.filter_pairlist(pairlist, tickers)
|
||||||
self._pair_cache['pairlist'] = pairlist.copy()
|
self._pair_cache['pairlist'] = pairlist.copy()
|
||||||
@ -147,11 +152,11 @@ class VolumePairList(IPairList):
|
|||||||
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
||||||
:return: new whitelist
|
:return: new whitelist
|
||||||
"""
|
"""
|
||||||
# Use the incoming pairlist.
|
|
||||||
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
|
||||||
|
|
||||||
# get lookback period in ms, for exchange ohlcv fetch
|
|
||||||
if self._use_range:
|
if self._use_range:
|
||||||
|
# Create bare minimum from tickers structure.
|
||||||
|
filtered_tickers: List[Dict[str, Any]] = [{'symbol': k} for k in pairlist]
|
||||||
|
|
||||||
|
# get lookback period in ms, for exchange ohlcv fetch
|
||||||
since_ms = int(arrow.utcnow()
|
since_ms = int(arrow.utcnow()
|
||||||
.floor('minute')
|
.floor('minute')
|
||||||
.shift(minutes=-(self._lookback_period * self._tf_in_min)
|
.shift(minutes=-(self._lookback_period * self._tf_in_min)
|
||||||
@ -208,6 +213,9 @@ class VolumePairList(IPairList):
|
|||||||
filtered_tickers[i]['quoteVolume'] = quoteVolume
|
filtered_tickers[i]['quoteVolume'] = quoteVolume
|
||||||
else:
|
else:
|
||||||
filtered_tickers[i]['quoteVolume'] = 0
|
filtered_tickers[i]['quoteVolume'] = 0
|
||||||
|
else:
|
||||||
|
# Tickers mode - filter based on incomming pairlist.
|
||||||
|
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
||||||
|
|
||||||
if self._min_value > 0:
|
if self._min_value > 0:
|
||||||
filtered_tickers = [
|
filtered_tickers = [
|
||||||
|
@ -104,6 +104,7 @@ def patch_exchange(
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
|
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency', MagicMock())
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency', MagicMock())
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
|
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title()))
|
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title()))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2))
|
mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2))
|
||||||
|
@ -107,6 +107,8 @@ def exchange_conf():
|
|||||||
config['exchange']['key'] = ''
|
config['exchange']['key'] = ''
|
||||||
config['exchange']['secret'] = ''
|
config['exchange']['secret'] = ''
|
||||||
config['dry_run'] = False
|
config['dry_run'] = False
|
||||||
|
config['bid_strategy']['use_order_book'] = True
|
||||||
|
config['ask_strategy']['use_order_book'] = True
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,6 +167,7 @@ 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')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
|
|
||||||
exchange = ExchangeResolver.load_exchange('zaif', default_conf)
|
exchange = ExchangeResolver.load_exchange('zaif', default_conf)
|
||||||
assert isinstance(exchange, Exchange)
|
assert isinstance(exchange, Exchange)
|
||||||
@ -570,6 +571,7 @@ def test__load_async_markets(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_markets')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
exchange = Exchange(default_conf)
|
exchange = Exchange(default_conf)
|
||||||
exchange._api_async.load_markets = get_mock_coro(None)
|
exchange._api_async.load_markets = get_mock_coro(None)
|
||||||
exchange._load_async_markets()
|
exchange._load_async_markets()
|
||||||
@ -591,6 +593,7 @@ def test__load_markets(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
assert log_has('Unable to initialize markets.', caplog)
|
assert log_has('Unable to initialize markets.', caplog)
|
||||||
|
|
||||||
@ -659,6 +662,7 @@ def test_validate_stakecurrency(default_conf, stake_currency, 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._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
@ -731,6 +735,7 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
@ -757,6 +762,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
|
||||||
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')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'):
|
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'):
|
||||||
@ -777,6 +783,7 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
@ -796,6 +803,7 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
|
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
@ -812,6 +820,7 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
|
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
assert type(api_mock).load_markets.call_count == 1
|
assert type(api_mock).load_markets.call_count == 1
|
||||||
@ -852,6 +861,7 @@ def test_validate_timeframes(default_conf, mocker, timeframe):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
@ -936,10 +946,49 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_order_types(default_conf, mocker):
|
def test_validate_pricing(default_conf, mocker):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
has = {
|
||||||
|
'fetchL2OrderBook': True,
|
||||||
|
'fetchTicker': True,
|
||||||
|
}
|
||||||
|
type(api_mock).has = PropertyMock(return_value=has)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||||
|
mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.name', 'Binance')
|
||||||
|
ExchangeResolver.load_exchange('binance', default_conf)
|
||||||
|
has.update({'fetchTicker': False})
|
||||||
|
with pytest.raises(OperationalException, match="Ticker pricing not available for .*"):
|
||||||
|
ExchangeResolver.load_exchange('binance', default_conf)
|
||||||
|
|
||||||
|
has.update({'fetchTicker': True})
|
||||||
|
|
||||||
|
default_conf['ask_strategy']['use_order_book'] = True
|
||||||
|
ExchangeResolver.load_exchange('binance', default_conf)
|
||||||
|
has.update({'fetchL2OrderBook': False})
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException, match="Orderbook not available for .*"):
|
||||||
|
ExchangeResolver.load_exchange('binance', default_conf)
|
||||||
|
|
||||||
|
has.update({'fetchL2OrderBook': True})
|
||||||
|
|
||||||
|
# Binance has no tickers on futures
|
||||||
|
default_conf['trading_mode'] = TradingMode.FUTURES
|
||||||
|
default_conf['margin_mode'] = MarginMode.ISOLATED
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException, match="Ticker pricing not available for .*"):
|
||||||
|
ExchangeResolver.load_exchange('binance', default_conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_ordertypes(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
|
|
||||||
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
|
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
|
||||||
@ -948,6 +997,7 @@ def test_validate_order_types(default_conf, mocker):
|
|||||||
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')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
|
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
|
||||||
|
|
||||||
default_conf['order_types'] = {
|
default_conf['order_types'] = {
|
||||||
@ -988,6 +1038,7 @@ def test_validate_order_types_not_in_config(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||||
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_pricing')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
|
||||||
conf = copy.deepcopy(default_conf)
|
conf = copy.deepcopy(default_conf)
|
||||||
@ -1002,6 +1053,7 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
|
||||||
default_conf['startup_candle_count'] = 20
|
default_conf['startup_candle_count'] = 20
|
||||||
@ -1572,6 +1624,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 +1693,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 +1705,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 +1713,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 +1730,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):
|
||||||
@ -2977,7 +3097,8 @@ def test_merge_ft_has_dict(default_conf, mocker):
|
|||||||
_load_async_markets=MagicMock(),
|
_load_async_markets=MagicMock(),
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
validate_timeframes=MagicMock(),
|
validate_timeframes=MagicMock(),
|
||||||
validate_stakecurrency=MagicMock()
|
validate_stakecurrency=MagicMock(),
|
||||||
|
validate_pricing=MagicMock(),
|
||||||
)
|
)
|
||||||
ex = Exchange(default_conf)
|
ex = Exchange(default_conf)
|
||||||
assert ex._ft_has == Exchange._ft_has_default
|
assert ex._ft_has == Exchange._ft_has_default
|
||||||
@ -3011,6 +3132,7 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
|
|||||||
_load_async_markets=MagicMock(),
|
_load_async_markets=MagicMock(),
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
validate_timeframes=MagicMock(),
|
validate_timeframes=MagicMock(),
|
||||||
|
validate_pricing=MagicMock(),
|
||||||
markets=PropertyMock(return_value=markets))
|
markets=PropertyMock(return_value=markets))
|
||||||
ex = Exchange(default_conf)
|
ex = Exchange(default_conf)
|
||||||
|
|
||||||
@ -3102,6 +3224,7 @@ def test_get_markets(default_conf, mocker, markets_static,
|
|||||||
_load_async_markets=MagicMock(),
|
_load_async_markets=MagicMock(),
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
validate_timeframes=MagicMock(),
|
validate_timeframes=MagicMock(),
|
||||||
|
validate_pricing=MagicMock(),
|
||||||
markets=PropertyMock(return_value=markets_static))
|
markets=PropertyMock(return_value=markets_static))
|
||||||
ex = Exchange(default_conf)
|
ex = Exchange(default_conf)
|
||||||
pairs = ex.get_markets(base_currencies,
|
pairs = ex.get_markets(base_currencies,
|
||||||
|
@ -15,6 +15,7 @@ def test_validate_order_types_gateio(default_conf, mocker):
|
|||||||
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')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
|
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
|
||||||
exch = ExchangeResolver.load_exchange('gateio', default_conf, True)
|
exch = ExchangeResolver.load_exchange('gateio', default_conf, True)
|
||||||
assert isinstance(exch, Gateio)
|
assert isinstance(exch, Gateio)
|
||||||
|
@ -587,10 +587,10 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
|||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||||
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
|
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
|
||||||
"BTC", "binance", ['LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC', 'HOT/BTC']),
|
"BTC", "binance", ['LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC', 'HOT/BTC']),
|
||||||
# expecting pairs from default tickers, because 1h candles are not available
|
# expecting pairs as input, because 1h candles are not available
|
||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||||
"lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}],
|
"lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}],
|
||||||
"BTC", "binance", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']),
|
"BTC", "binance", ['ETH/BTC', 'LTC/BTC', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
|
||||||
# ftx data is already in Quote currency, therefore won't require conversion
|
# ftx data is already in Quote currency, therefore won't require conversion
|
||||||
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume",
|
||||||
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
|
"lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}],
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
# pragma pylint: disable=missing-docstring,C0103
|
# pragma pylint: disable=missing-docstring,C0103
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.misc import (decimals_per_coin, file_dump_json, file_load_json, format_ms_time,
|
from freqtrade.misc import (decimals_per_coin, deep_merge_dicts, file_dump_json, file_load_json,
|
||||||
pair_to_filename, parse_db_uri_for_logging, plural, render_template,
|
format_ms_time, pair_to_filename, parse_db_uri_for_logging, plural,
|
||||||
render_template_with_fallback, round_coin_value, safe_value_fallback,
|
render_template, render_template_with_fallback, round_coin_value,
|
||||||
safe_value_fallback2, shorten_date)
|
safe_value_fallback, safe_value_fallback2, shorten_date)
|
||||||
|
|
||||||
|
|
||||||
def test_decimals_per_coin():
|
def test_decimals_per_coin():
|
||||||
@ -203,3 +204,16 @@ def test_render_template_fallback(mocker):
|
|||||||
def test_parse_db_uri_for_logging(conn_url, expected) -> None:
|
def test_parse_db_uri_for_logging(conn_url, expected) -> None:
|
||||||
|
|
||||||
assert parse_db_uri_for_logging(conn_url) == expected
|
assert parse_db_uri_for_logging(conn_url) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_deep_merge_dicts():
|
||||||
|
a = {'first': {'rows': {'pass': 'dog', 'number': '1', 'test': None}}}
|
||||||
|
b = {'first': {'rows': {'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
|
||||||
|
res = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '5', 'test': 'asdf'}}}
|
||||||
|
res2 = {'first': {'rows': {'pass': 'dog', 'fail': 'cat', 'number': '1', 'test': None}}}
|
||||||
|
assert deep_merge_dicts(b, deepcopy(a)) == res
|
||||||
|
|
||||||
|
assert deep_merge_dicts(a, deepcopy(b)) == res2
|
||||||
|
|
||||||
|
res2['first']['rows']['test'] = 'asdf'
|
||||||
|
assert deep_merge_dicts(a, deepcopy(b), allow_null_overrides=False) == res2
|
||||||
|
Loading…
Reference in New Issue
Block a user