diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 17a8f27b9..cf2d5519b 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -10,7 +10,7 @@ import arrow from pandas import DataFrame, to_datetime from freqtrade import constants -from freqtrade.exchange import get_ticker_history +from freqtrade.exchange import Exchange from freqtrade.persistence import Trade from freqtrade.strategy.resolver import StrategyResolver, IStrategy @@ -110,14 +110,14 @@ class Analyze(object): dataframe = self.populate_sell_trend(dataframe) return dataframe - def get_signal(self, pair: str, interval: str) -> Tuple[bool, bool]: + def get_signal(self, exchange: Exchange, pair: str, interval: str) -> Tuple[bool, bool]: """ Calculates current signal based several technical analysis indicators :param pair: pair in format ANT/BTC :param interval: Interval to use (in min) :return: (Buy, Sell) A bool-tuple indicating buy/sell signal """ - ticker_hist = get_ticker_history(pair, interval) + ticker_hist = exchange.get_ticker_history(pair, interval) if not ticker_hist: logger.warning('Empty ticker history for pair %s', pair) return False, False diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 54d564f04..acfefdad4 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -12,16 +12,8 @@ from freqtrade import constants, OperationalException, DependencyException, Temp logger = logging.getLogger(__name__) -# Current selected exchange -_API: ccxt.Exchange = None - -_CONF: Dict = {} API_RETRY_COUNT = 4 -_CACHED_TICKER: Dict[str, Any] = {} - -# Holds all open sell orders for dry_run -_DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {} # Urls to exchange markets, insert quote and base with .format() _EXCHANGE_URLS = { @@ -48,390 +40,378 @@ def retrier(f): return wrapper -def init_ccxt(exchange_config: dict) -> ccxt.Exchange: - """ - Initialize ccxt with given config and return valid - ccxt instance. - :param config: config to use - :return: ccxt - """ - # Find matching class for the given exchange name - name = exchange_config['name'] +class Exchange(object): - if name not in ccxt.exchanges: - raise OperationalException(f'Exchange {name} is not supported') - try: - api = getattr(ccxt, name.lower())({ - 'apiKey': exchange_config.get('key'), - 'secret': exchange_config.get('secret'), - 'password': exchange_config.get('password'), - 'uid': exchange_config.get('uid', ''), - 'enableRateLimit': True, - }) - except (KeyError, AttributeError): - raise OperationalException(f'Exchange {name} is not supported') + # Current selected exchange + _api: ccxt.Exchange = None + _conf: Dict = {} + _cached_ticker: Dict[str, Any] = {} - return api + # Holds all open sell orders for dry_run + _dry_run_open_orders: Dict[str, Any] = {} + def __init__(self, config: dict) -> None: + """ + Initializes this module with the given config, + it does basic validation whether the specified + exchange and pairs are valid. + :return: None + """ + self._conf.update(config) -def init(config: dict) -> None: - """ - Initializes this module with the given config, - it does basic validation whether the specified - exchange and pairs are valid. - :param config: config to use - :return: None - """ - global _CONF, _API + if config['dry_run']: + logger.info('Instance is running with dry_run enabled') - _CONF.update(config) + exchange_config = config['exchange'] + self._api = self._init_ccxt(exchange_config) - if config['dry_run']: - logger.info('Instance is running with dry_run enabled') + logger.info('Using Exchange "%s"', self.name) - exchange_config = config['exchange'] - _API = init_ccxt(exchange_config) + # Check if all pairs are available + self.validate_pairs(config['exchange']['pair_whitelist']) - logger.info('Using Exchange "%s"', get_name()) + def _init_ccxt(self, exchange_config: dict) -> ccxt.Exchange: + """ + Initialize ccxt with given config and return valid + ccxt instance. + """ + # Find matching class for the given exchange name + name = exchange_config['name'] - # Check if all pairs are available - validate_pairs(config['exchange']['pair_whitelist']) - - -def validate_pairs(pairs: List[str]) -> None: - """ - Checks if all given pairs are tradable on the current exchange. - Raises OperationalException if one pair is not available. - :param pairs: list of pairs - :return: None - """ - - try: - markets = _API.load_markets() - except ccxt.BaseError as e: - logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e) - return - - stake_cur = _CONF['stake_currency'] - for pair in pairs: - # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs - # TODO: add a support for having coins in BTC/USDT format - if not pair.endswith(stake_cur): - raise OperationalException( - f'Pair {pair} not compatible with stake_currency: {stake_cur}') - if pair not in markets: - raise OperationalException( - f'Pair {pair} is not available at {get_name()}') - - -def exchange_has(endpoint: str) -> bool: - """ - Checks if exchange implements a specific API endpoint. - Wrapper around ccxt 'has' attribute - :param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers') - :return: bool - """ - return endpoint in _API.has and _API.has[endpoint] - - -def buy(pair: str, rate: float, amount: float) -> Dict: - if _CONF['dry_run']: - global _DRY_RUN_OPEN_ORDERS - order_id = f'dry_run_buy_{randint(0, 10**6)}' - _DRY_RUN_OPEN_ORDERS[order_id] = { - 'pair': pair, - 'price': rate, - 'amount': amount, - 'type': 'limit', - 'side': 'buy', - 'remaining': 0.0, - 'datetime': arrow.utcnow().isoformat(), - 'status': 'closed', - 'fee': None - } - return {'id': order_id} - - try: - return _API.create_limit_buy_order(pair, amount, rate) - except ccxt.InsufficientFunds as e: - raise DependencyException( - f'Insufficient funds to create limit buy order on market {pair}.' - f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' - f'Message: {e}') - except ccxt.InvalidOrder as e: - raise DependencyException( - f'Could not create limit buy order on market {pair}.' - f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' - f'Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place buy order due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -def sell(pair: str, rate: float, amount: float) -> Dict: - if _CONF['dry_run']: - global _DRY_RUN_OPEN_ORDERS - order_id = f'dry_run_sell_{randint(0, 10**6)}' - _DRY_RUN_OPEN_ORDERS[order_id] = { - 'pair': pair, - 'price': rate, - 'amount': amount, - 'type': 'limit', - 'side': 'sell', - 'remaining': 0.0, - 'datetime': arrow.utcnow().isoformat(), - 'status': 'closed' - } - return {'id': order_id} - - try: - return _API.create_limit_sell_order(pair, amount, rate) - except ccxt.InsufficientFunds as e: - raise DependencyException( - f'Insufficient funds to create limit sell order on market {pair}.' - f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).' - f'Message: {e}') - except ccxt.InvalidOrder as e: - raise DependencyException( - f'Could not create limit sell order on market {pair}.' - f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).' - f'Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -@retrier -def get_balance(currency: str) -> float: - if _CONF['dry_run']: - return 999.9 - - # ccxt exception is already handled by get_balances - balances = get_balances() - balance = balances.get(currency) - if balance is None: - raise TemporaryError( - f'Could not get {currency} balance due to malformed exchange response: {balances}') - return balance['free'] - - -@retrier -def get_balances() -> dict: - if _CONF['dry_run']: - return {} - - try: - balances = _API.fetch_balance() - # Remove additional info from ccxt results - balances.pop("info", None) - balances.pop("free", None) - balances.pop("total", None) - balances.pop("used", None) - - return balances - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not get balance due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -@retrier -def get_tickers() -> Dict: - try: - return _API.fetch_tickers() - except ccxt.NotSupported as e: - raise OperationalException( - f'Exchange {_API.name} does not support fetching tickers in batch.' - f'Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not load tickers due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -@retrier -def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict: - global _CACHED_TICKER - if refresh or pair not in _CACHED_TICKER.keys(): + if name not in ccxt.exchanges: + raise OperationalException(f'Exchange {name} is not supported') try: - data = _API.fetch_ticker(pair) + api = getattr(ccxt, name.lower())({ + 'apiKey': exchange_config.get('key'), + 'secret': exchange_config.get('secret'), + 'password': exchange_config.get('password'), + 'uid': exchange_config.get('uid', ''), + 'enableRateLimit': True, + }) + except (KeyError, AttributeError): + raise OperationalException(f'Exchange {name} is not supported') + + return api + + @property + def name(self) -> str: + """exchange Name (from ccxt)""" + return self._api.name + + @property + def id(self) -> str: + """exchange ccxt id""" + return self._api.id + + def validate_pairs(self, pairs: List[str]) -> None: + """ + Checks if all given pairs are tradable on the current exchange. + Raises OperationalException if one pair is not available. + :param pairs: list of pairs + :return: None + """ + + try: + markets = self._api.load_markets() + except ccxt.BaseError as e: + logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e) + return + + stake_cur = self._conf['stake_currency'] + for pair in pairs: + # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs + # TODO: add a support for having coins in BTC/USDT format + if not pair.endswith(stake_cur): + raise OperationalException( + f'Pair {pair} not compatible with stake_currency: {stake_cur}') + if pair not in markets: + raise OperationalException( + f'Pair {pair} is not available at {self.name}') + + def exchange_has(self, endpoint: str) -> bool: + """ + Checks if exchange implements a specific API endpoint. + Wrapper around ccxt 'has' attribute + :param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers') + :return: bool + """ + return endpoint in self._api.has and self._api.has[endpoint] + + def buy(self, pair: str, rate: float, amount: float) -> Dict: + if self._conf['dry_run']: + order_id = f'dry_run_buy_{randint(0, 10**6)}' + self._dry_run_open_orders[order_id] = { + 'pair': pair, + 'price': rate, + 'amount': amount, + 'type': 'limit', + 'side': 'buy', + 'remaining': 0.0, + 'datetime': arrow.utcnow().isoformat(), + 'status': 'closed', + 'fee': None + } + return {'id': order_id} + + try: + return self._api.create_limit_buy_order(pair, amount, rate) + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to create limit buy order on market {pair}.' + f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not create limit buy order on market {pair}.' + f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place buy order due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + + def sell(self, pair: str, rate: float, amount: float) -> Dict: + if self._conf['dry_run']: + order_id = f'dry_run_sell_{randint(0, 10**6)}' + self._dry_run_open_orders[order_id] = { + 'pair': pair, + 'price': rate, + 'amount': amount, + 'type': 'limit', + 'side': 'sell', + 'remaining': 0.0, + 'datetime': arrow.utcnow().isoformat(), + 'status': 'closed' + } + return {'id': order_id} + + try: + return self._api.create_limit_sell_order(pair, amount, rate) + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to create limit sell order on market {pair}.' + f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not create limit sell order on market {pair}.' + f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + + @retrier + def get_balance(self, currency: str) -> float: + if self._conf['dry_run']: + return 999.9 + + # ccxt exception is already handled by get_balances + balances = self.get_balances() + balance = balances.get(currency) + if balance is None: + raise TemporaryError( + f'Could not get {currency} balance due to malformed exchange response: {balances}') + return balance['free'] + + @retrier + def get_balances(self) -> dict: + if self._conf['dry_run']: + return {} + + try: + balances = self._api.fetch_balance() + # Remove additional info from ccxt results + balances.pop("info", None) + balances.pop("free", None) + balances.pop("total", None) + balances.pop("used", None) + + return balances + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get balance due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + + @retrier + def get_tickers(self) -> Dict: + try: + return self._api.fetch_tickers() + except ccxt.NotSupported as e: + raise OperationalException( + f'Exchange {self._api.name} does not support fetching tickers in batch.' + f'Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not load tickers due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + + @retrier + def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: + if refresh or pair not in self._cached_ticker.keys(): try: - _CACHED_TICKER[pair] = { - 'bid': float(data['bid']), - 'ask': float(data['ask']), - } - except KeyError: - logger.debug("Could not cache ticker data for %s", pair) + data = self._api.fetch_ticker(pair) + try: + self._cached_ticker[pair] = { + 'bid': float(data['bid']), + 'ask': float(data['ask']), + } + except KeyError: + logger.debug("Could not cache ticker data for %s", pair) + return data + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + else: + logger.info("returning cached ticker-data for %s", pair) + return self._cached_ticker[pair] + + @retrier + def get_ticker_history(self, pair: str, tick_interval: str, + since_ms: Optional[int] = None) -> List[Dict]: + try: + # last item should be in the time interval [now - tick_interval, now] + till_time_ms = arrow.utcnow().shift( + minutes=-constants.TICKER_INTERVAL_MINUTES[tick_interval] + ).timestamp * 1000 + # it looks as if some exchanges return cached data + # and they update it one in several minute, so 10 mins interval + # is necessary to skeep downloading of an empty array when all + # chached data was already downloaded + till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000) + + data: List[Dict[Any, Any]] = [] + while not since_ms or since_ms < till_time_ms: + data_part = self._api.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms) + + # Because some exchange sort Tickers ASC and other DESC. + # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) + # when GDAX returns a list of tickers DESC (newest first, oldest last) + data_part = sorted(data_part, key=lambda x: x[0]) + + if not data_part: + break + + logger.debug('Downloaded data for %s time range [%s, %s]', + pair, + arrow.get(data_part[0][0] / 1000).format(), + arrow.get(data_part[-1][0] / 1000).format()) + + data.extend(data_part) + since_ms = data[-1][0] + 1 + return data + except ccxt.NotSupported as e: + raise OperationalException( + f'Exchange {self._api.name} does not support fetching historical candlestick data.' + f'Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(f'Could not fetch ticker data. Msg: {e}') + + @retrier + def cancel_order(self, order_id: str, pair: str) -> None: + if self._conf['dry_run']: + return + + try: + return self._api.cancel_order(order_id, pair) + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not cancel order. Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') except ccxt.BaseError as e: raise OperationalException(e) - else: - logger.info("returning cached ticker-data for %s", pair) - return _CACHED_TICKER[pair] + @retrier + def get_order(self, order_id: str, pair: str) -> Dict: + if self._conf['dry_run']: + order = self._dry_run_open_orders[order_id] + order.update({ + 'id': order_id + }) + return order + try: + return self._api.fetch_order(order_id, pair) + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not get order. Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get order due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) -@retrier -def get_ticker_history(pair: str, tick_interval: str, since_ms: Optional[int] = None) -> List[Dict]: - try: - # last item should be in the time interval [now - tick_interval, now] - till_time_ms = arrow.utcnow().shift( - minutes=-constants.TICKER_INTERVAL_MINUTES[tick_interval] - ).timestamp * 1000 - # it looks as if some exchanges return cached data - # and they update it one in several minute, so 10 mins interval - # is necessary to skeep downloading of an empty array when all - # chached data was already downloaded - till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000) + @retrier + def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: + if self._conf['dry_run']: + return [] + if not self.exchange_has('fetchMyTrades'): + return [] + try: + my_trades = self._api.fetch_my_trades(pair, since.timestamp()) + matched_trades = [trade for trade in my_trades if trade['order'] == order_id] - data: List[Dict[Any, Any]] = [] - while not since_ms or since_ms < till_time_ms: - data_part = _API.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms) + return matched_trades - # Because some exchange sort Tickers ASC and other DESC. - # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) - # when GDAX returns a list of tickers DESC (newest first, oldest last) - data_part = sorted(data_part, key=lambda x: x[0]) + except ccxt.NetworkError as e: + raise TemporaryError( + f'Could not get trades due to networking error. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) - if not data_part: - break + def get_pair_detail_url(self, pair: str) -> str: + try: + url_base = self._api.urls.get('www') + base, quote = pair.split('/') - logger.debug('Downloaded data for %s time range [%s, %s]', - pair, - arrow.get(data_part[0][0] / 1000).format(), - arrow.get(data_part[-1][0] / 1000).format()) + return url_base + _EXCHANGE_URLS[self._api.id].format(base=base, quote=quote) + except KeyError: + logger.warning('Could not get exchange url for %s', self.name) + return "" - data.extend(data_part) - since_ms = data[-1][0] + 1 + @retrier + def get_markets(self) -> List[dict]: + try: + return self._api.fetch_markets() + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not load markets due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) - return data - except ccxt.NotSupported as e: - raise OperationalException( - f'Exchange {_API.name} does not support fetching historical candlestick data.' - f'Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(f'Could not fetch ticker data. Msg: {e}') + @retrier + def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1, + price=1, taker_or_maker='maker') -> float: + try: + # validate that markets are loaded before trying to get fee + if self._api.markets is None or len(self._api.markets) == 0: + self._api.load_markets() + return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount, + price=price, takerOrMaker=taker_or_maker)['rate'] + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get fee info due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) -@retrier -def cancel_order(order_id: str, pair: str) -> None: - if _CONF['dry_run']: - return - - try: - return _API.cancel_order(order_id, pair) - except ccxt.InvalidOrder as e: - raise DependencyException( - f'Could not cancel order. Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -@retrier -def get_order(order_id: str, pair: str) -> Dict: - if _CONF['dry_run']: - order = _DRY_RUN_OPEN_ORDERS[order_id] - order.update({ - 'id': order_id - }) - return order - try: - return _API.fetch_order(order_id, pair) - except ccxt.InvalidOrder as e: - raise DependencyException( - f'Could not get order. Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not get order due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -@retrier -def get_trades_for_order(order_id: str, pair: str, since: datetime) -> List: - if _CONF['dry_run']: - return [] - if not exchange_has('fetchMyTrades'): - return [] - try: - my_trades = _API.fetch_my_trades(pair, since.timestamp()) - matched_trades = [trade for trade in my_trades if trade['order'] == order_id] - - return matched_trades - - except ccxt.NetworkError as e: - raise TemporaryError( - f'Could not get trades due to networking error. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -def get_pair_detail_url(pair: str) -> str: - try: - url_base = _API.urls.get('www') - base, quote = pair.split('/') - - return url_base + _EXCHANGE_URLS[_API.id].format(base=base, quote=quote) - except KeyError: - logger.warning('Could not get exchange url for %s', get_name()) - return "" - - -@retrier -def get_markets() -> List[dict]: - try: - return _API.fetch_markets() - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not load markets due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -def get_name() -> str: - return _API.name - - -def get_id() -> str: - return _API.id - - -@retrier -def get_fee(symbol='ETH/BTC', type='', side='', amount=1, - price=1, taker_or_maker='maker') -> float: - try: + def get_amount_lots(self, pair: str, amount: float) -> float: + """ + get buyable amount rounding, .. + """ # validate that markets are loaded before trying to get fee - if _API.markets is None or len(_API.markets) == 0: - _API.load_markets() - - return _API.calculate_fee(symbol=symbol, type=type, side=side, amount=amount, - price=price, takerOrMaker=taker_or_maker)['rate'] - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not get fee info due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) - - -def get_amount_lots(pair: str, amount: float) -> float: - """ - get buyable amount rounding, .. - """ - # validate that markets are loaded before trying to get fee - if not _API.markets: - _API.load_markets() - return _API.amount_to_lots(pair, amount) + if not self._api.markets: + self._api.load_markets() + return self._api.amount_to_lots(pair, amount) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 157de862f..02375ca80 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -14,11 +14,11 @@ import requests from cachetools import TTLCache, cached from freqtrade import ( - DependencyException, OperationalException, TemporaryError, - exchange, persistence, __version__, + DependencyException, OperationalException, TemporaryError, persistence, __version__, ) from freqtrade import constants from freqtrade.analyze import Analyze +from freqtrade.exchange import Exchange from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.persistence import Trade from freqtrade.rpc.rpc_manager import RPCManager @@ -54,7 +54,7 @@ class FreqtradeBot(object): self.fiat_converter = CryptoToFiatConverter() self.rpc: RPCManager = RPCManager(self) self.persistence = None - self.exchange = None + self.exchange = Exchange(self.config) self._init_modules() @@ -66,7 +66,6 @@ class FreqtradeBot(object): # Initialize all modules persistence.init(self.config) - exchange.init(self.config) # Set initial application state initial_state = self.config.get('initial_state') @@ -186,13 +185,13 @@ class FreqtradeBot(object): :return: List of pairs """ - if not exchange.exchange_has('fetchTickers'): + if not self.exchange.exchange_has('fetchTickers'): raise OperationalException( 'Exchange does not support dynamic whitelist.' 'Please edit your config and restart the bot' ) - tickers = exchange.get_tickers() + tickers = self.exchange.get_tickers() # check length so that we make sure that '/' is actually in the string tickers = [v for k, v in tickers.items() if len(k.split('/')) == 2 and k.split('/')[1] == base_currency] @@ -210,7 +209,7 @@ class FreqtradeBot(object): black_listed """ sanitized_whitelist = whitelist - markets = exchange.get_markets() + markets = self.exchange.get_markets() markets = [m for m in markets if m['quote'] == self.config['stake_currency']] known_pairs = set() @@ -255,7 +254,7 @@ class FreqtradeBot(object): interval = self.analyze.get_ticker_interval() stake_currency = self.config['stake_currency'] fiat_currency = self.config['fiat_display_currency'] - exc_name = exchange.get_name() + exc_name = self.exchange.name logger.info( 'Checking buy signals to create a new trade with stake_amount: %f ...', @@ -263,7 +262,7 @@ class FreqtradeBot(object): ) whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist']) # Check if stake_amount is fulfilled - if exchange.get_balance(stake_currency) < stake_amount: + if self.exchange.get_balance(stake_currency) < stake_amount: raise DependencyException( f'stake amount is not fulfilled (currency={stake_currency})') @@ -278,19 +277,19 @@ class FreqtradeBot(object): # Pick pair based on buy signals for _pair in whitelist: - (buy, sell) = self.analyze.get_signal(_pair, interval) + (buy, sell) = self.analyze.get_signal(self.exchange, _pair, interval) if buy and not sell: pair = _pair break else: return False pair_s = pair.replace('_', '/') - pair_url = exchange.get_pair_detail_url(pair) + pair_url = self.exchange.get_pair_detail_url(pair) # Calculate amount - buy_limit = self.get_target_bid(exchange.get_ticker(pair)) + buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) amount = stake_amount / buy_limit - order_id = exchange.buy(pair, buy_limit, amount)['id'] + order_id = self.exchange.buy(pair, buy_limit, amount)['id'] stake_amount_fiat = self.fiat_converter.convert_amount( stake_amount, @@ -305,7 +304,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ {stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`""" ) # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL - fee = exchange.get_fee(symbol=pair, taker_or_maker='maker') + fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') trade = Trade( pair=pair, stake_amount=stake_amount, @@ -315,7 +314,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ open_rate=buy_limit, open_rate_requested=buy_limit, open_date=datetime.utcnow(), - exchange=exchange.get_id(), + exchange=self.exchange.id, open_order_id=order_id ) Trade.session.add(trade) @@ -348,7 +347,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ if trade.open_order_id: # Update trade with order values logger.info('Found open order for %s', trade) - order = exchange.get_order(trade.open_order_id, trade.pair) + order = self.exchange.get_order(trade.open_order_id, trade.pair) # Try update amount (binance-fix) try: new_amount = self.get_real_amount(trade, order) @@ -372,7 +371,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ def get_real_amount(self, trade: Trade, order: Dict) -> float: """ Get real amount for the trade - Necessary for exchanges which charge fees in base currency (e.g. binance) + Necessary for self.exchanges which charge fees in base currency (e.g. binance) """ order_amount = order['amount'] # Only run for closed orders @@ -388,7 +387,8 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ return new_amount # Fallback to Trades - trades = exchange.get_trades_for_order(trade.open_order_id, trade.pair, trade.open_date) + trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair, + trade.open_date) if len(trades) == 0: logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) @@ -420,12 +420,13 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ raise ValueError(f'attempt to handle closed trade: {trade}') logger.debug('Handling %s ...', trade) - current_rate = exchange.get_ticker(trade.pair)['bid'] + current_rate = self.exchange.get_ticker(trade.pair)['bid'] (buy, sell) = (False, False) if self.config.get('experimental', {}).get('use_sell_signal'): - (buy, sell) = self.analyze.get_signal(trade.pair, self.analyze.get_ticker_interval()) + (buy, sell) = self.analyze.get_signal(self.exchange, + trade.pair, self.analyze.get_ticker_interval()) if self.analyze.should_sell(trade, current_rate, datetime.utcnow(), buy, sell): self.execute_sell(trade, current_rate) @@ -449,7 +450,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ # updated via /forcesell in a different thread. if not trade.open_order_id: continue - order = exchange.get_order(trade.open_order_id, trade.pair) + order = self.exchange.get_order(trade.open_order_id, trade.pair) except requests.exceptions.RequestException: logger.info( 'Cannot query order for %s due to %s', @@ -475,7 +476,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ :return: True if order was fully cancelled """ pair_s = trade.pair.replace('_', '/') - exchange.cancel_order(trade.open_order_id, trade.pair) + self.exchange.cancel_order(trade.open_order_id, trade.pair) if order['remaining'] == order['amount']: # if trade is not partially completed, just delete the trade Trade.session.delete(trade) @@ -502,7 +503,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ pair_s = trade.pair.replace('_', '/') if order['remaining'] == order['amount']: # if trade is not partially completed, just cancel the trade - exchange.cancel_order(trade.open_order_id, trade.pair) + self.exchange.cancel_order(trade.open_order_id, trade.pair) trade.close_rate = None trade.close_profit = None trade.close_date = None @@ -525,15 +526,15 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ exc = trade.exchange pair = trade.pair # Execute sell and update trade record - order_id = exchange.sell(str(trade.pair), limit, trade.amount)['id'] + order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id'] trade.open_order_id = order_id trade.close_rate_requested = limit fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) profit_trade = trade.calc_profit(rate=limit) - current_rate = exchange.get_ticker(trade.pair)['bid'] + current_rate = self.exchange.get_ticker(trade.pair)['bid'] profit = trade.calc_profit_percent(limit) - pair_url = exchange.get_pair_detail_url(trade.pair) + pair_url = self.exchange.get_pair_detail_url(trade.pair) gain = "profit" if fmt_exp_profit > 0 else "loss" message = f"*{exc}:* Selling\n" \ diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 867e8c7dc..1e76808e7 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -7,8 +7,8 @@ import os from typing import Optional, List, Dict, Tuple, Any import arrow -from freqtrade import misc, constants -from freqtrade.exchange import get_ticker_history +from freqtrade import misc, constants, OperationalException +from freqtrade.exchange import Exchange from freqtrade.arguments import TimeRange logger = logging.getLogger(__name__) @@ -83,6 +83,7 @@ def load_data(datadir: str, ticker_interval: str, pairs: List[str], refresh_pairs: Optional[bool] = False, + exchange: Optional[Exchange] = None, timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]: """ Loads ticker history data for the given parameters @@ -93,7 +94,10 @@ def load_data(datadir: str, # If the user force the refresh of pairs if refresh_pairs: logger.info('Download data for all pairs and store them in %s', datadir) - download_pairs(datadir, pairs, ticker_interval, timerange=timerange) + if not exchange: + raise OperationalException("Exchange needs to be initialized when " + "calling load_data with refresh_pairs=True") + download_pairs(datadir, exchange, pairs, ticker_interval, timerange=timerange) for pair in pairs: pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) @@ -119,13 +123,14 @@ def make_testdata_path(datadir: str) -> str: ) -def download_pairs(datadir, pairs: List[str], +def download_pairs(datadir, exchange: Exchange, pairs: List[str], ticker_interval: str, timerange: TimeRange = TimeRange(None, None, 0, 0)) -> bool: """For each pairs passed in parameters, download the ticker intervals""" for pair in pairs: try: download_backtesting_testdata(datadir, + exchange=exchange, pair=pair, tick_interval=ticker_interval, timerange=timerange) @@ -183,6 +188,7 @@ def load_cached_data_for_updating(filename: str, def download_backtesting_testdata(datadir: str, + exchange: Exchange, pair: str, tick_interval: str = '5m', timerange: Optional[TimeRange] = None) -> None: @@ -216,7 +222,8 @@ def download_backtesting_testdata(datadir: str, logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None') logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None') - new_data = get_ticker_history(pair=pair, tick_interval=tick_interval, since_ms=since_ms) + new_data = exchange.get_ticker_history(pair=pair, tick_interval=tick_interval, + since_ms=since_ms) data.extend(new_data) logger.debug("New Start: %s", misc.format_ms_time(data[0][0])) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 23bd7741a..9a19d1412 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -14,7 +14,7 @@ from pandas import DataFrame from tabulate import tabulate import freqtrade.optimize as optimize -from freqtrade import exchange +from freqtrade.exchange import Exchange from freqtrade.analyze import Analyze from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration @@ -61,7 +61,7 @@ class Backtesting(object): self.config['exchange']['password'] = '' self.config['exchange']['uid'] = '' self.config['dry_run'] = True - exchange.init(self.config) + self.exchange = Exchange(self.config) @staticmethod def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: @@ -130,7 +130,7 @@ class Backtesting(object): stake_amount = args['stake_amount'] max_open_trades = args.get('max_open_trades', 0) - fee = exchange.get_fee() + fee = self.exchange.get_fee() trade = Trade( open_rate=buy_row.close, open_date=buy_row.date, @@ -256,7 +256,7 @@ class Backtesting(object): if self.config.get('live'): logger.info('Downloading data for all pairs in whitelist ...') for pair in pairs: - data[pair] = exchange.get_ticker_history(pair, self.ticker_interval) + data[pair] = self.exchange.get_ticker_history(pair, self.ticker_interval) else: logger.info('Using local backtesting data (using whitelist in given config) ...') @@ -267,6 +267,7 @@ class Backtesting(object): pairs=pairs, ticker_interval=self.ticker_interval, refresh_pairs=self.config.get('refresh_pairs', False), + exchange=self.exchange, timerange=timerange ) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 34802f920..ee6ecb770 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -12,7 +12,6 @@ import sqlalchemy as sql from numpy import mean, nan_to_num from pandas import DataFrame -from freqtrade import exchange from freqtrade.misc import shorten_date from freqtrade.persistence import Trade from freqtrade.state import State @@ -71,9 +70,9 @@ class RPC(object): for trade in trades: order = None if trade.open_order_id: - order = exchange.get_order(trade.open_order_id, trade.pair) + order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair) # calculate profit and send message to user - current_rate = exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = '{:.2f}%'.format( round(trade.close_profit * 100, 2) @@ -91,7 +90,7 @@ class RPC(object): .format( trade_id=trade.id, pair=trade.pair, - market_url=exchange.get_pair_detail_url(trade.pair), + market_url=self._freqtrade.exchange.get_pair_detail_url(trade.pair), date=arrow.get(trade.open_date).humanize(), open_rate=trade.open_rate, close_rate=trade.close_rate, @@ -116,7 +115,7 @@ class RPC(object): trades_list = [] for trade in trades: # calculate profit and send message to user - current_rate = exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] trades_list.append([ trade.id, trade.pair, @@ -201,7 +200,7 @@ class RPC(object): profit_closed_percent.append(profit_percent) else: # Get current rate - current_rate = exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] profit_percent = trade.calc_profit_percent(rate=current_rate) profit_all_coin.append( @@ -258,7 +257,7 @@ class RPC(object): """ Returns current account balance per crypto """ output = [] total = 0.0 - for coin, balance in exchange.get_balances().items(): + for coin, balance in self._freqtrade.exchange.get_balances().items(): if not balance['total']: continue @@ -266,9 +265,9 @@ class RPC(object): rate = 1.0 else: if coin == 'USDT': - rate = 1.0 / exchange.get_ticker('BTC/USDT', False)['bid'] + rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid'] else: - rate = exchange.get_ticker(coin + '/BTC', False)['bid'] + rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid'] est_btc: float = rate * balance['total'] total = total + est_btc output.append( @@ -318,13 +317,13 @@ class RPC(object): def _exec_forcesell(trade: Trade) -> None: # Check if there is there is an open order if trade.open_order_id: - order = exchange.get_order(trade.open_order_id, trade.pair) + order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair) # Cancel open LIMIT_BUY orders and close trade if order and order['status'] == 'open' \ and order['type'] == 'limit' \ and order['side'] == 'buy': - exchange.cancel_order(trade.open_order_id, trade.pair) + self._freqtrade.exchange.cancel_order(trade.open_order_id, trade.pair) trade.close(order.get('price') or trade.open_rate) # Do the best effort, if we don't know 'filled' amount, don't try selling if order['filled'] is None: @@ -338,7 +337,7 @@ class RPC(object): return # Get current rate and execute sell - current_rate = exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] self._freqtrade.execute_sell(trade, current_rate) # ---- EOF def _exec_forcesell ---- diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 1311687b7..ce22cd193 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -13,6 +13,7 @@ from telegram import Chat, Message, Update from freqtrade.analyze import Analyze from freqtrade import constants +from freqtrade.exchange import Exchange from freqtrade.freqtradebot import FreqtradeBot logging.getLogger('').setLevel(logging.INFO) @@ -26,6 +27,20 @@ def log_has(line, logs): False) +def patch_exchange(mocker, api_mock=None) -> None: + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + if api_mock: + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + else: + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock()) + + +def get_patched_exchange(mocker, config, api_mock=None) -> Exchange: + patch_exchange(mocker, api_mock) + exchange = Exchange(config) + return exchange + + # Functions for recurrent object patching def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: """ @@ -39,7 +54,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) + patch_exchange(mocker, None) mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock()) mocker.patch('freqtrade.freqtradebot.Analyze.get_signal', MagicMock()) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 97a723929..3c68e8493 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -3,33 +3,20 @@ import logging from copy import deepcopy from random import randint +from datetime import datetime from unittest.mock import MagicMock, PropertyMock import ccxt import pytest -import freqtrade.exchange as exchange from freqtrade import OperationalException, DependencyException, TemporaryError -from freqtrade.exchange import (init, validate_pairs, buy, sell, get_balance, get_balances, - get_ticker, get_ticker_history, cancel_order, get_name, get_fee, - get_id, get_pair_detail_url, get_amount_lots) -from freqtrade.tests.conftest import log_has - -API_INIT = False - - -def maybe_init_api(conf, mocker, force=False): - global API_INIT - if force or not API_INIT: - mocker.patch('freqtrade.exchange.validate_pairs', - side_effect=lambda s: True) - init(config=conf) - API_INIT = True +from freqtrade.exchange import Exchange, API_RETRY_COUNT +from freqtrade.tests.conftest import log_has, get_patched_exchange def test_init(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - maybe_init_api(default_conf, mocker, True) + get_patched_exchange(mocker, default_conf) assert log_has('Instance is running with dry_run enabled', caplog.record_tuples) @@ -39,7 +26,7 @@ def test_init_exception(default_conf): with pytest.raises( OperationalException, match='Exchange {} is not supported'.format(default_conf['exchange']['name'])): - init(config=default_conf) + Exchange(default_conf) def test_validate_pairs(default_conf, mocker): @@ -50,18 +37,17 @@ def test_validate_pairs(default_conf, mocker): id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock - mocker.patch('freqtrade.exchange._API', api_mock) - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) - validate_pairs(default_conf['exchange']['pair_whitelist']) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + Exchange(default_conf) def test_validate_pairs_not_available(default_conf, mocker): api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value={}) - mocker.patch('freqtrade.exchange._API', api_mock) - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + with pytest.raises(OperationalException, match=r'not available'): - validate_pairs(default_conf['exchange']['pair_whitelist']) + Exchange(default_conf) def test_validate_pairs_not_compatible(default_conf, mocker): @@ -71,25 +57,27 @@ def test_validate_pairs_not_compatible(default_conf, mocker): }) conf = deepcopy(default_conf) conf['stake_currency'] = 'ETH' - mocker.patch('freqtrade.exchange._API', api_mock) - mocker.patch.dict('freqtrade.exchange._CONF', conf) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + with pytest.raises(OperationalException, match=r'not compatible'): - validate_pairs(conf['exchange']['pair_whitelist']) + Exchange(conf) def test_validate_pairs_exception(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() - api_mock.name = 'Binance' - mocker.patch('freqtrade.exchange._API', api_mock) - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) + mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance')) api_mock.load_markets = MagicMock(return_value={}) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) + with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'): - validate_pairs(default_conf['exchange']['pair_whitelist']) + Exchange(default_conf) api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError()) - validate_pairs(default_conf['exchange']['pair_whitelist']) + + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + Exchange(default_conf) assert log_has('Unable to validate pairs (assuming they are correct). Reason: ', caplog.record_tuples) @@ -99,22 +87,21 @@ def test_validate_pairs_stake_exception(default_conf, mocker, caplog): conf = deepcopy(default_conf) conf['stake_currency'] = 'ETH' api_mock = MagicMock() - api_mock.name = 'binance' - mocker.patch('freqtrade.exchange._API', api_mock) - mocker.patch.dict('freqtrade.exchange._CONF', conf) + api_mock.name = MagicMock(return_value='binance') + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) with pytest.raises( OperationalException, match=r'Pair ETH/BTC not compatible with stake_currency: ETH' ): - validate_pairs(default_conf['exchange']['pair_whitelist']) + Exchange(conf) def test_buy_dry_run(default_conf, mocker): default_conf['dry_run'] = True - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) + exchange = get_patched_exchange(mocker, default_conf) - order = buy(pair='ETH/BTC', rate=200, amount=1) + order = exchange.buy(pair='ETH/BTC', rate=200, amount=1) assert 'id' in order assert 'dry_run_buy_' in order['id'] @@ -128,12 +115,10 @@ def test_buy_prod(default_conf, mocker): 'foo': 'bar' } }) - mocker.patch('freqtrade.exchange._API', api_mock) - default_conf['dry_run'] = False - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) + exchange = get_patched_exchange(mocker, default_conf, api_mock) - order = buy(pair='ETH/BTC', rate=200, amount=1) + order = exchange.buy(pair='ETH/BTC', rate=200, amount=1) assert 'id' in order assert 'info' in order assert order['id'] == order_id @@ -141,30 +126,30 @@ def test_buy_prod(default_conf, mocker): # test exception handling with pytest.raises(DependencyException): api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds) - mocker.patch('freqtrade.exchange._API', api_mock) - buy(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.buy(pair='ETH/BTC', rate=200, amount=1) with pytest.raises(DependencyException): api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder) - mocker.patch('freqtrade.exchange._API', api_mock) - buy(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.buy(pair='ETH/BTC', rate=200, amount=1) with pytest.raises(TemporaryError): api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError) - mocker.patch('freqtrade.exchange._API', api_mock) - buy(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.buy(pair='ETH/BTC', rate=200, amount=1) with pytest.raises(OperationalException): api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) - buy(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.buy(pair='ETH/BTC', rate=200, amount=1) def test_sell_dry_run(default_conf, mocker): default_conf['dry_run'] = True - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) + exchange = get_patched_exchange(mocker, default_conf) - order = sell(pair='ETH/BTC', rate=200, amount=1) + order = exchange.sell(pair='ETH/BTC', rate=200, amount=1) assert 'id' in order assert 'dry_run_sell_' in order['id'] @@ -178,12 +163,11 @@ def test_sell_prod(default_conf, mocker): 'foo': 'bar' } }) - mocker.patch('freqtrade.exchange._API', api_mock) - default_conf['dry_run'] = False - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) - order = sell(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + order = exchange.sell(pair='ETH/BTC', rate=200, amount=1) assert 'id' in order assert 'info' in order assert order['id'] == order_id @@ -191,53 +175,52 @@ def test_sell_prod(default_conf, mocker): # test exception handling with pytest.raises(DependencyException): api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds) - mocker.patch('freqtrade.exchange._API', api_mock) - sell(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.sell(pair='ETH/BTC', rate=200, amount=1) with pytest.raises(DependencyException): api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder) - mocker.patch('freqtrade.exchange._API', api_mock) - sell(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.sell(pair='ETH/BTC', rate=200, amount=1) with pytest.raises(TemporaryError): api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError) - mocker.patch('freqtrade.exchange._API', api_mock) - sell(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.sell(pair='ETH/BTC', rate=200, amount=1) with pytest.raises(OperationalException): api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) - sell(pair='ETH/BTC', rate=200, amount=1) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.sell(pair='ETH/BTC', rate=200, amount=1) def test_get_balance_dry_run(default_conf, mocker): default_conf['dry_run'] = True - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) - assert get_balance(currency='BTC') == 999.9 + exchange = get_patched_exchange(mocker, default_conf) + assert exchange.get_balance(currency='BTC') == 999.9 def test_get_balance_prod(default_conf, mocker): api_mock = MagicMock() api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}}) - mocker.patch('freqtrade.exchange._API', api_mock) - default_conf['dry_run'] = False - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) - assert get_balance(currency='BTC') == 123.4 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + assert exchange.get_balance(currency='BTC') == 123.4 with pytest.raises(OperationalException): api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) - get_balance(currency='BTC') + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + exchange.get_balance(currency='BTC') def test_get_balances_dry_run(default_conf, mocker): default_conf['dry_run'] = True - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) - - assert get_balances() == {} + exchange = get_patched_exchange(mocker, default_conf) + assert exchange.get_balances() == {} def test_get_balances_prod(default_conf, mocker): @@ -253,33 +236,73 @@ def test_get_balances_prod(default_conf, mocker): '2ST': balance_item, '3ST': balance_item }) - mocker.patch('freqtrade.exchange._API', api_mock) - default_conf['dry_run'] = False - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) - - assert len(get_balances()) == 3 - assert get_balances()['1ST']['free'] == 10.0 - assert get_balances()['1ST']['total'] == 10.0 - assert get_balances()['1ST']['used'] == 0.0 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + assert len(exchange.get_balances()) == 3 + assert exchange.get_balances()['1ST']['free'] == 10.0 + assert exchange.get_balances()['1ST']['total'] == 10.0 + assert exchange.get_balances()['1ST']['used'] == 0.0 with pytest.raises(TemporaryError): api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError) - mocker.patch('freqtrade.exchange._API', api_mock) - get_balances() - assert api_mock.fetch_balance.call_count == exchange.API_RETRY_COUNT + 1 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_balances() + assert api_mock.fetch_balance.call_count == API_RETRY_COUNT + 1 with pytest.raises(OperationalException): api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) - get_balances() + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_balances() assert api_mock.fetch_balance.call_count == 1 -# This test is somewhat redundant with -# test_exchange_bittrex.py::test_exchange_bittrex_get_ticker +def test_get_tickers(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, + } + } + api_mock.fetch_tickers = MagicMock(return_value=tick) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + # retrieve original ticker + tickers = exchange.get_tickers() + + assert 'ETH/BTC' in tickers + assert 'BCH/BTC' in tickers + assert tickers['ETH/BTC']['bid'] == 0.5 + assert tickers['ETH/BTC']['ask'] == 1 + assert tickers['BCH/BTC']['bid'] == 0.6 + assert tickers['BCH/BTC']['ask'] == 0.5 + + with pytest.raises(TemporaryError): # test retrier + api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NetworkError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_tickers() + + with pytest.raises(OperationalException): + api_mock.fetch_tickers = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_tickers() + + with pytest.raises(OperationalException): + api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_tickers() + + api_mock.fetch_tickers = MagicMock(return_value={}) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_tickers() + + def test_get_ticker(default_conf, mocker): - maybe_init_api(default_conf, mocker) api_mock = MagicMock() tick = { 'symbol': 'ETH/BTC', @@ -288,10 +311,9 @@ def test_get_ticker(default_conf, mocker): 'last': 0.0001, } api_mock.fetch_ticker = MagicMock(return_value=tick) - mocker.patch('freqtrade.exchange._API', api_mock) - + exchange = get_patched_exchange(mocker, default_conf, api_mock) # retrieve original ticker - ticker = get_ticker(pair='ETH/BTC') + ticker = exchange.get_ticker(pair='ETH/BTC') assert ticker['bid'] == 0.00001098 assert ticker['ask'] == 0.00001099 @@ -304,38 +326,38 @@ def test_get_ticker(default_conf, mocker): 'last': 42, } api_mock.fetch_ticker = MagicMock(return_value=tick) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) # if not caching the result we should get the same ticker # if not fetching a new result we should get the cached ticker - ticker = get_ticker(pair='ETH/BTC') + ticker = exchange.get_ticker(pair='ETH/BTC') assert api_mock.fetch_ticker.call_count == 1 assert ticker['bid'] == 0.5 assert ticker['ask'] == 1 - assert 'ETH/BTC' in exchange._CACHED_TICKER - assert exchange._CACHED_TICKER['ETH/BTC']['bid'] == 0.5 - assert exchange._CACHED_TICKER['ETH/BTC']['ask'] == 1 + assert 'ETH/BTC' in exchange._cached_ticker + assert exchange._cached_ticker['ETH/BTC']['bid'] == 0.5 + assert exchange._cached_ticker['ETH/BTC']['ask'] == 1 # Test caching api_mock.fetch_ticker = MagicMock() - get_ticker(pair='ETH/BTC', refresh=False) + exchange.get_ticker(pair='ETH/BTC', refresh=False) assert api_mock.fetch_ticker.call_count == 0 with pytest.raises(TemporaryError): # test retrier api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError) - mocker.patch('freqtrade.exchange._API', api_mock) - get_ticker(pair='ETH/BTC', refresh=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_ticker(pair='ETH/BTC', refresh=True) with pytest.raises(OperationalException): api_mock.fetch_ticker = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) - get_ticker(pair='ETH/BTC', refresh=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_ticker(pair='ETH/BTC', refresh=True) api_mock.fetch_ticker = MagicMock(return_value={}) - mocker.patch('freqtrade.exchange._API', api_mock) - get_ticker(pair='ETH/BTC', refresh=True) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_ticker(pair='ETH/BTC', refresh=True) def make_fetch_ohlcv_mock(data): @@ -361,10 +383,10 @@ def test_get_ticker_history(default_conf, mocker): ] type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) # retrieve original ticker - ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) + ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval']) assert ticks[0][0] == 1511686200000 assert ticks[0][1] == 1 assert ticks[0][2] == 2 @@ -384,9 +406,9 @@ def test_get_ticker_history(default_conf, mocker): ] ] api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick)) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) - ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) + ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval']) assert ticks[0][0] == 1511686210000 assert ticks[0][1] == 6 assert ticks[0][2] == 7 @@ -396,15 +418,15 @@ def test_get_ticker_history(default_conf, mocker): with pytest.raises(TemporaryError): # test retrier api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) # new symbol to get around cache - get_ticker_history('ABCD/BTC', default_conf['ticker_interval']) + exchange.get_ticker_history('ABCD/BTC', default_conf['ticker_interval']) with pytest.raises(OperationalException): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) # new symbol to get around cache - get_ticker_history('EFGH/BTC', default_conf['ticker_interval']) + exchange.get_ticker_history('EFGH/BTC', default_conf['ticker_interval']) def test_get_ticker_history_sort(default_conf, mocker): @@ -426,10 +448,11 @@ def test_get_ticker_history_sort(default_conf, mocker): ] type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) - mocker.patch('freqtrade.exchange._API', api_mock) + + exchange = get_patched_exchange(mocker, default_conf, api_mock) # Test the ticker history sort - ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) + ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval']) assert ticks[0][0] == 1527830400000 assert ticks[0][1] == 0.07649 assert ticks[0][2] == 0.07651 @@ -460,10 +483,9 @@ def test_get_ticker_history_sort(default_conf, mocker): ] type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) - mocker.patch('freqtrade.exchange._API', api_mock) - + exchange = get_patched_exchange(mocker, default_conf, api_mock) # Test the ticker history sort - ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) + ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval']) assert ticks[0][0] == 1527827700000 assert ticks[0][1] == 0.07659999 assert ticks[0][2] == 0.0766 @@ -481,117 +503,195 @@ def test_get_ticker_history_sort(default_conf, mocker): def test_cancel_order_dry_run(default_conf, mocker): default_conf['dry_run'] = True - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) - - assert cancel_order(order_id='123', pair='TKN/BTC') is None + exchange = get_patched_exchange(mocker, default_conf) + assert exchange.cancel_order(order_id='123', pair='TKN/BTC') is None # Ensure that if not dry_run, we should call API def test_cancel_order(default_conf, mocker): default_conf['dry_run'] = False - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) + # mocker.patch.dict('freqtrade.exchange.._CONF', default_conf) api_mock = MagicMock() api_mock.cancel_order = MagicMock(return_value=123) - mocker.patch('freqtrade.exchange._API', api_mock) - assert cancel_order(order_id='_', pair='TKN/BTC') == 123 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123 with pytest.raises(TemporaryError): api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError) - mocker.patch('freqtrade.exchange._API', api_mock) - cancel_order(order_id='_', pair='TKN/BTC') - assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1 + + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.cancel_order(order_id='_', pair='TKN/BTC') + assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1 with pytest.raises(DependencyException): api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder) - mocker.patch('freqtrade.exchange._API', api_mock) - cancel_order(order_id='_', pair='TKN/BTC') - assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.cancel_order(order_id='_', pair='TKN/BTC') + assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1 with pytest.raises(OperationalException): api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) - cancel_order(order_id='_', pair='TKN/BTC') + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.cancel_order(order_id='_', pair='TKN/BTC') assert api_mock.cancel_order.call_count == 1 def test_get_order(default_conf, mocker): default_conf['dry_run'] = True - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) order = MagicMock() order.myid = 123 - exchange._DRY_RUN_OPEN_ORDERS['X'] = order + exchange = get_patched_exchange(mocker, default_conf) + exchange._dry_run_open_orders['X'] = order print(exchange.get_order('X', 'TKN/BTC')) assert exchange.get_order('X', 'TKN/BTC').myid == 123 default_conf['dry_run'] = False - mocker.patch.dict('freqtrade.exchange._CONF', default_conf) api_mock = MagicMock() api_mock.fetch_order = MagicMock(return_value=456) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) assert exchange.get_order('X', 'TKN/BTC') == 456 with pytest.raises(TemporaryError): api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange.get_order(order_id='_', pair='TKN/BTC') - assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1 + assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1 with pytest.raises(DependencyException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange.get_order(order_id='_', pair='TKN/BTC') - assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1 + assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1 with pytest.raises(OperationalException): api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError) - mocker.patch('freqtrade.exchange._API', api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange.get_order(order_id='_', pair='TKN/BTC') assert api_mock.fetch_order.call_count == 1 -def test_get_name(default_conf, mocker): - mocker.patch('freqtrade.exchange.validate_pairs', +def test_name(default_conf, mocker): + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', side_effect=lambda s: True) default_conf['exchange']['name'] = 'binance' - init(default_conf) + exchange = Exchange(default_conf) - assert get_name() == 'Binance' + assert exchange.name == 'Binance' -def test_get_id(default_conf, mocker): - mocker.patch('freqtrade.exchange.validate_pairs', +def test_id(default_conf, mocker): + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', side_effect=lambda s: True) default_conf['exchange']['name'] = 'binance' - init(default_conf) - - assert get_id() == 'binance' + exchange = Exchange(default_conf) + assert exchange.id == 'binance' -def test_get_pair_detail_url(default_conf, mocker): - mocker.patch('freqtrade.exchange.validate_pairs', +def test_get_pair_detail_url(default_conf, mocker, caplog): + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', side_effect=lambda s: True) default_conf['exchange']['name'] = 'binance' - init(default_conf) + exchange = Exchange(default_conf) - url = get_pair_detail_url('TKN/ETH') + url = exchange.get_pair_detail_url('TKN/ETH') assert 'TKN' in url assert 'ETH' in url - url = get_pair_detail_url('LOOONG/BTC') + url = exchange.get_pair_detail_url('LOOONG/BTC') assert 'LOOONG' in url assert 'BTC' in url default_conf['exchange']['name'] = 'bittrex' - init(default_conf) + exchange = Exchange(default_conf) - url = get_pair_detail_url('TKN/ETH') + url = exchange.get_pair_detail_url('TKN/ETH') assert 'TKN' in url assert 'ETH' in url - url = get_pair_detail_url('LOOONG/BTC') + url = exchange.get_pair_detail_url('LOOONG/BTC') assert 'LOOONG' in url assert 'BTC' in url + default_conf['exchange']['name'] = 'poloniex' + exchange = Exchange(default_conf) + url = exchange.get_pair_detail_url('LOOONG/BTC') + assert '' == url + assert log_has('Could not get exchange url for Poloniex', caplog.record_tuples) + + +def test_get_trades_for_order(default_conf, mocker): + order_id = 'ABCD-ABCD' + since = datetime(2018, 5, 5) + default_conf["dry_run"] = False + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + api_mock = MagicMock() + + api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV', + 'order': 'ABCD-ABCD', + 'info': {'pair': 'XLTCZBTC', + 'time': 1519860024.4388, + 'type': 'buy', + 'ordertype': 'limit', + 'price': '20.00000', + 'cost': '38.62000', + 'fee': '0.06179', + 'vol': '5', + 'id': 'ABCD-ABCD'}, + 'timestamp': 1519860024438, + 'datetime': '2018-02-28T23:20:24.438Z', + 'symbol': 'LTC/BTC', + 'type': 'limit', + 'side': 'buy', + 'price': 165.0, + 'amount': 0.2340606, + 'fee': {'cost': 0.06179, 'currency': 'BTC'} + }]) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since) + assert len(orders) == 1 + assert orders[0]['price'] == 165 + + # test Exceptions + with pytest.raises(OperationalException): + api_mock = MagicMock() + api_mock.fetch_my_trades = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_trades_for_order(order_id, 'LTC/BTC', since) + + with pytest.raises(TemporaryError): + api_mock = MagicMock() + api_mock.fetch_my_trades = MagicMock(side_effect=ccxt.NetworkError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_trades_for_order(order_id, 'LTC/BTC', since) + assert api_mock.fetch_my_trades.call_count == API_RETRY_COUNT + 1 + + +def test_get_markets(default_conf, mocker, markets): + api_mock = MagicMock() + api_mock.fetch_markets = markets + exchange = get_patched_exchange(mocker, default_conf, api_mock) + ret = exchange.get_markets() + assert isinstance(ret, list) + assert len(ret) == 3 + + assert ret[0]["id"] == "ethbtc" + assert ret[0]["symbol"] == "ETH/BTC" + + # test Exceptions + with pytest.raises(OperationalException): + api_mock = MagicMock() + api_mock.fetch_markets = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_markets() + + with pytest.raises(TemporaryError): + api_mock = MagicMock() + api_mock.fetch_markets = MagicMock(side_effect=ccxt.NetworkError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_markets() + assert api_mock.fetch_markets.call_count == API_RETRY_COUNT + 1 + def test_get_fee(default_conf, mocker): api_mock = MagicMock() @@ -601,12 +701,32 @@ def test_get_fee(default_conf, mocker): 'rate': 0.025, 'cost': 0.05 }) - mocker.patch('freqtrade.exchange._API', api_mock) - assert get_fee() == 0.025 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + assert exchange.get_fee() == 0.025 + + # test Exceptions + with pytest.raises(OperationalException): + api_mock = MagicMock() + api_mock.calculate_fee = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_fee() + + with pytest.raises(TemporaryError): + api_mock = MagicMock() + api_mock.calculate_fee = MagicMock(side_effect=ccxt.NetworkError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_fee() + assert api_mock.calculate_fee.call_count == API_RETRY_COUNT + 1 def test_get_amount_lots(default_conf, mocker): api_mock = MagicMock() api_mock.amount_to_lots = MagicMock(return_value=1.0) - mocker.patch('freqtrade.exchange._API', api_mock) - assert get_amount_lots('LTC/BTC', 1.54) == 1 + api_mock.markets = None + marketmock = MagicMock() + api_mock.load_markets = marketmock + exchange = get_patched_exchange(mocker, default_conf, api_mock) + + assert exchange.get_amount_lots('LTC/BTC', 1.54) == 1 + assert marketmock.call_count == 1 diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 31080c503..1702818b1 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -15,7 +15,7 @@ from freqtrade import optimize from freqtrade.analyze import Analyze from freqtrade.arguments import Arguments, TimeRange from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration -from freqtrade.tests.conftest import log_has +from freqtrade.tests.conftest import log_has, patch_exchange def get_args(args) -> List[str]: @@ -83,7 +83,7 @@ def load_data_test(what): def simple_backtest(config, contour, num_results, mocker) -> None: - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + patch_exchange(mocker) backtesting = Backtesting(config) data = load_data_test(contour) @@ -101,7 +101,8 @@ def simple_backtest(config, contour, num_results, mocker) -> None: assert len(results) == num_results -def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False, timerange=None): +def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False, + timerange=None, exchange=None): tickerdata = optimize.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange) pairdata = {'UNITTEST/BTC': tickerdata} return pairdata @@ -118,7 +119,7 @@ def _load_pair_as_ticks(pair, tickfreq): def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None): data = optimize.load_data(None, ticker_interval='8m', pairs=[pair]) data = trim_dictlist(data, -201) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + patch_exchange(mocker) backtesting = Backtesting(conf) return { 'stake_amount': conf['stake_amount'], @@ -272,8 +273,8 @@ def test_start(mocker, fee, default_conf, caplog) -> None: Test start() function """ start_mock = MagicMock() - mocker.patch('freqtrade.exchange.get_fee', fee) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock) mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) @@ -296,7 +297,7 @@ def test_backtesting_init(mocker, default_conf) -> None: """ Test Backtesting._init() method """ - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + patch_exchange(mocker) backtesting = Backtesting(default_conf) assert backtesting.config == default_conf assert isinstance(backtesting.analyze, Analyze) @@ -310,7 +311,7 @@ def test_tickerdata_to_dataframe(default_conf, mocker) -> None: """ Test Backtesting.tickerdata_to_dataframe() method """ - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + patch_exchange(mocker) timerange = TimeRange(None, 'line', 0, -100) tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tickerlist = {'UNITTEST/BTC': tick} @@ -329,7 +330,7 @@ def test_get_timeframe(default_conf, mocker) -> None: """ Test Backtesting.get_timeframe() method """ - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + patch_exchange(mocker) backtesting = Backtesting(default_conf) data = backtesting.tickerdata_to_dataframe( @@ -348,7 +349,7 @@ def test_generate_text_table(default_conf, mocker): """ Test Backtesting.generate_text_table() method """ - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + patch_exchange(mocker) backtesting = Backtesting(default_conf) results = pd.DataFrame( @@ -385,8 +386,8 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None: mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.optimize.load_data', mocked_load_data) - mocker.patch('freqtrade.exchange.get_ticker_history') - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history') + patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.optimize.backtesting.Backtesting', backtest=MagicMock(), @@ -426,8 +427,8 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None: mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.optimize.load_data', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.get_ticker_history') - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history') + patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.optimize.backtesting.Backtesting', backtest=MagicMock(), @@ -454,8 +455,8 @@ def test_backtest(default_conf, fee, mocker) -> None: """ Test Backtesting.backtest() method """ - mocker.patch('freqtrade.exchange.get_fee', fee) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + patch_exchange(mocker) backtesting = Backtesting(default_conf) data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC']) @@ -476,8 +477,8 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None: """ Test Backtesting.backtest() method with 1 min ticker """ - mocker.patch('freqtrade.exchange.get_fee', fee) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + patch_exchange(mocker) backtesting = Backtesting(default_conf) # Run a backtesting for an exiting 5min ticker_interval @@ -499,7 +500,7 @@ def test_processed(default_conf, mocker) -> None: """ Test Backtesting.backtest() method with offline data """ - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + patch_exchange(mocker) backtesting = Backtesting(default_conf) dict_of_tickerrows = load_data_test('raise') @@ -513,7 +514,7 @@ def test_processed(default_conf, mocker) -> None: def test_backtest_pricecontours(default_conf, fee, mocker) -> None: - mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) tests = [['raise', 18], ['lower', 0], ['sine', 16]] for [contour, numres] in tests: simple_backtest(default_conf, contour, numres, mocker) @@ -521,8 +522,8 @@ def test_backtest_pricecontours(default_conf, fee, mocker) -> None: # Test backtest using offline data (testdata directory) def test_backtest_ticks(default_conf, fee, mocker): - mocker.patch('freqtrade.exchange.get_fee', fee) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + patch_exchange(mocker) ticks = [1, 5] fun = Backtesting(default_conf).populate_buy_trend for _ in ticks: @@ -541,7 +542,6 @@ def test_backtest_clash_buy_sell(mocker, default_conf): sell_value = 1 return _trend(dataframe, buy_value, sell_value) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtesting = Backtesting(default_conf) backtesting.populate_buy_trend = fun # Override @@ -557,7 +557,6 @@ def test_backtest_only_sell(mocker, default_conf): sell_value = 1 return _trend(dataframe, buy_value, sell_value) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtesting = Backtesting(default_conf) backtesting.populate_buy_trend = fun # Override @@ -567,8 +566,7 @@ def test_backtest_only_sell(mocker, default_conf): def test_backtest_alternate_buy_sell(default_conf, fee, mocker): - mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC') backtesting = Backtesting(default_conf) backtesting.populate_buy_trend = _trend_alternate # Override @@ -583,8 +581,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): def test_backtest_record(default_conf, fee, mocker): names = [] records = [] - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) + patch_exchange(mocker) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch( 'freqtrade.optimize.backtesting.file_dump_json', new=lambda n, r: (names.append(n), records.append(r)) @@ -632,9 +630,9 @@ def test_backtest_record(default_conf, fee, mocker): def test_backtest_start_live(default_conf, mocker, caplog): conf = deepcopy(default_conf) conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] - mocker.patch('freqtrade.exchange.get_ticker_history', - new=lambda n, i: _load_pair_as_ticks(n, i)) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', + new=lambda s, n, i: _load_pair_as_ticks(n, i)) + patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock()) mocker.patch('freqtrade.configuration.open', mocker.mock_open( diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 9b66a29e2..8ad1932af 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -10,7 +10,7 @@ import pytest from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.optimize.hyperopt import Hyperopt, start from freqtrade.strategy.resolver import StrategyResolver -from freqtrade.tests.conftest import log_has +from freqtrade.tests.conftest import log_has, patch_exchange from freqtrade.tests.optimize.test_backtesting import get_args # Avoid to reinit the same object again and again @@ -22,8 +22,7 @@ _HYPEROPT = None def init_hyperopt(default_conf, mocker): global _HYPEROPT_INITIALIZED, _HYPEROPT if not _HYPEROPT_INITIALIZED: - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) + patch_exchange(mocker) _HYPEROPT = Hyperopt(default_conf) _HYPEROPT_INITIALIZED = True @@ -66,7 +65,7 @@ def test_start(mocker, default_conf, caplog) -> None: lambda *args, **kwargs: default_conf ) mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) - mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) + patch_exchange(mocker) args = [ '--config', 'config.json', @@ -182,7 +181,7 @@ def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None: mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) - mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) + patch_exchange(mocker) StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) @@ -226,7 +225,7 @@ def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) -> conf.update({'epochs': 1}) conf.update({'timerange': None}) conf.update({'spaces': 'all'}) - mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) + patch_exchange(mocker) StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) @@ -267,7 +266,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) + patch_exchange(mocker) StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) @@ -338,7 +337,7 @@ def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None: trials = create_trials(mocker) mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) + patch_exchange(mocker) mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) conf = deepcopy(default_conf) @@ -503,7 +502,7 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None: 'freqtrade.optimize.hyperopt.Hyperopt.backtest', MagicMock(return_value=backtest_result) ) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) + patch_exchange(mocker) optimizer_param = { 'adx': {'enabled': False}, diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index bac8a6b36..624f8ed77 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -12,7 +12,7 @@ from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \ download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \ load_cached_data_for_updating from freqtrade.arguments import TimeRange -from freqtrade.tests.conftest import log_has +from freqtrade.tests.conftest import log_has, get_patched_exchange # Change this if modifying UNITTEST/BTC testdatafile _BTC_UNITTEST_LENGTH = 13681 @@ -49,12 +49,11 @@ def _clean_test_file(file: str) -> None: os.rename(file_swp, file) -def test_load_data_30min_ticker(ticker_history, mocker, caplog) -> None: +def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None: """ Test load_data() with 30 min ticker """ - mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) - + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json') _backup_file(file, copy_file=True) optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m') @@ -63,11 +62,11 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog) -> None: _clean_test_file(file) -def test_load_data_5min_ticker(ticker_history, mocker, caplog) -> None: +def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None: """ Test load_data() with 5 min ticker """ - mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json') _backup_file(file, copy_file=True) @@ -81,7 +80,7 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None: """ Test load_data() with 1 min ticker """ - mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json') _backup_file(file, copy_file=True) @@ -91,12 +90,12 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None: _clean_test_file(file) -def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog) -> None: +def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog, default_conf) -> None: """ Test load_data() with 1 min ticker """ - mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) - + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) + exchange = get_patched_exchange(mocker, default_conf) file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json') _backup_file(file) @@ -114,6 +113,7 @@ def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog) -> None: optimize.load_data(None, ticker_interval='1m', refresh_pairs=True, + exchange=exchange, pairs=['MEME/BTC']) assert os.path.isfile(file) is True assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples) @@ -124,9 +124,9 @@ def test_testdata_path() -> None: assert os.path.join('freqtrade', 'tests', 'testdata') in make_testdata_path(None) -def test_download_pairs(ticker_history, mocker) -> None: - mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) - +def test_download_pairs(ticker_history, mocker, default_conf) -> None: + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) + exchange = get_patched_exchange(mocker, default_conf) file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json') file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json') file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json') @@ -140,7 +140,8 @@ def test_download_pairs(ticker_history, mocker) -> None: assert os.path.isfile(file1_1) is False assert os.path.isfile(file2_1) is False - assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='1m') is True + assert download_pairs(None, exchange, + pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='1m') is True assert os.path.isfile(file1_1) is True assert os.path.isfile(file2_1) is True @@ -152,7 +153,8 @@ def test_download_pairs(ticker_history, mocker) -> None: assert os.path.isfile(file1_5) is False assert os.path.isfile(file2_5) is False - assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='5m') is True + assert download_pairs(None, exchange, + pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='5m') is True assert os.path.isfile(file1_5) is True assert os.path.isfile(file2_5) is True @@ -265,30 +267,32 @@ def test_load_cached_data_for_updating(mocker) -> None: assert start_ts is None -def test_download_pairs_exception(ticker_history, mocker, caplog) -> None: - mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) +def test_download_pairs_exception(ticker_history, mocker, caplog, default_conf) -> None: + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata', side_effect=BaseException('File Error')) + exchange = get_patched_exchange(mocker, default_conf) file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json') file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json') _backup_file(file1_1) _backup_file(file1_5) - download_pairs(None, pairs=['MEME/BTC'], ticker_interval='1m') + download_pairs(None, exchange, pairs=['MEME/BTC'], ticker_interval='1m') # clean files freshly downloaded _clean_test_file(file1_1) _clean_test_file(file1_5) assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples) -def test_download_backtesting_testdata(ticker_history, mocker) -> None: - mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) +def test_download_backtesting_testdata(ticker_history, mocker, default_conf) -> None: + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) + exchange = get_patched_exchange(mocker, default_conf) # Download a 1 min ticker file file1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'XEL_BTC-1m.json') _backup_file(file1) - download_backtesting_testdata(None, pair="XEL/BTC", tick_interval='1m') + download_backtesting_testdata(None, exchange, pair="XEL/BTC", tick_interval='1m') assert os.path.isfile(file1) is True _clean_test_file(file1) @@ -296,21 +300,21 @@ def test_download_backtesting_testdata(ticker_history, mocker) -> None: file2 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'STORJ_BTC-5m.json') _backup_file(file2) - download_backtesting_testdata(None, pair="STORJ/BTC", tick_interval='5m') + download_backtesting_testdata(None, exchange, pair="STORJ/BTC", tick_interval='5m') assert os.path.isfile(file2) is True _clean_test_file(file2) -def test_download_backtesting_testdata2(mocker) -> None: +def test_download_backtesting_testdata2(mocker, default_conf) -> None: tick = [ [1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839], [1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199] ] json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None) - mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick) - - download_backtesting_testdata(None, pair="UNITTEST/BTC", tick_interval='1m') - download_backtesting_testdata(None, pair="UNITTEST/BTC", tick_interval='3m') + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=tick) + exchange = get_patched_exchange(mocker, default_conf) + download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='1m') + download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='3m') assert json_dump_mock.call_count == 2 @@ -326,8 +330,10 @@ def test_load_tickerdata_file() -> None: def test_init(default_conf, mocker) -> None: + exchange = get_patched_exchange(mocker, default_conf) assert {} == optimize.load_data( '', + exchange=exchange, pairs=[], refresh_pairs=True, ticker_interval=default_conf['ticker_interval'] diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index b49b7fdcb..cc3a78a0e 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -33,7 +33,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -79,7 +79,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -112,7 +112,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -167,7 +167,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -190,7 +190,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, # Update the ticker with a market going up mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_up ) @@ -205,7 +205,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, # Update the ticker with a market going up mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_up ) @@ -243,7 +243,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -262,7 +262,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, trade.update(limit_buy_order) # Update the ticker with a market going up mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_up, get_fee=fee @@ -314,7 +314,7 @@ def test_rpc_balance_handle(default_conf, mocker): mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_balances=MagicMock(return_value=mock_balance) ) @@ -342,7 +342,7 @@ def test_rpc_start(mocker, default_conf) -> None: patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock() ) @@ -368,7 +368,7 @@ def test_rpc_stop(mocker, default_conf) -> None: patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock() ) @@ -396,7 +396,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, cancel_order=cancel_order_mock, @@ -441,7 +441,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: trade = Trade.query.filter(Trade.id == '1').first() filled_amount = trade.amount / 2 mocker.patch( - 'freqtrade.freqtradebot.exchange.get_order', + 'freqtrade.exchange.Exchange.get_order', return_value={ 'status': 'open', 'type': 'limit', @@ -460,7 +460,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it mocker.patch( - 'freqtrade.freqtradebot.exchange.get_order', + 'freqtrade.exchange.Exchange.get_order', return_value={ 'status': 'open', 'type': 'limit', @@ -476,7 +476,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: freqtradebot.create_trade() # make an limit-sell open trade mocker.patch( - 'freqtrade.freqtradebot.exchange.get_order', + 'freqtrade.exchange.Exchange.get_order', return_value={ 'status': 'open', 'type': 'limit', @@ -497,7 +497,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_balances=MagicMock(return_value=ticker), get_ticker=ticker, @@ -535,7 +535,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_balances=MagicMock(return_value=ticker), get_ticker=ticker, diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index f022c09e4..0a7d9690f 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -20,7 +20,7 @@ from freqtrade.persistence import Trade from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import authorized_only from freqtrade.state import State -from freqtrade.tests.conftest import get_patched_freqtradebot, log_has +from freqtrade.tests.conftest import get_patched_freqtradebot, patch_exchange, log_has from freqtrade.tests.test_freqtradebot import patch_get_signal, patch_coinmarketcap @@ -100,7 +100,7 @@ def test_authorized_only(default_conf, mocker, caplog) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) + patch_exchange(mocker, None) chat = Chat(0, 0) update = Update(randint(1, 100)) @@ -131,8 +131,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) - + patch_exchange(mocker, None) chat = Chat(0xdeadbeef, 0) update = Update(randint(1, 100)) update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) @@ -162,7 +161,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) + patch_exchange(mocker) update = Update(randint(1, 100)) update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0)) @@ -198,7 +197,7 @@ def test_status(default_conf, update, mocker, fee, ticker) -> None: patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_pair_detail_url=MagicMock(), @@ -238,7 +237,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee, @@ -284,7 +283,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': 'mocked_order_id'}), @@ -341,7 +340,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, return_value=15000.0 ) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -410,7 +409,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker ) @@ -450,7 +449,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -484,7 +483,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, msg_mock.reset_mock() # Update the ticker with a market going up - mocker.patch('freqtrade.freqtradebot.exchange.get_ticker', ticker_sell_up) + mocker.patch('freqtrade.exchange.Exchange.get_ticker', ticker_sell_up) trade.update(limit_sell_order) trade.close_date = datetime.utcnow() @@ -549,9 +548,8 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) - mocker.patch('freqtrade.freqtradebot.exchange.get_balances', return_value=mock_balance) - mocker.patch('freqtrade.freqtradebot.exchange.get_ticker', side_effect=mock_ticker) + mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance) + mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) msg_mock = MagicMock() mocker.patch.multiple( @@ -560,7 +558,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) telegram._balance(bot=MagicMock(), update=update) @@ -579,9 +577,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None: Test _balance() method when the Exchange platform returns nothing """ patch_get_signal(mocker, (True, False)) - patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) - mocker.patch('freqtrade.freqtradebot.exchange.get_balances', return_value={}) + mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) msg_mock = MagicMock() mocker.patch.multiple( @@ -590,7 +586,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) telegram._balance(bot=MagicMock(), update=update) @@ -603,17 +599,14 @@ def test_start_handle(default_conf, update, mocker) -> None: """ Test _start() method """ - patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _send_msg=msg_mock ) - mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) freqtradebot.state = State.STOPPED @@ -627,17 +620,14 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None: """ Test _start() method """ - patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _send_msg=msg_mock ) - mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) freqtradebot.state = State.RUNNING @@ -653,16 +643,14 @@ def test_stop_handle(default_conf, update, mocker) -> None: Test _stop() method """ patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _send_msg=msg_mock ) - mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) freqtradebot.state = State.RUNNING @@ -678,16 +666,14 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: Test _stop() method """ patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _send_msg=msg_mock ) - mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) freqtradebot.state = State.STOPPED @@ -701,16 +687,14 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: def test_reload_conf_handle(default_conf, update, mocker) -> None: """ Test _reload_conf() method """ patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _send_msg=msg_mock ) - mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) freqtradebot.state = State.RUNNING @@ -731,7 +715,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, moc rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -747,7 +731,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, moc assert trade # Increase the price and sell it - mocker.patch('freqtrade.freqtradebot.exchange.get_ticker', ticker_sell_up) + mocker.patch('freqtrade.exchange.Exchange.get_ticker', ticker_sell_up) update.message.text = '/forcesell 1' telegram._forcesell(bot=MagicMock(), update=update) @@ -771,7 +755,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_do rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -785,7 +769,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_do # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_down ) @@ -814,9 +798,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) - mocker.patch('freqtrade.exchange.get_pair_detail_url', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.get_pair_detail_url', MagicMock()) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -853,7 +837,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: _init=MagicMock(), _send_msg=msg_mock ) - mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) freqtradebot = FreqtradeBot(default_conf) telegram = Telegram(freqtradebot) @@ -896,7 +880,7 @@ def test_performance_handle(default_conf, update, ticker, fee, _send_msg=msg_mock ) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -936,7 +920,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None: _init=MagicMock(), _send_msg=msg_mock ) - mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) freqtradebot = FreqtradeBot(default_conf) telegram = Telegram(freqtradebot) @@ -960,12 +944,12 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: _send_msg=msg_mock ) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': 'mocked_order_id'}) ) - mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) freqtradebot = FreqtradeBot(default_conf) telegram = Telegram(freqtradebot) @@ -995,14 +979,14 @@ def test_help_handle(default_conf, update, mocker) -> None: Test _help() method """ patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _send_msg=msg_mock ) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) telegram._help(bot=MagicMock(), update=update) @@ -1015,14 +999,13 @@ def test_version_handle(default_conf, update, mocker) -> None: Test _version() method """ patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _send_msg=msg_mock ) - freqtradebot = FreqtradeBot(default_conf) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) telegram._version(bot=MagicMock(), update=update) @@ -1035,11 +1018,10 @@ def test_send_msg(default_conf, mocker) -> None: Test send_msg() method """ patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) conf = deepcopy(default_conf) bot = MagicMock() - freqtradebot = FreqtradeBot(conf) + freqtradebot = get_patched_freqtradebot(mocker, conf) telegram = Telegram(freqtradebot) telegram._config['telegram']['enabled'] = True @@ -1052,12 +1034,11 @@ def test_send_msg_network_error(default_conf, mocker, caplog) -> None: Test send_msg() method """ patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) conf = deepcopy(default_conf) bot = MagicMock() bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) - freqtradebot = FreqtradeBot(conf) + freqtradebot = get_patched_freqtradebot(mocker, conf) telegram = Telegram(freqtradebot) telegram._config['telegram']['enabled'] = True diff --git a/freqtrade/tests/test_acl_pair.py b/freqtrade/tests/test_acl_pair.py index 07375260e..608e6a4a1 100644 --- a/freqtrade/tests/test_acl_pair.py +++ b/freqtrade/tests/test_acl_pair.py @@ -32,7 +32,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets): freqtradebot = tt.get_patched_freqtradebot(mocker, conf) - mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets) + mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) refreshedwhitelist = freqtradebot._refresh_whitelist( conf['exchange']['pair_whitelist'] + ['XXX/BTC'] ) @@ -46,7 +46,7 @@ def test_refresh_whitelist(mocker, markets): conf = whitelist_conf() freqtradebot = tt.get_patched_freqtradebot(mocker, conf) - mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets) + mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist']) # List ordered by BaseVolume @@ -59,7 +59,7 @@ def test_refresh_whitelist_dynamic(mocker, markets, tickers): conf = whitelist_conf() freqtradebot = tt.get_patched_freqtradebot(mocker, conf) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', get_markets=markets, get_tickers=tickers, exchange_has=MagicMock(return_value=True) @@ -78,7 +78,7 @@ def test_refresh_whitelist_dynamic(mocker, markets, tickers): def test_refresh_whitelist_dynamic_empty(mocker, markets_empty): conf = whitelist_conf() freqtradebot = tt.get_patched_freqtradebot(mocker, conf) - mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets_empty) + mocker.patch('freqtrade.exchange.Exchange.get_markets', markets_empty) # argument: use the whitelist dynamically by exchange-volume whitelist = [] diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index e8d0816aa..89dac21c6 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -14,7 +14,7 @@ from pandas import DataFrame from freqtrade.analyze import Analyze, SignalType from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.arguments import TimeRange -from freqtrade.tests.conftest import log_has +from freqtrade.tests.conftest import log_has, get_patched_exchange # Avoid to reinit the same object again and again _ANALYZE = Analyze({'strategy': 'DefaultStrategy'}) @@ -66,16 +66,16 @@ def test_populates_sell_trend(result): assert 'sell' in dataframe.columns -def test_returns_latest_buy_signal(mocker): - mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) - +def test_returns_latest_buy_signal(mocker, default_conf): + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock()) + exchange = get_patched_exchange(mocker, default_conf) mocker.patch.multiple( 'freqtrade.analyze.Analyze', analyze_ticker=MagicMock( return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) ) - assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False) + assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (True, False) mocker.patch.multiple( 'freqtrade.analyze.Analyze', @@ -83,11 +83,12 @@ def test_returns_latest_buy_signal(mocker): return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) ) - assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True) + assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (False, True) -def test_returns_latest_sell_signal(mocker): - mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) +def test_returns_latest_sell_signal(mocker, default_conf): + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock()) + exchange = get_patched_exchange(mocker, default_conf) mocker.patch.multiple( 'freqtrade.analyze.Analyze', analyze_ticker=MagicMock( @@ -95,7 +96,7 @@ def test_returns_latest_sell_signal(mocker): ) ) - assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True) + assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (False, True) mocker.patch.multiple( 'freqtrade.analyze.Analyze', @@ -103,45 +104,49 @@ def test_returns_latest_sell_signal(mocker): return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) ) - assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False) + assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (True, False) def test_get_signal_empty(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None) - assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval']) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=None) + exchange = get_patched_exchange(mocker, default_conf) + assert (False, False) == _ANALYZE.get_signal(exchange, 'foo', default_conf['ticker_interval']) assert log_has('Empty ticker history for pair foo', caplog.record_tuples) def test_get_signal_exception_valueerror(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1) + exchange = get_patched_exchange(mocker, default_conf) mocker.patch.multiple( 'freqtrade.analyze.Analyze', analyze_ticker=MagicMock( side_effect=ValueError('xyz') ) ) - assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval']) + assert (False, False) == _ANALYZE.get_signal(exchange, 'foo', default_conf['ticker_interval']) assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples) def test_get_signal_empty_dataframe(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1) + exchange = get_patched_exchange(mocker, default_conf) mocker.patch.multiple( 'freqtrade.analyze.Analyze', analyze_ticker=MagicMock( return_value=DataFrame([]) ) ) - assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval']) + assert (False, False) == _ANALYZE.get_signal(exchange, 'xyz', default_conf['ticker_interval']) assert log_has('Empty dataframe for pair xyz', caplog.record_tuples) def test_get_signal_old_dataframe(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1) + exchange = get_patched_exchange(mocker, default_conf) # FIX: The get_signal function has hardcoded 10, which we must inturn hardcode oldtime = arrow.utcnow() - datetime.timedelta(minutes=11) ticks = DataFrame([{'buy': 1, 'date': oldtime}]) @@ -151,15 +156,16 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog): return_value=DataFrame(ticks) ) ) - assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval']) + assert (False, False) == _ANALYZE.get_signal(exchange, 'xyz', default_conf['ticker_interval']) assert log_has( 'Outdated history for pair xyz. Last tick is 11 minutes old', caplog.record_tuples ) -def test_get_signal_handles_exceptions(mocker): - mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) +def test_get_signal_handles_exceptions(mocker, default_conf): + mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock()) + exchange = get_patched_exchange(mocker, default_conf) mocker.patch.multiple( 'freqtrade.analyze.Analyze', analyze_ticker=MagicMock( @@ -167,7 +173,7 @@ def test_get_signal_handles_exceptions(mocker): ) ) - assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, False) + assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (False, False) def test_parse_ticker_dataframe(ticker_history): diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 1d272428e..9eae2fdf5 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -18,7 +18,7 @@ from freqtrade import DependencyException, OperationalException, TemporaryError from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.state import State -from freqtrade.tests.conftest import log_has, patch_coinmarketcap +from freqtrade.tests.conftest import log_has, patch_coinmarketcap, patch_exchange # Functions for recurrent object patching @@ -32,7 +32,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) + patch_exchange(mocker) patch_coinmarketcap(mocker) return FreqtradeBot(config) @@ -47,7 +47,7 @@ def patch_get_signal(mocker, value=(True, False)) -> None: """ mocker.patch( 'freqtrade.freqtradebot.Analyze.get_signal', - side_effect=lambda s, t: value + side_effect=lambda e, s, t: value ) @@ -187,9 +187,9 @@ def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None: Test _gen_pair_whitelist() method """ freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.freqtradebot.exchange.get_tickers', tickers) - mocker.patch('freqtrade.freqtradebot.exchange.exchange_has', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + # mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) # Test to retrieved BTC sorted on quoteVolume (default) whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC') @@ -224,7 +224,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> Non patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -261,7 +261,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, fee, patch_coinmarketcap(mocker) buy_mock = MagicMock(return_value={'id': limit_buy_order['id']}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=buy_mock, @@ -285,7 +285,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -306,7 +306,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocke patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -333,7 +333,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -362,7 +362,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None: patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker_history=MagicMock(return_value=20), get_balance=MagicMock(return_value=20), @@ -387,7 +387,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, patch_RPCManager(mocker) patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_markets=markets, @@ -428,7 +428,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non patch_RPCManager(mocker) patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_markets=markets, @@ -450,7 +450,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) -> msg_mock = patch_RPCManager(mocker) patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_markets=markets, @@ -474,7 +474,7 @@ def test_process_trade_handling( patch_RPCManager(mocker) patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_markets=markets, @@ -495,29 +495,32 @@ def test_process_trade_handling( assert result is False -def test_balance_fully_ask_side(mocker) -> None: +def test_balance_fully_ask_side(mocker, default_conf) -> None: """ Test get_target_bid() method """ - freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 0.0}}) + default_conf['bid_strategy']['ask_last_balance'] = 0.0 + freqtrade = get_patched_freqtradebot(mocker, default_conf) assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20 -def test_balance_fully_last_side(mocker) -> None: +def test_balance_fully_last_side(mocker, default_conf) -> None: """ Test get_target_bid() method """ - freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 1.0}}) + default_conf['bid_strategy']['ask_last_balance'] = 1.0 + freqtrade = get_patched_freqtradebot(mocker, default_conf) assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10 -def test_balance_bigger_last_ask(mocker) -> None: +def test_balance_bigger_last_ask(mocker, default_conf) -> None: """ Test get_target_bid() method """ - freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 1.0}}) + default_conf['bid_strategy']['ask_last_balance'] = 1.0 + freqtrade = get_patched_freqtradebot(mocker, default_conf) assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5 @@ -556,8 +559,8 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=limit_buy_order) - mocker.patch('freqtrade.freqtradebot.exchange.get_trades_for_order', return_value=[]) + mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=limit_buy_order['amount']) @@ -590,7 +593,7 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf, Test the exceptions in process_maybe_execute_sell() """ freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=limit_buy_order) + mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) trade = MagicMock() trade.open_order_id = '123' @@ -620,7 +623,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock patch_get_signal(mocker) patch_RPCManager(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ 'bid': 0.00001172, @@ -669,7 +672,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, patch_coinmarketcap(mocker) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -727,7 +730,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, ca patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -764,7 +767,7 @@ def test_handle_trade_experimental( patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -794,7 +797,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, fe patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), @@ -824,7 +827,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe cancel_order_mock = MagicMock() patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_order=MagicMock(return_value=limit_buy_order_old), @@ -865,7 +868,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, patch_coinmarketcap(mocker) cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_order=MagicMock(return_value=limit_sell_order_old), @@ -905,7 +908,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old patch_coinmarketcap(mocker) cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_order=MagicMock(return_value=limit_buy_order_old_partial), @@ -953,7 +956,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) - handle_timedout_limit_sell=MagicMock(), ) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')), @@ -993,7 +996,7 @@ def test_handle_timedout_limit_buy(mocker, default_conf) -> None: patch_coinmarketcap(mocker) cancel_order_mock = MagicMock() mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), cancel_order=cancel_order_mock ) @@ -1019,7 +1022,7 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None: cancel_order_mock = MagicMock() patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), cancel_order=cancel_order_mock ) @@ -1045,7 +1048,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N rpc_mock = patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -1061,7 +1064,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_up ) @@ -1087,7 +1090,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) patch_coinmarketcap(mocker) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -1102,7 +1105,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_down ) @@ -1127,7 +1130,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, rpc_mock = patch_RPCManager(mocker) patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -1142,7 +1145,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, # Increase the price and sell it mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_up ) @@ -1168,7 +1171,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, rpc_mock = patch_RPCManager(mocker) patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, get_fee=fee @@ -1183,7 +1186,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, # Decrease the price and sell it mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker_sell_down ) @@ -1207,7 +1210,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mock patch_coinmarketcap(mocker) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ 'bid': 0.00002172, @@ -1240,7 +1243,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, moc patch_coinmarketcap(mocker) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ 'bid': 0.00002172, @@ -1273,7 +1276,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker patch_coinmarketcap(mocker) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ 'bid': 0.00000172, @@ -1306,7 +1309,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke patch_coinmarketcap(mocker) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( - 'freqtrade.freqtradebot.exchange', + 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=MagicMock(return_value={ 'bid': 0.00000172, @@ -1337,12 +1340,12 @@ def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, ca Test get_real_amount - fee in quote currency """ - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -1364,12 +1367,12 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker): Test get_real_amount - fee in quote currency """ - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[]) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) amount = buy_order_fee['amount'] trade = Trade( pair='LTC/ETH', @@ -1395,8 +1398,8 @@ def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, mo patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -1421,8 +1424,8 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, mock patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -1444,8 +1447,8 @@ def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, c patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order2) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order2) amount = float(sum(x['amount'] for x in trades_for_order2)) trade = Trade( pair='LTC/ETH', @@ -1472,8 +1475,9 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[trades_for_order]) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', + return_value=[trades_for_order]) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -1500,8 +1504,8 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[]) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) amount = float(sum(x['amount'] for x in trades_for_order)) trade = Trade( pair='LTC/ETH', @@ -1525,8 +1529,8 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee, patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) - mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) amount = sum(x['amount'] for x in trades_for_order) trade = Trade( pair='LTC/ETH', @@ -1547,7 +1551,7 @@ def test_get_real_amount_open_trade(default_conf, mocker): patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True)) amount = 12345 trade = Trade( pair='LTC/ETH', diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 9640a7350..414dcdbe9 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -13,7 +13,7 @@ from freqtrade.arguments import Arguments from freqtrade.freqtradebot import FreqtradeBot from freqtrade.main import main, set_loggers, reconfigure from freqtrade.state import State -from freqtrade.tests.conftest import log_has +from freqtrade.tests.conftest import log_has, patch_exchange def test_parse_args_backtesting(mocker) -> None: @@ -70,6 +70,7 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None: Test main() function In this test we are skipping the while True loop by throwing an exception. """ + patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', _init_modules=MagicMock(), @@ -97,6 +98,7 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: Test main() function In this test we are skipping the while True loop by throwing an exception. """ + patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', _init_modules=MagicMock(), @@ -124,6 +126,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: Test main() function In this test we are skipping the while True loop by throwing an exception. """ + patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', _init_modules=MagicMock(), @@ -151,6 +154,7 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: Test main() function In this test we are skipping the while True loop by throwing an exception. """ + patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', _init_modules=MagicMock(), @@ -178,6 +182,7 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: def test_reconfigure(mocker, default_conf) -> None: """ Test recreate() function """ + patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', _init_modules=MagicMock(), diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index 9aedbecb9..2f76c1232 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -6,7 +6,8 @@ import sys import os import arrow -from freqtrade import (exchange, arguments, misc) +from freqtrade import (arguments, misc) +from freqtrade.exchange import Exchange DEFAULT_DL_PATH = 'user_data/data' @@ -39,16 +40,21 @@ if args.days: print(f'About to download pairs: {PAIRS} to {dl_path}') + # Init exchange -exchange._API = exchange.init_ccxt({'key': '', - 'secret': '', - 'name': args.exchange}) +exchange = Exchange({'key': '', + 'secret': '', + 'stake_currency': '', + 'dry_run': True, + 'exchange': { + 'name': args.exchange, + 'pair_whitelist': [] + } + }) pairs_not_available = [] -# Make sure API markets is initialized -exchange._API.load_markets() for pair in PAIRS: - if pair not in exchange._API.markets: + if pair not in exchange._api.markets: pairs_not_available.append(pair) print(f"skipping pair {pair}") continue diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index ce1a4b819..7f9641222 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -35,10 +35,10 @@ from plotly import tools from plotly.offline import plot import freqtrade.optimize as optimize -from freqtrade import exchange from freqtrade import persistence from freqtrade.analyze import Analyze from freqtrade.arguments import Arguments +from freqtrade.exchange import Exchange from freqtrade.optimize.backtesting import setup_configuration from freqtrade.persistence import Trade @@ -73,7 +73,7 @@ def plot_analyzed_dataframe(args: Namespace) -> None: # Load the strategy try: analyze = Analyze(_CONF) - exchange.init(_CONF) + exchange = Exchange(_CONF) except AttributeError: logger.critical( 'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"',