From ef5a0b9afc4713bcdb2e0dc792bd9db62a851771 Mon Sep 17 00:00:00 2001 From: Crypto God Date: Fri, 15 Feb 2019 22:50:11 +0100 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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 dffb27326e8fd3dcfab49df3ae3f25de30e15500 Mon Sep 17 00:00:00 2001 From: gautier pialat Date: Mon, 18 Feb 2019 17:16:27 +0100 Subject: [PATCH 19/41] prevent convert LF->CRLF line ending on window During docker built on windows if in global git config core.autocrlf = true then when have this message : Step 6/12 : RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib* ---> Running in c0a626821132 /tmp/install_ta-lib.sh: 4: /tmp/install_ta-lib.sh: Syntax error: "&&" unexpected this behave is because file is in the wrong line ending format Has script files run on linux they need to use LF lines ending so we need to ensure they are not converted to CRLF lines ending if global git config is at core.autocrlf = true. --- docs/installation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index e5f514eb1..08592880f 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -115,10 +115,16 @@ Once you have Docker installed, simply create the config file (e.g. `config.json **1.1. Clone the git repository** +Linux ```bash git clone https://github.com/freqtrade/freqtrade.git ``` +Windows (docker or WSL) +```bash +git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git +``` + **1.2. (Optional) Checkout the develop branch** ```bash From 481cf02db927a6a486c97342732ebe13581214b9 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 19 Feb 2019 19:15:22 +0100 Subject: [PATCH 20/41] 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 58864adc4a51b49bd2818955c6ece65e00b16d5c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Feb 2019 19:54:53 +0100 Subject: [PATCH 21/41] Move coveralls to after_success --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3dfcf6111..84f3c78d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,6 @@ jobs: - stage: tests script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ - - coveralls name: pytest - script: - cp config.json.example config.json @@ -48,6 +47,8 @@ jobs: - build_helpers/publish_docker.sh name: "Build and test and push docker image" +after_success: + - coveralls notifications: slack: From bb31e64752884de61ece8cf455a44953744e7770 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 19 Feb 2019 21:56:20 +0100 Subject: [PATCH 22/41] 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 23/41] 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 bd6644a91a5a049bc0a31cebb70eeb5c8d940801 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Feb 2019 13:31:05 +0100 Subject: [PATCH 24/41] Update ccxt from 1.18.247 to 1.18.270 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c9cedcb0c..b4dd302e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.247 +ccxt==1.18.270 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 1cd54829cc22c54d8344b2585016a476da21cf22 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Feb 2019 13:31:06 +0100 Subject: [PATCH 25/41] Update flake8 from 3.7.5 to 3.7.6 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1bbf5cbe1..9fbc99d16 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ # Include all requirements to run the bot. -r requirements.txt -flake8==3.7.5 +flake8==3.7.6 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 pytest==4.2.1 From 3ec3438acfe1a5e1e9e63dcec098b0cb3e18efb0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Feb 2019 13:31:07 +0100 Subject: [PATCH 26/41] Update pytest from 4.2.1 to 4.3.0 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9fbc99d16..9aa755827 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ flake8==3.7.6 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 -pytest==4.2.1 +pytest==4.3.0 pytest-mock==1.10.1 pytest-asyncio==0.10.0 pytest-cov==2.6.1 From 7dbb7a52edaebd651ec43127c466f449f4a7ab55 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Feb 2019 13:31:08 +0100 Subject: [PATCH 27/41] Update coveralls from 1.5.1 to 1.6.0 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9aa755827..34d59d802 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,5 +8,5 @@ pytest==4.3.0 pytest-mock==1.10.1 pytest-asyncio==0.10.0 pytest-cov==2.6.1 -coveralls==1.5.1 +coveralls==1.6.0 mypy==0.670 From ba23f58ff30aba7c1f5b5919bf24b45493b1fe5b Mon Sep 17 00:00:00 2001 From: gautier pialat Date: Wed, 20 Feb 2019 18:09:26 +0100 Subject: [PATCH 28/41] change the way to clone git rep for Window with docker --- docs/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 08592880f..80223f954 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -115,12 +115,12 @@ Once you have Docker installed, simply create the config file (e.g. `config.json **1.1. Clone the git repository** -Linux +Linux/Mac/Windows with WSL ```bash git clone https://github.com/freqtrade/freqtrade.git ``` -Windows (docker or WSL) +Windows with docker ```bash git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git ``` From 4315c157c75527e408d30491ad270e5fdbbefe75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Feb 2019 20:13:23 +0100 Subject: [PATCH 29/41] 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 From af02e34b5720349858a98230855690089371565a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Feb 2019 21:04:52 +0100 Subject: [PATCH 30/41] Version bump to 0.18.1 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index ca148f518..0aa01211d 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ FreqTrade bot """ -__version__ = '0.18.1-dev' +__version__ = '0.18.1' class DependencyException(BaseException): From d9129cb9c5505f6263af2882b593ea94b1953af4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Feb 2019 21:07:54 +0100 Subject: [PATCH 31/41] Develop version bump to 0.18.2-dev --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 0aa01211d..0d1ae9c26 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ FreqTrade bot """ -__version__ = '0.18.1' +__version__ = '0.18.2-dev' class DependencyException(BaseException): From eb211706913b12fd5845ae5c606dc23adefbc0b4 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Thu, 21 Feb 2019 00:26:02 +0300 Subject: [PATCH 32/41] added amount_reserve_percent into config json-schema --- freqtrade/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 6c71ddf7b..ff250c1f1 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -67,6 +67,7 @@ CONF_SCHEMA = { }, 'minProperties': 1 }, + 'amount_reserve_percent': {'type': 'number', 'minimum': 0.0, 'maximum': 0.5}, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'trailing_stop': {'type': 'boolean'}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, From 2aba9c081cd9a5a38de6f5e523a1bbcd322fca76 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 21 Feb 2019 00:46:35 +0300 Subject: [PATCH 33/41] fixed typos in comments --- 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 47886989e..d05436a36 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -540,9 +540,9 @@ class Exchange(object): input_coroutines = [] - # Gather corotines to run + # Gather coroutines to run for pair, ticker_interval in set(pair_list): - # Calculating ticker interval in second + # Calculating ticker interval in seconds interval_in_sec = constants.TICKER_INTERVAL_MINUTES[ticker_interval] * 60 if not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0) From c1ef6940b094958efc52ca5807fc39354caaff2c Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 21 Feb 2019 00:47:18 +0300 Subject: [PATCH 34/41] removed wrong comment: tuple is not created here --- freqtrade/exchange/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index d05436a36..c53858076 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -159,7 +159,6 @@ class Exchange(object): return self._api.id def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame: - # create key tuple if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] else: From 285183372650e27490bb67dd5e519c487f23f48b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 21 Feb 2019 01:20:24 +0300 Subject: [PATCH 35/41] added _now_is_time_to_refresh() --- freqtrade/exchange/__init__.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index c53858076..145e802fa 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -541,12 +541,8 @@ class Exchange(object): # Gather coroutines to run for pair, ticker_interval in set(pair_list): - # Calculating ticker interval in seconds - 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 - and (pair, ticker_interval) in self._klines): + if not ((pair, ticker_interval) in self._klines) \ + or self._now_is_time_to_refresh(pair, ticker_interval): input_coroutines.append(self._async_get_candle_history(pair, ticker_interval)) else: logger.debug("Using cached ohlcv data for %s, %s ...", pair, ticker_interval) @@ -570,6 +566,13 @@ class Exchange(object): ticks, tick_interval, fill_missing=True) return tickers + def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool: + # Calculating ticker interval in seconds + interval_in_sec = constants.TICKER_INTERVAL_MINUTES[ticker_interval] * 60 + + return not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0) + + interval_in_sec) >= arrow.utcnow().timestamp) + @retrier_async async def _async_get_candle_history(self, pair: str, tick_interval: str, since_ms: Optional[int] = None) -> Tuple[str, str, List]: From e987a915e8d69f7f51cf0e3e3fc298acae885857 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Feb 2019 06:56:22 +0100 Subject: [PATCH 36/41] Rename exchange file --- freqtrade/exchange/{__init__.py => exchange.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename freqtrade/exchange/{__init__.py => exchange.py} (100%) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/exchange.py similarity index 100% rename from freqtrade/exchange/__init__.py rename to freqtrade/exchange/exchange.py From e0f426d8637ff26314c824381dd0415d636ff20c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Feb 2019 06:59:52 +0100 Subject: [PATCH 37/41] Allow import freqtrade.exchange.* --- freqtrade/exchange/__init__.py | 2 ++ freqtrade/tests/exchange/test_exchange.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 freqtrade/exchange/__init__.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py new file mode 100644 index 000000000..204ed971e --- /dev/null +++ b/freqtrade/exchange/__init__.py @@ -0,0 +1,2 @@ +from freqtrade.exchange.exchange import Exchange # noqa: F401 +from freqtrade.exchange.kraken import Kraken # noqa: F401 diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 72919103c..ca76a0bd7 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -12,7 +12,8 @@ import pytest from pandas import DataFrame from freqtrade import DependencyException, OperationalException, TemporaryError -from freqtrade.exchange import API_RETRY_COUNT, Exchange +from freqtrade.exchange import Exchange +from freqtrade.exchange.exchange import API_RETRY_COUNT from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re from freqtrade.resolvers.exchange_resolver import ExchangeResolver @@ -1058,7 +1059,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker): ] exchange = get_patched_exchange(mocker, default_conf) exchange._api_async.fetch_ohlcv = get_mock_coro(tick) - sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) + sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the ticker history sort res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) assert res[0] == 'ETH/BTC' From be754244a3c9f153a5920353d5450f25b8058122 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Feb 2019 07:07:45 +0100 Subject: [PATCH 38/41] Only resolve exchanges from correct location --- freqtrade/resolvers/exchange_resolver.py | 22 +++++++++++----------- freqtrade/tests/exchange/test_exchange.py | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index a68219527..8d1845c71 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -2,9 +2,9 @@ This module loads custom exchanges """ import logging -from pathlib import Path from freqtrade.exchange import Exchange +import freqtrade.exchange as exchanges from freqtrade.resolvers import IResolver logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class ExchangeResolver(IResolver): def __init__(self, exchange_name: str, config: dict) -> None: """ Load the custom class from config parameter - :param config: configuration dictionary or None + :param config: configuration dictionary """ try: self.exchange = self._load_exchange(exchange_name, kwargs={'config': config}) @@ -32,22 +32,22 @@ class ExchangeResolver(IResolver): def _load_exchange( self, exchange_name: str, kwargs: dict) -> Exchange: """ - Search and loads the specified exchange. + Loads the specified exchange. + Only checks for exchanges exported in freqtrade.exchanges :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 """ - abs_path = Path(__file__).parent.parent.joinpath('exchange').resolve() try: - exchange = self._search_object(directory=abs_path, object_type=Exchange, - object_name=exchange_name, - kwargs=kwargs) + ex_class = getattr(exchanges, exchange_name) + + exchange = ex_class(kwargs['config']) if exchange: - logger.info("Using resolved exchange %s from '%s'", exchange_name, abs_path) + logger.info("Using resolved exchange %s", exchange_name) return exchange - except FileNotFoundError: - logger.warning('Path "%s" does not exist', abs_path.relative_to(Path.cwd())) + except AttributeError: + # Pass and raise ImportError instead + pass raise ImportError( "Impossible to load Exchange '{}'. This class does not exist" diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index ca76a0bd7..15da5e924 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -12,7 +12,7 @@ import pytest from pandas import DataFrame from freqtrade import DependencyException, OperationalException, TemporaryError -from freqtrade.exchange import Exchange +from freqtrade.exchange import Exchange, Kraken from freqtrade.exchange.exchange import API_RETRY_COUNT from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re from freqtrade.resolvers.exchange_resolver import ExchangeResolver @@ -121,6 +121,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): exchange = ExchangeResolver('Kraken', default_conf).exchange assert isinstance(exchange, Exchange) + assert isinstance(exchange, Kraken) assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog.record_tuples) From 7738ebbc0ff08b5b53c274e10b6e1a4dbd2ad8b1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 21 Feb 2019 13:31:05 +0100 Subject: [PATCH 39/41] Update ccxt from 1.18.270 to 1.18.280 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b4dd302e3..6ebbaa1ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.270 +ccxt==1.18.280 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 29b8b79732fcc02ed7a03737cafc5b6adb56e2d1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 22 Feb 2019 13:30:08 +0100 Subject: [PATCH 40/41] Update ccxt from 1.18.280 to 1.18.281 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ebbaa1ca..cd1d89c6d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.280 +ccxt==1.18.281 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From a1b00f90535132c7df53444bd8da29808e0fa05a Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 22 Feb 2019 17:37:59 +0300 Subject: [PATCH 41/41] Edge question added; minor improvements (sections for Hyperopt and Edge) --- docs/faq.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 4bbf28fe6..3eaa771ee 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,4 +1,6 @@ -# freqtrade FAQ +# Freqtrade FAQ + +### Freqtrade commons #### I have waited 5 minutes, why hasn't the bot made any trades yet?! @@ -34,7 +36,9 @@ perform anymore BUYS? You can use the `/forcesell all` command from Telegram. -### How many epoch do I need to get a good Hyperopt result? +### Hyperopt module + +#### How many epoch do I need to get a good Hyperopt result? Per default Hyperopts without `-e` or `--epochs` parameter will only run 100 epochs, means 100 evals of your triggers, guards, .... Too few to find a great result (unless if you are very lucky), so you probably @@ -68,3 +72,18 @@ but it will give the idea. With only these triggers and guards there is already 8*10^9*10 evaluations. A roughly total of 80 billion evals. Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th of the search space. + +### Edge module + +#### Edge implements interesting approach for controlling position size, is there any theory behind it? + +The Edge module is mostly a result of brainstorming of [@mishaker](https://github.com/mishaker) and [@creslinux](https://github.com/creslinux) freqtrade team members. + +You can find further info on expectancy, winrate, risk management and position size in the following sources: +* https://www.tradeciety.com/ultimate-math-guide-for-traders/ +* http://www.vantharp.com/tharp-concepts/expectancy.asp +* https://samuraitradingacademy.com/trading-expectancy/ +* https://www.learningmarkets.com/determining-expectancy-in-your-trading/ +* http://www.lonestocktrader.com/make-money-trading-positive-expectancy/ +* https://www.babypips.com/trading/trade-expectancy-matter +