From ef5a0b9afc4713bcdb2e0dc792bd9db62a851771 Mon Sep 17 00:00:00 2001 From: Crypto God Date: Fri, 15 Feb 2019 22:50:11 +0100 Subject: [PATCH 01/22] add Kraken specifics --- config_kraken.json.example | 71 ++++++++++++++++++++++++++++++++++ freqtrade/exchange/__init__.py | 34 ++++++++++------ 2 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 config_kraken.json.example diff --git a/config_kraken.json.example b/config_kraken.json.example new file mode 100644 index 000000000..76801ba56 --- /dev/null +++ b/config_kraken.json.example @@ -0,0 +1,71 @@ +{ + "max_open_trades": 5, + "stake_currency": "EUR", + "stake_amount": 10, + "fiat_display_currency": "EUR", + "ticker_interval" : "5m", + "dry_run": true, + "db_url": "sqlite:///tradesv3.dryrun.sqlite", + "trailing_stop": false, + "unfilledtimeout": { + "buy": 10, + "sell": 30 + }, + "bid_strategy": { + "ask_last_balance": 0.0, + "use_order_book": false, + "order_book_top": 1, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "ask_strategy":{ + "use_order_book": false, + "order_book_min": 1, + "order_book_max": 9 + }, + "exchange": { + "name": "kraken", + "key": "", + "secret": "", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 3000 + }, + "pair_whitelist": [ + "ETH/EUR", + "BTC/EUR", + "BCH/EUR" + ], + "pair_blacklist": [ + + ] + }, + "edge": { + "enabled": false, + "process_throttle_secs": 3600, + "calculate_since_number_of_days": 7, + "capital_available_percentage": 0.5, + "allowed_risk": 0.01, + "stoploss_range_min": -0.01, + "stoploss_range_max": -0.1, + "stoploss_range_step": -0.01, + "minimum_winrate": 0.60, + "minimum_expectancy": 0.20, + "min_trade_number": 10, + "max_trade_duration_minute": 1440, + "remove_pumps": false + }, + "telegram": { + "enabled": false, + "token": "", + "chat_id": "" + }, + "initial_state": "running", + "forcebuy_enable": false, + "internals": { + "process_throttle_secs": 5 + } +} diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 47886989e..b6ef219c4 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -304,11 +304,14 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - if time_in_force == 'gtc': - return self._api.create_order(pair, ordertype, 'buy', amount, rate) - else: - return self._api.create_order(pair, ordertype, 'buy', - amount, rate, {'timeInForce': time_in_force}) + params = {} + if time_in_force != 'gtc': + params.update({'timeInForce': time_in_force}) + if self.id == "kraken": + params.update({"trading_agreement": "agree"}) + + return self._api.create_order(pair, ordertype, 'buy', + amount, rate, params) except ccxt.InsufficientFunds as e: raise DependencyException( @@ -347,11 +350,14 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - if time_in_force == 'gtc': - return self._api.create_order(pair, ordertype, 'sell', amount, rate) - else: - return self._api.create_order(pair, ordertype, 'sell', - amount, rate, {'timeInForce': time_in_force}) + params = {} + if time_in_force != 'gtc': + params.update({'timeInForce': time_in_force}) + if self.id == "kraken": + params.update({"trading_agreement": "agree"}) + + return self._api.create_order(pair, ordertype, 'sell', + amount, rate, params) except ccxt.InsufficientFunds as e: raise DependencyException( @@ -403,8 +409,12 @@ class Exchange(object): return self._dry_run_open_orders[order_id] try: + params = {'stopPrice': stop_price} + if self.id == "kraken": + params.update({"trading_agreement": "agree"}) + order = self._api.create_order(pair, 'stop_loss_limit', 'sell', - amount, rate, {'stopPrice': stop_price}) + amount, rate, params) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s' % (pair, stop_price, rate)) return order @@ -546,7 +556,7 @@ class Exchange(object): interval_in_sec = constants.TICKER_INTERVAL_MINUTES[ticker_interval] * 60 if not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0) - + interval_in_sec) >= arrow.utcnow().timestamp + + interval_in_sec) >= arrow.utcnow().timestamp and (pair, ticker_interval) in self._klines): input_coroutines.append(self._async_get_candle_history(pair, ticker_interval)) else: From 3953092edd96af383a1ec8434a910c159bda87ae Mon Sep 17 00:00:00 2001 From: Crypto God Date: Fri, 15 Feb 2019 22:50:31 +0100 Subject: [PATCH 02/22] output error message --- freqtrade/data/history.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 7d89f7ad6..4e1b2729c 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -229,7 +229,7 @@ def download_pair_history(datadir: Optional[Path], misc.file_dump_json(filename, data) return True - except BaseException: - logger.info('Failed to download the pair: "%s", Interval: %s', - pair, tick_interval) + except BaseException as e: + logger.info('Failed to download the pair: "%s", Interval: %s, Msg: %s', + pair, tick_interval, e) return False From 3aa614b98361f12697aecb69d08b81b6eb31b428 Mon Sep 17 00:00:00 2001 From: Crypto God Date: Fri, 15 Feb 2019 22:51:09 +0100 Subject: [PATCH 03/22] bump version --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index ca148f518..0d1ae9c26 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ FreqTrade bot """ -__version__ = '0.18.1-dev' +__version__ = '0.18.2-dev' class DependencyException(BaseException): From 54d5bce445f0b77dad77abe6e8f0324deaf0209b Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 03:59:40 +0100 Subject: [PATCH 04/22] undo kraken specific changes --- freqtrade/exchange/__init__.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index b6ef219c4..9a34839e7 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -304,14 +304,11 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - params = {} if time_in_force != 'gtc': - params.update({'timeInForce': time_in_force}) - if self.id == "kraken": - params.update({"trading_agreement": "agree"}) - - return self._api.create_order(pair, ordertype, 'buy', - amount, rate, params) + return self._api.create_order(pair, ordertype, 'buy', amount, rate) + else: + return self._api.create_order(pair, ordertype, 'buy', + amount, rate, {'timeInForce': time_in_force}) except ccxt.InsufficientFunds as e: raise DependencyException( @@ -350,14 +347,11 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - params = {} if time_in_force != 'gtc': - params.update({'timeInForce': time_in_force}) - if self.id == "kraken": - params.update({"trading_agreement": "agree"}) - - return self._api.create_order(pair, ordertype, 'sell', - amount, rate, params) + return self._api.create_order(pair, ordertype, 'sell', amount, rate) + else: + return self._api.create_order(pair, ordertype, 'sell', + amount, rate, {'timeInForce': time_in_force}) except ccxt.InsufficientFunds as e: raise DependencyException( @@ -409,12 +403,9 @@ class Exchange(object): return self._dry_run_open_orders[order_id] try: - params = {'stopPrice': stop_price} - if self.id == "kraken": - params.update({"trading_agreement": "agree"}) order = self._api.create_order(pair, 'stop_loss_limit', 'sell', - amount, rate, params) + amount, rate, {'stopPrice': stop_price}) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s' % (pair, stop_price, rate)) return order From 32b02c9925bc95875a2843d306e9fa7ab2592bcc Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 04:01:17 +0100 Subject: [PATCH 05/22] kraken subclass --- freqtrade/exchange/kraken.py | 171 +++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 freqtrade/exchange/kraken.py diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py new file mode 100644 index 000000000..e8a0e4ee1 --- /dev/null +++ b/freqtrade/exchange/kraken.py @@ -0,0 +1,171 @@ +""" Kraken exchange subclass """ +import logging +from random import randint +from typing import Dict + +import arrow +import ccxt + +from freqtrade import OperationalException, DependencyException, TemporaryError +from freqtrade.exchange import Exchange + +logger = logging.getLogger(__name__) + + +class Kraken(Exchange): + + def __init__(self, config: dict) -> None: + super().__init__(config) + + self._params = {"trading_agreement": "agree"} + + def buy(self, pair: str, ordertype: str, amount: float, + rate: float, time_in_force) -> 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': ordertype, + 'side': 'buy', + 'remaining': 0.0, + 'datetime': arrow.utcnow().isoformat(), + 'status': 'closed', + 'fee': None + } + return {'id': order_id} + + try: + # Set the precision for amount and price(rate) as accepted by the exchange + amount = self.symbol_amount_prec(pair, amount) + rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None + + params = self._params.copy() + if time_in_force != 'gtc': + params.update({'timeInForce': time_in_force}) + + return self._api.create_order(pair, ordertype, 'buy', + amount, rate, params) + + 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, ordertype: str, amount: float, + rate: float, time_in_force='gtc') -> 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': ordertype, + 'side': 'sell', + 'remaining': 0.0, + 'datetime': arrow.utcnow().isoformat(), + 'status': 'closed' + } + return {'id': order_id} + + try: + # Set the precision for amount and price(rate) as accepted by the exchange + amount = self.symbol_amount_prec(pair, amount) + rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None + + params = self._params.copy() + if time_in_force != 'gtc': + params.update({'timeInForce': time_in_force}) + + return self._api.create_order(pair, ordertype, 'sell', + amount, rate, params) + + 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) + + def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: + """ + creates a stoploss limit order. + NOTICE: it is not supported by all exchanges. only binance is tested for now. + """ + + # Set the precision for amount and price(rate) as accepted by the exchange + amount = self.symbol_amount_prec(pair, amount) + rate = self.symbol_price_prec(pair, rate) + stop_price = self.symbol_price_prec(pair, stop_price) + + # Ensure rate is less than stop price + if stop_price <= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + + if self._conf['dry_run']: + order_id = f'dry_run_buy_{randint(0, 10**6)}' + self._dry_run_open_orders[order_id] = { + 'info': {}, + 'id': order_id, + 'pair': pair, + 'price': stop_price, + 'amount': amount, + 'type': 'stop_loss_limit', + 'side': 'sell', + 'remaining': amount, + 'datetime': arrow.utcnow().isoformat(), + 'status': 'open', + 'fee': None + } + return self._dry_run_open_orders[order_id] + + try: + + params = self._params.copy() + params.update({'stopPrice': stop_price}) + + order = self._api.create_order(pair, 'stop_loss_limit', 'sell', + amount, rate, params) + logger.info('stoploss limit order added for %s. ' + 'stop price: %s. limit: %s' % (pair, stop_price, rate)) + return order + + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to place stoploss limit order on market {pair}. ' + f'Tried to put a stoploss amount {amount} with ' + f'stop {stop_price} and limit {rate} (total {rate*amount}).' + f'Message: {e}') + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not place stoploss limit order on market {pair}.' + f'Tried to place stoploss amount {amount} with ' + f'stop {stop_price} and limit {rate} (total {rate*amount}).' + f'Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place stoploss limit order due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) From ca388a9acf3835acd77b279994a57fb51bcda546 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 04:01:43 +0100 Subject: [PATCH 06/22] create exchange_resolver --- freqtrade/resolvers/exchange_resolver.py | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 freqtrade/resolvers/exchange_resolver.py diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py new file mode 100644 index 000000000..568fb0ac4 --- /dev/null +++ b/freqtrade/resolvers/exchange_resolver.py @@ -0,0 +1,57 @@ +""" +This module loads custom exchanges +""" +import logging +from pathlib import Path + +from freqtrade.exchange import Exchange +from freqtrade.resolvers import IResolver + +logger = logging.getLogger(__name__) + + +class ExchangeResolver(IResolver): + """ + This class contains all the logic to load a custom exchange class + """ + + __slots__ = ['exchange'] + + def __init__(self, exchange_name: str, freqtrade, config: dict) -> None: + """ + Load the custom class from config parameter + :param config: configuration dictionary or None + """ + self.pairlist = self._load_exchange(exchange_name, kwargs={'freqtrade': freqtrade, + 'config': config}) + + def _load_exchange( + self, exchange_name: str, kwargs: dict) -> Exchange: + """ + Search and loads the specified exchange. + :param exchange_name: name of the module to import + :param extra_dir: additional directory to search for the given exchange + :return: Exchange instance or None + """ + current_path = Path(__file__).parent.parent.joinpath('exchange').resolve() + + abs_paths = [ + current_path.parent.parent.joinpath('user_data/exchange'), + current_path, + ] + + for _path in abs_paths: + try: + pairlist = self._search_object(directory=_path, object_type=Exchange, + object_name=exchange_name, + kwargs=kwargs) + if pairlist: + logger.info('Using resolved exchange %s from \'%s\'', exchange_name, _path) + return pairlist + except FileNotFoundError: + logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) + + raise ImportError( + "Impossible to load Exchange '{}'. This class does not exist" + " or contains Python code errors".format(exchange_name) + ) From 2fb36b116d591b1893a31ecae8ce1de5e544f5e2 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 04:15:11 +0100 Subject: [PATCH 07/22] change variable names --- freqtrade/resolvers/exchange_resolver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index 568fb0ac4..b2c301982 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -42,12 +42,12 @@ class ExchangeResolver(IResolver): for _path in abs_paths: try: - pairlist = self._search_object(directory=_path, object_type=Exchange, + exchange = self._search_object(directory=_path, object_type=Exchange, object_name=exchange_name, kwargs=kwargs) - if pairlist: + if exchange: logger.info('Using resolved exchange %s from \'%s\'', exchange_name, _path) - return pairlist + return exchange except FileNotFoundError: logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) From c315f63e4bd5f2bc631a9aadb649ebc32d051338 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 04:18:56 +0100 Subject: [PATCH 08/22] use exchange_resolver in freqbot --- freqtrade/freqtradebot.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9c211608f..2b0904abb 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,7 +20,7 @@ from freqtrade.edge import Edge from freqtrade.exchange import Exchange from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType -from freqtrade.resolvers import StrategyResolver, PairListResolver +from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver from freqtrade.state import State from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.wallets import Wallets @@ -55,7 +55,10 @@ class FreqtradeBot(object): self.strategy: IStrategy = StrategyResolver(self.config).strategy self.rpc: RPCManager = RPCManager(self) - self.exchange = Exchange(self.config) + + exchange_name = self.config.get('exchange', {}).get('name', 'binance') + self.exchange = ExchangeResolver(exchange_name, self, self.config) + self.wallets = Wallets(self.exchange) self.dataprovider = DataProvider(self.config, self.exchange) From c879591f45fa31bcd346853fca23c7c69a92dc3c Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 04:22:24 +0100 Subject: [PATCH 09/22] add exchange_resolver to resolver init --- freqtrade/resolvers/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/resolvers/__init__.py b/freqtrade/resolvers/__init__.py index da2987b27..5cf6c616a 100644 --- a/freqtrade/resolvers/__init__.py +++ b/freqtrade/resolvers/__init__.py @@ -1,4 +1,5 @@ from freqtrade.resolvers.iresolver import IResolver # noqa: F401 +from freqtrade.resolvers.exchange_resolver import ExchangeResolver # noqa: F401 from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401 from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401 from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401 From d3ead2cd09832892c5d70fda69372a0dba68ea4a Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 04:25:39 +0100 Subject: [PATCH 10/22] exchange import is not needed anymore --- freqtrade/freqtradebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2b0904abb..fb132d605 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -17,7 +17,6 @@ from freqtrade import (DependencyException, OperationalException, from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge -from freqtrade.exchange import Exchange from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver From fe792882b5f7457f8c1a0e2f33c5976ea28149c0 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 14:42:55 +0100 Subject: [PATCH 11/22] load generic class if no subclass exists --- freqtrade/freqtradebot.py | 9 +++++++-- freqtrade/resolvers/exchange_resolver.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fb132d605..588b959d3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -17,6 +17,7 @@ from freqtrade import (DependencyException, OperationalException, from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge +from freqtrade.exchange import Exchange from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver @@ -55,8 +56,12 @@ class FreqtradeBot(object): self.rpc: RPCManager = RPCManager(self) - exchange_name = self.config.get('exchange', {}).get('name', 'binance') - self.exchange = ExchangeResolver(exchange_name, self, self.config) + exchange_name = self.config.get('exchange', {}).get('name', 'bittrex') + try: + self.exchange = ExchangeResolver(exchange_name, self, self.config).exchange + except ImportError: + logger.info(f"No {exchange_name} specific subclass found. Using the generic class instead.") + self.exchange = Exchange(self.config) self.wallets = Wallets(self.exchange) self.dataprovider = DataProvider(self.config, self.exchange) diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index b2c301982..5d46becf5 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -22,7 +22,7 @@ class ExchangeResolver(IResolver): Load the custom class from config parameter :param config: configuration dictionary or None """ - self.pairlist = self._load_exchange(exchange_name, kwargs={'freqtrade': freqtrade, + self.exchange = self._load_exchange(exchange_name, kwargs={'freqtrade': freqtrade, 'config': config}) def _load_exchange( From 5e8a7a03c392eed82b0a194aaa848c3e8eed44a9 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 15:26:33 +0100 Subject: [PATCH 12/22] correct time_in_force param --- freqtrade/exchange/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 9a34839e7..8e52ade52 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -304,7 +304,7 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - if time_in_force != 'gtc': + if time_in_force == 'gtc': return self._api.create_order(pair, ordertype, 'buy', amount, rate) else: return self._api.create_order(pair, ordertype, 'buy', @@ -347,7 +347,7 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - if time_in_force != 'gtc': + if time_in_force == 'gtc': return self._api.create_order(pair, ordertype, 'sell', amount, rate) else: return self._api.create_order(pair, ordertype, 'sell', From 39c28626aac7bfafd06e1269804e5c8c1a39e842 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 15:29:58 +0100 Subject: [PATCH 13/22] remove error message to make pytest pass --- freqtrade/data/history.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 4e1b2729c..7d89f7ad6 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -229,7 +229,7 @@ def download_pair_history(datadir: Optional[Path], misc.file_dump_json(filename, data) return True - except BaseException as e: - logger.info('Failed to download the pair: "%s", Interval: %s, Msg: %s', - pair, tick_interval, e) + except BaseException: + logger.info('Failed to download the pair: "%s", Interval: %s', + pair, tick_interval) return False From da4faacd6b096209d9a18aac593ec5ea6e8920c2 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 15:34:44 +0100 Subject: [PATCH 14/22] flake8 --- freqtrade/freqtradebot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ff4f963f9..3706834b4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -60,7 +60,8 @@ class FreqtradeBot(object): try: self.exchange = ExchangeResolver(exchange_name, self, self.config).exchange except ImportError: - logger.info(f"No {exchange_name} specific subclass found. Using the generic class instead.") + logger.info( + f"No {exchange_name} specific subclass found. Using the generic class instead.") self.exchange = Exchange(self.config) self.wallets = Wallets(self.exchange) From d8feceebb58ef16c97819f1ed1e76aebf086f148 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 15:54:22 +0100 Subject: [PATCH 15/22] fix type-hints --- freqtrade/exchange/kraken.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index e8a0e4ee1..8fcaf1263 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -14,11 +14,11 @@ logger = logging.getLogger(__name__) class Kraken(Exchange): + _params: Dict = {"trading_agreement": "agree"} + def __init__(self, config: dict) -> None: super().__init__(config) - self._params = {"trading_agreement": "agree"} - def buy(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force) -> Dict: if self._conf['dry_run']: From 2103ae5fdf8bd2abed9138594fd6b8105a55b156 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 23:26:10 +0100 Subject: [PATCH 16/22] change rateLimit to 1000 --- config_kraken.json.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config_kraken.json.example b/config_kraken.json.example index 76801ba56..7a47b701f 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -32,7 +32,7 @@ "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { "enableRateLimit": true, - "rateLimit": 3000 + "rateLimit": 1000 }, "pair_whitelist": [ "ETH/EUR", @@ -40,7 +40,7 @@ "BCH/EUR" ], "pair_blacklist": [ - + ] }, "edge": { From 4241caef95113baae3982ef545ffa10c2034d437 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 23:34:15 +0100 Subject: [PATCH 17/22] changes to base and subclass --- freqtrade/exchange/__init__.py | 28 +++--- freqtrade/exchange/kraken.py | 159 --------------------------------- 2 files changed, 17 insertions(+), 170 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 8e52ade52..2b6d13fcf 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -67,6 +67,7 @@ def retrier(f): class Exchange(object): _conf: Dict = {} + _params: Dict = {} def __init__(self, config: dict) -> None: """ @@ -304,11 +305,12 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - if time_in_force == 'gtc': - return self._api.create_order(pair, ordertype, 'buy', amount, rate) - else: - return self._api.create_order(pair, ordertype, 'buy', - amount, rate, {'timeInForce': time_in_force}) + params = self._params.copy() + if time_in_force != 'gtc': + params.update({'timeInForce': time_in_force}) + + return self._api.create_order(pair, ordertype, 'buy', + amount, rate, params) except ccxt.InsufficientFunds as e: raise DependencyException( @@ -347,11 +349,12 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - if time_in_force == 'gtc': - return self._api.create_order(pair, ordertype, 'sell', amount, rate) - else: - return self._api.create_order(pair, ordertype, 'sell', - amount, rate, {'timeInForce': time_in_force}) + params = self._params.copy() + if time_in_force != 'gtc': + params.update({'timeInForce': time_in_force}) + + return self._api.create_order(pair, ordertype, 'sell', + amount, rate, params) except ccxt.InsufficientFunds as e: raise DependencyException( @@ -404,8 +407,11 @@ class Exchange(object): try: + params = self._params.copy() + params.update({'stopPrice': stop_price}) + order = self._api.create_order(pair, 'stop_loss_limit', 'sell', - amount, rate, {'stopPrice': stop_price}) + amount, rate, params) logger.info('stoploss limit order added for %s. ' 'stop price: %s. limit: %s' % (pair, stop_price, rate)) return order diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 8fcaf1263..91b41a159 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,12 +1,7 @@ """ Kraken exchange subclass """ import logging -from random import randint from typing import Dict -import arrow -import ccxt - -from freqtrade import OperationalException, DependencyException, TemporaryError from freqtrade.exchange import Exchange logger = logging.getLogger(__name__) @@ -15,157 +10,3 @@ logger = logging.getLogger(__name__) class Kraken(Exchange): _params: Dict = {"trading_agreement": "agree"} - - def __init__(self, config: dict) -> None: - super().__init__(config) - - def buy(self, pair: str, ordertype: str, amount: float, - rate: float, time_in_force) -> 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': ordertype, - 'side': 'buy', - 'remaining': 0.0, - 'datetime': arrow.utcnow().isoformat(), - 'status': 'closed', - 'fee': None - } - return {'id': order_id} - - try: - # Set the precision for amount and price(rate) as accepted by the exchange - amount = self.symbol_amount_prec(pair, amount) - rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - - params = self._params.copy() - if time_in_force != 'gtc': - params.update({'timeInForce': time_in_force}) - - return self._api.create_order(pair, ordertype, 'buy', - amount, rate, params) - - 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, ordertype: str, amount: float, - rate: float, time_in_force='gtc') -> 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': ordertype, - 'side': 'sell', - 'remaining': 0.0, - 'datetime': arrow.utcnow().isoformat(), - 'status': 'closed' - } - return {'id': order_id} - - try: - # Set the precision for amount and price(rate) as accepted by the exchange - amount = self.symbol_amount_prec(pair, amount) - rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - - params = self._params.copy() - if time_in_force != 'gtc': - params.update({'timeInForce': time_in_force}) - - return self._api.create_order(pair, ordertype, 'sell', - amount, rate, params) - - 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) - - def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: - """ - creates a stoploss limit order. - NOTICE: it is not supported by all exchanges. only binance is tested for now. - """ - - # Set the precision for amount and price(rate) as accepted by the exchange - amount = self.symbol_amount_prec(pair, amount) - rate = self.symbol_price_prec(pair, rate) - stop_price = self.symbol_price_prec(pair, stop_price) - - # Ensure rate is less than stop price - if stop_price <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') - - if self._conf['dry_run']: - order_id = f'dry_run_buy_{randint(0, 10**6)}' - self._dry_run_open_orders[order_id] = { - 'info': {}, - 'id': order_id, - 'pair': pair, - 'price': stop_price, - 'amount': amount, - 'type': 'stop_loss_limit', - 'side': 'sell', - 'remaining': amount, - 'datetime': arrow.utcnow().isoformat(), - 'status': 'open', - 'fee': None - } - return self._dry_run_open_orders[order_id] - - try: - - params = self._params.copy() - params.update({'stopPrice': stop_price}) - - order = self._api.create_order(pair, 'stop_loss_limit', 'sell', - amount, rate, params) - logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s' % (pair, stop_price, rate)) - return order - - except ccxt.InsufficientFunds as e: - raise DependencyException( - f'Insufficient funds to place stoploss limit order on market {pair}. ' - f'Tried to put a stoploss amount {amount} with ' - f'stop {stop_price} and limit {rate} (total {rate*amount}).' - f'Message: {e}') - except ccxt.InvalidOrder as e: - raise DependencyException( - f'Could not place stoploss limit order on market {pair}.' - f'Tried to place stoploss amount {amount} with ' - f'stop {stop_price} and limit {rate} (total {rate*amount}).' - f'Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place stoploss limit order due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) From eed1c2344db484f208bd0875cd172da66effc7bd Mon Sep 17 00:00:00 2001 From: iuvbio Date: Mon, 18 Feb 2019 01:03:09 +0100 Subject: [PATCH 18/22] delete unnecessary arguments --- freqtrade/freqtradebot.py | 2 +- freqtrade/resolvers/exchange_resolver.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3706834b4..f9c0b7b52 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -58,7 +58,7 @@ class FreqtradeBot(object): exchange_name = self.config.get('exchange', {}).get('name', 'bittrex') try: - self.exchange = ExchangeResolver(exchange_name, self, self.config).exchange + self.exchange = ExchangeResolver(exchange_name, self.config).exchange except ImportError: logger.info( f"No {exchange_name} specific subclass found. Using the generic class instead.") diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index 5d46becf5..4d78684ec 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -17,13 +17,12 @@ class ExchangeResolver(IResolver): __slots__ = ['exchange'] - def __init__(self, exchange_name: str, freqtrade, config: dict) -> None: + def __init__(self, exchange_name: str, config: dict) -> None: """ Load the custom class from config parameter :param config: configuration dictionary or None """ - self.exchange = self._load_exchange(exchange_name, kwargs={'freqtrade': freqtrade, - 'config': config}) + self.exchange = self._load_exchange(exchange_name, kwargs={'config': config}) def _load_exchange( self, exchange_name: str, kwargs: dict) -> Exchange: From 481cf02db927a6a486c97342732ebe13581214b9 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 19 Feb 2019 19:15:22 +0100 Subject: [PATCH 19/22] add test and fix exchange_resolver --- .gitignore | 1 + freqtrade/freqtradebot.py | 2 +- freqtrade/resolvers/exchange_resolver.py | 26 +++++++----------- freqtrade/tests/conftest.py | 7 ++++- freqtrade/tests/exchange/test_exchange.py | 32 +++++++++++++++++++++++ 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index b52a31d8e..9ed046c40 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ target/ # Jupyter Notebook .ipynb_checkpoints +*.ipynb # pyenv .python-version diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f9c0b7b52..f8d61d4aa 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -56,7 +56,7 @@ class FreqtradeBot(object): self.rpc: RPCManager = RPCManager(self) - exchange_name = self.config.get('exchange', {}).get('name', 'bittrex') + exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() try: self.exchange = ExchangeResolver(exchange_name, self.config).exchange except ImportError: diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index 4d78684ec..b834ad242 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -32,23 +32,17 @@ class ExchangeResolver(IResolver): :param extra_dir: additional directory to search for the given exchange :return: Exchange instance or None """ - current_path = Path(__file__).parent.parent.joinpath('exchange').resolve() + abs_path = Path(__file__).parent.parent.joinpath('exchange').resolve() - abs_paths = [ - current_path.parent.parent.joinpath('user_data/exchange'), - current_path, - ] - - for _path in abs_paths: - try: - exchange = self._search_object(directory=_path, object_type=Exchange, - object_name=exchange_name, - kwargs=kwargs) - if exchange: - logger.info('Using resolved exchange %s from \'%s\'', exchange_name, _path) - return exchange - except FileNotFoundError: - logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) + try: + exchange = self._search_object(directory=abs_path, object_type=Exchange, + object_name=exchange_name, + kwargs=kwargs) + if exchange: + logger.info('Using resolved exchange %s from \'%s\'', exchange_name, abs_path) + return exchange + except FileNotFoundError: + logger.warning('Path "%s" does not exist', abs_path.relative_to(Path.cwd())) raise ImportError( "Impossible to load Exchange '{}'. This class does not exist" diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 809dc12e0..d6628d925 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -16,6 +16,7 @@ from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.exchange import Exchange from freqtrade.edge import Edge, PairInfo from freqtrade.freqtradebot import FreqtradeBot +from freqtrade.resolvers import ExchangeResolver logging.getLogger('').setLevel(logging.INFO) @@ -49,7 +50,11 @@ def patch_exchange(mocker, api_mock=None, id='bittrex') -> None: def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange: patch_exchange(mocker, api_mock, id) - exchange = Exchange(config) + config["exchange"]["name"] = id + try: + exchange = ExchangeResolver(id.title(), config).exchange + except ImportError: + exchange = Exchange(config) return exchange diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index b384035b0..746f101ba 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -531,6 +531,38 @@ def test_buy_considers_time_in_force(default_conf, mocker): assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc'} +def test_buy_kraken_trading_agreement(default_conf, mocker): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + order_type = 'market' + time_in_force = 'ioc' + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + default_conf['dry_run'] = False + + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") + + order = exchange.buy(pair='ETH/BTC', ordertype=order_type, + amount=1, rate=200, time_in_force=time_in_force) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + assert api_mock.create_order.call_args[0][1] == order_type + assert api_mock.create_order.call_args[0][2] == 'buy' + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] is None + assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', + 'trading_agreement': 'agree'} + + def test_sell_dry_run(default_conf, mocker): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf) From bb31e64752884de61ece8cf455a44953744e7770 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 19 Feb 2019 21:56:20 +0100 Subject: [PATCH 20/22] add test_sell_kraken_trading_agreement --- freqtrade/tests/exchange/test_exchange.py | 32 ++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 746f101ba..6fb80194d 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -563,6 +563,35 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): 'trading_agreement': 'agree'} +def test_sell_kraken_trading_agreement(default_conf, mocker): + api_mock = MagicMock() + order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) + order_type = 'market' + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + default_conf['dry_run'] = False + + mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") + + order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' + assert api_mock.create_order.call_args[0][1] == order_type + assert api_mock.create_order.call_args[0][2] == 'sell' + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] is None + assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'} + + def test_sell_dry_run(default_conf, mocker): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf) @@ -584,11 +613,12 @@ def test_sell_prod(default_conf, mocker): }) default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock) mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + exchange = get_patched_exchange(mocker, default_conf, api_mock) order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + assert 'id' in order assert 'info' in order assert order['id'] == order_id From 3e2f90a32a0da999a5509791e35340116833d170 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 19 Feb 2019 22:27:20 +0100 Subject: [PATCH 21/22] formatting --- freqtrade/resolvers/exchange_resolver.py | 2 +- freqtrade/resolvers/hyperopt_resolver.py | 2 +- freqtrade/resolvers/iresolver.py | 2 +- freqtrade/resolvers/pairlist_resolver.py | 2 +- freqtrade/resolvers/strategy_resolver.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index b834ad242..62e89f445 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -39,7 +39,7 @@ class ExchangeResolver(IResolver): object_name=exchange_name, kwargs=kwargs) if exchange: - logger.info('Using resolved exchange %s from \'%s\'', exchange_name, abs_path) + logger.info("Using resolved exchange %s from '%s'", exchange_name, abs_path) return exchange except FileNotFoundError: logger.warning('Path "%s" does not exist', abs_path.relative_to(Path.cwd())) diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 6bf7fa17d..e7683bc78 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -63,7 +63,7 @@ class HyperOptResolver(IResolver): hyperopt = self._search_object(directory=_path, object_type=IHyperOpt, object_name=hyperopt_name) if hyperopt: - logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, _path) + logger.info("Using resolved hyperopt %s from '%s'", hyperopt_name, _path) return hyperopt except FileNotFoundError: logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index aee292926..852d1dc0c 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -47,7 +47,7 @@ class IResolver(object): :param directory: relative or absolute directory path :return: object instance """ - logger.debug('Searching for %s %s in \'%s\'', object_type.__name__, object_name, directory) + logger.debug("Searching for %s %s in '%s'", object_type.__name__, object_name, directory) for entry in directory.iterdir(): # Only consider python files if not str(entry).endswith('.py'): diff --git a/freqtrade/resolvers/pairlist_resolver.py b/freqtrade/resolvers/pairlist_resolver.py index 286cea5bf..4306a9669 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -48,7 +48,7 @@ class PairListResolver(IResolver): object_name=pairlist_name, kwargs=kwargs) if pairlist: - logger.info('Using resolved pairlist %s from \'%s\'', pairlist_name, _path) + logger.info("Using resolved pairlist %s from '%s'", pairlist_name, _path) return pairlist except FileNotFoundError: logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 01467b0a1..c49da9205 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -149,7 +149,7 @@ class StrategyResolver(IResolver): strategy = self._search_object(directory=_path, object_type=IStrategy, object_name=strategy_name, kwargs={'config': config}) if strategy: - logger.info('Using resolved strategy %s from \'%s\'', strategy_name, _path) + logger.info("Using resolved strategy %s from '%s'", strategy_name, _path) strategy._populate_fun_len = len( getfullargspec(strategy.populate_indicators).args) strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) From 4315c157c75527e408d30491ad270e5fdbbefe75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Feb 2019 20:13:23 +0100 Subject: [PATCH 22/22] Move exception handling to resolver, add test --- freqtrade/freqtradebot.py | 8 +------- freqtrade/resolvers/exchange_resolver.py | 7 ++++++- freqtrade/tests/exchange/test_exchange.py | 20 +++++++++++++++++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 656744ab6..92bdbc042 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -17,7 +17,6 @@ from freqtrade import (DependencyException, OperationalException, from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge -from freqtrade.exchange import Exchange from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver @@ -57,12 +56,7 @@ class FreqtradeBot(object): self.rpc: RPCManager = RPCManager(self) exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() - try: - self.exchange = ExchangeResolver(exchange_name, self.config).exchange - except ImportError: - logger.info( - f"No {exchange_name} specific subclass found. Using the generic class instead.") - self.exchange = Exchange(self.config) + self.exchange = ExchangeResolver(exchange_name, self.config).exchange self.wallets = Wallets(self.exchange) self.dataprovider = DataProvider(self.config, self.exchange) diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index 62e89f445..a68219527 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -22,7 +22,12 @@ class ExchangeResolver(IResolver): Load the custom class from config parameter :param config: configuration dictionary or None """ - self.exchange = self._load_exchange(exchange_name, kwargs={'config': config}) + try: + self.exchange = self._load_exchange(exchange_name, kwargs={'config': config}) + except ImportError: + logger.info( + f"No {exchange_name} specific subclass found. Using the generic class instead.") + self.exchange = Exchange(config) def _load_exchange( self, exchange_name: str, kwargs: dict) -> Exchange: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 6fb80194d..72919103c 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -13,7 +13,8 @@ from pandas import DataFrame from freqtrade import DependencyException, OperationalException, TemporaryError from freqtrade.exchange import API_RETRY_COUNT, Exchange -from freqtrade.tests.conftest import get_patched_exchange, log_has +from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re +from freqtrade.resolvers.exchange_resolver import ExchangeResolver # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines @@ -106,6 +107,23 @@ def test_init_exception(default_conf, mocker): Exchange(default_conf) +def test_exchange_resolver(default_conf, mocker, caplog): + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock())) + mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + exchange = ExchangeResolver('Binance', default_conf).exchange + assert isinstance(exchange, Exchange) + assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", + caplog.record_tuples) + caplog.clear() + + exchange = ExchangeResolver('Kraken', default_conf).exchange + assert isinstance(exchange, Exchange) + assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", + caplog.record_tuples) + + def test_symbol_amount_prec(default_conf, mocker): ''' Test rounds down to 4 Decimal places