From ef5a0b9afc4713bcdb2e0dc792bd9db62a851771 Mon Sep 17 00:00:00 2001 From: Crypto God Date: Fri, 15 Feb 2019 22:50:11 +0100 Subject: [PATCH 001/457] 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 002/457] 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 003/457] 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 b7afcf3416aaca550edce9485d01b783a8cb1d8a Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sat, 16 Feb 2019 22:56:04 +0100 Subject: [PATCH 004/457] add VolumePrecisionPairList --- freqtrade/__init__.py | 2 +- freqtrade/constants.py | 2 +- freqtrade/data/history.py | 6 +- freqtrade/pairlist/VolumePrecisionPairList.py | 86 +++++++++++++++++++ 4 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 freqtrade/pairlist/VolumePrecisionPairList.py 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): diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 8fbcdfed7..840d348f0 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -17,7 +17,7 @@ REQUIRED_ORDERTIF = ['buy', 'sell'] REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] -AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] +AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'VolumePrecisionPairList'] TICKER_INTERVAL_MINUTES = { '1m': 1, diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 7d89f7ad6..a7a3a61cf 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\n' + 'Error message: %s', pair, tick_interval, e) return False diff --git a/freqtrade/pairlist/VolumePrecisionPairList.py b/freqtrade/pairlist/VolumePrecisionPairList.py new file mode 100644 index 000000000..634988668 --- /dev/null +++ b/freqtrade/pairlist/VolumePrecisionPairList.py @@ -0,0 +1,86 @@ +""" +Static List provider + +Provides lists as configured in config.json + + """ +import logging +from typing import List +from cachetools import TTLCache, cached + +from freqtrade.pairlist.IPairList import IPairList +from freqtrade import OperationalException +logger = logging.getLogger(__name__) + +SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] + + +class VolumePrecisionPairList(IPairList): + + def __init__(self, freqtrade, config: dict) -> None: + super().__init__(freqtrade, config) + self._whitelistconf = self._config.get('pairlist', {}).get('config') + if 'number_assets' not in self._whitelistconf: + raise OperationalException( + f'`number_assets` not specified. Please check your configuration ' + 'for "pairlist.config.number_assets"') + self._number_pairs = self._whitelistconf['number_assets'] + self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') + + if not self._freqtrade.exchange.exchange_has('fetchTickers'): + raise OperationalException( + 'Exchange does not support dynamic whitelist.' + 'Please edit your config and restart the bot' + ) + if not self._validate_keys(self._sort_key): + raise OperationalException( + f'key {self._sort_key} not in {SORT_VALUES}') + + def _validate_keys(self, key): + return key in SORT_VALUES + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + -> Please overwrite in subclasses + """ + return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs." + + def refresh_pairlist(self) -> None: + """ + Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively + -> Please overwrite in subclasses + """ + # Generate dynamic whitelist + pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) + # Validate whitelist to only have active market pairs + self._whitelist = self._validate_whitelist(pairs)[:self._number_pairs] + + @cached(TTLCache(maxsize=1, ttl=1800)) + def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: + """ + Updates the whitelist with with a dynamically generated list + :param base_currency: base currency as str + :param key: sort key (defaults to 'quoteVolume') + :return: List of pairs + """ + + tickers = self._freqtrade.exchange.get_tickers() + # check length so that we make sure that '/' is actually in the string + tickers = [v for k, v in tickers.items() + if len(k.split('/')) == 2 and k.split('/')[1] == base_currency] + + if self._freqtrade.strategy.stoploss is not None: + precisions = [self._freqtrade.exchange.markets[ + t["symbol"]]["precision"].get("price") for t in tickers] + tickers = [t for t, p in zip(tickers, precisions) if ( + self._freqtrade.exchange.symbol_price_prec( + t["symbol"], + self._freqtrade.get_target_bid( + t["symbol"], t) * (1 + self._freqtrade.strategy.stoploss) + ) < self._freqtrade.get_target_bid(t["symbol"], t) + )] + + sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) + pairs = [s['symbol'] for s in sorted_tickers] + return pairs From 54d5bce445f0b77dad77abe6e8f0324deaf0209b Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 03:59:40 +0100 Subject: [PATCH 005/457] 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 006/457] 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 007/457] 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 008/457] 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 009/457] 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 010/457] 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 011/457] 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 012/457] 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 013/457] 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 014/457] 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 015/457] 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 016/457] 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 0572336ff790b39bc5d3719504590cc471b6251d Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 16:12:40 +0100 Subject: [PATCH 017/457] revert changes to history --- 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 a7a3a61cf..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\n' - 'Error message: %s', pair, tick_interval, e) + except BaseException: + logger.info('Failed to download the pair: "%s", Interval: %s', + pair, tick_interval) return False From 2103ae5fdf8bd2abed9138594fd6b8105a55b156 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Feb 2019 23:26:10 +0100 Subject: [PATCH 018/457] 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 019/457] 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 020/457] 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 2f225e23408bc6c47118840bfddfb829ba6d6776 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 19 Feb 2019 15:14:47 +0300 Subject: [PATCH 021/457] multiple --config options --- freqtrade/arguments.py | 76 ++++++++++++++++++++------------------ freqtrade/configuration.py | 22 +++++++---- freqtrade/constants.py | 1 + freqtrade/misc.py | 18 +++++++++ 4 files changed, 74 insertions(+), 43 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 9b1b9a925..53a46d141 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -6,9 +6,7 @@ import argparse import os import re from typing import List, NamedTuple, Optional - import arrow - from freqtrade import __version__, constants @@ -55,6 +53,11 @@ class Arguments(object): """ parsed_arg = self.parser.parse_args(self.args) + # Workaround issue in argparse with action='append' and default value + # (see https://bugs.python.org/issue16399) + if parsed_arg.config is None: + parsed_arg.config = [constants.DEFAULT_CONFIG] + return parsed_arg def common_args_parser(self) -> None: @@ -63,7 +66,7 @@ class Arguments(object): """ self.parser.add_argument( '-v', '--verbose', - help='verbose mode (-vv for more, -vvv to get all messages)', + help='Verbose mode (-vv for more, -vvv to get all messages).', action='count', dest='loglevel', default=0, @@ -75,15 +78,15 @@ class Arguments(object): ) self.parser.add_argument( '-c', '--config', - help='specify configuration file (default: %(default)s)', + help='Specify configuration file (default: %(default)s).', dest='config', - default='config.json', + action='append', type=str, metavar='PATH', ) self.parser.add_argument( '-d', '--datadir', - help='path to backtest data', + help='Path to backtest data.', dest='datadir', default=None, type=str, @@ -91,7 +94,7 @@ class Arguments(object): ) self.parser.add_argument( '-s', '--strategy', - help='specify strategy class name (default: %(default)s)', + help='Specify strategy class name (default: %(default)s).', dest='strategy', default='DefaultStrategy', type=str, @@ -99,14 +102,14 @@ class Arguments(object): ) self.parser.add_argument( '--strategy-path', - help='specify additional strategy lookup path', + help='Specify additional strategy lookup path.', dest='strategy_path', type=str, metavar='PATH', ) self.parser.add_argument( '--customhyperopt', - help='specify hyperopt class name (default: %(default)s)', + help='Specify hyperopt class name (default: %(default)s).', dest='hyperopt', default=constants.DEFAULT_HYPEROPT, type=str, @@ -114,8 +117,8 @@ class Arguments(object): ) self.parser.add_argument( '--dynamic-whitelist', - help='dynamically generate and update whitelist' - ' based on 24h BaseVolume (default: %(const)s)' + help='Dynamically generate and update whitelist' + ' based on 24h BaseVolume (default: %(const)s).' ' DEPRECATED.', dest='dynamic_whitelist', const=constants.DYNAMIC_WHITELIST, @@ -126,7 +129,7 @@ class Arguments(object): self.parser.add_argument( '--db-url', help='Override trades database URL, this is useful if dry_run is enabled' - ' or in custom deployments (default: %(default)s)', + ' or in custom deployments (default: %(default)s).', dest='db_url', type=str, metavar='PATH', @@ -139,7 +142,7 @@ class Arguments(object): """ parser.add_argument( '--eps', '--enable-position-stacking', - help='Allow buying the same pair multiple times (position stacking)', + help='Allow buying the same pair multiple times (position stacking).', action='store_true', dest='position_stacking', default=False @@ -148,20 +151,20 @@ class Arguments(object): parser.add_argument( '--dmmp', '--disable-max-market-positions', help='Disable applying `max_open_trades` during backtest ' - '(same as setting `max_open_trades` to a very high number)', + '(same as setting `max_open_trades` to a very high number).', action='store_false', dest='use_max_market_positions', default=True ) parser.add_argument( '-l', '--live', - help='using live data', + help='Use live data.', action='store_true', dest='live', ) parser.add_argument( '-r', '--refresh-pairs-cached', - help='refresh the pairs files in tests/testdata with the latest data from the ' + help='Refresh the pairs files in tests/testdata with the latest data from the ' 'exchange. Use it if you want to run your backtesting with up-to-date data.', action='store_true', dest='refresh_pairs', @@ -178,8 +181,8 @@ class Arguments(object): ) parser.add_argument( '--export', - help='export backtest results, argument are: trades\ - Example --export=trades', + help='Export backtest results, argument are: trades. ' + 'Example --export=trades', type=str, default=None, dest='export', @@ -203,14 +206,14 @@ class Arguments(object): """ parser.add_argument( '-r', '--refresh-pairs-cached', - help='refresh the pairs files in tests/testdata with the latest data from the ' + help='Refresh the pairs files in tests/testdata with the latest data from the ' 'exchange. Use it if you want to run your edge with up-to-date data.', action='store_true', dest='refresh_pairs', ) parser.add_argument( '--stoplosses', - help='defines a range of stoploss against which edge will assess the strategy ' + help='Defines a range of stoploss against which edge will assess the strategy ' 'the format is "min,max,step" (without any space).' 'example: --stoplosses=-0.01,-0.1,-0.001', type=str, @@ -226,14 +229,14 @@ class Arguments(object): """ parser.add_argument( '-i', '--ticker-interval', - help='specify ticker interval (1m, 5m, 30m, 1h, 1d)', + help='Specify ticker interval (1m, 5m, 30m, 1h, 1d).', dest='ticker_interval', type=str, ) parser.add_argument( '--timerange', - help='specify what timerange of data to use.', + help='Specify what timerange of data to use.', default=None, type=str, dest='timerange', @@ -246,7 +249,7 @@ class Arguments(object): """ parser.add_argument( '--eps', '--enable-position-stacking', - help='Allow buying the same pair multiple times (position stacking)', + help='Allow buying the same pair multiple times (position stacking).', action='store_true', dest='position_stacking', default=False @@ -255,14 +258,14 @@ class Arguments(object): parser.add_argument( '--dmmp', '--disable-max-market-positions', help='Disable applying `max_open_trades` during backtest ' - '(same as setting `max_open_trades` to a very high number)', + '(same as setting `max_open_trades` to a very high number).', action='store_false', dest='use_max_market_positions', default=True ) parser.add_argument( '-e', '--epochs', - help='specify number of epochs (default: %(default)d)', + help='Specify number of epochs (default: %(default)d).', dest='epochs', default=constants.HYPEROPT_EPOCH, type=int, @@ -271,7 +274,7 @@ class Arguments(object): parser.add_argument( '-s', '--spaces', help='Specify which parameters to hyperopt. Space separate list. \ - Default: %(default)s', + Default: %(default)s.', choices=['all', 'buy', 'sell', 'roi', 'stoploss'], default='all', nargs='+', @@ -288,19 +291,19 @@ class Arguments(object): subparsers = self.parser.add_subparsers(dest='subparser') # Add backtesting subcommand - backtesting_cmd = subparsers.add_parser('backtesting', help='backtesting module') + backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.') backtesting_cmd.set_defaults(func=backtesting.start) self.optimizer_shared_options(backtesting_cmd) self.backtesting_options(backtesting_cmd) # Add edge subcommand - edge_cmd = subparsers.add_parser('edge', help='edge module') + edge_cmd = subparsers.add_parser('edge', help='Edge module.') edge_cmd.set_defaults(func=edge_cli.start) self.optimizer_shared_options(edge_cmd) self.edge_options(edge_cmd) # Add hyperopt subcommand - hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module') + hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.') hyperopt_cmd.set_defaults(func=hyperopt.start) self.optimizer_shared_options(hyperopt_cmd) self.hyperopt_options(hyperopt_cmd) @@ -364,7 +367,7 @@ class Arguments(object): """ self.parser.add_argument( '--pairs-file', - help='File containing a list of pairs to download', + help='File containing a list of pairs to download.', dest='pairs_file', default=None, metavar='PATH', @@ -372,7 +375,7 @@ class Arguments(object): self.parser.add_argument( '--export', - help='Export files to given dir', + help='Export files to given dir.', dest='export', default=None, metavar='PATH', @@ -380,7 +383,8 @@ class Arguments(object): self.parser.add_argument( '-c', '--config', - help='specify configuration file, used for additional exchange parameters', + help='Specify configuration file, used for additional exchange parameters. ' + 'Multiple --config options may be used.', dest='config', default=None, type=str, @@ -389,7 +393,7 @@ class Arguments(object): self.parser.add_argument( '--days', - help='Download data for number of days', + help='Download data for given number of days.', dest='days', type=int, metavar='INT', @@ -398,7 +402,7 @@ class Arguments(object): self.parser.add_argument( '--exchange', - help='Exchange name (default: %(default)s). Only valid if no config is provided', + help='Exchange name (default: %(default)s). Only valid if no config is provided.', dest='exchange', type=str, default='bittrex' @@ -407,7 +411,7 @@ class Arguments(object): self.parser.add_argument( '-t', '--timeframes', help='Specify which tickers to download. Space separated list. \ - Default: %(default)s', + Default: %(default)s.', choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w'], default=['1m', '5m'], @@ -417,7 +421,7 @@ class Arguments(object): self.parser.add_argument( '--erase', - help='Clean all existing data for the selected exchange/pairs/timeframes', + help='Clean all existing data for the selected exchange/pairs/timeframes.', dest='erase', action='store_true' ) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index d972f50b8..bddf60028 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -13,6 +13,8 @@ from jsonschema.exceptions import ValidationError, best_match from freqtrade import OperationalException, constants from freqtrade.state import RunMode +from freqtrade.misc import deep_merge_dicts + logger = logging.getLogger(__name__) @@ -45,8 +47,18 @@ class Configuration(object): Extract information for sys.argv and load the bot configuration :return: Configuration dictionary """ - logger.info('Using config: %s ...', self.args.config) - config = self._load_config_file(self.args.config) + config: Dict[str, Any] = {} + # Now expecting a list of config filenames here, not a string + for path in self.args.config: + logger.info('Using config: %s ...', path) + # Merge config options, overwriting old values + config = deep_merge_dicts(self._load_config_file(path), config) + + if 'internals' not in config: + config['internals'] = {} + + logger.info('Validating configuration ...') + self._validate_config(config) # Set strategy if not specified in config and or if it's non default if self.args.strategy != constants.DEFAULT_STRATEGY or not config.get('strategy'): @@ -93,11 +105,7 @@ class Configuration(object): f'Config file "{path}" not found!' ' Please create a config file or check whether it exists.') - if 'internals' not in conf: - conf['internals'] = {} - logger.info('Validating configuration ...') - - return self._validate_config(conf) + return conf def _load_common_config(self, config: Dict[str, Any]) -> Dict[str, Any]: """ diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 8fbcdfed7..c214f0de9 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -3,6 +3,7 @@ """ bot constants """ +DEFAULT_CONFIG = 'config.json' DYNAMIC_WHITELIST = 20 # pairs PROCESS_THROTTLE_SECS = 5 # sec TICKER_INTERVAL = 5 # min diff --git a/freqtrade/misc.py b/freqtrade/misc.py index d03187d77..38f758669 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -113,3 +113,21 @@ def format_ms_time(date: int) -> str: : epoch-string in ms """ return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S') + + +def deep_merge_dicts(source, destination): + """ + >>> a = { 'first' : { 'rows' : { 'pass' : 'dog', 'number' : '1' } } } + >>> b = { 'first' : { 'rows' : { 'fail' : 'cat', 'number' : '5' } } } + >>> merge(b, a) == { 'first' : { 'rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } } + True + """ + for key, value in source.items(): + if isinstance(value, dict): + # get node or create one + node = destination.setdefault(key, {}) + deep_merge_dicts(value, node) + else: + destination[key] = value + + return destination From 481cf02db927a6a486c97342732ebe13581214b9 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 19 Feb 2019 19:15:22 +0100 Subject: [PATCH 022/457] 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 023/457] 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 024/457] 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 e495ffec78a0aa0d666863cbd81e06dd988478d3 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 20 Feb 2019 02:38:16 +0100 Subject: [PATCH 025/457] align dry_run_orders --- freqtrade/exchange/__init__.py | 68 +++++++++++++++------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 2b6d13fcf..078c8e6be 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -283,21 +283,32 @@ class Exchange(object): price = ceil(big_price) / pow(10, symbol_prec) return price + def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, + rate: float, params: Dict = {}) -> Dict: + order_id = f'dry_run_buy_{randint(0, 10**6)}' + dry_order = { + "id": order_id, + 'pair': pair, + 'price': rate, + 'amount': amount, + 'type': ordertype, + 'side': 'buy', + 'remaining': 0.0, + 'datetime': arrow.utcnow().isoformat(), + 'status': 'closed', + 'fee': None # should this be None or skipped? + } + return order_id, dry_order + + def create_order(self, pair: str, ordertype: str, side: str, amount: float, + rate: float, params: Dict = {}) -> Dict: + pass # TODO: finish this + 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 - } + order_id, dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) + self._dry_run_open_orders[order_id] = dry_order return {'id': order_id} try: @@ -331,17 +342,8 @@ class Exchange(object): 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' - } + order_id, dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) + self._dry_run_open_orders[order_id] = dry_order return {'id': order_id} try: @@ -389,21 +391,11 @@ class Exchange(object): '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] + order_id, dry_order = self.dry_run_order( + pair, "stop_loss_limit", "sell", amount, stop_price, rate) + dry_order.update({"info": {}, "remaining": amount, "status": "open"}) + self._dry_run_open_orders[order_id] = dry_order + return dry_order try: From 5906d37818d4039f7f9452d9574846443b7e8fa1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 15:12:04 +0300 Subject: [PATCH 026/457] code cleanup in _process() --- freqtrade/freqtradebot.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f67be724c..e12456cf8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -7,7 +7,7 @@ import logging import time import traceback from datetime import datetime -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Dict, List, Optional, Tuple import arrow from requests.exceptions import RequestException @@ -166,15 +166,11 @@ class FreqtradeBot(object): trades = Trade.query.filter(Trade.is_open.is_(True)).all() # Extend active-pair whitelist with pairs from open trades - # ensures that tickers are downloaded for open trades - self.active_pair_whitelist.extend([trade.pair for trade in trades - if trade.pair not in self.active_pair_whitelist]) + # It ensures that tickers are downloaded for open trades + self._extend_whitelist_with_trades(self.active_pair_whitelist) - # Create pair-whitelist tuple with (pair, ticker_interval) - pair_whitelist_tuple = [(pair, self.config['ticker_interval']) - for pair in self.active_pair_whitelist] # Refreshing candles - self.dataprovider.refresh(pair_whitelist_tuple, + self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), self.strategy.informative_pairs()) # First process current opened trades @@ -204,6 +200,19 @@ class FreqtradeBot(object): self.state = State.STOPPED return state_changed + def _extend_whitelist_with_trades(self, whitelist: List[str]): + # Query trades from persistence layer + trades = Trade.query.filter(Trade.is_open.is_(True)).all() + + # Extend whitelist with pairs from open trades + whitelist.extend([trade.pair for trade in trades if trade.pair not in whitelist]) + + def _create_pair_whitelist(self, pairs: List[str]) -> List[Tuple[str, str]]: + """ + Create pair-whitelist tuple with (pair, ticker_interval) + """ + return [(pair, self.config['ticker_interval']) for pair in pairs] + def get_target_bid(self, pair: str) -> float: """ Calculates bid target between current ask price and last price From 199e3d2234b6c7d3361bff5f8b5fd1920e8db6ac Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 15:13:21 +0300 Subject: [PATCH 027/457] typo in a comment --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e12456cf8..eda648a64 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -157,7 +157,7 @@ class FreqtradeBot(object): self.pairlists.refresh_pairlist() self.active_pair_whitelist = self.pairlists.whitelist - # Calculating Edge positiong + # Calculating Edge positioning if self.edge: self.edge.calculate() self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist) From fac0e4e603109856c7ca877f0e75f59570cece35 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 15:56:26 +0300 Subject: [PATCH 028/457] more code cleanup in _process() --- freqtrade/freqtradebot.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index eda648a64..6f884a98f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -163,11 +163,11 @@ class FreqtradeBot(object): self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist) # Query trades from persistence layer - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + trades = self._query_trades() # Extend active-pair whitelist with pairs from open trades # It ensures that tickers are downloaded for open trades - self._extend_whitelist_with_trades(self.active_pair_whitelist) + self._extend_whitelist_with_trades(self.active_pair_whitelist, trades) # Refreshing candles self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), @@ -200,10 +200,11 @@ class FreqtradeBot(object): self.state = State.STOPPED return state_changed - def _extend_whitelist_with_trades(self, whitelist: List[str]): + def _query_trades(self) -> List[Any]: # Query trades from persistence layer - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + return Trade.query.filter(Trade.is_open.is_(True)).all() + def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]): # Extend whitelist with pairs from open trades whitelist.extend([trade.pair for trade in trades if trade.pair not in whitelist]) From 7bc874c7fd7e0e750b1a16968875f88cf21f4d55 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 16:12:17 +0300 Subject: [PATCH 029/457] comments adjusted --- freqtrade/freqtradebot.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6f884a98f..295c204e3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -201,11 +201,15 @@ class FreqtradeBot(object): return state_changed def _query_trades(self) -> List[Any]: - # Query trades from persistence layer + """ + Query trades from persistence layer + """ return Trade.query.filter(Trade.is_open.is_(True)).all() def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]): - # Extend whitelist with pairs from open trades + """ + Extend whitelist with pairs from open trades + """ whitelist.extend([trade.pair for trade in trades if trade.pair not in whitelist]) def _create_pair_whitelist(self, pairs: List[str]) -> List[Tuple[str, str]]: From c08a2b6638f735c0f188b2f94d59f539d714cf0d Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 16:23:09 +0300 Subject: [PATCH 030/457] help message fixed --- freqtrade/arguments.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 53a46d141..f462926f9 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -78,7 +78,8 @@ class Arguments(object): ) self.parser.add_argument( '-c', '--config', - help='Specify configuration file (default: %(default)s).', + help='Specify configuration file (default: %(default)s). ' + 'Multiple --config options may be used.', dest='config', action='append', type=str, From 87c82dea3d93a8321a6594e024e48ad3b30c5b17 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 17:00:35 +0300 Subject: [PATCH 031/457] support for multiple --config in the download_backtest_data.py utility --- freqtrade/arguments.py | 4 ++-- scripts/download_backtest_data.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index f462926f9..62f22befc 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -384,10 +384,10 @@ class Arguments(object): self.parser.add_argument( '-c', '--config', - help='Specify configuration file, used for additional exchange parameters. ' + help='Specify configuration file (default: %(default)s). ' 'Multiple --config options may be used.', dest='config', - default=None, + action='append', type=str, metavar='PATH', ) diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index c8fd08747..df3e4bb0c 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -5,12 +5,14 @@ import json import sys from pathlib import Path import arrow +from typing import Any, Dict -from freqtrade import arguments +from freqtrade.arguments import Arguments from freqtrade.arguments import TimeRange from freqtrade.exchange import Exchange from freqtrade.data.history import download_pair_history from freqtrade.configuration import Configuration, set_loggers +from freqtrade.misc import deep_merge_dicts import logging logging.basicConfig( @@ -21,7 +23,7 @@ set_loggers(0) DEFAULT_DL_PATH = 'user_data/data' -arguments = arguments.Arguments(sys.argv[1:], 'download utility') +arguments = Arguments(sys.argv[1:], 'download utility') arguments.testdata_dl_options() args = arguments.parse_args() @@ -29,7 +31,15 @@ timeframes = args.timeframes if args.config: configuration = Configuration(args) - config = configuration._load_config_file(args.config) + + config: Dict[str, Any] = {} + # Now expecting a list of config filenames here, not a string + for path in args.config: + print('Using config: %s ...', path) + # Merge config options, overwriting old values + config = deep_merge_dicts(configuration._load_config_file(path), config) + +### config = configuration._load_config_file(args.config) config['stake_currency'] = '' # Ensure we do not use Exchange credentials From 4fbba98168dfaca60733b055dc21e10f22ea5b12 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 17:54:20 +0300 Subject: [PATCH 032/457] tests adjusted for multiple --config options --- freqtrade/tests/test_arguments.py | 15 ++++++++++----- freqtrade/tests/test_configuration.py | 12 +++++++----- freqtrade/tests/test_main.py | 4 ++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/freqtrade/tests/test_arguments.py b/freqtrade/tests/test_arguments.py index 042d43ed2..0952d1c5d 100644 --- a/freqtrade/tests/test_arguments.py +++ b/freqtrade/tests/test_arguments.py @@ -16,7 +16,7 @@ def test_parse_args_none() -> None: def test_parse_args_defaults() -> None: args = Arguments([], '').get_parsed_arg() - assert args.config == 'config.json' + assert args.config == ['config.json'] assert args.strategy_path is None assert args.datadir is None assert args.loglevel == 0 @@ -24,10 +24,15 @@ def test_parse_args_defaults() -> None: def test_parse_args_config() -> None: args = Arguments(['-c', '/dev/null'], '').get_parsed_arg() - assert args.config == '/dev/null' + assert args.config == ['/dev/null'] args = Arguments(['--config', '/dev/null'], '').get_parsed_arg() - assert args.config == '/dev/null' + assert args.config == ['/dev/null'] + + args = Arguments(['--config', '/dev/null', + '--config', '/dev/zero'], + '').get_parsed_arg() + assert args.config == ['/dev/null', '/dev/zero'] def test_parse_args_db_url() -> None: @@ -139,7 +144,7 @@ def test_parse_args_backtesting_custom() -> None: 'TestStrategy' ] call_args = Arguments(args, '').get_parsed_arg() - assert call_args.config == 'test_conf.json' + assert call_args.config == ['test_conf.json'] assert call_args.live is True assert call_args.loglevel == 0 assert call_args.subparser == 'backtesting' @@ -158,7 +163,7 @@ def test_parse_args_hyperopt_custom() -> None: '--spaces', 'buy' ] call_args = Arguments(args, '').get_parsed_arg() - assert call_args.config == 'test_conf.json' + assert call_args.config == ['test_conf.json'] assert call_args.epochs == 20 assert call_args.loglevel == 0 assert call_args.subparser == 'hyperopt' diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 67445238b..9f11e8ac2 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -50,18 +50,20 @@ def test_load_config_file(default_conf, mocker, caplog) -> None: validated_conf = configuration._load_config_file('somefile') assert file_mock.call_count == 1 assert validated_conf.items() >= default_conf.items() - assert 'internals' in validated_conf - assert log_has('Validating configuration ...', caplog.record_tuples) def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: default_conf['max_open_trades'] = 0 - file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open( + mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) - Configuration(Namespace())._load_config_file('somefile') - assert file_mock.call_count == 1 + args = Arguments([], '').get_parsed_arg() + configuration = Configuration(args) + validated_conf = configuration.load_config() + + assert validated_conf['max_open_trades'] == 0 + assert 'internals' in validated_conf assert log_has('Validating configuration ...', caplog.record_tuples) diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 7aae98ebe..51c95a4a9 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -22,7 +22,7 @@ def test_parse_args_backtesting(mocker) -> None: main(['backtesting']) assert backtesting_mock.call_count == 1 call_args = backtesting_mock.call_args[0][0] - assert call_args.config == 'config.json' + assert call_args.config == ['config.json'] assert call_args.live is False assert call_args.loglevel == 0 assert call_args.subparser == 'backtesting' @@ -35,7 +35,7 @@ def test_main_start_hyperopt(mocker) -> None: main(['hyperopt']) assert hyperopt_mock.call_count == 1 call_args = hyperopt_mock.call_args[0][0] - assert call_args.config == 'config.json' + assert call_args.config == ['config.json'] assert call_args.loglevel == 0 assert call_args.subparser == 'hyperopt' assert call_args.func is not None From da5bef501e4a3f7330beac5c1d44b3192385515b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Feb 2019 17:55:20 +0300 Subject: [PATCH 033/457] cleanup --- scripts/download_backtest_data.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index df3e4bb0c..5dee41bdd 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -39,8 +39,6 @@ if args.config: # Merge config options, overwriting old values config = deep_merge_dicts(configuration._load_config_file(path), config) -### config = configuration._load_config_file(args.config) - config['stake_currency'] = '' # Ensure we do not use Exchange credentials config['exchange']['key'] = '' From 4315c157c75527e408d30491ad270e5fdbbefe75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Feb 2019 20:13:23 +0100 Subject: [PATCH 034/457] 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 d9129cb9c5505f6263af2882b593ea94b1953af4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Feb 2019 21:07:54 +0100 Subject: [PATCH 035/457] 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 036/457] 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 037/457] 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 038/457] 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 039/457] 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 b5758e67f90680afbbe696f64d71add57952ab45 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 21 Feb 2019 00:29:59 +0100 Subject: [PATCH 040/457] order creation cleanup --- freqtrade/exchange/__init__.py | 129 ++++++++++++--------------------- 1 file changed, 46 insertions(+), 83 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 078c8e6be..170f94182 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -284,9 +284,9 @@ class Exchange(object): return price def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, params: Dict = {}) -> Dict: - order_id = f'dry_run_buy_{randint(0, 10**6)}' - dry_order = { + rate: float, params: Dict = {}) -> Tuple[str, Dict[str, Any]]: + order_id = f'dry_run_{side}_{randint(0, 10**6)}' + dry_order = { # TODO: ad additional entry should be added for stoploss limit "id": order_id, 'pair': pair, 'price': rate, @@ -302,84 +302,68 @@ class Exchange(object): def create_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, params: Dict = {}) -> Dict: - pass # TODO: finish this + try: + return self._api.create_order(pair, ordertype, side, + amount, rate, params) + + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to create {ordertype} {side} order on market {pair}.' + f'Tried to {side} amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not create {ordertype} {side} order on market {pair}.' + f'Tried to {side} amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) def buy(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force) -> Dict: + if self._conf['dry_run']: order_id, dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) self._dry_run_open_orders[order_id] = dry_order 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 + # 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}) + 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) + return self.create_order(pair, ordertype, 'buy', amount, rate, params) def sell(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force='gtc') -> Dict: + if self._conf['dry_run']: order_id, dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) self._dry_run_open_orders[order_id] = dry_order 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 + # 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}) + 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) + return self.create_order(pair, ordertype, 'sell', amount, rate, params) 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. """ - + ordertype = "stop_loss_limit" # 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) @@ -392,39 +376,18 @@ class Exchange(object): if self._conf['dry_run']: order_id, dry_order = self.dry_run_order( - pair, "stop_loss_limit", "sell", amount, stop_price, rate) + pair, ordertype, "sell", amount, stop_price) dry_order.update({"info": {}, "remaining": amount, "status": "open"}) self._dry_run_open_orders[order_id] = dry_order return dry_order - try: + params = self._params.copy() + params.update({'stopPrice': stop_price}) - 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) + order = self.create_order(pair, ordertype, 'sell', amount, rate, params) + logger.info('stoploss limit order added for %s. ' + 'stop price: %s. limit: %s' % (pair, stop_price, rate)) + return order @retrier def get_balance(self, currency: str) -> float: From e987a915e8d69f7f51cf0e3e3fc298acae885857 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Feb 2019 06:56:22 +0100 Subject: [PATCH 041/457] 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 042/457] 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 043/457] 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 044/457] 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 69bb6ebaf6506ad7f65a72379880ea87d9539344 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 21 Feb 2019 22:43:15 +0100 Subject: [PATCH 045/457] fix 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 db77d0d5c..2580bf6b1 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -285,7 +285,7 @@ class Exchange(object): def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, params: Dict = {}) -> Tuple[str, Dict[str, Any]]: order_id = f'dry_run_{side}_{randint(0, 10**6)}' - dry_order = { # TODO: ad additional entry should be added for stoploss limit + dry_order = { # TODO: additional entry should be added for stoploss limit "id": order_id, 'pair': pair, 'price': rate, @@ -295,7 +295,7 @@ class Exchange(object): 'remaining': 0.0, 'datetime': arrow.utcnow().isoformat(), 'status': 'closed', - 'fee': None # should this be None or skipped? + 'fee': None } return order_id, dry_order From b79d967371c175f38706bb6409181d71d85f09de Mon Sep 17 00:00:00 2001 From: iuvbio Date: Fri, 22 Feb 2019 01:48:35 +0100 Subject: [PATCH 046/457] add tests, further consolidate orders --- freqtrade/exchange/__init__.py | 36 +++++++--------- freqtrade/tests/exchange/test_exchange.py | 50 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 2580bf6b1..26fd56b59 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -283,7 +283,7 @@ class Exchange(object): return price def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, params: Dict = {}) -> Tuple[str, Dict[str, Any]]: + rate: float, params: Dict = {}) -> Dict[str, Any]: order_id = f'dry_run_{side}_{randint(0, 10**6)}' dry_order = { # TODO: additional entry should be added for stoploss limit "id": order_id, @@ -297,11 +297,15 @@ class Exchange(object): 'status': 'closed', 'fee': None } - return order_id, dry_order + return dry_order def create_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, params: Dict = {}) -> Dict: 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 + return self._api.create_order(pair, ordertype, side, amount, rate, params) @@ -325,13 +329,9 @@ class Exchange(object): rate: float, time_in_force) -> Dict: if self._conf['dry_run']: - order_id, dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) - self._dry_run_open_orders[order_id] = dry_order - return {'id': order_id} - - # 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 + dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) + self._dry_run_open_orders[dry_order["id"]] = dry_order + return {"id": dry_order["id"]} params = self._params.copy() if time_in_force != 'gtc': @@ -343,13 +343,9 @@ class Exchange(object): rate: float, time_in_force='gtc') -> Dict: if self._conf['dry_run']: - order_id, dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) - self._dry_run_open_orders[order_id] = dry_order - return {'id': order_id} - - # 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 + dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) + self._dry_run_open_orders[dry_order["id"]] = dry_order + return {"id": dry_order["id"]} params = self._params.copy() if time_in_force != 'gtc': @@ -363,9 +359,7 @@ class Exchange(object): NOTICE: it is not supported by all exchanges. only binance is tested for now. """ ordertype = "stop_loss_limit" - # 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 @@ -374,10 +368,10 @@ class Exchange(object): 'In stoploss limit order, stop price should be more than limit price') if self._conf['dry_run']: - order_id, dry_order = self.dry_run_order( + dry_order = self.dry_run_order( pair, ordertype, "sell", amount, stop_price) dry_order.update({"info": {}, "remaining": amount, "status": "open"}) - self._dry_run_open_orders[order_id] = dry_order + self._dry_run_open_orders[dry_order["id"]] = dry_order return dry_order params = self._params.copy() diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 72919103c..c646c0db7 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -441,6 +441,56 @@ def test_exchange_has(default_conf, mocker): assert not exchange.exchange_has("deadbeef") +@pytest.mark.parametrize("side", [ + ("buy"), + ("sell") +]) +def test_dry_run_order(default_conf, mocker, side): + default_conf['dry_run'] = True + exchange = get_patched_exchange(mocker, default_conf) + + order = exchange.dry_run_order( + pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) + assert 'id' in order + assert f'dry_run_{side}_' in order["id"] + + +@pytest.mark.parametrize("side", [ + ("buy"), + ("sell") +]) +@pytest.mark.parametrize("ordertype,rate", [ + ("market", None), + ("limit", 200), + ("stop_loss_limit", 200) +]) +def test_create_order(default_conf, mocker, side, ordertype, rate): + api_mock = MagicMock() + order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6)) + 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) + + order = exchange.create_order( + pair='ETH/BTC', ordertype=ordertype, side=side, 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] == ordertype + assert api_mock.create_order.call_args[0][2] == side + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] is rate + + def test_buy_dry_run(default_conf, mocker): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf) From 29b8b79732fcc02ed7a03737cafc5b6adb56e2d1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 22 Feb 2019 13:30:08 +0100 Subject: [PATCH 047/457] 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 048/457] 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 + From 9c54886f14cb3a000d93b0899938c3376c6c050e Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 22 Feb 2019 19:33:05 +0300 Subject: [PATCH 049/457] minor: formatting math expression in FAQ --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 3eaa771ee..127f69e9f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -69,7 +69,7 @@ be evaluated The following calculation is still very rough and not very precise 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. +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. From 9a097214a615f40aa10473b13984cf5d61a7c9d2 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Fri, 22 Feb 2019 19:22:48 +0100 Subject: [PATCH 050/457] return complete dry_order in buy and sell --- freqtrade/exchange/exchange.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 26fd56b59..c3d562aa9 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -331,7 +331,7 @@ class Exchange(object): if self._conf['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) self._dry_run_open_orders[dry_order["id"]] = dry_order - return {"id": dry_order["id"]} + return dry_order # {"id": dry_order["id"]} params = self._params.copy() if time_in_force != 'gtc': @@ -345,7 +345,7 @@ class Exchange(object): if self._conf['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) self._dry_run_open_orders[dry_order["id"]] = dry_order - return {"id": dry_order["id"]} + return dry_order # {"id": dry_order["id"]} params = self._params.copy() if time_in_force != 'gtc': @@ -527,7 +527,7 @@ class Exchange(object): 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) + + interval_in_sec) >= arrow.utcnow().timestamp) @retrier_async async def _async_get_candle_history(self, pair: str, tick_interval: str, From cc0fae8e4e71a5ca2f57d0cc2c1766a867fcbb5b Mon Sep 17 00:00:00 2001 From: iuvbio Date: Fri, 22 Feb 2019 21:13:08 +0100 Subject: [PATCH 051/457] change < to <= --- freqtrade/pairlist/VolumePrecisionPairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/VolumePrecisionPairList.py b/freqtrade/pairlist/VolumePrecisionPairList.py index 634988668..e7147c3b2 100644 --- a/freqtrade/pairlist/VolumePrecisionPairList.py +++ b/freqtrade/pairlist/VolumePrecisionPairList.py @@ -78,7 +78,7 @@ class VolumePrecisionPairList(IPairList): t["symbol"], self._freqtrade.get_target_bid( t["symbol"], t) * (1 + self._freqtrade.strategy.stoploss) - ) < self._freqtrade.get_target_bid(t["symbol"], t) + ) <= self._freqtrade.get_target_bid(t["symbol"], t) )] sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) From 98bca30dfb19825c2e47899d48065465c4a338d8 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Fri, 22 Feb 2019 21:16:31 +0100 Subject: [PATCH 052/457] reorganize imports --- freqtrade/pairlist/VolumePrecisionPairList.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/VolumePrecisionPairList.py b/freqtrade/pairlist/VolumePrecisionPairList.py index e7147c3b2..8caaa6f39 100644 --- a/freqtrade/pairlist/VolumePrecisionPairList.py +++ b/freqtrade/pairlist/VolumePrecisionPairList.py @@ -3,13 +3,16 @@ Static List provider Provides lists as configured in config.json - """ +""" import logging from typing import List + from cachetools import TTLCache, cached from freqtrade.pairlist.IPairList import IPairList from freqtrade import OperationalException + + logger = logging.getLogger(__name__) SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] From 634ce87bba37caedbbab1e6956b483a276c8da45 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 23 Feb 2019 13:32:04 +0100 Subject: [PATCH 053/457] Update ccxt from 1.18.281 to 1.18.287 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cd1d89c6d..aa6051bdd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.281 +ccxt==1.18.287 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From ec6794b9ba6810ce176e6adc1c9af68114501db4 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sat, 23 Feb 2019 16:03:15 +0100 Subject: [PATCH 054/457] fix dry_orders --- freqtrade/exchange/exchange.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index c3d562aa9..94c0e6b3b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -290,15 +290,28 @@ class Exchange(object): 'pair': pair, 'price': rate, 'amount': amount, + "cost": amount * rate, 'type': ordertype, 'side': 'buy', - 'remaining': 0.0, + 'remaining': amount, 'datetime': arrow.utcnow().isoformat(), - 'status': 'closed', - 'fee': None + 'status': "open", + 'fee': None, + "info": {} } + self.store_dry_order(dry_order) return dry_order + def store_dry_order(self, dry_order: Dict) -> None: + closed_order = dry_order.copy() + if closed_order["type"] in ["market", "limit"]: + closed_order.update({ + "status": "closed", + "filled": closed_order["amount"], + "remaining": 0 + }) + self._dry_run_open_orders[closed_order["id"]] = closed_order + def create_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, params: Dict = {}) -> Dict: try: @@ -330,8 +343,7 @@ class Exchange(object): if self._conf['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) - self._dry_run_open_orders[dry_order["id"]] = dry_order - return dry_order # {"id": dry_order["id"]} + return dry_order params = self._params.copy() if time_in_force != 'gtc': @@ -344,8 +356,7 @@ class Exchange(object): if self._conf['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) - self._dry_run_open_orders[dry_order["id"]] = dry_order - return dry_order # {"id": dry_order["id"]} + return dry_order params = self._params.copy() if time_in_force != 'gtc': @@ -370,8 +381,6 @@ class Exchange(object): if self._conf['dry_run']: dry_order = self.dry_run_order( pair, ordertype, "sell", amount, stop_price) - dry_order.update({"info": {}, "remaining": amount, "status": "open"}) - self._dry_run_open_orders[dry_order["id"]] = dry_order return dry_order params = self._params.copy() @@ -586,9 +595,6 @@ class Exchange(object): def get_order(self, order_id: str, pair: str) -> Dict: if self._conf['dry_run']: order = self._dry_run_open_orders[order_id] - order.update({ - 'id': order_id - }) return order try: return self._api.fetch_order(order_id, pair) From 403ed48c3e06537005e0efa024b727f8f2a7dccc Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sat, 23 Feb 2019 16:28:13 +0100 Subject: [PATCH 055/457] rename _store_dry_order --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 94c0e6b3b..23926d00f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -299,10 +299,10 @@ class Exchange(object): 'fee': None, "info": {} } - self.store_dry_order(dry_order) + self._store_dry_order(dry_order) return dry_order - def store_dry_order(self, dry_order: Dict) -> None: + def _store_dry_order(self, dry_order: Dict) -> None: closed_order = dry_order.copy() if closed_order["type"] in ["market", "limit"]: closed_order.update({ From 5fac4f7b45a99c5d70c81f6a92910fa86178109c Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sun, 24 Feb 2019 13:09:32 +0300 Subject: [PATCH 056/457] Edge doc file minor improvements, typos, formatting --- docs/edge.md | 197 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 118 insertions(+), 79 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index b208cb318..a4acffc44 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -3,159 +3,198 @@ This page explains how to use Edge Positioning module in your bot in order to enter into a trade only if the trade has a reasonable win rate and risk reward ratio, and consequently adjust your position size and stoploss. !!! Warning - Edge positioning is not compatible with dynamic whitelist. it overrides dynamic whitelist. + Edge positioning is not compatible with dynamic whitelist. If enabled, it overrides the dynamic whitelist option. !!! Note - Edge won't consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else will be ignored in its calculation. + Edge does not consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else are ignored in its calculation. ## Introduction -Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.

-But it doesn't mean there is no rule, it only means rules should work "most of the time". Let's play a game: we toss a coin, heads: I give you 10$, tails: You give me 10$. Is it an interesting game ? no, it is quite boring, isn't it?

-But let's say the probability that we have heads is 80%, and the probability that we have tails is 20%. Now it is becoming interesting ... -That means 10$ x 80% versus 10$ x 20%. 8$ versus 2$. That means over time you will win 8$ risking only 2$ on each toss of coin.

-Let's complicate it more: you win 80% of the time but only 2$, I win 20% of the time but 8$. The calculation is: 80% * 2$ versus 20% * 8$. It is becoming boring again because overtime you win $1.6$ (80% x 2$) and me $1.6 (20% * 8$) too.

-The question is: How do you calculate that? how do you know if you wanna play? +Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose. + +But it doesn't mean there is no rule, it only means rules should work "most of the time". Let's play a game: we toss a coin, heads: I give you 10$, tails: you give me 10$. Is it an interesting game? No, it's quite boring, isn't it? + +But let's say the probability that we have heads is 80% (because our coin has the displaced distribution of mass or other defect), and the probability that we have tails is 20%. Now it is becoming interesting... + +That means 10$ X 80% versus 10$ X 20%. 8$ versus 2$. That means over time you will win 8$ risking only 2$ on each toss of coin. + +Let's complicate it more: you win 80% of the time but only 2$, I win 20% of the time but 8$. The calculation is: 80% X 2$ versus 20% X 8$. It is becoming boring again because overtime you win $1.6$ (80% X 2$) and me $1.6 (20% X 8$) too. + +The question is: How do you calculate that? How do you know if you wanna play? + The answer comes to two factors: - Win Rate - Risk Reward Ratio - ### Win Rate -Means over X trades what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only If you won or not). +Win Rate (*W*) is is the mean over some amount of trades (*N*) what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only if you won or not). + W = (Number of winning trades) / (Total number of trades) = (Number of winning trades) / N -`W = (Number of winning trades) / (Total number of trades)` +Complementary Loss Rate (*L*) is defined as + + L = (Number of losing trades) / (Total number of trades) = (Number of losing trades) / N + +or, which is the same, as + + R = 1 – W ### Risk Reward Ratio -Risk Reward Ratio is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose: +Risk Reward Ratio (*R*) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose: -`R = Profit / Loss` + R = Profit / Loss Over time, on many trades, you can calculate your risk reward by dividing your average profit on winning trades by your average loss on losing trades: -`Average profit = (Sum of profits) / (Number of winning trades)` + Average profit = (Sum of profits) / (Number of winning trades) -`Average loss = (Sum of losses) / (Number of losing trades)` + Average loss = (Sum of losses) / (Number of losing trades) -`R = (Average profit) / (Average loss)` + R = (Average profit) / (Average loss) ### Expectancy +At this point we can combine *W* and *R* to create an expectancy ratio. This is a simple process of multiplying the risk reward ratio by the percentage of winning trades and subtracting the percentage of losing trades, which is calculated as follows: -At this point we can combine W and R to create an expectancy ratio. This is a simple process of multiplying the risk reward ratio by the percentage of winning trades, and subtracting the percentage of losing trades, which is calculated as follows: - -Expectancy Ratio = (Risk Reward Ratio x Win Rate) – Loss Rate + Expectancy Ratio = (Risk Reward Ratio X Win Rate) – Loss Rate = (R X W) – L So lets say your Win rate is 28% and your Risk Reward Ratio is 5: -`Expectancy = (5 * 0.28) - 0.72 = 0.68` + Expectancy = (5 X 0.28) – 0.72 = 0.68 -Superficially, this means that on average you expect this strategy’s trades to return .68 times the size of your losers. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ. +Superficially, this means that on average you expect this strategy’s trades to return .68 times the size of your loses. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ. It is important to remember that any system with an expectancy greater than 0 is profitable using past data. The key is finding one that will be profitable in the future. -You can also use this number to evaluate the effectiveness of modifications to this system. +You can also use this value to evaluate the effectiveness of modifications to this system. -**NOTICE:** It's important to keep in mind that Edge is testing your expectancy using historical data , there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology, but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades. +**NOTICE:** It's important to keep in mind that Edge is testing your expectancy using historical data, there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology, but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades. ## How does it work? -If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over X trades for each stoploss. Here is an example: +If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over *N* trades for each stoploss. Here is an example: | Pair | Stoploss | Win Rate | Risk Reward Ratio | Expectancy | |----------|:-------------:|-------------:|------------------:|-----------:| -| XZC/ETH | -0.03 | 0.52 |1.359670 | 0.228 | | XZC/ETH | -0.01 | 0.50 |1.176384 | 0.088 | | XZC/ETH | -0.02 | 0.51 |1.115941 | 0.079 | +| XZC/ETH | -0.03 | 0.52 |1.359670 | 0.228 | +| XZC/ETH | -0.04 | 0.51 |1.234539 | 0.117 | The goal here is to find the best stoploss for the strategy in order to have the maximum expectancy. In the above example stoploss at 3% leads to the maximum expectancy according to historical data. -Edge then forces stoploss to your strategy dynamically. +Edge module then forces stoploss value it evaluated to your strategy dynamically. ### Position size -Edge dictates the stake amount for each trade to the bot according to the following factors: +Edge also dictates the stake amount for each trade to the bot according to the following factors: - Allowed capital at risk - Stoploss Allowed capital at risk is calculated as follows: -**allowed capital at risk** = **capital_available_percentage** X **allowed risk per trade** + Allowed capital at risk = (Capital available_percentage) X (Allowed risk per trade) -**Stoploss** is calculated as described above against historical data. +Stoploss is calculated as described above against historical data. Your position size then will be: -**position size** = **allowed capital at risk** / **stoploss** + Position size = (Allowed capital at risk) / Stoploss -Example:
-Let's say the stake currency is ETH and you have 10 ETH on the exchange, your **capital_available_percentage** is 50% and you would allow 1% of risk for each trade. thus your available capital for trading is **10 x 0.5 = 5 ETH** and allowed capital at risk would be **5 x 0.01 = 0.05 ETH**.
-Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.05 / 0.02 = 2.5ETH**.
-Bot takes a position of 2.5ETH on XLM/ETH (call it trade 1). Up next, you receive another buy signal while trade 1 is still open. This time on BTC/ETH market. Edge calculated stoploss for this market at 4%. So your position size would be 0.05 / 0.04 = 1.25ETH (call it trade 2).
-Note that available capital for trading didn’t change for trade 2 even if you had already trade 1. The available capital doesn’t mean the free amount on your wallet.
-Now you have two trades open. The Bot receives yet another buy signal for another market: **ADA/ETH**. This time the stoploss is calculated at 1%. So your position size is **0.05 / 0.01 = 5ETH**. But there are already 4ETH blocked in two previous trades. So the position size for this third trade would be 1ETH.
-Available capital doesn’t change before a position is sold. Let’s assume that trade 1 receives a sell signal and it is sold with a profit of 1ETH. Your total capital on exchange would be 11 ETH and the available capital for trading becomes 5.5ETH.
-So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75**. +Example: + +Let's say the stake currency is ETH and you have 10 ETH on the exchange, your capital available percentage is 50% and you would allow 1% of risk for each trade. thus your available capital for trading is **10 x 0.5 = 5 ETH** and allowed capital at risk would be **5 x 0.01 = 0.05 ETH**. + +Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.05 / 0.02 = 2.5 ETH**. + +Bot takes a position of 2.5 ETH on XLM/ETH (call it trade 1). Up next, you receive another buy signal while trade 1 is still open. This time on **BTC/ETH** market. Edge calculated stoploss for this market at 4%. So your position size would be 0.05 / 0.04 = 1.25 ETH (call it trade 2). + +Note that available capital for trading didn’t change for trade 2 even if you had already trade 1. The available capital doesn’t mean the free amount on your wallet. + +Now you have two trades open. The bot receives yet another buy signal for another market: **ADA/ETH**. This time the stoploss is calculated at 1%. So your position size is **0.05 / 0.01 = 5 ETH**. But there are already 3.75 ETH blocked in two previous trades. So the position size for this third trade would be **5 – 3.75 = 1.25 ETH**. + +Available capital doesn’t change before a position is sold. Let’s assume that trade 1 receives a sell signal and it is sold with a profit of 1 ETH. Your total capital on exchange would be 11 ETH and the available capital for trading becomes 5.5 ETH. + +So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75 ETH**. ## Configurations -Edge has following configurations: +Edge module has following configuration options: #### enabled -If true, then Edge will run periodically.
-(default to false) +If true, then Edge will run periodically. + +(defaults to false) #### process_throttle_secs -How often should Edge run in seconds?
-(default to 3600 so one hour) +How often should Edge run in seconds? + +(defaults to 3600 so one hour) #### calculate_since_number_of_days Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy -Note that it downloads historical data so increasing this number would lead to slowing down the bot.
-(default to 7) +Note that it downloads historical data so increasing this number would lead to slowing down the bot. + +(defaults to 7) #### capital_available_percentage -This is the percentage of the total capital on exchange in stake currency.
-As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital.
-(default to 0.5) +This is the percentage of the total capital on exchange in stake currency. + +As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. + +(defaults to 0.5) #### allowed_risk -Percentage of allowed risk per trade.
-(default to 0.01 [1%]) +Percentage of allowed risk per trade. + +(defaults to 0.01 so 1%) #### stoploss_range_min -Minimum stoploss.
-(default to -0.01) +Minimum stoploss. + +(defaults to -0.01) #### stoploss_range_max -Maximum stoploss.
-(default to -0.10) +Maximum stoploss. + +(defaults to -0.10) #### stoploss_range_step -As an example if this is set to -0.01 then Edge will test the strategy for [-0.01, -0,02, -0,03 ..., -0.09, -0.10] ranges. -Note than having a smaller step means having a bigger range which could lead to slow calculation.
-if you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10.
-(default to -0.01) +As an example if this is set to -0.01 then Edge will test the strategy for \[-0.01, -0,02, -0,03 ..., -0.09, -0.10\] ranges. +Note than having a smaller step means having a bigger range which could lead to slow calculation. + +If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. + +(defaults to -0.01) #### minimum_winrate -It filters pairs which don't have at least minimum_winrate. -This comes handy if you want to be conservative and don't comprise win rate in favor of risk reward ratio.
-(default to 0.60) +It filters out pairs which don't have at least minimum_winrate. + +This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. + +(defaults to 0.60) #### minimum_expectancy -It filters paris which have an expectancy lower than this number . -Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.
-(default to 0.20) +It filters out pairs which have the expectancy lower than this number. + +Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return. + +(defaults to 0.20) #### min_trade_number -When calculating W and R and E (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something.
-(default to 10, it is highly recommended not to decrease this number) +When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. + +Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. + +(defaults to 10, it is highly recommended not to decrease this number) #### max_trade_duration_minute -Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.
-**NOTICE:** While configuring this value, you should take into consideration your ticker interval. as an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. default value is set assuming your strategy interval is relatively small (1m or 5m, etc).
-(default to 1 day, 1440 = 60 * 24) +Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign. + +**NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.). + +(defaults to 1 day, i.e. to 60 * 24 = 1440 minutes) #### remove_pumps -Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.
-(default to false) +Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off. + +(defaults to false) ## Running Edge independently @@ -199,14 +238,14 @@ python3 ./freqtrade/main.py edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step python3 ./freqtrade/main.py edge --timerange=20181110-20181113 ``` -Doing --timerange=-200 will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop. +Doing `--timerange=-200` will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop. The full timerange specification: -* Use last 123 tickframes of data: --timerange=-123 -* Use first 123 tickframes of data: --timerange=123- -* Use tickframes from line 123 through 456: --timerange=123-456 -* Use tickframes till 2018/01/31: --timerange=-20180131 -* Use tickframes since 2018/01/31: --timerange=20180131- -* Use tickframes since 2018/01/31 till 2018/03/01 : --timerange=20180131-20180301 -* Use tickframes between POSIX timestamps 1527595200 1527618600: --timerange=1527595200-1527618600 +* Use last 123 tickframes of data: `--timerange=-123` +* Use first 123 tickframes of data: `--timerange=123-` +* Use tickframes from line 123 through 456: `--timerange=123-456` +* Use tickframes till 2018/01/31: `--timerange=-20180131` +* Use tickframes since 2018/01/31: `--timerange=20180131-` +* Use tickframes since 2018/01/31 till 2018/03/01 : `--timerange=20180131-20180301` +* Use tickframes between POSIX timestamps 1527595200 1527618600: `--timerange=1527595200-1527618600` From 9b288c6933792353c1db7209df0fffd7153b435e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 13:29:22 +0100 Subject: [PATCH 057/457] Add test to specifically test for merged dict --- freqtrade/tests/test_configuration.py | 37 ++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 9f11e8ac2..51098baaa 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -1,15 +1,15 @@ # pragma pylint: disable=missing-docstring, protected-access, invalid-name import json -from argparse import Namespace import logging +from argparse import Namespace +from copy import deepcopy from unittest.mock import MagicMock import pytest -from jsonschema import validate, ValidationError, Draft4Validator +from jsonschema import Draft4Validator, ValidationError, validate -from freqtrade import constants -from freqtrade import OperationalException +from freqtrade import OperationalException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL @@ -67,6 +67,35 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: assert log_has('Validating configuration ...', caplog.record_tuples) +def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None: + conf1 = deepcopy(default_conf) + conf2 = deepcopy(default_conf) + del conf1['exchange']['key'] + del conf1['exchange']['secret'] + del conf2['exchange']['name'] + conf2['exchange']['pair_whitelist'] += ['NANO/BTC'] + + config_files = [conf1, conf2] + + configsmock = MagicMock(side_effect=config_files) + mocker.patch('freqtrade.configuration.Configuration._load_config_file', configsmock) + + arg_list = ['-c', 'test_conf.json', '--config', 'test2_conf.json', ] + args = Arguments(arg_list, '').get_parsed_arg() + configuration = Configuration(args) + validated_conf = configuration.load_config() + + exchange_conf = default_conf['exchange'] + assert validated_conf['exchange']['name'] == exchange_conf['name'] + assert validated_conf['exchange']['key'] == exchange_conf['key'] + assert validated_conf['exchange']['secret'] == exchange_conf['secret'] + assert validated_conf['exchange']['pair_whitelist'] != conf1['exchange']['pair_whitelist'] + assert validated_conf['exchange']['pair_whitelist'] == conf2['exchange']['pair_whitelist'] + + assert 'internals' in validated_conf + assert log_has('Validating configuration ...', caplog.record_tuples) + + def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> None: default_conf['max_open_trades'] = -1 mocker.patch('freqtrade.configuration.open', mocker.mock_open( From 3673dba1e2020e0dc27a3703dcd5087daadf7005 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 24 Feb 2019 13:32:05 +0100 Subject: [PATCH 058/457] Update ccxt from 1.18.287 to 1.18.290 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index aa6051bdd..13584ebc0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.287 +ccxt==1.18.290 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 417bf2c93538fe8cd48ee03f1f1b2786b112327e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 24 Feb 2019 13:32:06 +0100 Subject: [PATCH 059/457] Update jsonschema from 2.6.0 to 3.0.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 13584ebc0..3257de6e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pandas==0.24.1 scikit-learn==0.20.2 joblib==0.13.2 scipy==1.2.1 -jsonschema==2.6.0 +jsonschema==3.0.0 TA-Lib==0.4.17 tabulate==0.8.3 coinmarketcap==5.0.3 From 06f486a8ebd840c4ea9c0567f187a644be89701e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 19:30:05 +0100 Subject: [PATCH 060/457] Add binance exchange subclass --- freqtrade/exchange/__init__.py | 1 + freqtrade/exchange/binance.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 freqtrade/exchange/binance.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 204ed971e..f6db04da6 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -1,2 +1,3 @@ from freqtrade.exchange.exchange import Exchange # noqa: F401 from freqtrade.exchange.kraken import Kraken # noqa: F401 +from freqtrade.exchange.binance import Binance # noqa: F401 diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py new file mode 100644 index 000000000..c8bb92168 --- /dev/null +++ b/freqtrade/exchange/binance.py @@ -0,0 +1,26 @@ +""" Binance exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + +logger = logging.getLogger(__name__) + + +class Binance(Exchange): + + _ft_has = { + "stoploss_on_exchange": True, + } + + def get_order_book(self, pair: str, limit: int = 100) -> dict: + """ + get order book level 2 from exchange + + 20180619: binance support limits but only on specific range + """ + limit_range = [5, 10, 20, 50, 100, 500, 1000] + # get next-higher step in the limit_range list + limit = min(list(filter(lambda x: limit <= x, limit_range))) + + return self.super().get_order_book(pair, limit) From 455b16836680fa1c16da53edcff9fe9ca38bfc5d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 19:35:29 +0100 Subject: [PATCH 061/457] add _ft_has to exchangeclass --- freqtrade/exchange/exchange.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 23926d00f..b6ff261af 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -24,7 +24,7 @@ API_RETRY_COUNT = 4 # Urls to exchange markets, insert quote and base with .format() _EXCHANGE_URLS = { ccxt.bittrex.__name__: '/Market/Index?MarketName={quote}-{base}', - ccxt.binance.__name__: '/tradeDetail.html?symbol={base}_{quote}' + ccxt.binance.__name__: '/tradeDetail.html?symbol={base}_{quote}', } @@ -69,11 +69,17 @@ class Exchange(object): _conf: Dict = {} _params: Dict = {} + # Dict to specify which options each exchange implements + # TODO: this should be merged with attributes from subclasses + # To avoid having to copy/paste this to all subclasses. + _ft_has = { + "stoploss_on_exchange": False, + } + def __init__(self, config: dict) -> None: """ Initializes this module with the given config, - it does basic validation whether the specified - exchange and pairs are valid. + it does basic validation whether the specified exchange and pairs are valid. :return: None """ self._conf.update(config) @@ -236,8 +242,8 @@ class Exchange(object): raise OperationalException( f'Exchange {self.name} does not support market orders.') - if order_types.get('stoploss_on_exchange'): - if self.name != 'Binance': + if (order_types.get("stoploss_on_exchange") + and not self._ft_has.get("stoploss_on_exchange", False)): raise OperationalException( 'On exchange stoploss is not supported for %s.' % self.name ) @@ -368,6 +374,7 @@ class Exchange(object): """ creates a stoploss limit order. NOTICE: it is not supported by all exchanges. only binance is tested for now. + TODO: implementation maybe needs to be move to the binance subclass """ ordertype = "stop_loss_limit" @@ -617,15 +624,6 @@ class Exchange(object): 20180619: binance support limits but only on specific range """ try: - if self._api.name == 'Binance': - limit_range = [5, 10, 20, 50, 100, 500, 1000] - # get next-higher step in the limit_range list - limit = min(list(filter(lambda x: limit <= x, limit_range))) - # above script works like loop below (but with slightly better performance): - # for limitx in limit_range: - # if limit <= limitx: - # limit = limitx - # break return self._api.fetch_l2_order_book(pair, limit) except ccxt.NotSupported as e: From a05155cb75efcfb7a2283ee0296db36e4674f318 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 19:41:47 +0100 Subject: [PATCH 062/457] Adapt failing test --- freqtrade/exchange/binance.py | 2 +- freqtrade/tests/exchange/test_exchange.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index c8bb92168..998f389f0 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,4 +23,4 @@ class Binance(Exchange): # get next-higher step in the limit_range list limit = min(list(filter(lambda x: limit <= x, limit_range))) - return self.super().get_order_book(pair, limit) + return super(Binance, self).get_order_book(pair, limit) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 153be89c1..837c767f6 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -113,7 +113,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): 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 + exchange = ExchangeResolver('Bittrex', default_conf).exchange assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog.record_tuples) From e0b634ba3bd1d7852849b8ee40bf2ac866c38ef0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 19:59:45 +0100 Subject: [PATCH 063/457] Parametrize exchanges and test multiple exchanges --- freqtrade/tests/exchange/test_exchange.py | 127 ++++++++++++---------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 837c767f6..e306d7d23 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -18,6 +18,10 @@ from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re from freqtrade.resolvers.exchange_resolver import ExchangeResolver +# Make sure to always keep one exchange here which is NOT subclassed!! +EXCHANGES = ['bittrex', 'binance', 'kraken', ] + + # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines def get_mock_coro(return_value): async def mock_coro(*args, **kwargs): @@ -26,16 +30,17 @@ def get_mock_coro(return_value): return Mock(wraps=mock_coro) -def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs): +def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + fun, mock_ccxt_fun, **kwargs): with pytest.raises(TemporaryError): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1 with pytest.raises(OperationalException): api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) getattr(exchange, fun)(**kwargs) assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 @@ -763,7 +768,8 @@ def test_get_balances_dry_run(default_conf, mocker): assert exchange.get_balances() == {} -def test_get_balances_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_balances_prod(default_conf, mocker, exchange_name): balance_item = { 'free': 10.0, 'total': 10.0, @@ -777,17 +783,18 @@ def test_get_balances_prod(default_conf, mocker): '3ST': balance_item }) default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert len(exchange.get_balances()) == 3 assert exchange.get_balances()['1ST']['free'] == 10.0 assert exchange.get_balances()['1ST']['total'] == 10.0 assert exchange.get_balances()['1ST']['used'] == 0.0 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "get_balances", "fetch_balance") -def test_get_tickers(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_tickers(default_conf, mocker, exchange_name): api_mock = MagicMock() tick = {'ETH/BTC': { 'symbol': 'ETH/BTC', @@ -802,7 +809,7 @@ def test_get_tickers(default_conf, mocker): } } api_mock.fetch_tickers = MagicMock(return_value=tick) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker tickers = exchange.get_tickers() @@ -813,20 +820,21 @@ def test_get_tickers(default_conf, mocker): assert tickers['BCH/BTC']['bid'] == 0.6 assert tickers['BCH/BTC']['ask'] == 0.5 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "get_tickers", "fetch_tickers") with pytest.raises(OperationalException): api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() api_mock.fetch_tickers = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_tickers() -def test_get_ticker(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_ticker(default_conf, mocker, exchange_name): api_mock = MagicMock() tick = { 'symbol': 'ETH/BTC', @@ -836,7 +844,7 @@ def test_get_ticker(default_conf, mocker): } api_mock.fetch_ticker = MagicMock(return_value=tick) api_mock.markets = {'ETH/BTC': {}} - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # retrieve original ticker ticker = exchange.get_ticker(pair='ETH/BTC') @@ -851,7 +859,7 @@ def test_get_ticker(default_conf, mocker): 'last': 42, } api_mock.fetch_ticker = MagicMock(return_value=tick) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # if not caching the result we should get the same ticker # if not fetching a new result we should get the cached ticker @@ -870,20 +878,21 @@ def test_get_ticker(default_conf, mocker): exchange.get_ticker(pair='ETH/BTC', refresh=False) assert api_mock.fetch_ticker.call_count == 0 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "get_ticker", "fetch_ticker", pair='ETH/BTC', refresh=True) api_mock.fetch_ticker = MagicMock(return_value={}) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_ticker(pair='ETH/BTC', refresh=True) with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'): exchange.get_ticker(pair='XRP/ETH', refresh=True) -def test_get_history(default_conf, mocker, caplog): - exchange = get_patched_exchange(mocker, default_conf) +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_history(default_conf, mocker, caplog, exchange_name): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) tick = [ [ arrow.utcnow().timestamp * 1000, # unix timestamp ms @@ -1051,12 +1060,13 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): assert log_has("Async code raised an exception: TypeError", caplog.record_tuples) -def test_get_order_book(default_conf, mocker, order_book_l2): - default_conf['exchange']['name'] = 'binance' +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_order_book(default_conf, mocker, order_book_l2, exchange_name): + default_conf['exchange']['name'] = exchange_name api_mock = MagicMock() api_mock.fetch_l2_order_book = order_book_l2 - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_book = exchange.get_order_book(pair='ETH/BTC', limit=10) assert 'bids' in order_book assert 'asks' in order_book @@ -1064,19 +1074,20 @@ def test_get_order_book(default_conf, mocker, order_book_l2): assert len(order_book['asks']) == 10 -def test_get_order_book_exception(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_order_book_exception(default_conf, mocker, exchange_name): api_mock = MagicMock() with pytest.raises(OperationalException): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order_book(pair='ETH/BTC', limit=50) with pytest.raises(TemporaryError): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order_book(pair='ETH/BTC', limit=50) with pytest.raises(OperationalException): api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order_book(pair='ETH/BTC', limit=50) @@ -1089,8 +1100,9 @@ def make_fetch_ohlcv_mock(data): return fetch_ohlcv_mock +@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.asyncio -async def test___async_get_candle_history_sort(default_conf, mocker): +async def test___async_get_candle_history_sort(default_conf, mocker, exchange_name): def sort_data(data, key): return sorted(data, key=key) @@ -1108,7 +1120,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker): [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867] ] - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange._api_async.fetch_ohlcv = get_mock_coro(tick) sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the ticker history sort @@ -1170,36 +1182,39 @@ async def test___async_get_candle_history_sort(default_conf, mocker): assert ticks[9][5] == 2.31452783 -def test_cancel_order_dry_run(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) assert exchange.cancel_order(order_id='123', pair='TKN/BTC') is None # Ensure that if not dry_run, we should call API -def test_cancel_order(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_cancel_order(default_conf, mocker, exchange_name): default_conf['dry_run'] = False api_mock = MagicMock() api_mock.cancel_order = MagicMock(return_value=123) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123 with pytest.raises(DependencyException): api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.cancel_order(order_id='_', pair='TKN/BTC') assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, "cancel_order", "cancel_order", order_id='_', pair='TKN/BTC') -def test_get_order(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_order(default_conf, mocker, exchange_name): default_conf['dry_run'] = True order = MagicMock() order.myid = 123 - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange._dry_run_open_orders['X'] = order print(exchange.get_order('X', 'TKN/BTC')) assert exchange.get_order('X', 'TKN/BTC').myid == 123 @@ -1207,33 +1222,28 @@ def test_get_order(default_conf, mocker): default_conf['dry_run'] = False api_mock = MagicMock() api_mock.fetch_order = MagicMock(return_value=456) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.get_order('X', 'TKN/BTC') == 456 with pytest.raises(DependencyException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order(order_id='_', pair='TKN/BTC') assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_order', 'fetch_order', order_id='_', pair='TKN/BTC') -def test_name(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_name(default_conf, mocker, exchange_name): mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - default_conf['exchange']['name'] = 'binance' + default_conf['exchange']['name'] = exchange_name exchange = Exchange(default_conf) - assert exchange.name == 'Binance' - - -def test_id(default_conf, mocker): - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - default_conf['exchange']['name'] = 'binance' - exchange = Exchange(default_conf) - assert exchange.id == 'binance' + assert exchange.name == exchange_name.title() + assert exchange.id == exchange_name def test_get_pair_detail_url(default_conf, mocker, caplog): @@ -1267,7 +1277,8 @@ def test_get_pair_detail_url(default_conf, mocker, caplog): assert log_has('Could not get exchange url for Poloniex', caplog.record_tuples) -def test_get_trades_for_order(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_trades_for_order(default_conf, mocker, exchange_name): order_id = 'ABCD-ABCD' since = datetime(2018, 5, 5) default_conf["dry_run"] = False @@ -1294,13 +1305,13 @@ def test_get_trades_for_order(default_conf, mocker): 'amount': 0.2340606, 'fee': {'cost': 0.06179, 'currency': 'BTC'} }]) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since) assert len(orders) == 1 assert orders[0]['price'] == 165 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_trades_for_order', 'fetch_my_trades', order_id=order_id, pair='LTC/BTC', since=since) @@ -1308,10 +1319,11 @@ def test_get_trades_for_order(default_conf, mocker): assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == [] -def test_get_markets(default_conf, mocker, markets): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_markets(default_conf, mocker, markets, exchange_name): api_mock = MagicMock() api_mock.fetch_markets = markets - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) ret = exchange.get_markets() assert isinstance(ret, list) assert len(ret) == 6 @@ -1319,11 +1331,12 @@ def test_get_markets(default_conf, mocker, markets): assert ret[0]["id"] == "ethbtc" assert ret[0]["symbol"] == "ETH/BTC" - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_markets', 'fetch_markets') -def test_get_fee(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_fee(default_conf, mocker, exchange_name): api_mock = MagicMock() api_mock.calculate_fee = MagicMock(return_value={ 'type': 'taker', @@ -1331,11 +1344,11 @@ def test_get_fee(default_conf, mocker): 'rate': 0.025, 'cost': 0.05 }) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.get_fee() == 0.025 - ccxt_exceptionhandlers(mocker, default_conf, api_mock, + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_fee', 'calculate_fee') From 5c18346cd53f7e5be834a3b7bbbf0168d5d509a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 20:01:20 +0100 Subject: [PATCH 064/457] Add typehint to binance dict --- freqtrade/exchange/binance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 998f389f0..60d35f343 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -9,7 +9,7 @@ logger = logging.getLogger(__name__) class Binance(Exchange): - _ft_has = { + _ft_has: Dict = { "stoploss_on_exchange": True, } From 31be4d24549c6e924573a9f5fa9be82b7dff32e6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 20:08:27 +0100 Subject: [PATCH 065/457] Add parametrized tests --- freqtrade/tests/exchange/test_exchange.py | 64 ++++++++++++++--------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index e306d7d23..d904eb2c5 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, Kraken +from freqtrade.exchange import Exchange, Kraken, Binance 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 @@ -127,6 +127,15 @@ def test_exchange_resolver(default_conf, mocker, caplog): exchange = ExchangeResolver('Kraken', default_conf).exchange assert isinstance(exchange, Exchange) assert isinstance(exchange, Kraken) + assert not isinstance(exchange, Binance) + assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", + caplog.record_tuples) + + exchange = ExchangeResolver('Binance', default_conf).exchange + assert isinstance(exchange, Exchange) + assert isinstance(exchange, Binance) + assert not isinstance(exchange, Kraken) + assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog.record_tuples) @@ -452,9 +461,10 @@ def test_exchange_has(default_conf, mocker): ("buy"), ("sell") ]) -def test_dry_run_order(default_conf, mocker, side): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_dry_run_order(default_conf, mocker, side, exchange_name): default_conf['dry_run'] = True - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) order = exchange.dry_run_order( pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) @@ -471,7 +481,8 @@ def test_dry_run_order(default_conf, mocker, side): ("limit", 200), ("stop_loss_limit", 200) ]) -def test_create_order(default_conf, mocker, side, ordertype, rate): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_create_order(default_conf, mocker, side, ordertype, rate, exchange_name): api_mock = MagicMock() order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6)) api_mock.create_order = MagicMock(return_value={ @@ -483,7 +494,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate): 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) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order( pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200) @@ -508,7 +519,8 @@ def test_buy_dry_run(default_conf, mocker): assert 'dry_run_buy_' in order['id'] -def test_buy_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_buy_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_type = 'market' @@ -522,7 +534,7 @@ def test_buy_prod(default_conf, mocker): 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) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) @@ -553,25 +565,25 @@ def test_buy_prod(default_conf, mocker): # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) @@ -676,7 +688,8 @@ def test_sell_dry_run(default_conf, mocker): assert 'dry_run_sell_' in order['id'] -def test_sell_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_sell_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) order_type = 'market' @@ -690,7 +703,7 @@ def test_sell_prod(default_conf, mocker): 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) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) @@ -715,22 +728,22 @@ def test_sell_prod(default_conf, mocker): # test exception handling with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) @@ -741,23 +754,24 @@ def test_get_balance_dry_run(default_conf, mocker): assert exchange.get_balance(currency='BTC') == 999.9 -def test_get_balance_prod(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_balance_prod(default_conf, mocker, exchange_name): api_mock = MagicMock() api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}}) default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.get_balance(currency='BTC') == 123.4 with pytest.raises(OperationalException): api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_balance(currency='BTC') with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'): - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={})) exchange.get_balance(currency='BTC') @@ -971,7 +985,8 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: @pytest.mark.asyncio -async def test__async_get_candle_history(default_conf, mocker, caplog): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name): tick = [ [ arrow.utcnow().timestamp * 1000, # unix timestamp ms @@ -984,11 +999,10 @@ async def test__async_get_candle_history(default_conf, mocker, caplog): ] caplog.set_level(logging.DEBUG) - exchange = get_patched_exchange(mocker, default_conf) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) # Monkey-patch async function exchange._api_async.fetch_ohlcv = get_mock_coro(tick) - exchange = Exchange(default_conf) pair = 'ETH/BTC' res = await exchange._async_get_candle_history(pair, "5m") assert type(res) is tuple @@ -1007,7 +1021,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog): api_mock = MagicMock() with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_get_candle_history(pair, "5m", (arrow.utcnow().timestamp - 2000) * 1000) From f2fd5205ef043d4d2d0b1d326e144b4079e4359d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 20:13:38 +0100 Subject: [PATCH 066/457] Fix typo --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b6ff261af..de2670de7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -374,7 +374,7 @@ class Exchange(object): """ creates a stoploss limit order. NOTICE: it is not supported by all exchanges. only binance is tested for now. - TODO: implementation maybe needs to be move to the binance subclass + TODO: implementation maybe needs to be moved to the binance subclass """ ordertype = "stop_loss_limit" From 006635003ef8576348057aa6ec5b3a8227456edd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Feb 2019 20:18:41 +0100 Subject: [PATCH 067/457] Fix small typos --- freqtrade/exchange/exchange.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index de2670de7..42ef46eb8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -244,9 +244,9 @@ class Exchange(object): if (order_types.get("stoploss_on_exchange") and not self._ft_has.get("stoploss_on_exchange", False)): - raise OperationalException( - 'On exchange stoploss is not supported for %s.' % self.name - ) + raise OperationalException( + 'On exchange stoploss is not supported for %s.' % self.name + ) def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: """ @@ -621,7 +621,6 @@ class Exchange(object): Notes: 20180619: bittrex doesnt support limits -.- - 20180619: binance support limits but only on specific range """ try: From 185bd1e53c5fd858161a94210514636a6192a303 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 25 Feb 2019 13:32:04 +0100 Subject: [PATCH 068/457] Update ccxt from 1.18.290 to 1.18.292 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3257de6e9..1a1d3436c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.290 +ccxt==1.18.292 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 0c53bd6dd48c71837bc4ffec6cfe468f788e7b13 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Feb 2019 20:00:17 +0100 Subject: [PATCH 069/457] Complete refactor, moving query_trades to persistance as get_open_trades --- freqtrade/freqtradebot.py | 12 ++------ freqtrade/persistence.py | 9 +++++- freqtrade/rpc/rpc.py | 8 ++--- freqtrade/tests/test_persistence.py | 45 +++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 295c204e3..392101e26 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -163,7 +163,7 @@ class FreqtradeBot(object): self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist) # Query trades from persistence layer - trades = self._query_trades() + trades = Trade.get_open_trades() # Extend active-pair whitelist with pairs from open trades # It ensures that tickers are downloaded for open trades @@ -200,12 +200,6 @@ class FreqtradeBot(object): self.state = State.STOPPED return state_changed - def _query_trades(self) -> List[Any]: - """ - Query trades from persistence layer - """ - return Trade.query.filter(Trade.is_open.is_(True)).all() - def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]): """ Extend whitelist with pairs from open trades @@ -265,7 +259,7 @@ class FreqtradeBot(object): avaliable_amount = self.wallets.get_free(self.config['stake_currency']) if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: - open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all()) + open_trades = len(Trade.get_open_trades()) if open_trades >= self.config['max_open_trades']: logger.warning('Can\'t open a new trade: max number of trades is reached') return None @@ -323,7 +317,7 @@ class FreqtradeBot(object): whitelist = copy.deepcopy(self.active_pair_whitelist) # Remove currently opened and latest pairs from whitelist - for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): + for trade in Trade.get_open_trades(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index f9b34fc64..f603b139f 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -5,7 +5,7 @@ This module contains the class to persist trades into SQLite import logging from datetime import datetime from decimal import Decimal -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional import arrow from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String, @@ -371,3 +371,10 @@ class Trade(_DECL_BASE): .filter(Trade.is_open.is_(True))\ .scalar() return total_open_stake_amount or 0 + + @staticmethod + def get_open_trades() -> List[Any]: + """ + Query trades from persistence layer + """ + return Trade.query.filter(Trade.is_open.is_(True)).all() diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index e83d9d41b..5aa9bae35 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -83,7 +83,7 @@ class RPC(object): a remotely exposed function """ # Fetch open trade - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + trades = Trade.get_open_trades() if not trades: raise RPCException('no active trade') else: @@ -118,7 +118,7 @@ class RPC(object): return results def _rpc_status_table(self) -> DataFrame: - trades = Trade.query.filter(Trade.is_open.is_(True)).all() + trades = Trade.get_open_trades() if not trades: raise RPCException('no active order') else: @@ -366,7 +366,7 @@ class RPC(object): if trade_id == 'all': # Execute sell for all open orders - for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): + for trade in Trade.get_open_trades(): _exec_forcesell(trade) Trade.session.flush() return @@ -442,7 +442,7 @@ class RPC(object): if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') - return Trade.query.filter(Trade.is_open.is_(True)).all() + return Trade.get_open_trades() def _rpc_whitelist(self) -> Dict: """ Returns the currently active whitelist""" diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index be6efc2ff..a9519e693 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -629,3 +629,48 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): assert round(trade.stop_loss, 8) == 1.26 assert trade.max_rate == 1.4 assert trade.initial_stop_loss == 0.95 + + +def test_get_open(default_conf, fee): + init(default_conf) + + # Simulate dry_run entries + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + amount=123.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_rate=0.123, + exchange='bittrex', + open_order_id='dry_run_buy_12345' + ) + Trade.session.add(trade) + + trade = Trade( + pair='ETC/BTC', + stake_amount=0.001, + amount=123.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_rate=0.123, + exchange='bittrex', + is_open=False, + open_order_id='dry_run_sell_12345' + ) + Trade.session.add(trade) + + # Simulate prod entry + trade = Trade( + pair='ETC/BTC', + stake_amount=0.001, + amount=123.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_rate=0.123, + exchange='bittrex', + open_order_id='prod_buy_12345' + ) + Trade.session.add(trade) + + assert len(Trade.get_open_trades()) == 2 From ef18ddd866e21f23eba077e063c3fba6b920d109 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 26 Feb 2019 13:32:03 +0100 Subject: [PATCH 070/457] Update ccxt from 1.18.292 to 1.18.296 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1a1d3436c..ff62c1a29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.292 +ccxt==1.18.296 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From bcf5b5fdcb5838f79c7397231854b807227a98a6 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 26 Feb 2019 13:32:04 +0100 Subject: [PATCH 071/457] Update flake8 from 3.7.6 to 3.7.7 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 34d59d802..859d80482 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.6 +flake8==3.7.7 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 pytest==4.3.0 From 6c75b8a36a690d06e989fda4279daf4a3a0c6071 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Feb 2019 20:16:34 +0100 Subject: [PATCH 072/457] Remove pair market url --- freqtrade/exchange/exchange.py | 10 ---------- freqtrade/freqtradebot.py | 4 ---- freqtrade/rpc/rpc.py | 1 - freqtrade/rpc/telegram.py | 6 +++--- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 42ef46eb8..874ed93aa 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -654,16 +654,6 @@ class Exchange(object): except ccxt.BaseError as e: raise OperationalException(e) - def get_pair_detail_url(self, pair: str) -> str: - try: - url_base = self._api.urls.get('www') - base, quote = pair.split('/') - - return url_base + _EXCHANGE_URLS[self._api.id].format(base=base, quote=quote) - except KeyError: - logger.warning('Could not get exchange url for %s', self.name) - return "" - @retrier def get_markets(self) -> List[dict]: try: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d61553305..c51dd4673 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -376,7 +376,6 @@ class FreqtradeBot(object): :return: None """ pair_s = pair.replace('_', '/') - pair_url = self.exchange.get_pair_detail_url(pair) stake_currency = self.config['stake_currency'] fiat_currency = self.config.get('fiat_display_currency', None) time_in_force = self.strategy.order_time_in_force['buy'] @@ -441,7 +440,6 @@ class FreqtradeBot(object): 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), 'pair': pair_s, - 'market_url': pair_url, 'limit': buy_limit_filled_price, 'stake_amount': stake_amount, 'stake_currency': stake_currency, @@ -849,7 +847,6 @@ class FreqtradeBot(object): profit_trade = trade.calc_profit(rate=limit) current_rate = self.exchange.get_ticker(trade.pair)['bid'] profit_percent = trade.calc_profit_percent(limit) - pair_url = self.exchange.get_pair_detail_url(trade.pair) gain = "profit" if profit_percent > 0 else "loss" msg = { @@ -857,7 +854,6 @@ class FreqtradeBot(object): 'exchange': trade.exchange.capitalize(), 'pair': trade.pair, 'gain': gain, - 'market_url': pair_url, 'limit': limit, 'amount': trade.amount, 'open_rate': trade.open_rate, diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 5aa9bae35..af64c3d67 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -103,7 +103,6 @@ class RPC(object): results.append(dict( trade_id=trade.id, pair=trade.pair, - market_url=self._freqtrade.exchange.get_pair_detail_url(trade.pair), date=arrow.get(trade.open_date), open_rate=trade.open_rate, close_rate=trade.close_rate, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 3ce7c9167..9caa7288f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -125,7 +125,7 @@ class Telegram(RPC): else: msg['stake_amount_fiat'] = 0 - message = ("*{exchange}:* Buying [{pair}]({market_url})\n" + message = ("*{exchange}:* Buying {pair}\n" "with limit `{limit:.8f}\n" "({stake_amount:.6f} {stake_currency}").format(**msg) @@ -137,7 +137,7 @@ class Telegram(RPC): msg['amount'] = round(msg['amount'], 8) msg['profit_percent'] = round(msg['profit_percent'] * 100, 2) - message = ("*{exchange}:* Selling [{pair}]({market_url})\n" + message = ("*{exchange}:* Selling {pair}\n" "*Limit:* `{limit:.8f}`\n" "*Amount:* `{amount:.8f}`\n" "*Open Rate:* `{open_rate:.8f}`\n" @@ -193,7 +193,7 @@ class Telegram(RPC): messages = [ "*Trade ID:* `{trade_id}`\n" - "*Current Pair:* [{pair}]({market_url})\n" + "*Current Pair:* {pair}\n" "*Open Since:* `{date}`\n" "*Amount:* `{amount}`\n" "*Open Rate:* `{open_rate:.8f}`\n" From 5c3177cc79ab54486af2d226f91b88c2f6405e9d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Feb 2019 20:16:47 +0100 Subject: [PATCH 073/457] Adapt documentation to remove market_url --- docs/webhook-config.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index e5509d6c9..2b5365e32 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -41,7 +41,6 @@ Possible parameters are: * exchange * pair -* market_url * limit * stake_amount * stake_amount_fiat @@ -56,7 +55,6 @@ Possible parameters are: * exchange * pair * gain -* market_url * limit * amount * open_rate From 79aac473b300a51f2536aa45cb136c0dd7f350ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Feb 2019 20:17:27 +0100 Subject: [PATCH 074/457] Remove market_url from tests --- freqtrade/tests/exchange/test_exchange.py | 31 ----------------------- freqtrade/tests/rpc/test_rpc.py | 2 -- freqtrade/tests/rpc/test_rpc_telegram.py | 28 +++++--------------- freqtrade/tests/rpc/test_rpc_webhook.py | 3 --- freqtrade/tests/test_freqtradebot.py | 5 ---- 5 files changed, 7 insertions(+), 62 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index d904eb2c5..e24d15aed 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1260,37 +1260,6 @@ def test_name(default_conf, mocker, exchange_name): assert exchange.id == exchange_name -def test_get_pair_detail_url(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - default_conf['exchange']['name'] = 'binance' - exchange = Exchange(default_conf) - - url = exchange.get_pair_detail_url('TKN/ETH') - assert 'TKN' in url - assert 'ETH' in url - - url = exchange.get_pair_detail_url('LOOONG/BTC') - assert 'LOOONG' in url - assert 'BTC' in url - - default_conf['exchange']['name'] = 'bittrex' - exchange = Exchange(default_conf) - - url = exchange.get_pair_detail_url('TKN/ETH') - assert 'TKN' in url - assert 'ETH' in url - - url = exchange.get_pair_detail_url('LOOONG/BTC') - assert 'LOOONG' in url - assert 'BTC' in url - - default_conf['exchange']['name'] = 'poloniex' - exchange = Exchange(default_conf) - url = exchange.get_pair_detail_url('LOOONG/BTC') - assert '' == url - assert log_has('Could not get exchange url for Poloniex', caplog.record_tuples) - - @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_trades_for_order(default_conf, mocker, exchange_name): order_id = 'ABCD-ABCD' diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index bb685cad5..2de2668e8 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -51,7 +51,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: assert { 'trade_id': 1, 'pair': 'ETH/BTC', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'date': ANY, 'open_rate': 1.099e-05, 'close_rate': None, @@ -72,7 +71,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: assert { 'trade_id': 1, 'pair': 'ETH/BTC', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'date': ANY, 'open_rate': 1.099e-05, 'close_rate': None, diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 686a92469..9964973e1 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -5,7 +5,7 @@ import re from datetime import datetime from random import randint -from unittest.mock import MagicMock, ANY +from unittest.mock import MagicMock import arrow import pytest @@ -183,7 +183,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, - get_pair_detail_url=MagicMock(), get_fee=fee, get_markets=markets ) @@ -195,7 +194,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: _rpc_trade_status=MagicMock(return_value=[{ 'trade_id': 1, 'pair': 'ETH/BTC', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'date': arrow.utcnow(), 'open_rate': 1.099e-05, 'close_rate': None, @@ -270,7 +268,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No telegram._status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 - assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0] + assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None: @@ -721,7 +719,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'profit', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.172e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, @@ -776,7 +773,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'loss', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.044e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, @@ -796,7 +792,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.get_pair_detail_url', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, @@ -823,7 +818,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'loss', - 'market_url': ANY, 'limit': 1.098e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, @@ -1100,7 +1094,6 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None: 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.099e-05, 'stake_amount': 0.001, 'stake_amount_fiat': 0.0, @@ -1108,7 +1101,7 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None: 'fiat_currency': 'USD' }) assert msg_mock.call_args[0][0] \ - == '*Bittrex:* Buying [ETH/BTC](https://bittrex.com/Market/Index?MarketName=BTC-ETH)\n' \ + == '*Bittrex:* Buying ETH/BTC\n' \ 'with limit `0.00001099\n' \ '(0.001000 BTC,0.000 USD)`' @@ -1129,7 +1122,6 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'exchange': 'Binance', 'pair': 'KEY/ETH', 'gain': 'loss', - 'market_url': 'https://www.binance.com/tradeDetail.html?symbol=KEY_ETH', 'limit': 3.201e-05, 'amount': 1333.3333333333335, 'open_rate': 7.5e-05, @@ -1141,8 +1133,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'sell_reason': SellType.STOP_LOSS.value }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Selling [KEY/ETH]' - '(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n' + == ('*Binance:* Selling KEY/ETH\n' '*Limit:* `0.00003201`\n' '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00007500`\n' @@ -1156,7 +1147,6 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'exchange': 'Binance', 'pair': 'KEY/ETH', 'gain': 'loss', - 'market_url': 'https://www.binance.com/tradeDetail.html?symbol=KEY_ETH', 'limit': 3.201e-05, 'amount': 1333.3333333333335, 'open_rate': 7.5e-05, @@ -1167,8 +1157,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'sell_reason': SellType.STOP_LOSS.value }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Selling [KEY/ETH]' - '(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n' + == ('*Binance:* Selling KEY/ETH\n' '*Limit:* `0.00003201`\n' '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00007500`\n' @@ -1256,7 +1245,6 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.099e-05, 'stake_amount': 0.001, 'stake_amount_fiat': 0.0, @@ -1264,7 +1252,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'fiat_currency': None }) assert msg_mock.call_args[0][0] \ - == '*Bittrex:* Buying [ETH/BTC](https://bittrex.com/Market/Index?MarketName=BTC-ETH)\n' \ + == '*Bittrex:* Buying ETH/BTC\n' \ 'with limit `0.00001099\n' \ '(0.001000 BTC)`' @@ -1284,7 +1272,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'exchange': 'Binance', 'pair': 'KEY/ETH', 'gain': 'loss', - 'market_url': 'https://www.binance.com/tradeDetail.html?symbol=KEY_ETH', 'limit': 3.201e-05, 'amount': 1333.3333333333335, 'open_rate': 7.5e-05, @@ -1296,8 +1283,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'sell_reason': SellType.STOP_LOSS.value }) assert msg_mock.call_args[0][0] \ - == '*Binance:* Selling [KEY/ETH]' \ - '(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n' \ + == '*Binance:* Selling KEY/ETH\n' \ '*Limit:* `0.00003201`\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00007500`\n' \ diff --git a/freqtrade/tests/rpc/test_rpc_webhook.py b/freqtrade/tests/rpc/test_rpc_webhook.py index 002308815..da7aec0a6 100644 --- a/freqtrade/tests/rpc/test_rpc_webhook.py +++ b/freqtrade/tests/rpc/test_rpc_webhook.py @@ -48,7 +48,6 @@ def test_send_msg(default_conf, mocker): 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', - 'market_url': "http://mockedurl/ETH_BTC", 'limit': 0.005, 'stake_amount': 0.8, 'stake_amount_fiat': 500, @@ -73,7 +72,6 @@ def test_send_msg(default_conf, mocker): 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': "profit", - 'market_url': "http://mockedurl/ETH_BTC", 'limit': 0.005, 'amount': 0.8, 'open_rate': 0.004, @@ -127,7 +125,6 @@ def test_exception_send_msg(default_conf, mocker, caplog): 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', - 'market_url': "http://mockedurl/ETH_BTC", 'limit': 0.005, 'stake_amount': 0.8, 'stake_amount_fiat': 500, diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index a0ac6ee99..2f66a5153 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1872,7 +1872,6 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'profit', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.172e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, @@ -1919,7 +1918,6 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'loss', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.044e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, @@ -1974,7 +1972,6 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'loss', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.08801e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, @@ -2146,7 +2143,6 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'profit', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.172e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, @@ -2194,7 +2190,6 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, 'exchange': 'Bittrex', 'pair': 'ETH/BTC', 'gain': 'loss', - 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'limit': 1.044e-05, 'amount': 90.99181073703367, 'open_rate': 1.099e-05, From ef264841534811fb7717152200b0e23d08947662 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Feb 2019 21:01:50 +0100 Subject: [PATCH 075/457] Super() should not be called with parameters source: https://realpython.com/python-super/ --- freqtrade/exchange/binance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 60d35f343..127f4e916 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,4 +23,4 @@ class Binance(Exchange): # get next-higher step in the limit_range list limit = min(list(filter(lambda x: limit <= x, limit_range))) - return super(Binance, self).get_order_book(pair, limit) + return super().get_order_book(pair, limit) From 4c2961f0d9c39fe1d7a477e50855bfa9f3aae27f Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 27 Feb 2019 06:07:16 +0300 Subject: [PATCH 076/457] eliminate recursion in _detect_next_stop_or_sell_point() --- freqtrade/edge/__init__.py | 130 ++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index baef811de..e95b4a3c4 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -351,7 +351,7 @@ class Edge(): return result def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column, - ohlc_columns, stoploss, pair, start_point=0): + ohlc_columns, stoploss, pair): """ Iterate through ohlc_columns recursively in order to find the next trade Next trade opens from the first buy signal noticed to @@ -362,80 +362,80 @@ class Edge(): """ result: list = [] - open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal) + start_point = 0 - # return empty if we don't find trade entry (i.e. buy==1) or - # we find a buy but at the of array - if open_trade_index == -1 or open_trade_index == len(buy_column) - 1: - return [] - else: - open_trade_index += 1 # when a buy signal is seen, - # trade opens in reality on the next candle + while True: + open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal) - stop_price_percentage = stoploss + 1 - open_price = ohlc_columns[open_trade_index, 0] - stop_price = (open_price * stop_price_percentage) + # return empty if we don't find trade entry (i.e. buy==1) or + # we find a buy but at the of array + if open_trade_index == -1 or open_trade_index == len(buy_column) - 1: + break + else: + open_trade_index += 1 # when a buy signal is seen, + # trade opens in reality on the next candle - # Searching for the index where stoploss is hit - stop_index = utf1st.find_1st( - ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller) + stop_price_percentage = stoploss + 1 + open_price = ohlc_columns[open_trade_index, 0] + stop_price = (open_price * stop_price_percentage) - # If we don't find it then we assume stop_index will be far in future (infinite number) - if stop_index == -1: - stop_index = float('inf') + # Searching for the index where stoploss is hit + stop_index = utf1st.find_1st( + ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller) - # Searching for the index where sell is hit - sell_index = utf1st.find_1st(sell_column[open_trade_index:], 1, utf1st.cmp_equal) + # If we don't find it then we assume stop_index will be far in future (infinite number) + if stop_index == -1: + stop_index = float('inf') - # If we don't find it then we assume sell_index will be far in future (infinite number) - if sell_index == -1: - sell_index = float('inf') + # Searching for the index where sell is hit + sell_index = utf1st.find_1st(sell_column[open_trade_index:], 1, utf1st.cmp_equal) - # Check if we don't find any stop or sell point (in that case trade remains open) - # It is not interesting for Edge to consider it so we simply ignore the trade - # And stop iterating there is no more entry - if stop_index == sell_index == float('inf'): - return [] + # If we don't find it then we assume sell_index will be far in future (infinite number) + if sell_index == -1: + sell_index = float('inf') - if stop_index <= sell_index: - exit_index = open_trade_index + stop_index - exit_type = SellType.STOP_LOSS - exit_price = stop_price - elif stop_index > sell_index: - # if exit is SELL then we exit at the next candle - exit_index = open_trade_index + sell_index + 1 + # Check if we don't find any stop or sell point (in that case trade remains open) + # It is not interesting for Edge to consider it so we simply ignore the trade + # And stop iterating there is no more entry + if stop_index == sell_index == float('inf'): + break - # check if we have the next candle - if len(ohlc_columns) - 1 < exit_index: - return [] + if stop_index <= sell_index: + exit_index = open_trade_index + stop_index + exit_type = SellType.STOP_LOSS + exit_price = stop_price + elif stop_index > sell_index: + # if exit is SELL then we exit at the next candle + exit_index = open_trade_index + sell_index + 1 - exit_type = SellType.SELL_SIGNAL - exit_price = ohlc_columns[exit_index, 0] + # check if we have the next candle + if len(ohlc_columns) - 1 < exit_index: + break - trade = {'pair': pair, - 'stoploss': stoploss, - 'profit_percent': '', - 'profit_abs': '', - 'open_time': date_column[open_trade_index], - 'close_time': date_column[exit_index], - 'open_index': start_point + open_trade_index, - 'close_index': start_point + exit_index, - 'trade_duration': '', - 'open_rate': round(open_price, 15), - 'close_rate': round(exit_price, 15), - 'exit_type': exit_type - } + exit_type = SellType.SELL_SIGNAL + exit_price = ohlc_columns[exit_index, 0] - result.append(trade) + trade = {'pair': pair, + 'stoploss': stoploss, + 'profit_percent': '', + 'profit_abs': '', + 'open_time': date_column[open_trade_index], + 'close_time': date_column[exit_index], + 'open_index': start_point + open_trade_index, + 'close_index': start_point + exit_index, + 'trade_duration': '', + 'open_rate': round(open_price, 15), + 'close_rate': round(exit_price, 15), + 'exit_type': exit_type + } - # Calling again the same function recursively but giving - # it a view of exit_index till the end of array - return result + self._detect_next_stop_or_sell_point( - buy_column[exit_index:], - sell_column[exit_index:], - date_column[exit_index:], - ohlc_columns[exit_index:], - stoploss, - pair, - (start_point + exit_index) - ) + result.append(trade) + + # giving a view of exit_index till the end of array + buy_column = buy_column[exit_index:] + sell_column = sell_column[exit_index:] + date_column = date_column[exit_index:] + ohlc_columns = ohlc_columns[exit_index:] + start_point += exit_index + + return result From 761861f0b7a87480f6af0e3837d3281ee430820b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 27 Feb 2019 13:35:06 +0300 Subject: [PATCH 077/457] comments: removed mentioning recursion, typos, etc. --- freqtrade/edge/__init__.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index e95b4a3c4..b4dfa5624 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -353,11 +353,12 @@ class Edge(): def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column, ohlc_columns, stoploss, pair): """ - Iterate through ohlc_columns recursively in order to find the next trade + Iterate through ohlc_columns in order to find the next trade Next trade opens from the first buy signal noticed to The sell or stoploss signal after it. - It then calls itself cutting OHLC, buy_column, sell_colum and date_column - Cut from (the exit trade index) + 1 + It then cuts OHLC, buy_column, sell_column and date_column. + Cut from (the exit trade index) + 1. + Author: https://github.com/mishaker """ @@ -367,13 +368,14 @@ class Edge(): while True: open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal) - # return empty if we don't find trade entry (i.e. buy==1) or - # we find a buy but at the of array + # Return empty if we don't find trade entry (i.e. buy==1) or + # we find a buy but at the end of array if open_trade_index == -1 or open_trade_index == len(buy_column) - 1: break else: - open_trade_index += 1 # when a buy signal is seen, + # When a buy signal is seen, # trade opens in reality on the next candle + open_trade_index += 1 stop_price_percentage = stoploss + 1 open_price = ohlc_columns[open_trade_index, 0] @@ -405,10 +407,10 @@ class Edge(): exit_type = SellType.STOP_LOSS exit_price = stop_price elif stop_index > sell_index: - # if exit is SELL then we exit at the next candle + # If exit is SELL then we exit at the next candle exit_index = open_trade_index + sell_index + 1 - # check if we have the next candle + # Check if we have the next candle if len(ohlc_columns) - 1 < exit_index: break @@ -431,7 +433,7 @@ class Edge(): result.append(trade) - # giving a view of exit_index till the end of array + # Giving a view of exit_index till the end of array buy_column = buy_column[exit_index:] sell_column = sell_column[exit_index:] date_column = date_column[exit_index:] From 768f62a24a019c0cdb115308bc27beec0ffb922e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 27 Feb 2019 13:32:04 +0100 Subject: [PATCH 078/457] Update ccxt from 1.18.296 to 1.18.297 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ff62c1a29..dbdb41e72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.296 +ccxt==1.18.297 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 38d09f9e7853d34b0e7ebb354792db4dcdde5d89 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 27 Feb 2019 13:32:05 +0100 Subject: [PATCH 079/457] Update numpy from 1.16.1 to 1.16.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dbdb41e72..ac8707565 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ cachetools==3.1.0 requests==2.21.0 urllib3==1.24.1 wrapt==1.11.1 -numpy==1.16.1 +numpy==1.16.2 pandas==0.24.1 scikit-learn==0.20.2 joblib==0.13.2 From e5498ca20f3490c4eb80e85ad6b6d57c1767c3b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Feb 2019 17:51:00 +0100 Subject: [PATCH 080/457] Add libssl-dev to fix #1604 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ded74bd18..aeefc0722 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.7.2-slim-stretch RUN apt-get update \ - && apt-get -y install curl build-essential \ + && apt-get -y install curl build-essential libssl-dev \ && apt-get clean \ && pip install --upgrade pip From 13de66d559287326619a5fe5a58dd31befa845a2 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 28 Feb 2019 13:32:06 +0100 Subject: [PATCH 081/457] Update ccxt from 1.18.297 to 1.18.304 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ac8707565..baff3ef9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.297 +ccxt==1.18.304 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 4df44d8b3256e5d7d7294a8f560716a75efbb2e1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 1 Mar 2019 01:26:29 +0300 Subject: [PATCH 082/457] wallets cleanup --- freqtrade/constants.py | 2 ++ freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_wallets.py | 42 ++++++++++++++++----------------- freqtrade/wallets.py | 39 ++++++++++++++++-------------- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4d0907d78..6e504913e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -20,6 +20,7 @@ REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] +DRY_RUN_WALLET = 999.9 TICKER_INTERVAL_MINUTES = { '1m': 1, @@ -60,6 +61,7 @@ CONF_SCHEMA = { }, 'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT}, 'dry_run': {'type': 'boolean'}, + 'dry_run_wallet': {'type': 'number'}, 'process_only_new_candles': {'type': 'boolean'}, 'minimal_roi': { 'type': 'object', diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c51dd4673..9671c4fa9 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').title() self.exchange = ExchangeResolver(exchange_name, self.config).exchange - self.wallets = Wallets(self.exchange) + self.wallets = Wallets(self.config, self.exchange) self.dataprovider = DataProvider(self.config, self.exchange) # Attach Dataprovider to Strategy baseclass diff --git a/freqtrade/tests/test_wallets.py b/freqtrade/tests/test_wallets.py index 8d9adc74c..2c493cfc3 100644 --- a/freqtrade/tests/test_wallets.py +++ b/freqtrade/tests/test_wallets.py @@ -23,13 +23,13 @@ def test_sync_wallet_at_boot(mocker, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert len(freqtrade.wallets.wallets) == 2 - assert freqtrade.wallets.wallets['BNT'].free == 1.0 - assert freqtrade.wallets.wallets['BNT'].used == 2.0 - assert freqtrade.wallets.wallets['BNT'].total == 3.0 - assert freqtrade.wallets.wallets['GAS'].free == 0.260739 - assert freqtrade.wallets.wallets['GAS'].used == 0.0 - assert freqtrade.wallets.wallets['GAS'].total == 0.260739 + assert len(freqtrade.wallets._wallets) == 2 + assert freqtrade.wallets._wallets['BNT'].free == 1.0 + assert freqtrade.wallets._wallets['BNT'].used == 2.0 + assert freqtrade.wallets._wallets['BNT'].total == 3.0 + assert freqtrade.wallets._wallets['GAS'].free == 0.260739 + assert freqtrade.wallets._wallets['GAS'].used == 0.0 + assert freqtrade.wallets._wallets['GAS'].total == 0.260739 assert freqtrade.wallets.get_free('BNT') == 1.0 mocker.patch.multiple( @@ -50,13 +50,13 @@ def test_sync_wallet_at_boot(mocker, default_conf): freqtrade.wallets.update() - assert len(freqtrade.wallets.wallets) == 2 - assert freqtrade.wallets.wallets['BNT'].free == 1.2 - assert freqtrade.wallets.wallets['BNT'].used == 1.9 - assert freqtrade.wallets.wallets['BNT'].total == 3.5 - assert freqtrade.wallets.wallets['GAS'].free == 0.270739 - assert freqtrade.wallets.wallets['GAS'].used == 0.1 - assert freqtrade.wallets.wallets['GAS'].total == 0.260439 + assert len(freqtrade.wallets._wallets) == 2 + assert freqtrade.wallets._wallets['BNT'].free == 1.2 + assert freqtrade.wallets._wallets['BNT'].used == 1.9 + assert freqtrade.wallets._wallets['BNT'].total == 3.5 + assert freqtrade.wallets._wallets['GAS'].free == 0.270739 + assert freqtrade.wallets._wallets['GAS'].used == 0.1 + assert freqtrade.wallets._wallets['GAS'].total == 0.260439 assert freqtrade.wallets.get_free('GAS') == 0.270739 assert freqtrade.wallets.get_used('GAS') == 0.1 assert freqtrade.wallets.get_total('GAS') == 0.260439 @@ -81,11 +81,11 @@ def test_sync_wallet_missing_data(mocker, default_conf): freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert len(freqtrade.wallets.wallets) == 2 - assert freqtrade.wallets.wallets['BNT'].free == 1.0 - assert freqtrade.wallets.wallets['BNT'].used == 2.0 - assert freqtrade.wallets.wallets['BNT'].total == 3.0 - assert freqtrade.wallets.wallets['GAS'].free == 0.260739 - assert freqtrade.wallets.wallets['GAS'].used is None - assert freqtrade.wallets.wallets['GAS'].total == 0.260739 + assert len(freqtrade.wallets._wallets) == 2 + assert freqtrade.wallets._wallets['BNT'].free == 1.0 + assert freqtrade.wallets._wallets['BNT'].used == 2.0 + assert freqtrade.wallets._wallets['BNT'].total == 3.0 + assert freqtrade.wallets._wallets['GAS'].free == 0.260739 + assert freqtrade.wallets._wallets['GAS'].used is None + assert freqtrade.wallets._wallets['GAS'].total == 0.260739 assert freqtrade.wallets.get_free('GAS') == 0.260739 diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 1f1d2c511..c8ab90276 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -1,15 +1,16 @@ # pragma pylint: disable=W0603 """ Wallet """ + import logging -from typing import Dict, Any, NamedTuple +from typing import Dict, NamedTuple from freqtrade.exchange import Exchange +from freqtrade import constants logger = logging.getLogger(__name__) # wallet data structure class Wallet(NamedTuple): - exchange: str currency: str free: float = 0 used: float = 0 @@ -18,17 +19,19 @@ class Wallet(NamedTuple): class Wallets(object): - def __init__(self, exchange: Exchange) -> None: - self.exchange = exchange - self.wallets: Dict[str, Any] = {} + def __init__(self, config: dict, exchange: Exchange) -> None: + self._config = config + self._exchange = exchange + self._wallets: Dict[str, Wallet] = {} + self.update() def get_free(self, currency) -> float: - if self.exchange._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self.wallets.get(currency) + balance = self._wallets.get(currency) if balance and balance.free: return balance.free else: @@ -36,10 +39,10 @@ class Wallets(object): def get_used(self, currency) -> float: - if self.exchange._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self.wallets.get(currency) + balance = self._wallets.get(currency) if balance and balance.used: return balance.used else: @@ -47,25 +50,25 @@ class Wallets(object): def get_total(self, currency) -> float: - if self.exchange._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) - balance = self.wallets.get(currency) + balance = self._wallets.get(currency) if balance and balance.total: return balance.total else: return 0 def update(self) -> None: - balances = self.exchange.get_balances() + + balances = self._exchange.get_balances() for currency in balances: - self.wallets[currency] = Wallet( - self.exchange.id, + self._wallets[currency] = Wallet( currency, balances[currency].get('free', None), balances[currency].get('used', None), balances[currency].get('total', None) ) - logger.info('Wallets synced ...') + logger.info('Wallets synced.') From b792f00553362a0e5ba17be7577e08e3d0f3b360 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 1 Mar 2019 02:13:16 +0300 Subject: [PATCH 083/457] exchange cleanup --- freqtrade/exchange/exchange.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 874ed93aa..474595787 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -66,7 +66,7 @@ def retrier(f): class Exchange(object): - _conf: Dict = {} + _config: Dict = {} _params: Dict = {} # Dict to specify which options each exchange implements @@ -82,7 +82,7 @@ class Exchange(object): it does basic validation whether the specified exchange and pairs are valid. :return: None """ - self._conf.update(config) + self._config.update(config) self._cached_ticker: Dict[str, Any] = {} @@ -212,7 +212,7 @@ class Exchange(object): logger.warning('Unable to validate pairs (assuming they are correct).') # return - stake_cur = self._conf['stake_currency'] + stake_cur = self._config['stake_currency'] for pair in pairs: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs # TODO: add a support for having coins in BTC/USDT format @@ -347,7 +347,7 @@ class Exchange(object): def buy(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force) -> Dict: - if self._conf['dry_run']: + if self._config['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate) return dry_order @@ -360,7 +360,7 @@ class Exchange(object): def sell(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force='gtc') -> Dict: - if self._conf['dry_run']: + if self._config['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate) return dry_order @@ -385,7 +385,7 @@ class Exchange(object): raise OperationalException( 'In stoploss limit order, stop price should be more than limit price') - if self._conf['dry_run']: + if self._config['dry_run']: dry_order = self.dry_run_order( pair, ordertype, "sell", amount, stop_price) return dry_order @@ -400,8 +400,8 @@ class Exchange(object): @retrier def get_balance(self, currency: str) -> float: - if self._conf['dry_run']: - return 999.9 + if self._config['dry_run']: + return constants.DRY_RUN_WALLET # ccxt exception is already handled by get_balances balances = self.get_balances() @@ -413,7 +413,7 @@ class Exchange(object): @retrier def get_balances(self) -> dict: - if self._conf['dry_run']: + if self._config['dry_run']: return {} try: @@ -584,7 +584,7 @@ class Exchange(object): @retrier def cancel_order(self, order_id: str, pair: str) -> None: - if self._conf['dry_run']: + if self._config['dry_run']: return try: @@ -600,7 +600,7 @@ class Exchange(object): @retrier def get_order(self, order_id: str, pair: str) -> Dict: - if self._conf['dry_run']: + if self._config['dry_run']: order = self._dry_run_open_orders[order_id] return order try: @@ -637,7 +637,7 @@ class Exchange(object): @retrier def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: - if self._conf['dry_run']: + if self._config['dry_run']: return [] if not self.exchange_has('fetchMyTrades'): return [] From e8ea2e6f05b50b91ad1eafe03dade853c9c6eda7 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 1 Mar 2019 13:32:06 +0100 Subject: [PATCH 084/457] Update ccxt from 1.18.304 to 1.18.309 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index baff3ef9c..e6b2f1b2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.304 +ccxt==1.18.309 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 0fc5445003acc3d9edf9ee585c4b1ba0feff9009 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 1 Mar 2019 13:32:07 +0100 Subject: [PATCH 085/457] Update jsonschema from 3.0.0 to 3.0.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e6b2f1b2e..8a7c95ddb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pandas==0.24.1 scikit-learn==0.20.2 joblib==0.13.2 scipy==1.2.1 -jsonschema==3.0.0 +jsonschema==3.0.1 TA-Lib==0.4.17 tabulate==0.8.3 coinmarketcap==5.0.3 From 28a70eba07976e81994141e697e6c8b6ac1cb377 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 2 Mar 2019 13:32:03 +0100 Subject: [PATCH 086/457] Update ccxt from 1.18.309 to 1.18.313 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8a7c95ddb..8cee146f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.309 +ccxt==1.18.313 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 6bcfe6587749e8cb9f205d862bfe596e93e94829 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 2 Mar 2019 13:32:04 +0100 Subject: [PATCH 087/457] Update scikit-learn from 0.20.2 to 0.20.3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8cee146f5..a72198ac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ urllib3==1.24.1 wrapt==1.11.1 numpy==1.16.2 pandas==0.24.1 -scikit-learn==0.20.2 +scikit-learn==0.20.3 joblib==0.13.2 scipy==1.2.1 jsonschema==3.0.1 From 24c587518ad60775f5fa3b69ba52c438034b42b6 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sat, 2 Mar 2019 17:24:28 +0100 Subject: [PATCH 088/457] add precision_filter --- config_full.json.example | 3 ++- freqtrade/pairlist/IPairList.py | 3 ++- freqtrade/pairlist/VolumePairList.py | 32 ++++++++++++++++++++++------ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 23a36dd4c..0f46a62e3 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -49,7 +49,8 @@ "method": "VolumePairList", "config": { "number_assets": 20, - "sort_key": "quoteVolume" + "sort_key": "quoteVolume", + "precision_filter": false } }, "exchange": { diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 6b5b0db4b..4e9e28b3d 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -18,6 +18,7 @@ class IPairList(ABC): self._config = config self._whitelist = self._config['exchange']['pair_whitelist'] self._blacklist = self._config['exchange'].get('pair_blacklist', []) + self._markets = self._freqtrade.exchange.markets @property def name(self) -> str: @@ -66,7 +67,7 @@ class IPairList(ABC): black_listed """ sanitized_whitelist = whitelist - markets = self._freqtrade.exchange.get_markets() + markets = list(self._markets.values()) # Filter to markets in stake currency markets = [m for m in markets if m['quote'] == self._config['stake_currency']] diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 262e4bf59..7f1985d43 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -1,5 +1,5 @@ """ -Static List provider +Volume PairList provider Provides lists as configured in config.json @@ -26,6 +26,7 @@ class VolumePairList(IPairList): 'for "pairlist.config.number_assets"') self._number_pairs = self._whitelistconf['number_assets'] self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') + self._precision_filter = self._whitelistconf.get('precision_filter', False) if not self._freqtrade.exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -52,9 +53,9 @@ class VolumePairList(IPairList): -> Please overwrite in subclasses """ # Generate dynamic whitelist - pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) - # Validate whitelist to only have active market pairs - self._whitelist = self._validate_whitelist(pairs)[:self._number_pairs] + self._whitelist = self._gen_pair_whitelist( + self._config['stake_currency'], self._sort_key)[:self._number_pairs] + logger.info(f"Searching pairs: {self._whitelist}") @cached(TTLCache(maxsize=1, ttl=1800)) def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: @@ -69,7 +70,26 @@ class VolumePairList(IPairList): # check length so that we make sure that '/' is actually in the string tickers = [v for k, v in tickers.items() if len(k.split('/')) == 2 and k.split('/')[1] == base_currency] - sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) - pairs = [s['symbol'] for s in sorted_tickers] + # Validate whitelist to only have active market pairs + valid_pairs = self._validate_whitelist([s['symbol'] for s in sorted_tickers]) + valid_tickers = [t for t in sorted_tickers if t["symbol"] in valid_pairs] + + if self._freqtrade.strategy.stoploss is not None and self._precision_filter: + + logger.debug(f"Markets: {list(self._markets)}") + stop_prices = [self._freqtrade.get_target_bid(t["symbol"], t) + * (1 + self._freqtrade.strategy.stoploss) for t in valid_tickers] + rates = [sp * 0.99 for sp in stop_prices] + logger.debug("\n".join([f"{sp} : {r}" for sp, r in zip(stop_prices[:10], rates[:10])])) + for i, t in enumerate(valid_tickers): + sp = self._freqtrade.exchange.symbol_price_prec(t["symbol"], stop_prices[i]) + r = self._freqtrade.exchange.symbol_price_prec(t["symbol"], rates[i]) + logger.debug(f"{t['symbol']} - {sp} : {r}") + if sp <= r: + logger.info(f"Removed {t['symbol']} from whitelist, " + f"because stop price {sp} would be <= stop limit {r}") + valid_tickers.remove(t) + + pairs = [s['symbol'] for s in valid_tickers] return pairs From c36fa0c7e2b368a2b20ba1997de8394e6aec4f39 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sat, 2 Mar 2019 17:24:48 +0100 Subject: [PATCH 089/457] add ticker argumet to get_target_bid --- freqtrade/freqtradebot.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 92bdbc042..86f739f2f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -206,7 +206,7 @@ class FreqtradeBot(object): self.state = State.STOPPED return state_changed - def get_target_bid(self, pair: str) -> float: + def get_target_bid(self, pair: str, ticker: Dict = None) -> float: """ Calculates bid target between current ask price and last price :return: float: Price @@ -223,8 +223,9 @@ class FreqtradeBot(object): logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate) used_rate = order_book_rate else: - logger.info('Using Last Ask / Last Price') - ticker = self.exchange.get_ticker(pair) + if not ticker: + logger.info('Using Last Ask / Last Price') + ticker = self.exchange.get_ticker(pair) if ticker['ask'] < ticker['last']: ticker_rate = ticker['ask'] else: @@ -269,12 +270,10 @@ class FreqtradeBot(object): return stake_amount def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]: - markets = self.exchange.get_markets() - markets = [m for m in markets if m['symbol'] == pair] - if not markets: - raise ValueError(f'Can\'t get market information for symbol {pair}') - - market = markets[0] + try: + market = self.exchange.markets[pair] + except KeyError: + raise ValueError(f"Can't get market information for symbol {pair}") if 'limits' not in market: return None From e1ae0d7e903df034a17c4f1608420126cc41042a Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sat, 2 Mar 2019 18:53:42 +0100 Subject: [PATCH 090/457] remove markets changes --- freqtrade/exchange/exchange.py | 4 +- freqtrade/freqtradebot.py | 16 ++-- freqtrade/pairlist/IPairList.py | 6 +- freqtrade/tests/conftest.py | 94 +++++++++++++++++++++++ freqtrade/tests/exchange/test_exchange.py | 2 +- freqtrade/tests/pairlist/test_pairlist.py | 10 ++- 6 files changed, 117 insertions(+), 15 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 25d3e62c0..0ca939ec5 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -715,7 +715,9 @@ class Exchange(object): @retrier def get_markets(self) -> List[dict]: try: - return self._api.fetch_markets() + markets = self._api.fetch_markets() + self.markets.update({m["symbol"]: m for m in markets}) + return markets except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load markets due to {e.__class__.__name__}. Message: {e}') diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 86f739f2f..28a7c9146 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -206,7 +206,7 @@ class FreqtradeBot(object): self.state = State.STOPPED return state_changed - def get_target_bid(self, pair: str, ticker: Dict = None) -> float: + def get_target_bid(self, pair: str, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price :return: float: Price @@ -223,9 +223,11 @@ class FreqtradeBot(object): logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate) used_rate = order_book_rate else: - if not ticker: + if not tick: logger.info('Using Last Ask / Last Price') ticker = self.exchange.get_ticker(pair) + else: + ticker = tick if ticker['ask'] < ticker['last']: ticker_rate = ticker['ask'] else: @@ -270,10 +272,12 @@ class FreqtradeBot(object): return stake_amount def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]: - try: - market = self.exchange.markets[pair] - except KeyError: - raise ValueError(f"Can't get market information for symbol {pair}") + markets = self.exchange.get_markets() + markets = [m for m in markets if m['symbol'] == pair] + if not markets: + raise ValueError(f'Can\'t get market information for symbol {pair}') + + market = markets[0] if 'limits' not in market: return None diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 4e9e28b3d..7675c1eee 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -18,7 +18,7 @@ class IPairList(ABC): self._config = config self._whitelist = self._config['exchange']['pair_whitelist'] self._blacklist = self._config['exchange'].get('pair_blacklist', []) - self._markets = self._freqtrade.exchange.markets + self._markets = self._freqtrade.exchange.get_markets() @property def name(self) -> str: @@ -67,7 +67,7 @@ class IPairList(ABC): black_listed """ sanitized_whitelist = whitelist - markets = list(self._markets.values()) + markets = self._freqtrade.exchange.get_markets() # Filter to markets in stake currency markets = [m for m in markets if m['quote'] == self._config['stake_currency']] @@ -75,7 +75,7 @@ class IPairList(ABC): for market in markets: pair = market['symbol'] - # pair is not int the generated dynamic market, or in the blacklist ... ignore it + # pair is not in the generated dynamic market, or in the blacklist ... ignore it if pair not in whitelist or pair in self.blacklist: continue # else the pair is valid diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index d6628d925..98171844a 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -375,6 +375,78 @@ def markets(): }, }, 'info': '', + }, + { + 'id': 'BTTBTC', + 'symbol': 'BTT/BTC', + 'base': 'BTT', + 'quote': 'BTC', + 'active': True, + 'precision': { + 'base': 8, + 'quote': 8, + 'amount': 0, + 'price': 8 + }, + 'limits': { + 'amount': { + 'min': 1.0, + 'max': 90000000.0 + }, + 'price': { + 'min': None, + 'max': None + }, + 'cost': { + 'min': 0.001, + 'max': None + } + }, + 'info': "", + }, + { + 'id': 'USDT-ETH', + 'symbol': 'ETH/USDT', + 'base': 'ETH', + 'quote': 'USDT', + 'precision': { + 'amount': 8, + 'price': 8 + }, + 'limits': { + 'amount': { + 'min': 0.02214286, + 'max': None + }, + 'price': { + 'min': 1e-08, + 'max': None + } + }, + 'active': True, + 'info': "" + }, + { + 'id': 'USDT-LTC', + 'symbol': 'LTC/USDT', + 'base': 'LTC', + 'quote': 'USDT', + 'active': True, + 'precision': { + 'amount': 8, + 'price': 8 + }, + 'limits': { + 'amount': { + 'min': 0.06646786, + 'max': None + }, + 'price': { + 'min': 1e-08, + 'max': None + } + }, + 'info': "" } ]) @@ -642,6 +714,28 @@ def tickers(): 'quoteVolume': 1401.65697943, 'info': {} }, + 'BTT/BTC': { + 'symbol': 'BTT/BTC', + 'timestamp': 1550936557206, + 'datetime': '2019-02-23T15:42:37.206Z', + 'high': 0.00000026, + 'low': 0.00000024, + 'bid': 0.00000024, + 'bidVolume': 2446894197.0, + 'ask': 0.00000025, + 'askVolume': 2447913837.0, + 'vwap': 0.00000025, + 'open': 0.00000026, + 'close': 0.00000024, + 'last': 0.00000024, + 'previousClose': 0.00000026, + 'change': -0.00000002, + 'percentage': -7.692, + 'average': None, + 'baseVolume': 4886464537.0, + 'quoteVolume': 1215.14489611, + 'info': {} + }, 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 15da5e924..4fadfd4b7 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1264,7 +1264,7 @@ def test_get_markets(default_conf, mocker, markets): exchange = get_patched_exchange(mocker, default_conf, api_mock) ret = exchange.get_markets() assert isinstance(ret, list) - assert len(ret) == 6 + assert len(ret) == 9 assert ret[0]["id"] == "ethbtc" assert ret[0]["symbol"] == "ETH/BTC" diff --git a/freqtrade/tests/pairlist/test_pairlist.py b/freqtrade/tests/pairlist/test_pairlist.py index 9f90aac6e..e78404587 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -80,7 +80,7 @@ def test_refresh_pairlist_dynamic(mocker, markets, tickers, whitelist_conf): freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) # argument: use the whitelist dynamically by exchange-volume - whitelist = ['ETH/BTC', 'TKN/BTC'] + whitelist = ['ETH/BTC', 'TKN/BTC', 'BTT/BTC'] freqtradebot.pairlists.refresh_pairlist() assert whitelist == freqtradebot.pairlists.whitelist @@ -116,17 +116,19 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) # Test to retrieved BTC sorted on quoteVolume (default) whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='quoteVolume') - assert whitelist == ['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC'] + assert whitelist == ['ETH/BTC', 'TKN/BTC', 'BTT/BTC'] # Test to retrieve BTC sorted on bidVolume whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='bidVolume') - assert whitelist == ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'BLK/BTC'] + assert whitelist == ['BTT/BTC', 'TKN/BTC', 'ETH/BTC'] # Test with USDT sorted on quoteVolume (default) + freqtrade.config['stake_currency'] = 'USDT' # this has to be set, otherwise markets are removed whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='USDT', key='quoteVolume') - assert whitelist == ['TKN/USDT', 'ETH/USDT', 'LTC/USDT', 'BLK/USDT'] + assert whitelist == ['ETH/USDT', 'LTC/USDT'] # Test with ETH (our fixture does not have ETH, so result should be empty) + freqtrade.config['stake_currency'] = 'ETH' whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='ETH', key='quoteVolume') assert whitelist == [] From 064f6629ab9303faf6a921ec4d5fea4a44c49f4e Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 3 Mar 2019 00:35:25 +0100 Subject: [PATCH 091/457] delete separate pairlist --- freqtrade/pairlist/VolumePrecisionPairList.py | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 freqtrade/pairlist/VolumePrecisionPairList.py diff --git a/freqtrade/pairlist/VolumePrecisionPairList.py b/freqtrade/pairlist/VolumePrecisionPairList.py deleted file mode 100644 index 8caaa6f39..000000000 --- a/freqtrade/pairlist/VolumePrecisionPairList.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Static List provider - -Provides lists as configured in config.json - -""" -import logging -from typing import List - -from cachetools import TTLCache, cached - -from freqtrade.pairlist.IPairList import IPairList -from freqtrade import OperationalException - - -logger = logging.getLogger(__name__) - -SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] - - -class VolumePrecisionPairList(IPairList): - - def __init__(self, freqtrade, config: dict) -> None: - super().__init__(freqtrade, config) - self._whitelistconf = self._config.get('pairlist', {}).get('config') - if 'number_assets' not in self._whitelistconf: - raise OperationalException( - f'`number_assets` not specified. Please check your configuration ' - 'for "pairlist.config.number_assets"') - self._number_pairs = self._whitelistconf['number_assets'] - self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') - - if not self._freqtrade.exchange.exchange_has('fetchTickers'): - raise OperationalException( - 'Exchange does not support dynamic whitelist.' - 'Please edit your config and restart the bot' - ) - if not self._validate_keys(self._sort_key): - raise OperationalException( - f'key {self._sort_key} not in {SORT_VALUES}') - - def _validate_keys(self, key): - return key in SORT_VALUES - - def short_desc(self) -> str: - """ - Short whitelist method description - used for startup-messages - -> Please overwrite in subclasses - """ - return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs." - - def refresh_pairlist(self) -> None: - """ - Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively - -> Please overwrite in subclasses - """ - # Generate dynamic whitelist - pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) - # Validate whitelist to only have active market pairs - self._whitelist = self._validate_whitelist(pairs)[:self._number_pairs] - - @cached(TTLCache(maxsize=1, ttl=1800)) - def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: - """ - Updates the whitelist with with a dynamically generated list - :param base_currency: base currency as str - :param key: sort key (defaults to 'quoteVolume') - :return: List of pairs - """ - - tickers = self._freqtrade.exchange.get_tickers() - # check length so that we make sure that '/' is actually in the string - tickers = [v for k, v in tickers.items() - if len(k.split('/')) == 2 and k.split('/')[1] == base_currency] - - if self._freqtrade.strategy.stoploss is not None: - precisions = [self._freqtrade.exchange.markets[ - t["symbol"]]["precision"].get("price") for t in tickers] - tickers = [t for t, p in zip(tickers, precisions) if ( - self._freqtrade.exchange.symbol_price_prec( - t["symbol"], - self._freqtrade.get_target_bid( - t["symbol"], t) * (1 + self._freqtrade.strategy.stoploss) - ) <= self._freqtrade.get_target_bid(t["symbol"], t) - )] - - sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) - pairs = [s['symbol'] for s in sorted_tickers] - return pairs From 13ba5ba0dbfbef83020bac5018e9070aeda2ed02 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 3 Mar 2019 13:32:03 +0100 Subject: [PATCH 092/457] Update ccxt from 1.18.313 to 1.18.322 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a72198ac3..7f66d84eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.313 +ccxt==1.18.322 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From df79098adc008922344efaf02e008cef211cf218 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 3 Mar 2019 13:37:54 +0100 Subject: [PATCH 093/457] update docs --- docs/configuration.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 108e264c6..c4b26eba9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -96,7 +96,7 @@ To allow the bot to trade all the available `stake_currency` in your account set "stake_amount" : "unlimited", ``` -In this case a trade amount is calclulated as: +In this case a trade amount is calclulated as: ```python currency_balanse / (max_open_trades - current_open_trades) @@ -280,13 +280,15 @@ By default, a Static Pairlist is used (configured as `"pair_whitelist"` under th * `"VolumePairList"` * Formerly available as `--dynamic-whitelist []` * Selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. + * Possibility to filter low-value coins that would not allow setting a stop loss ```json "pairlist": { "method": "VolumePairList", "config": { "number_assets": 20, - "sort_key": "quoteVolume" + "sort_key": "quoteVolume", + "precision_filter": false } }, ``` From e2cbb7e7dadba1d91390dc4c14c07e32fc5c5b23 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 3 Mar 2019 13:41:51 +0100 Subject: [PATCH 094/457] remove remnants markets and precisionlist --- freqtrade/constants.py | 2 +- freqtrade/exchange/exchange.py | 4 +--- freqtrade/pairlist/IPairList.py | 1 - freqtrade/pairlist/VolumePairList.py | 3 +-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index d0fafbd26..4d0907d78 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -19,7 +19,7 @@ REQUIRED_ORDERTIF = ['buy', 'sell'] REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] -AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'VolumePrecisionPairList'] +AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] TICKER_INTERVAL_MINUTES = { '1m': 1, diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f5503002b..874ed93aa 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -657,9 +657,7 @@ class Exchange(object): @retrier def get_markets(self) -> List[dict]: try: - markets = self._api.fetch_markets() - self.markets.update({m["symbol"]: m for m in markets}) - return markets + return self._api.fetch_markets() except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load markets due to {e.__class__.__name__}. Message: {e}') diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 7675c1eee..948abe113 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -18,7 +18,6 @@ class IPairList(ABC): self._config = config self._whitelist = self._config['exchange']['pair_whitelist'] self._blacklist = self._config['exchange'].get('pair_blacklist', []) - self._markets = self._freqtrade.exchange.get_markets() @property def name(self) -> str: diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 7f1985d43..eb03236e5 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -77,9 +77,8 @@ class VolumePairList(IPairList): if self._freqtrade.strategy.stoploss is not None and self._precision_filter: - logger.debug(f"Markets: {list(self._markets)}") stop_prices = [self._freqtrade.get_target_bid(t["symbol"], t) - * (1 + self._freqtrade.strategy.stoploss) for t in valid_tickers] + * (1 - abs(self._freqtrade.strategy.stoploss)) for t in valid_tickers] rates = [sp * 0.99 for sp in stop_prices] logger.debug("\n".join([f"{sp} : {r}" for sp, r in zip(stop_prices[:10], rates[:10])])) for i, t in enumerate(valid_tickers): From 3c5deb9aaf9882a7cd7ef2b0d30c0f77df4442ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 3 Mar 2019 15:31:48 +0100 Subject: [PATCH 095/457] Add test for precision_remove ... BTT should not be in the list when that is enabled. --- freqtrade/tests/conftest.py | 1 + freqtrade/tests/pairlist/test_pairlist.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 98171844a..6237a27c9 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -667,6 +667,7 @@ def tickers(): 'vwap': 0.01869197, 'open': 0.018585, 'close': 0.018573, + 'last': 0.018799, 'baseVolume': 81058.66, 'quoteVolume': 2247.48374509, }, diff --git a/freqtrade/tests/pairlist/test_pairlist.py b/freqtrade/tests/pairlist/test_pairlist.py index e78404587..dd6ebb62c 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -113,6 +113,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) + mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, p, r: round(r, 8)) # Test to retrieved BTC sorted on quoteVolume (default) whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='quoteVolume') @@ -132,6 +133,15 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='ETH', key='quoteVolume') assert whitelist == [] + freqtrade.pairlists._precision_filter = True + freqtrade.config['stake_currency'] = 'BTC' + # Retest First 2 test-cases to make sure BTT is not in it (too low priced) + whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='quoteVolume') + assert whitelist == ['ETH/BTC', 'TKN/BTC'] + + whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='bidVolume') + assert whitelist == ['TKN/BTC', 'ETH/BTC'] + def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: default_conf['pairlist'] = {'method': 'VolumePairList', From 2d0aca0d20a8ebf31134939008053d093220dfe3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 4 Mar 2019 07:24:05 +0100 Subject: [PATCH 096/457] Move --customhyperopts to hyperopt section --- freqtrade/arguments.py | 16 ++++++++-------- freqtrade/configuration.py | 8 +++++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 62f22befc..ee19f6fe1 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -108,14 +108,6 @@ class Arguments(object): type=str, metavar='PATH', ) - self.parser.add_argument( - '--customhyperopt', - help='Specify hyperopt class name (default: %(default)s).', - dest='hyperopt', - default=constants.DEFAULT_HYPEROPT, - type=str, - metavar='NAME', - ) self.parser.add_argument( '--dynamic-whitelist', help='Dynamically generate and update whitelist' @@ -248,6 +240,14 @@ class Arguments(object): """ Parses given arguments for Hyperopt scripts. """ + parser.add_argument( + '--customhyperopt', + help='Specify hyperopt class name (default: %(default)s).', + dest='hyperopt', + default=constants.DEFAULT_HYPEROPT, + type=str, + metavar='NAME', + ) parser.add_argument( '--eps', '--enable-position-stacking', help='Allow buying the same pair multiple times (position stacking).', diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index bddf60028..e96305993 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -67,9 +67,6 @@ class Configuration(object): if self.args.strategy_path: config.update({'strategy_path': self.args.strategy_path}) - # Add the hyperopt file to use - config.update({'hyperopt': self.args.hyperopt}) - # Load Common configuration config = self._load_common_config(config) @@ -276,6 +273,11 @@ class Configuration(object): Extract information for sys.argv and load Hyperopt configuration :return: configuration as dictionary """ + + if "hyperopt" in self.args: + # Add the hyperopt file to use + config.update({'hyperopt': self.args.hyperopt}) + # If --epochs is used we add it to the configuration if 'epochs' in self.args and self.args.epochs: config.update({'epochs': self.args.epochs}) From 2208a21a6c39768d536d09523e8c468cbaf44a96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 4 Mar 2019 07:24:41 +0100 Subject: [PATCH 097/457] Update help strings --- README.md | 39 +++++++++--------- docs/bot-usage.md | 101 +++++++++++++++++++++++----------------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 49b10e417..ade62ce94 100644 --- a/README.md +++ b/README.md @@ -68,39 +68,38 @@ For any other type of installation please refer to [Installation doc](https://ww ### Bot commands ``` -usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] - [--strategy-path PATH] [--customhyperopt NAME] - [--dynamic-whitelist [INT]] [--db-url PATH] - {backtesting,edge,hyperopt} ... +usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] + [--strategy-path PATH] [--dynamic-whitelist [INT]] + [--db-url PATH] + {backtesting,edge,hyperopt} ... Free, open source crypto trading bot positional arguments: {backtesting,edge,hyperopt} - backtesting backtesting module - edge edge module - hyperopt hyperopt module + backtesting Backtesting module. + edge Edge module. + hyperopt Hyperopt module. optional arguments: -h, --help show this help message and exit - -v, --verbose verbose mode (-vv for more, -vvv to get all messages) - --version show program\'s version number and exit + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --version show program's version number and exit -c PATH, --config PATH - specify configuration file (default: config.json) + Specify configuration file (default: None). Multiple + --config options may be used. -d PATH, --datadir PATH - path to backtest data + Path to backtest data. -s NAME, --strategy NAME - specify strategy class name (default: DefaultStrategy) - --strategy-path PATH specify additional strategy lookup path - --customhyperopt NAME - specify hyperopt class name (default: - DefaultHyperOpts) + Specify strategy class name (default: + DefaultStrategy). + --strategy-path PATH Specify additional strategy lookup path. --dynamic-whitelist [INT] - dynamically generate and update whitelist based on 24h - BaseVolume (default: 20) DEPRECATED. + Dynamically generate and update whitelist based on 24h + BaseVolume (default: 20). DEPRECATED. --db-url PATH Override trades database URL, this is useful if dry_run is enabled or in custom deployments (default: - None) + None). ``` ### Telegram RPC commands @@ -195,4 +194,4 @@ To run this bot we recommend you a cloud instance with a minimum of: - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) - [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended) -- [Docker](https://www.docker.com/products/docker) (Recommended) \ No newline at end of file +- [Docker](https://www.docker.com/products/docker) (Recommended) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 96b16b6b6..fa048cb92 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -6,39 +6,39 @@ This page explains the different parameters of the bot and how to run it. ## Bot commands ``` -usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] - [--strategy-path PATH] [--customhyperopt NAME] - [--dynamic-whitelist [INT]] [--db-url PATH] - {backtesting,edge,hyperopt} ... +usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] + [--strategy-path PATH] [--dynamic-whitelist [INT]] + [--db-url PATH] + {backtesting,edge,hyperopt} ... Free, open source crypto trading bot positional arguments: {backtesting,edge,hyperopt} - backtesting backtesting module - edge edge module - hyperopt hyperopt module + backtesting Backtesting module. + edge Edge module. + hyperopt Hyperopt module. optional arguments: -h, --help show this help message and exit - -v, --verbose verbose mode (-vv for more, -vvv to get all messages) - --version show program\'s version number and exit + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --version show program's version number and exit -c PATH, --config PATH - specify configuration file (default: config.json) + Specify configuration file (default: None). Multiple + --config options may be used. -d PATH, --datadir PATH - path to backtest data + Path to backtest data. -s NAME, --strategy NAME - specify strategy class name (default: DefaultStrategy) - --strategy-path PATH specify additional strategy lookup path - --customhyperopt NAME - specify hyperopt class name (default: - DefaultHyperOpts) + Specify strategy class name (default: + DefaultStrategy). + --strategy-path PATH Specify additional strategy lookup path. --dynamic-whitelist [INT] - dynamically generate and update whitelist based on 24h - BaseVolume (default: 20) DEPRECATED. + Dynamically generate and update whitelist based on 24h + BaseVolume (default: 20). DEPRECATED. --db-url PATH Override trades database URL, this is useful if dry_run is enabled or in custom deployments (default: - None) + None). + ``` ### How to use a different config file? @@ -129,27 +129,27 @@ python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.s Backtesting also uses the config specified via `-c/--config`. ``` -usage: main.py backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] - [--eps] [--dmmp] [-l] [-r] - [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] - [--export EXPORT] [--export-filename PATH] +usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] + [--eps] [--dmmp] [-l] [-r] + [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] + [--export EXPORT] [--export-filename PATH] optional arguments: -h, --help show this help message and exit -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL - specify ticker interval (1m, 5m, 30m, 1h, 1d) + Specify ticker interval (1m, 5m, 30m, 1h, 1d). --timerange TIMERANGE - specify what timerange of data to use. + Specify what timerange of data to use. --eps, --enable-position-stacking Allow buying the same pair multiple times (position - stacking) + stacking). --dmmp, --disable-max-market-positions Disable applying `max_open_trades` during backtest (same as setting `max_open_trades` to a very high - number) - -l, --live using live data + number). + -l, --live Use live data. -r, --refresh-pairs-cached - refresh the pairs files in tests/testdata with the + Refresh the pairs files in tests/testdata with the latest data from the exchange. Use it if you want to run your backtesting with up-to-date data. --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] @@ -159,7 +159,7 @@ optional arguments: this together with --export trades, the strategy-name is injected into the filename (so backtest-data.json becomes backtest-data-DefaultStrategy.json - --export EXPORT export backtest results, argument are: trades Example + --export EXPORT Export backtest results, argument are: trades. Example --export=trades --export-filename PATH Save backtest results to this filename requires @@ -189,29 +189,30 @@ To optimize your strategy, you can use hyperopt parameter hyperoptimization to find optimal parameter values for your stategy. ``` -usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--eps] [--dmmp] - [--timerange TIMERANGE] [-e INT] - [-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]] +usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] + [--customhyperopt NAME] [--eps] [--dmmp] [-e INT] + [-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] optional arguments: -h, --help show this help message and exit -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL - specify ticker interval (1m, 5m, 30m, 1h, 1d) + Specify ticker interval (1m, 5m, 30m, 1h, 1d). + --timerange TIMERANGE + Specify what timerange of data to use. + --customhyperopt NAME + Specify hyperopt class name (default: + DefaultHyperOpts). --eps, --enable-position-stacking Allow buying the same pair multiple times (position - stacking) + stacking). --dmmp, --disable-max-market-positions Disable applying `max_open_trades` during backtest (same as setting `max_open_trades` to a very high - number) - --timerange TIMERANGE - specify what timerange of data to use. - --hyperopt PATH specify hyperopt file (default: - freqtrade/optimize/default_hyperopt.py) - -e INT, --epochs INT specify number of epochs (default: 100) - -s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...], --spaces {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...] + number). + -e INT, --epochs INT Specify number of epochs (default: 100). + -s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...], --spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...] Specify which parameters to hyperopt. Space separate - list. Default: all + list. Default: all. ``` @@ -220,22 +221,22 @@ optional arguments: To know your trade expectacny and winrate against historical data, you can use Edge. ``` -usage: main.py edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [-r] - [--stoplosses STOPLOSS_RANGE] +usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [-r] + [--stoplosses STOPLOSS_RANGE] optional arguments: -h, --help show this help message and exit -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL - specify ticker interval (1m, 5m, 30m, 1h, 1d) + Specify ticker interval (1m, 5m, 30m, 1h, 1d). --timerange TIMERANGE - specify what timerange of data to use. + Specify what timerange of data to use. -r, --refresh-pairs-cached - refresh the pairs files in tests/testdata with the + Refresh the pairs files in tests/testdata with the latest data from the exchange. Use it if you want to run your edge with up-to-date data. --stoplosses STOPLOSS_RANGE - defines a range of stoploss against which edge will - assess the strategythe format is "min,max,step" + Defines a range of stoploss against which edge will + assess the strategy the format is "min,max,step" (without any space).example: --stoplosses=-0.01,-0.1,-0.001 ``` From b8eb3ecb1d6d91e2fb0fcc0d900f04ca36fda919 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 4 Mar 2019 07:24:49 +0100 Subject: [PATCH 098/457] Update hyperopts documentation to work and match the code --- docs/hyperopt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 0c18110bd..e4f636aac 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -152,7 +152,7 @@ Because hyperopt tries a lot of combinations to find the best parameters it will We strongly recommend to use `screen` or `tmux` to prevent any connection loss. ```bash -python3 ./freqtrade/main.py --hyperopt -c config.json hyperopt -e 5000 --spaces all +python3 ./freqtrade/main.py -c config.json hyperopt --customhyperopt -e 5000 --spaces all ``` Use `` as the name of the custom hyperopt used. From 460e0711c60299466107f5cbd8d8ba7da1bae4c4 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 4 Mar 2019 11:05:12 +0300 Subject: [PATCH 099/457] How to use multiple configuration files Description of multiple config command line options added. Concrete examples to the bot-configuration page (something like "Hiding your key and exchange secret") will follow. Please review grammar, wording etc. --- docs/bot-usage.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 96b16b6b6..51a810668 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -41,15 +41,45 @@ optional arguments: None) ``` -### How to use a different config file? +### How to use a different configuration file? -The bot allows you to select which config file you want to use. Per +The bot allows you to select which configuration file you want to use. Per default, the bot will load the file `./config.json` ```bash python3 ./freqtrade/main.py -c path/far/far/away/config.json ``` +### How to use multiple configuration files? + +The bot allows you to use multiple configuration files by specifying multiple +`-c/--config` configuration options in the command line. Configuration parameters +defined in the last configuration file override parameters with the same name +defined in the previous configuration file specified in the command line. + +For example, you can make a separate configuration file with your key and secrete +for the Exchange you use for trading, specify default configuration file with +empty key and secrete values while running in the Dry Mode (which does not actually +require them): + +```bash +python3 ./freqtrade/main.py -c ./config.json +``` + +and specify both configuration files when running in the normal Live Trade Mode: + +```bash +python3 ./freqtrade/main.py -c ./config.json -c path/to/secrets/keys.config.json +``` + +This could help you hide your private Exchange key and Exchange secrete on you local machine +by setting appropriate file permissions for the file which contains actual secrets and, additionally, +prevent unintended disclosure of sensitive private data when you publish examples +of your configuration in the project issues or in the Internet. + +See more details on this technique with examples in the documentation page on +[configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-configuration.md). + ### How to use **--strategy**? This parameter will allow you to load your custom strategy class. From f16913a76d49992f1685f413aa1f9101d913f72f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 4 Mar 2019 13:32:05 +0100 Subject: [PATCH 100/457] Update ccxt from 1.18.322 to 1.18.323 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7f66d84eb..31f8e5cc9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.322 +ccxt==1.18.323 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 386abc5eba8d493b5515ac1bd8f7efea33ef37af Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 4 Mar 2019 23:44:44 +0300 Subject: [PATCH 101/457] minor: doc update index.md --- docs/index.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/index.md b/docs/index.md index 92ea4fe43..9abc71747 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,27 +19,27 @@ Freqtrade is a cryptocurrency trading bot written in Python. Always start by running a trading bot in Dry-run and do not engage money before you understand how it works and what profit/loss you should expect. - We strongly recommend you to have coding and Python knowledge. Do not hesitate to read the source code and understand the mechanism of this bot. + We strongly recommend you to have basic coding skills and Python knowledge. Do not hesitate to read the source code and understand the mechanisms of this bot, algorithms and techniques implemented in it. ## Features - - Based on Python 3.6+: For botting on any operating system - Windows, macOS and Linux - - Persistence: Persistence is achieved through sqlite - - Dry-run: Run the bot without playing money. - - Backtesting: Run a simulation of your buy/sell strategy. + - Based on Python 3.6+: For botting on any operating system — Windows, macOS and Linux. + - Persistence: Persistence is achieved through sqlite database. + - Dry-run mode: Run the bot without playing money. + - Backtesting: Run a simulation of your buy/sell strategy with historical data. - Strategy Optimization by machine learning: Use machine learning to optimize your buy/sell strategy parameters with real exchange data. - - Edge position sizing Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. Learn more - - Whitelist crypto-currencies: Select which crypto-currency you want to trade or use dynamic whitelists. + - Edge position sizing: Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. + - Whitelist crypto-currencies: Select which crypto-currency you want to trade or use dynamic whitelists based on market (pair) trade volume. - Blacklist crypto-currencies: Select which crypto-currency you want to avoid. - - Manageable via Telegram: Manage the bot with Telegram - - Display profit/loss in fiat: Display your profit/loss in 33 fiat. - - Daily summary of profit/loss: Provide a daily summary of your profit/loss. - - Performance status report: Provide a performance status of your current trades. + - Manageable via Telegram: Manage the bot with Telegram. + - Display profit/loss in fiat: Display your profit/loss in any of 33 fiat currencies supported. + - Daily summary of profit/loss: Receive the daily summary of your profit/loss. + - Performance status report: Receive the performance status of your current trades. ## Requirements -### Uptodate clock -The clock must be accurate, syncronized to a NTP server very frequently to avoid problems with communication to the exchanges. +### Up to date clock +The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges. ### Hardware requirements To run this bot we recommend you a cloud instance with a minimum of: @@ -50,7 +50,7 @@ To run this bot we recommend you a cloud instance with a minimum of: ### Software requirements - Python 3.6.x -- pip +- pip (pip3) - git - TA-Lib - virtualenv (Recommended) @@ -59,9 +59,9 @@ To run this bot we recommend you a cloud instance with a minimum of: ## Support Help / Slack -For any questions not covered by the documentation or for further information about the bot, we encourage you to join our slack channel. +For any questions not covered by the documentation or for further information about the bot, we encourage you to join our Slack channel. Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) to join Slack channel. ## Ready to try? -Begin by reading our installation guide [here](installation). \ No newline at end of file +Begin by reading our installation guide [here](installation). From f6ca97d1dc1e122d904c58b1ab98479ee741b1fc Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Mar 2019 06:43:28 +0100 Subject: [PATCH 102/457] Update hyperopt doc to validate backtest results --- docs/hyperopt.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index e4f636aac..6e52be47f 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -285,8 +285,13 @@ This would translate to the following ROI table: ### Validate backtest result Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected. -To archive the same results (number of trades, ...) than during hyperopt, please use the command line flag `--disable-max-market-positions`. -This setting is the default for hyperopt for speed reasons. You can overwrite this in the configuration by setting `"position_stacking"=false` or by changing the relevant line in your hyperopt file [here](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L283). +To archive the same results (number of trades, ...) than during hyperopt, please use the command line flags `--disable-max-market-positions` and `--enable-position-stacking` for backtesting. + +This configuration is the default in hyperopt for performance reasons. + +You can overwrite position stacking in the configuration by explicitly setting `"position_stacking"=false` or by changing the relevant line in your hyperopt file [here](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L191). + +Enabling the market-position for hyperopt is currently not possible. !!! Note: Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality. From 71f5392f8928f1537b8d217c1ca3209b81536619 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 5 Mar 2019 12:44:06 +0300 Subject: [PATCH 103/457] typo fixed --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 36428d1c1..0f3ab648d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,7 +14,7 @@ nav: - Plotting: plotting.md - FAQ: faq.md - SQL Cheatsheet: sql_cheatsheet.md - - Sanbox testing: sandbox-testing.md + - Sandbox testing: sandbox-testing.md - Contributors guide: developer.md theme: name: material From 2f98dd0429ba085711fa215ef45bcf532f2b5d7c Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 5 Mar 2019 14:09:26 +0300 Subject: [PATCH 104/457] description for --dynamic-whitelist moved to new docs/deprecated.md --- docs/bot-usage.md | 33 +++++++++------------------------ docs/deprecated.md | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 docs/deprecated.md diff --git a/docs/bot-usage.md b/docs/bot-usage.md index f16cc3fdd..8b10fb6e9 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -78,7 +78,7 @@ prevent unintended disclosure of sensitive private data when you publish example of your configuration in the project issues or in the Internet. See more details on this technique with examples in the documentation page on -[configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-configuration.md). +[configuration](bot-configuration.md). ### How to use **--strategy**? @@ -101,7 +101,8 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy If the bot does not find your strategy file, it will display in an error message the reason (File not found, or errors in your code). -Learn more about strategy file in [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md). +Learn more about strategy file in +[optimize your bot](bot-optimization.md). ### How to use **--strategy-path**? @@ -119,29 +120,13 @@ This is very simple. Copy paste your strategy file into the folder ### How to use **--dynamic-whitelist**? !!! danger "DEPRECATED" - Dynamic-whitelist is deprecated. Please move your configurations to the configuration as outlined [here](/configuration/#dynamic-pairlists) + This command line option is deprecated. Please move your configurations using it +to the configurations that utilize the `StaticPairList` or `VolumePairList` methods set +in the configuration file +as outlined [here](configuration/#dynamic-pairlists) -Per default `--dynamic-whitelist` will retrieve the 20 currencies based -on BaseVolume. This value can be changed when you run the script. - -**By Default** -Get the 20 currencies based on BaseVolume. - -```bash -python3 ./freqtrade/main.py --dynamic-whitelist -``` - -**Customize the number of currencies to retrieve** -Get the 30 currencies based on BaseVolume. - -```bash -python3 ./freqtrade/main.py --dynamic-whitelist 30 -``` - -**Exception** -`--dynamic-whitelist` must be greater than 0. If you enter 0 or a -negative value (e.g -2), `--dynamic-whitelist` will use the default -value (20). +Description of this deprecated feature was moved to [here](deprecated.md). +Please no longer use it. ### How to use **--db-url**? diff --git a/docs/deprecated.md b/docs/deprecated.md new file mode 100644 index 000000000..25043d981 --- /dev/null +++ b/docs/deprecated.md @@ -0,0 +1,31 @@ +# Deprecated features + +This page contains description of the command line arguments, configuration parameters +and the bot features that were declared as DEPRECATED by the bot development team +and are no longer supported. Please avoid their usage in your configuration. + +### The **--dynamic-whitelist** command line option + +Per default `--dynamic-whitelist` will retrieve the 20 currencies based +on BaseVolume. This value can be changed when you run the script. + +**By Default** +Get the 20 currencies based on BaseVolume. + +```bash +python3 ./freqtrade/main.py --dynamic-whitelist +``` + +**Customize the number of currencies to retrieve** +Get the 30 currencies based on BaseVolume. + +```bash +python3 ./freqtrade/main.py --dynamic-whitelist 30 +``` + +**Exception** +`--dynamic-whitelist` must be greater than 0. If you enter 0 or a +negative value (e.g -2), `--dynamic-whitelist` will use the default +value (20). + + From ce46555e775b632e8713a3340031c398375b2170 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 5 Mar 2019 14:11:40 +0300 Subject: [PATCH 105/457] docs/configuration.md reviewed: formatting, wording, grammar, etc --- docs/configuration.md | 145 +++++++++++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 51 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c4b26eba9..87b4a6844 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,10 +67,10 @@ Mandatory Parameters are marked as **Required**. | `strategy_path` | null | Adds an additional strategy lookup path (must be a folder). | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. -### Parameters in strategy +### Parameters in the strategy -The following parameters can be set in either configuration or strategy. -Values in the configuration are always overwriting values set in the strategy. +The following parameters can be set in either configuration file or strategy. +Values set in the configuration file always overwrite values set in the strategy. * `minimal_roi` * `ticker_interval` @@ -87,7 +87,7 @@ Values in the configuration are always overwriting values set in the strategy. ### Understand stake_amount -`stake_amount` is an amount of crypto-currency your bot will use for each trade. +The `stake_amount` configuration parameter is an amount of crypto-currency your bot will use for each trade. The minimal value is 0.0005. If there is not enough crypto-currency in the account an exception is generated. To allow the bot to trade all the available `stake_currency` in your account set @@ -104,7 +104,7 @@ currency_balanse / (max_open_trades - current_open_trades) ### Understand minimal_roi -`minimal_roi` is a JSON object where the key is a duration +The `minimal_roi` configuration parameter is a JSON object where the key is a duration in minutes and the value is the minimum ROI in percent. See the example below: @@ -118,17 +118,17 @@ See the example below: ``` Most of the strategy files already include the optimal `minimal_roi` -value. This parameter is optional. If you use it, it will take over the +value. This parameter is optional. If you use it in the configuration file, it will take over the `minimal_roi` value from the strategy file. ### Understand stoploss -`stoploss` is loss in percentage that should trigger a sale. -For example value `-0.10` will cause immediate sell if the +The `stoploss` configuration parameter is loss in percentage that should trigger a sale. +For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. Most of the strategy files already include the optimal `stoploss` -value. This parameter is optional. If you use it, it will take over the +value. This parameter is optional. If you use it in the configuration file, it will take over the `stoploss` value from the strategy file. ### Understand trailing stoploss @@ -137,40 +137,51 @@ Go to the [trailing stoploss Documentation](stoploss.md) for details on trailing ### Understand initial_state -`initial_state` is an optional field that defines the initial application state. +The `initial_state` configuration parameter is an optional field that defines the initial application state. Possible values are `running` or `stopped`. (default=`running`) If the value is `stopped` the bot has to be started with `/start` first. ### Understand forcebuy_enable -`forcebuy_enable` enables the usage of forcebuy commands via Telegram. +The `forcebuy_enable` configuration parameter enables the usage of forcebuy commands via Telegram. This is disabled for security reasons by default, and will show a warning message on startup if enabled. -You send `/forcebuy ETH/BTC` to the bot, who buys the pair and holds it until a regular sell-signal appears (ROI, stoploss, /forcesell). +For example, you can send `/forcebuy ETH/BTC` Telegram command when this feature if enabled to the bot, +who then buys the pair and holds it until a regular sell-signal (ROI, stoploss, /forcesell) appears. + +This can be dangerous with some strategies, so use with care. -Can be dangerous with some strategies, so use with care See [the telegram documentation](telegram-usage.md) for details on usage. ### Understand process_throttle_secs -`process_throttle_secs` is an optional field that defines in seconds how long the bot should wait +The `process_throttle_secs` configuration parameter is an optional field that defines in seconds how long the bot should wait before asking the strategy if we should buy or a sell an asset. After each wait period, the strategy is asked again for every opened trade wether or not we should sell, and for all the remaining pairs (either the dynamic list of pairs or the static list of pairs) if we should buy. ### Understand ask_last_balance -`ask_last_balance` sets the bidding price. Value `0.0` will use `ask` price, `1.0` will +The `ask_last_balance` configuration parameter sets the bidding price. Value `0.0` will use `ask` price, `1.0` will use the `last` price and values between those interpolate between ask and last price. Using `ask` price will guarantee quick success in bid, but bot will also end up paying more then would probably have been necessary. ### Understand order_types -`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss). -This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations. +The `order_types` configuration parameter contains a dict mapping order-types to +market-types as well as stoploss on or off exchange type and stoploss on exchange +update interval in seconds. This allows to buy using limit orders, sell using +limit-orders, and create stoploss orders using market. It also allows to set the +stoploss "on exchange" which means stoploss order would be placed immediately once +the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are +both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically +and update it if necessary (e.x. in case of trailing stoploss). +This can be set in the configuration file or in the strategy. +Values set in the configuration file overwrites values set in the strategy. -If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start. -The below is the default which is used if this is not configured in either Strategy or configuration. +If this is configured, all 4 values (`buy`, `sell`, `stoploss` and +`stoploss_on_exchange`) need to be present, otherwise the bot will warn about it and fail to start. +The below is the default which is used if this is not configured in either strategy or configuration file. ```python "order_types": { @@ -184,22 +195,39 @@ The below is the default which is used if this is not configured in either Strat !!! Note Not all exchanges support "market" orders. - The following message will be shown if your exchange does not support market orders: `"Exchange does not support market orders."` + The following message will be shown if your exchange does not support market orders: + `"Exchange does not support market orders."` !!! Note - stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. For more information about how stoploss works please read [the stoploss documentation](stoploss.md). + Stoploss on exchange interval is not mandatory. Do not change its value if you are + unsure of what you are doing. For more information about how stoploss works please + read [the stoploss documentation](stoploss.md). ### Understand order_time_in_force -`order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:
+The `order_time_in_force` configuration parameter defines the policy by which the order +is executed on the exchange. Three commonly used time in force are: + **GTC (Goog Till Canceled):** -This is most of the time the default time in force. It means the order will remain on exchange till it is canceled by user. It can be fully or partially fulfilled. If partially fulfilled, the remaining will stay on the exchange till cancelled.
+ +This is most of the time the default time in force. It means the order will remain +on exchange till it is canceled by user. It can be fully or partially fulfilled. +If partially fulfilled, the remaining will stay on the exchange till cancelled. + **FOK (Full Or Kill):** -It means if the order is not executed immediately AND fully then it is canceled by the exchange.
+ +It means if the order is not executed immediately AND fully then it is canceled by the exchange. + **IOC (Immediate Or Canceled):** -It is the same as FOK (above) except it can be partially fulfilled. The remaining part is automatically cancelled by the exchange. -
-`order_time_in_force` contains a dict buy and sell time in force policy. This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations.
-possible values are: `gtc` (default), `fok` or `ioc`.
+ +It is the same as FOK (above) except it can be partially fulfilled. The remaining part +is automatically cancelled by the exchange. + +The `order_time_in_force` parameter contains a dict with buy and sell time in force policy values. +This can be set in the configuration file or in the strategy. +Values set in the configuration file overwrites values set in the strategy. + +The possible values are: `gtc` (default), `fok` or `ioc`. + ``` python "order_time_in_force": { "buy": "gtc", @@ -208,7 +236,8 @@ possible values are: `gtc` (default), `fok` or `ioc`.
``` !!! Warning - This is an ongoing work. For now it is supported only for binance and only for buy orders. Please don't change the default value unless you know what you are doing. + This is an ongoing work. For now it is supported only for binance and only for buy orders. + Please don't change the default value unless you know what you are doing. ### What values for exchange.name? @@ -224,35 +253,41 @@ The bot was tested with the following exchanges: Feel free to test other exchanges and submit your PR to improve the bot. -### What values for fiat_display_currency? +### What values can be used for fiat_display_currency? + +The `fiat_display_currency` configuration parameter sets the base currency to use for the +conversion from coin to fiat in the bot Telegram reports. + +The valid values are: -`fiat_display_currency` set the base currency to use for the conversion from coin to fiat in Telegram. -The valid values are:
```json "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD" ``` -In addition to FIAT currencies, a range of cryto currencies are supported. + +In addition to fiat currencies, a range of cryto currencies are supported. + The valid values are: + ```json "BTC", "ETH", "XRP", "LTC", "BCH", "USDT" ``` -## Switch to dry-run mode +## Switch to Dry-run mode -We recommend starting the bot in dry-run mode to see how your bot will -behave and how is the performance of your strategy. In Dry-run mode the +We recommend starting the bot in the Dry-run mode to see how your bot will +behave and what is the performance of your strategy. In the Dry-run mode the bot does not engage your money. It only runs a live simulation without -creating trades. +creating trades on the exchange. -1. Edit your `config.json` file -2. Switch dry-run to true and specify db_url for a persistent db +1. Edit your `config.json` configuration file. +2. Switch `dry-run` to `true` and specify `db_url` for a persistence database. ```json "dry_run": true, "db_url": "sqlite:///tradesv3.dryrun.sqlite", ``` -3. Remove your Exchange API key (change them by fake api credentials) +3. Remove your Exchange API key and secrete (change them by empty values or fake credentials): ```json "exchange": { @@ -263,24 +298,32 @@ creating trades. } ``` -Once you will be happy with your bot performance, you can switch it to -production mode. +Once you will be happy with your bot performance running in the Dry-run mode, +you can switch it to production mode. ### Dynamic Pairlists Dynamic pairlists select pairs for you based on the logic configured. -The bot runs against all pairs (with that stake) on the exchange, and a number of assets (`number_assets`) is selected based on the selected criteria. +The bot runs against all pairs (with that stake) on the exchange, and a number of assets +(`number_assets`) is selected based on the selected criteria. -By default, a Static Pairlist is used (configured as `"pair_whitelist"` under the `"exchange"` section of this configuration). +By default, the `StaticPairList` method is used. +The Pairlist method is configured as `pair_whitelist` parameter under the `exchange` +section of the configuration. **Available Pairlist methods:** -* `"StaticPairList"` - * uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist` -* `"VolumePairList"` - * Formerly available as `--dynamic-whitelist []` - * Selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. - * Possibility to filter low-value coins that would not allow setting a stop loss +* `StaticPairList` + * It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`. +* `VolumePairList` + * Formerly available as `--dynamic-whitelist []`. This command line +option is deprecated and should no longer be used. + * It selects `number_assets` top pairs based on `sort_key`, which can be one of +`askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. + * There is a possibility to filter low-value coins that would not allow setting a stop loss +(set `precision_filter` parameter to `true` for this). + +Example: ```json "pairlist": { @@ -295,7 +338,7 @@ By default, a Static Pairlist is used (configured as `"pair_whitelist"` under th ## Switch to production mode -In production mode, the bot will engage your money. Be careful a wrong +In production mode, the bot will engage your money. Be careful, since a wrong strategy can lose all your money. Be aware of what you are doing when you run it in production mode. From c032dd0f458338609b03a9c4852f7218206f3cbf Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 5 Mar 2019 14:29:55 +0300 Subject: [PATCH 106/457] new docs/deprecated.md added to the site menu --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 0f3ab648d..9a6fec851 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,7 @@ nav: - Hyperopt: hyperopt.md - Edge positioning: edge.md - Plotting: plotting.md + - Deprecated features: deprecated.md - FAQ: faq.md - SQL Cheatsheet: sql_cheatsheet.md - Sandbox testing: sandbox-testing.md From ae7c4c33c07763b65719b7a9a0fe4488e3f44905 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 5 Mar 2019 13:33:05 +0100 Subject: [PATCH 107/457] Update ccxt from 1.18.323 to 1.18.333 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 31f8e5cc9..97df4e872 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.323 +ccxt==1.18.333 SQLAlchemy==1.2.18 python-telegram-bot==11.1.0 arrow==0.13.1 From 735e78f01d750252f73f85d9a2d9db4580b99598 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 5 Mar 2019 13:33:06 +0100 Subject: [PATCH 108/457] Update sqlalchemy from 1.2.18 to 1.3.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 97df4e872..981bd6285 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ccxt==1.18.333 -SQLAlchemy==1.2.18 +SQLAlchemy==1.3.0 python-telegram-bot==11.1.0 arrow==0.13.1 cachetools==3.1.0 From 35250eb2301a77fd0b3ca41a0199b6107034fe57 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 6 Mar 2019 01:06:33 +0300 Subject: [PATCH 109/457] one more typo fixed (by @xmatthias) --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 87b4a6844..5d731bb57 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -207,7 +207,7 @@ The below is the default which is used if this is not configured in either strat The `order_time_in_force` configuration parameter defines the policy by which the order is executed on the exchange. Three commonly used time in force are: -**GTC (Goog Till Canceled):** +**GTC (Good Till Canceled):** This is most of the time the default time in force. It means the order will remain on exchange till it is canceled by user. It can be fully or partially fulfilled. From 962cfc5eb962df453808b8d60e9995b3ab724711 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 6 Mar 2019 13:33:04 +0100 Subject: [PATCH 110/457] Update ccxt from 1.18.333 to 1.18.342 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 981bd6285..92486ae04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.333 +ccxt==1.18.342 SQLAlchemy==1.3.0 python-telegram-bot==11.1.0 arrow==0.13.1 From 8624d83be08bb184346399a3a32be8a5d8de223f Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Wed, 6 Mar 2019 20:55:40 +0300 Subject: [PATCH 111/457] Remove deprecated --dynamic-whitelist from freqtrade.service --- freqtrade.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade.service b/freqtrade.service index 76f9b6016..9de9f13c7 100644 --- a/freqtrade.service +++ b/freqtrade.service @@ -6,7 +6,7 @@ After=network.target # Set WorkingDirectory and ExecStart to your file paths accordingly # NOTE: %h will be resolved to /home/ WorkingDirectory=%h/freqtrade -ExecStart=/usr/bin/freqtrade --dynamic-whitelist 40 +ExecStart=/usr/bin/freqtrade Restart=on-failure [Install] From e67ffd2d879325af137d37863ee5abd6d065ba0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 6 Mar 2019 19:55:34 +0100 Subject: [PATCH 112/457] Fix issue that backtest is broken when stoploss_on_exchange is on --- freqtrade/optimize/backtesting.py | 11 +++++---- freqtrade/tests/optimize/test_backtesting.py | 24 +++++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a8f4e530a..031b490c8 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -17,11 +17,10 @@ from freqtrade import optimize from freqtrade import DependencyException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration -from freqtrade.exchange import Exchange from freqtrade.data import history from freqtrade.misc import file_dump_json from freqtrade.persistence import Trade -from freqtrade.resolvers import StrategyResolver +from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.state import RunMode from freqtrade.strategy.interface import SellType, IStrategy @@ -79,8 +78,8 @@ class Backtesting(object): self.strategylist.append(StrategyResolver(self.config).strategy) # Load one strategy self._set_strategy(self.strategylist[0]) - - self.exchange = Exchange(self.config) + exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() + self.exchange = ExchangeResolver(exchange_name, self.config).exchange self.fee = self.exchange.get_fee() def _set_strategy(self, strategy): @@ -93,6 +92,10 @@ class Backtesting(object): self.tickerdata_to_dataframe = strategy.tickerdata_to_dataframe self.advise_buy = strategy.advise_buy self.advise_sell = strategy.advise_sell + # Set stoploss_on_exchange to false for backtesting, + # since a "perfect" stoploss-sell is assumed anyway + # And the regular "stoploss" function would not apply to that case + self.strategy.order_types['stoploss_on_exchange'] = False def _generate_text_table(self, data: Dict[str, Dict], results: DataFrame, skip_nan: bool = False) -> str: diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index e69b1374e..1d5fb1384 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -315,7 +315,28 @@ def test_start(mocker, fee, default_conf, caplog) -> None: assert start_mock.call_count == 1 -def test_backtesting_init(mocker, default_conf) -> None: +ORDER_TYPES = [ + { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': False + }, + { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': True + }] + + +@pytest.mark.parametrize("order_types", ORDER_TYPES) +def test_backtesting_init(mocker, default_conf, order_types) -> None: + """ + Check that stoploss_on_exchange is set to False while backtesting + since backtesting assumes a perfect stoploss anyway. + """ + default_conf["order_types"] = order_types patch_exchange(mocker) get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) @@ -326,6 +347,7 @@ def test_backtesting_init(mocker, default_conf) -> None: assert callable(backtesting.advise_sell) get_fee.assert_called() assert backtesting.fee == 0.5 + assert not backtesting.strategy.order_types["stoploss_on_exchange"] def test_tickerdata_to_dataframe_bt(default_conf, mocker) -> None: From 7b901e180a6c283365ee7de7940b8ad862969772 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 6 Mar 2019 21:37:52 +0100 Subject: [PATCH 113/457] update sql_cheatsheet --- docs/sql_cheatsheet.md | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index ff0b92347..80a8e74cb 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -44,6 +44,14 @@ CREATE TABLE trades ( open_date DATETIME NOT NULL, close_date DATETIME, open_order_id VARCHAR, + stop_loss FLOAT, + initial_stop_loss FLOAT, + stoploss_order_id VARCHAR, + stoploss_last_update DATETIME, + max_rate FLOAT, + sell_reason VARCHAR, + strategy VARCHAR, + ticker_interval INTEGER, PRIMARY KEY (id), CHECK (is_open IN (0, 1)) ); @@ -57,36 +65,38 @@ SELECT * FROM trades; ## Fix trade still open after a /forcesell +Note: This should not be necessary, as forcesell orders are closed automatically by the bot on the next iteration. + ```sql UPDATE trades -SET is_open=0, close_date=, close_rate=, close_profit=close_rate/open_rate-1 +SET is_open=0, close_date=, close_rate=, close_profit=close_rate/open_rate-1, sell_reason= WHERE id=; ``` -**Example:** +##### Example + ```sql UPDATE trades -SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496 +SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496, sell_reason='force_sell' WHERE id=31; ``` ## Insert manually a new trade ```sql -INSERT -INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) -VALUES ('BITTREX', 'BTC_', 1, 0.0025, 0.0025, , , , '') +INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) +VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, , , , '') ``` -**Example:** +##### Example: + ```sql -INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) VALUES ('BITTREX', 'BTC_ETC', 1, 0.0025, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000') +INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) +VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000') ``` ## Fix wrong fees in the table -If your DB was created before -[PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged -(before 12/23/17). +If your DB was created before [PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged (before 12/23/17). ```sql UPDATE trades SET fee=0.0025 WHERE fee=0.005; From 6b2f4b12fd0ec45fd5f33f3bad9f046263636fa1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 7 Mar 2019 13:33:07 +0100 Subject: [PATCH 114/457] Update ccxt from 1.18.342 to 1.18.345 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 92486ae04..f7eb4c695 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.342 +ccxt==1.18.345 SQLAlchemy==1.3.0 python-telegram-bot==11.1.0 arrow==0.13.1 From 2da0d479e7e36793513e5943075e47041a938f44 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 8 Mar 2019 13:33:06 +0100 Subject: [PATCH 115/457] Update ccxt from 1.18.345 to 1.18.347 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f7eb4c695..ff407848b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.345 +ccxt==1.18.347 SQLAlchemy==1.3.0 python-telegram-bot==11.1.0 arrow==0.13.1 From 9c1d4183fd9804dca09d13db171f1647d0c0710e Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 8 Mar 2019 20:18:45 +0300 Subject: [PATCH 116/457] typo in doc --- docs/edge.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/edge.md b/docs/edge.md index a4acffc44..7372e3373 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -36,7 +36,7 @@ Complementary Loss Rate (*L*) is defined as or, which is the same, as - R = 1 – W + L = 1 – W ### Risk Reward Ratio Risk Reward Ratio (*R*) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose: From 702153d08745a477d87abcdb5e5ee9c93d12d29c Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 8 Mar 2019 22:17:17 +0300 Subject: [PATCH 117/457] exchange.sandbox parameter was missing in the docs --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index 5d731bb57..d7e774595 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,6 +39,7 @@ Mandatory Parameters are marked as **Required**. | `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-strategy). | `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-strategy). | `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). +| `exchange.sandbox` | false | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. | `exchange.key` | key | API key to use for the exchange. Only required when you are in production mode. | `exchange.secret` | secret | API secret to use for the exchange. Only required when you are in production mode. | `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param. From 0a2cacbba82b0bbfce3489675d05c89d4d822643 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Mar 2019 21:17:12 +0100 Subject: [PATCH 118/457] Fix #1637 --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 874ed93aa..f6fb0a58a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -298,7 +298,7 @@ class Exchange(object): 'amount': amount, "cost": amount * rate, 'type': ordertype, - 'side': 'buy', + 'side': side, 'remaining': amount, 'datetime': arrow.utcnow().isoformat(), 'status': "open", From 4cd70138b6ace98febc7fa74aeab69c49dbce413 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Mar 2019 21:17:21 +0100 Subject: [PATCH 119/457] Add test to make sure this ain't reintroduced --- freqtrade/tests/exchange/test_exchange.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 3b8d3ad6f..fce9cba14 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -470,6 +470,9 @@ def test_dry_run_order(default_conf, mocker, side, exchange_name): pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) assert 'id' in order assert f'dry_run_{side}_' in order["id"] + assert order["side"] == side + assert order["type"] == "limit" + assert order["pair"] == "ETH/BTC" @pytest.mark.parametrize("side", [ From dba30bbfed9947f2ed5974394bc9ef5371468374 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Mar 2019 21:59:39 +0100 Subject: [PATCH 120/457] Update travis for coveralls --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 84f3c78d9..424ac579c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,9 @@ install: - pip install -r requirements-dev.txt - pip install -e . jobs: + allow_failures: + - script: coveralls + include: - stage: tests script: @@ -40,6 +43,8 @@ jobs: name: flake8 - script: mypy freqtrade name: mypy + - script: coveralls + name: "Coveralls" - stage: docker if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) @@ -47,9 +52,6 @@ jobs: - build_helpers/publish_docker.sh name: "Build and test and push docker image" -after_success: - - coveralls - notifications: slack: secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q= From 25529ad95feee3af68bfd7456ada2bbef15f18cd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Mar 2019 21:54:40 +0100 Subject: [PATCH 121/457] use || for coveralls --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 424ac579c..d24ffcf1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,13 +23,13 @@ install: - pip install -r requirements-dev.txt - pip install -e . jobs: - allow_failures: - - script: coveralls include: - stage: tests script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ + # Allow failure for coveralls + - coveralls || true name: pytest - script: - cp config.json.example config.json @@ -43,8 +43,6 @@ jobs: name: flake8 - script: mypy freqtrade name: mypy - - script: coveralls - name: "Coveralls" - stage: docker if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) From fa4c8110e781f67193e33a9f3e64d1f71d774ee6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Mar 2019 22:15:03 +0100 Subject: [PATCH 122/457] Rename cheatsheet header --- docs/sql_cheatsheet.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 80a8e74cb..e85aceec8 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -63,9 +63,14 @@ CREATE TABLE trades ( SELECT * FROM trades; ``` -## Fix trade still open after a /forcesell +## Fix trade still open after a manual sell on the exchange -Note: This should not be necessary, as forcesell orders are closed automatically by the bot on the next iteration. +!!! Warning: + Manually selling on the exchange should not be done by default, since the bot does not detect this and will try to sell anyway. + /foresell should accomplish the same thing. + +!!! Note: + This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. ```sql UPDATE trades From 3b805813cd49345936ee56219be83d4b92116704 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 9 Mar 2019 13:32:07 +0100 Subject: [PATCH 123/457] Update ccxt from 1.18.347 to 1.18.352 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ff407848b..57ec7ca05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.347 +ccxt==1.18.352 SQLAlchemy==1.3.0 python-telegram-bot==11.1.0 arrow==0.13.1 From 43d30180e8ce3df57f4adbd65b7457cae2f7ad2a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 9 Mar 2019 13:32:08 +0100 Subject: [PATCH 124/457] Update plotly from 3.6.1 to 3.7.0 --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index c01ea6a60..b49aad626 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==3.6.1 +plotly==3.7.0 From c122eab77b52d0cc9fd942f5a130ad0b04a1e236 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 9 Mar 2019 20:13:35 +0100 Subject: [PATCH 125/457] added trailing_only_offset_is_reached option --- freqtrade/strategy/interface.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1d6147357..29976fb4a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -331,7 +331,11 @@ class IStrategy(ABC): f"with offset {sl_offset:.4g} " f"since we have profit {current_profit:.4f}%") - trade.adjust_stop_loss(current_rate, stop_loss_value) + # if trailing_only_offset_is_reached is true, + # we update trailing stoploss only if offset is reached. + tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) + if tsl_only_offset and current_profit > sl_offset: + trade.adjust_stop_loss(current_rate, stop_loss_value) return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) From 9c1c962aa7394ce4e409e9957330de9c8da0adcd Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 9 Mar 2019 20:30:56 +0100 Subject: [PATCH 126/457] if condition fixed --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 29976fb4a..32efdeb17 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -334,7 +334,7 @@ class IStrategy(ABC): # if trailing_only_offset_is_reached is true, # we update trailing stoploss only if offset is reached. tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) - if tsl_only_offset and current_profit > sl_offset: + if not (tsl_only_offset and current_profit < sl_offset): trade.adjust_stop_loss(current_rate, stop_loss_value) return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) From 5f726d697bc2a4842ba441c92a47bb8e087bbe1f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 10 Mar 2019 13:32:05 +0100 Subject: [PATCH 127/457] Update ccxt from 1.18.352 to 1.18.353 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57ec7ca05..2df498452 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.352 +ccxt==1.18.353 SQLAlchemy==1.3.0 python-telegram-bot==11.1.0 arrow==0.13.1 From 0eaac1cd79547233e8dd9d5b3e0c46450e3c9343 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 10 Mar 2019 13:32:06 +0100 Subject: [PATCH 128/457] Update sqlalchemy from 1.3.0 to 1.3.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2df498452..f1ab77b0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ccxt==1.18.353 -SQLAlchemy==1.3.0 +SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 cachetools==3.1.0 From 0467004144bc35ca932d1aee5546a2f31825f260 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 10 Mar 2019 15:54:46 +0100 Subject: [PATCH 129/457] added trailing_only_offset_is_reached to full config --- config_full.json.example | 1 + 1 file changed, 1 insertion(+) diff --git a/config_full.json.example b/config_full.json.example index 0f46a62e3..be4e02039 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -9,6 +9,7 @@ "trailing_stop": false, "trailing_stop_positive": 0.005, "trailing_stop_positive_offset": 0.0051, + "trailing_only_offset_is_reached": false, "minimal_roi": { "40": 0.0, "30": 0.01, From ca496c13b8f037f0b0f99021101864fa61db876e Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 10 Mar 2019 17:11:28 +0100 Subject: [PATCH 130/457] TSL only offset test added --- freqtrade/tests/test_freqtradebot.py | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 2f66a5153..3be0e72c0 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2512,6 +2512,72 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value +def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, + caplog, mocker, markets) -> None: + buy_price = limit_buy_order['price'] + # buy_price: 0.00001099 + + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': buy_price, + 'ask': buy_price, + 'last': buy_price + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + get_markets=markets, + ) + + default_conf['trailing_stop'] = True + default_conf['trailing_stop_positive'] = 0.05 + default_conf['trailing_stop_positive_offset'] = 0.055 + default_conf['trailing_only_offset_is_reached'] = True + + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) + freqtrade.create_trade() + + trade = Trade.query.first() + trade.update(limit_buy_order) + caplog.set_level(logging.DEBUG) + # stop-loss not reached + assert freqtrade.handle_trade(trade) is False + assert trade.stop_loss == 0.0000098910 + + # Raise ticker above buy price + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.0000004, + 'ask': buy_price + 0.0000004, + 'last': buy_price + 0.0000004 + })) + + # stop-loss should not be adjusted as offset is not reached yet + assert freqtrade.handle_trade(trade) is False + + assert not log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.0000098910 + + # price rises above the offset (rises 12% when the offset is 5.5%) + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.0000014, + 'ask': buy_price + 0.0000014, + 'last': buy_price + 0.0000014 + })) + + assert freqtrade.handle_trade(trade) is False + assert log_has(f'using positive stop loss mode: 0.05 with offset 0.055 ' + f'since we have profit 0.1218%', + caplog.record_tuples) + assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.0000117705 + + def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None: patch_RPCManager(mocker) From 8730852d6ede9fb4c3fe6ffe07c0305ab8068cd4 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 10 Mar 2019 20:05:33 +0300 Subject: [PATCH 131/457] Support for systemd watchdog via sd_notify --- docs/bot-usage.md | 2 +- docs/configuration.md | 1 + docs/installation.md | 13 +++++++++++ freqtrade.service.watchdog | 30 +++++++++++++++++++++++++ freqtrade/arguments.py | 7 +++++- freqtrade/configuration.py | 4 ++++ freqtrade/constants.py | 3 ++- freqtrade/freqtradebot.py | 45 ++++++++++++++++++++++++++++++-------- requirements.txt | 5 ++++- 9 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 freqtrade.service.watchdog diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 8b10fb6e9..739a89c07 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -38,7 +38,7 @@ optional arguments: --db-url PATH Override trades database URL, this is useful if dry_run is enabled or in custom deployments (default: None). - + --sd-notify Notify systemd service manager. ``` ### How to use a different configuration file? diff --git a/docs/configuration.md b/docs/configuration.md index d7e774595..1874bef4b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,6 +67,7 @@ Mandatory Parameters are marked as **Required**. | `strategy` | DefaultStrategy | Defines Strategy class to use. | `strategy_path` | null | Adds an additional strategy lookup path (must be a folder). | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. +| `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. ### Parameters in the strategy diff --git a/docs/installation.md b/docs/installation.md index 80223f954..bd6c50c5a 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -428,6 +428,19 @@ For this to be persistent (run when user is logged out) you'll need to enable `l sudo loginctl enable-linger "$USER" ``` +If you run the bot as a service, you can use systemd service manager as a software watchdog monitoring freqtrade bot +state and restarting it in the case of failures. If the `internals.sd_notify` parameter is set to true in the +configuration or the `--sd-notify` command line option is used, the bot will send keep-alive ping messages to systemd +using the sd_notify (systemd notifications) protocol and will also tell systemd its current state (Running or Stopped) +when it changes. + +The `freqtrade.service.watchdog` file contains an example of the service unit configuration file which uses systemd +as the watchdog. + +!!! Note: +The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a +Docker container. + ------ ## Windows diff --git a/freqtrade.service.watchdog b/freqtrade.service.watchdog new file mode 100644 index 000000000..ba491fa53 --- /dev/null +++ b/freqtrade.service.watchdog @@ -0,0 +1,30 @@ +[Unit] +Description=Freqtrade Daemon +After=network.target + +[Service] +# Set WorkingDirectory and ExecStart to your file paths accordingly +# NOTE: %h will be resolved to /home/ +WorkingDirectory=%h/freqtrade +ExecStart=/usr/bin/freqtrade --sd-notify + +Restart=always +#Restart=on-failure + +# Note that we use Type=notify here +Type=notify + +# Currently required if Type=notify +NotifyAccess=all + +StartLimitInterval=1min +StartLimitBurst=5 + +TimeoutStartSec=1min + +# Use here (process_throttle_secs * 2) or longer time interval +WatchdogSec=20 + +[Install] +WantedBy=default.target + diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index ee19f6fe1..604386426 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -127,6 +127,12 @@ class Arguments(object): type=str, metavar='PATH', ) + self.parser.add_argument( + '--sd-notify', + help='Notify systemd service manager.', + action='store_true', + dest='sd_notify', + ) @staticmethod def backtesting_options(parser: argparse.ArgumentParser) -> None: @@ -140,7 +146,6 @@ class Arguments(object): dest='position_stacking', default=False ) - parser.add_argument( '--dmmp', '--disable-max-market-positions', help='Disable applying `max_open_trades` during backtest ' diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index e96305993..6ca0299f0 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -122,6 +122,10 @@ class Configuration(object): set_loggers(config['verbosity']) logger.info('Verbosity set to %s', config['verbosity']) + # Support for sd_notify + if self.args.sd_notify: + config['internals'].update({'sd_notify': True}) + # Add dynamic_whitelist if found if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist: # Update to volumePairList (the previous default) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4d0907d78..ff623df14 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -171,7 +171,8 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'process_throttle_secs': {'type': 'number'}, - 'interval': {'type': 'integer'} + 'interval': {'type': 'integer'}, + 'sd_notify': {'type': 'boolean'}, } } }, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dce3136df..4f3edd600 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -11,6 +11,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple import arrow from requests.exceptions import RequestException +import sdnotify from freqtrade import (DependencyException, OperationalException, TemporaryError, __version__, constants, persistence) @@ -51,6 +52,10 @@ class FreqtradeBot(object): # Init objects self.config = config + + self._sd_notify = sdnotify.SystemdNotifier() if \ + self.config.get('internals', {}).get('sd_notify', False) else None + self.strategy: IStrategy = StrategyResolver(self.config).strategy self.rpc: RPCManager = RPCManager(self) @@ -76,6 +81,11 @@ class FreqtradeBot(object): self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist'] self._init_modules() + # Tell the systemd that we completed initialization phase + if self._sd_notify: + logger.debug("sd_notify: READY=1") + self._sd_notify.notify('READY=1') + def _init_modules(self) -> None: """ Initializes all modules and updates the config @@ -99,6 +109,12 @@ class FreqtradeBot(object): :return: None """ logger.info('Cleaning up modules ...') + + # Tell systemd that we are stopping now + if self._sd_notify: + logger.debug("sd_notify: STOPPING=1") + self._sd_notify.notify('STOPPING=1') + self.rpc.cleanup() persistence.cleanup() @@ -119,16 +135,27 @@ class FreqtradeBot(object): if state == State.RUNNING: self.rpc.startup_messages(self.config, self.pairlists) - if state == State.STOPPED: - time.sleep(1) - elif state == State.RUNNING: - min_secs = self.config.get('internals', {}).get( - 'process_throttle_secs', - constants.PROCESS_THROTTLE_SECS - ) + throttle_secs = self.config.get('internals', {}).get( + 'process_throttle_secs', + constants.PROCESS_THROTTLE_SECS + ) + + if state == State.STOPPED: + # Ping systemd watchdog before sleeping in the stopped state + if self._sd_notify: + logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.") + self._sd_notify.notify('WATCHDOG=1\nSTATUS=State: STOPPED.') + + time.sleep(throttle_secs) + + elif state == State.RUNNING: + # Ping systemd watchdog before throttling + if self._sd_notify: + logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.") + self._sd_notify.notify('WATCHDOG=1\nSTATUS=State: RUNNING.') + + self._throttle(func=self._process, min_secs=throttle_secs) - self._throttle(func=self._process, - min_secs=min_secs) return state def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any: diff --git a/requirements.txt b/requirements.txt index f1ab77b0a..4c223eed4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,5 +22,8 @@ scikit-optimize==0.5.2 # find first, C search in arrays py_find_1st==1.1.3 -#Load ticker files 30% faster +# Load ticker files 30% faster python-rapidjson==0.7.0 + +# Notify systemd +sdnotify==0.3.2 From 513b96b61c43564053dcfd416feeef8e256fe0fb Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 11 Mar 2019 13:32:04 +0100 Subject: [PATCH 132/457] Update ccxt from 1.18.353 to 1.18.357 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f1ab77b0a..434d597f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.353 +ccxt==1.18.357 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 41add9f8cacd45948feba6425a15f33b78b86cd8 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 11 Mar 2019 13:14:55 +0300 Subject: [PATCH 133/457] code cleanup; added message to systemd for reconfiguration --- freqtrade/freqtradebot.py | 23 +++++++++++++++-------- freqtrade/main.py | 3 +++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4f3edd600..8018ed252 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -84,7 +84,7 @@ class FreqtradeBot(object): # Tell the systemd that we completed initialization phase if self._sd_notify: logger.debug("sd_notify: READY=1") - self._sd_notify.notify('READY=1') + self._sd_notify.notify("READY=1") def _init_modules(self) -> None: """ @@ -110,14 +110,21 @@ class FreqtradeBot(object): """ logger.info('Cleaning up modules ...') - # Tell systemd that we are stopping now - if self._sd_notify: - logger.debug("sd_notify: STOPPING=1") - self._sd_notify.notify('STOPPING=1') - self.rpc.cleanup() persistence.cleanup() + def stopping(self) -> None: + # Tell systemd that we are exiting now + if self._sd_notify: + logger.debug("sd_notify: STOPPING=1") + self._sd_notify.notify("STOPPING=1") + + def reconfigure(self) -> None: + # Tell systemd that we initiated reconfiguring + if self._sd_notify: + logger.debug("sd_notify: RELOADING=1") + self._sd_notify.notify("RELOADING=1") + def worker(self, old_state: State = None) -> State: """ Trading routine that must be run at each loop @@ -144,7 +151,7 @@ class FreqtradeBot(object): # Ping systemd watchdog before sleeping in the stopped state if self._sd_notify: logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.") - self._sd_notify.notify('WATCHDOG=1\nSTATUS=State: STOPPED.') + self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.") time.sleep(throttle_secs) @@ -152,7 +159,7 @@ class FreqtradeBot(object): # Ping systemd watchdog before throttling if self._sd_notify: logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.") - self._sd_notify.notify('WATCHDOG=1\nSTATUS=State: RUNNING.') + self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.") self._throttle(func=self._process, min_secs=throttle_secs) diff --git a/freqtrade/main.py b/freqtrade/main.py index 75b15915b..c41d54f0e 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -60,6 +60,7 @@ def main(sysargv: List[str]) -> None: logger.exception('Fatal exception!') finally: if freqtrade: + freqtrade.stopping() freqtrade.rpc.send_msg({ 'type': RPCMessageType.STATUS_NOTIFICATION, 'status': 'process died' @@ -72,6 +73,8 @@ def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot: """ Cleans up current instance, reloads the configuration and returns the new instance """ + freqtrade.reconfigure() + # Clean up current modules freqtrade.cleanup() From f9aa3c27bea314578f4bbd8585bd6fe06fb8a78d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Mar 2019 19:49:03 +0100 Subject: [PATCH 134/457] Catch ModuleNotFoundError when importing external code --- freqtrade/resolvers/iresolver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 852d1dc0c..6023bc2ba 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -31,7 +31,11 @@ class IResolver(object): # Generate spec based on absolute path spec = importlib.util.spec_from_file_location('unknown', str(module_path)) module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) # type: ignore # importlib does not use typehints + try: + spec.loader.exec_module(module) # type: ignore # importlib does not use typehints + except ModuleNotFoundError as err: + # Catch errors in case a specific module is not installed + logger.info(f"Could not import {module_path} due to '{err}'") valid_objects_gen = ( obj for name, obj in inspect.getmembers(module, inspect.isclass) From e666c6850e6a0da1bfe078c13d228bb5ee596a69 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Mar 2019 20:20:30 +0100 Subject: [PATCH 135/457] Fix tests so Market orders should not send timeInForce --- freqtrade/tests/exchange/test_exchange.py | 27 ++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index fce9cba14..26b5297ea 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -594,8 +594,6 @@ def test_buy_prod(default_conf, mocker, exchange_name): def test_buy_considers_time_in_force(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': { @@ -607,6 +605,25 @@ def test_buy_considers_time_in_force(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock) + order_type = 'limit' + time_in_force = 'ioc' + + 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] == 200 + assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc'} + + order_type = 'market' + time_in_force = 'ioc' + order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200, time_in_force=time_in_force) @@ -618,13 +635,13 @@ def test_buy_considers_time_in_force(default_conf, mocker): 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'} + assert api_mock.create_order.call_args[0][5] == {} 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' + order_type = 'limit' time_in_force = 'ioc' api_mock.create_order = MagicMock(return_value={ 'id': order_id, @@ -648,7 +665,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): 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][4] == 200 assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', 'trading_agreement': 'agree'} From c0f276a8920b8cc1919e1b1206d81b577c583dc0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Mar 2019 20:22:51 +0100 Subject: [PATCH 136/457] Move kraken specific tests to their own file --- freqtrade/tests/exchange/test_exchange.py | 68 +---------------------- freqtrade/tests/exchange/test_kraken.py | 67 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 65 deletions(-) create mode 100644 freqtrade/tests/exchange/test_kraken.py diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 26b5297ea..d24dd1757 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -4,7 +4,7 @@ import copy import logging from datetime import datetime from random import randint -from unittest.mock import Mock, MagicMock, PropertyMock +from unittest.mock import MagicMock, Mock, PropertyMock import arrow import ccxt @@ -12,11 +12,10 @@ import pytest from pandas import DataFrame from freqtrade import DependencyException, OperationalException, TemporaryError -from freqtrade.exchange import Exchange, Kraken, Binance +from freqtrade.exchange import Binance, 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 - +from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re # Make sure to always keep one exchange here which is NOT subclassed!! EXCHANGES = ['bittrex', 'binance', 'kraken', ] @@ -638,67 +637,6 @@ def test_buy_considers_time_in_force(default_conf, mocker): assert api_mock.create_order.call_args[0][5] == {} -def test_buy_kraken_trading_agreement(default_conf, mocker): - api_mock = MagicMock() - order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) - order_type = 'limit' - 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] == 200 - assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', - '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) diff --git a/freqtrade/tests/exchange/test_kraken.py b/freqtrade/tests/exchange/test_kraken.py new file mode 100644 index 000000000..8b81a08a9 --- /dev/null +++ b/freqtrade/tests/exchange/test_kraken.py @@ -0,0 +1,67 @@ +# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement +# pragma pylint: disable=protected-access +from random import randint +from unittest.mock import MagicMock + +from freqtrade.tests.conftest import get_patched_exchange + + +def test_buy_kraken_trading_agreement(default_conf, mocker): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + order_type = 'limit' + 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] == 200 + assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', + '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'} From 4705b7da0edb6ee405aba89c59dd42a22b6824cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Mar 2019 20:30:16 +0100 Subject: [PATCH 137/457] Add time_in_force test for sell --- freqtrade/tests/exchange/test_exchange.py | 60 +++++++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index d24dd1757..ff36ab91c 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -590,7 +590,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): amount=1, rate=200, time_in_force=time_in_force) -def test_buy_considers_time_in_force(default_conf, mocker): +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) api_mock.create_order = MagicMock(return_value={ @@ -602,7 +603,7 @@ def test_buy_considers_time_in_force(default_conf, mocker): 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) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_type = 'limit' time_in_force = 'ioc' @@ -618,7 +619,8 @@ def test_buy_considers_time_in_force(default_conf, mocker): 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] == 200 - assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc'} + assert "timeInForce" in api_mock.create_order.call_args[0][5] + assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force order_type = 'market' time_in_force = 'ioc' @@ -634,7 +636,8 @@ def test_buy_considers_time_in_force(default_conf, mocker): 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] == {} + # Market orders should not send timeInForce!! + assert "timeInForce" not in api_mock.create_order.call_args[0][5] def test_sell_dry_run(default_conf, mocker): @@ -705,6 +708,55 @@ def test_sell_prod(default_conf, mocker, exchange_name): exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): + api_mock = MagicMock() + order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) + 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=exchange_name) + + order_type = 'limit' + time_in_force = 'ioc' + + order = exchange.sell(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] == 'sell' + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] == 200 + assert "timeInForce" in api_mock.create_order.call_args[0][5] + assert api_mock.create_order.call_args[0][5]["timeInForce"] == time_in_force + + order_type = 'market' + time_in_force = 'ioc' + order = exchange.sell(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] == 'sell' + assert api_mock.create_order.call_args[0][3] == 1 + assert api_mock.create_order.call_args[0][4] is None + # Market orders should not send timeInForce!! + assert "timeInForce" not in api_mock.create_order.call_args[0][5] + + def test_get_balance_dry_run(default_conf, mocker): default_conf['dry_run'] = True From 0eb9dd5fe5d8381e2bce26effe66b3309d6ff85a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Mar 2019 20:30:36 +0100 Subject: [PATCH 138/457] Don't use timeInForce for market orders --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f6fb0a58a..32d952542 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -352,7 +352,7 @@ class Exchange(object): return dry_order params = self._params.copy() - if time_in_force != 'gtc': + if time_in_force != 'gtc' and ordertype != 'market': params.update({'timeInForce': time_in_force}) return self.create_order(pair, ordertype, 'buy', amount, rate, params) @@ -365,7 +365,7 @@ class Exchange(object): return dry_order params = self._params.copy() - if time_in_force != 'gtc': + if time_in_force != 'gtc' and ordertype != 'market': params.update({'timeInForce': time_in_force}) return self.create_order(pair, ordertype, 'sell', amount, rate, params) From 48d33b070f01a016c46df2ca2f68d26b9122b99f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2019 07:06:42 +0100 Subject: [PATCH 139/457] Add stoploss to startup messages --- freqtrade/rpc/rpc_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index de861677d..bc69c97ad 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -61,6 +61,8 @@ class RPCManager(object): stake_currency = config['stake_currency'] stake_amount = config['stake_amount'] minimal_roi = config['minimal_roi'] + stoploss = config['stoploss'] + trailing_stop = config['trailing_stop'] ticker_interval = config['ticker_interval'] exchange_name = config['exchange']['name'] strategy_name = config.get('strategy', '') @@ -69,6 +71,7 @@ class RPCManager(object): 'status': f'*Exchange:* `{exchange_name}`\n' f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' f'*Minimum ROI:* `{minimal_roi}`\n' + f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n' f'*Ticker Interval:* `{ticker_interval}`\n' f'*Strategy:* `{strategy_name}`' }) From 643262bc6a48a5761f7c42c73400b630026e30b8 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 13:03:29 +0100 Subject: [PATCH 140/457] add trailing stop loss config validator --- freqtrade/exchange/exchange.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 32d952542..96eebecc2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -111,6 +111,8 @@ class Exchange(object): self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {})) + self.validate_trailing_stoploss(config) + if config.get('ticker_interval'): # Check if timeframe is available self.validate_timeframes(config['ticker_interval']) @@ -257,6 +259,30 @@ class Exchange(object): raise OperationalException( f'Time in force policies are not supporetd for {self.name} yet.') + def validate_trailing_stoploss(self, config) -> None: + """ + Validates the trailing stoploss configuration + """ + + tsl = config.get('trailing_stop', False) + # Skip if trailing stoploss is not activated + if not tsl: + return + + tsl_positive = float(config.get('trailing_stop_positive', 0)) + tsl_offset = float(config.get('trailing_stop_positive_offset', 0)) + tsl_only_offset = config.get('trailing_only_offset_is_reached', False) + + if tsl_only_offset: + if tsl_positive == 0.0: + raise OperationalException( + f'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config') + if tsl_positive > 0 and tsl_offset > 0 and tsl_offset <= tsl_positive: + raise OperationalException( + f'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config') + def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. From 3e40f5c588a13d9cdac0598e0131ea6f9692e04d Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 13:09:27 +0100 Subject: [PATCH 141/457] if condition simplified --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 96eebecc2..8c4315906 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -278,10 +278,10 @@ class Exchange(object): raise OperationalException( f'The config trailing_only_offset_is_reached need ' 'trailing_stop_positive_offset to be more than 0 in your config') - if tsl_positive > 0 and tsl_offset > 0 and tsl_offset <= tsl_positive: + if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: raise OperationalException( f'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config') + 'to be greater than trailing_stop_positive_offset in your config') def exchange_has(self, endpoint: str) -> bool: """ From 36e95bc8689081a4a775808fb6531ba56a3f22a3 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 13:10:59 +0100 Subject: [PATCH 142/457] unnecessary variable removed --- freqtrade/exchange/exchange.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8c4315906..13ab51226 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -263,10 +263,8 @@ class Exchange(object): """ Validates the trailing stoploss configuration """ - - tsl = config.get('trailing_stop', False) # Skip if trailing stoploss is not activated - if not tsl: + if not config.get('trailing_stop', False): return tsl_positive = float(config.get('trailing_stop_positive', 0)) From 3e4c9c8713cc131aee3a5b9931a856f24b8fcc71 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 12 Mar 2019 13:32:05 +0100 Subject: [PATCH 143/457] Update ccxt from 1.18.357 to 1.18.358 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 434d597f9..6462cfd30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.357 +ccxt==1.18.358 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From f55d75e7fcb15f7a27c7cfd51981f63697019803 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:35:44 +0100 Subject: [PATCH 144/457] TSL validation tests added --- freqtrade/exchange/exchange.py | 8 +++---- freqtrade/tests/exchange/test_exchange.py | 27 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 13ab51226..88f255c85 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -274,12 +274,12 @@ class Exchange(object): if tsl_only_offset: if tsl_positive == 0.0: raise OperationalException( - f'The config trailing_only_offset_is_reached need ' - 'trailing_stop_positive_offset to be more than 0 in your config') + f'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.') if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: raise OperationalException( - f'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config') + f'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.') def exchange_has(self, endpoint: str) -> bool: """ diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index ff36ab91c..16e5e693b 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -432,6 +432,33 @@ def test_validate_order_types(default_conf, mocker): Exchange(default_conf) +def test_validate_tsl(default_conf, mocker): + api_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + default_conf['trailing_stop'] = True + default_conf['trailing_stop_positive'] = 0 + default_conf['trailing_stop_positive_offset'] = 0 + default_conf['trailing_only_offset_is_reached'] = False + + Exchange(default_conf) + + default_conf['trailing_only_offset_is_reached'] = True + with pytest.raises(OperationalException, + match=r'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.'): + Exchange(default_conf) + + default_conf['trailing_stop_positive_offset'] = 0.01 + default_conf['trailing_stop_positive'] = 0.015 + with pytest.raises(OperationalException, + match=r'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.'): + Exchange(default_conf) + + def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) From a772ab323e3212d7eaf91b881a358dcd01c856ba Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:43:53 +0100 Subject: [PATCH 145/457] adding the option to resolver --- freqtrade/constants.py | 1 + freqtrade/resolvers/strategy_resolver.py | 25 ++++++++++++------------ freqtrade/strategy/interface.py | 1 + 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4d0907d78..f0e9f7490 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -73,6 +73,7 @@ CONF_SCHEMA = { 'trailing_stop': {'type': 'boolean'}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, + 'trailing_only_offset_is_reached': {'type': 'boolean'}, 'unfilledtimeout': { 'type': 'object', 'properties': { diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index c49da9205..60d1fe21c 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -46,18 +46,19 @@ class StrategyResolver(IResolver): # Set attributes # Check if we need to override configuration # (Attribute name, default, experimental) - attributes = [("minimal_roi", None, False), - ("ticker_interval", None, False), - ("stoploss", None, False), - ("trailing_stop", None, False), - ("trailing_stop_positive", None, False), - ("trailing_stop_positive_offset", 0.0, False), - ("process_only_new_candles", None, False), - ("order_types", None, False), - ("order_time_in_force", None, False), - ("use_sell_signal", False, True), - ("sell_profit_only", False, True), - ("ignore_roi_if_buy_signal", False, True), + attributes = [("minimal_roi", None, False), + ("ticker_interval", None, False), + ("stoploss", None, False), + ("trailing_stop", None, False), + ("trailing_stop_positive", None, False), + ("trailing_stop_positive_offset", 0.0, False), + ("trailing_only_offset_is_reached", None, False), + ("process_only_new_candles", None, False), + ("order_types", None, False), + ("order_time_in_force", None, False), + ("use_sell_signal", False, True), + ("sell_profit_only", False, True), + ("ignore_roi_if_buy_signal", False, True), ] for attribute, default, experimental in attributes: if experimental: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 32efdeb17..41dcb8c57 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -73,6 +73,7 @@ class IStrategy(ABC): trailing_stop: bool = False trailing_stop_positive: float trailing_stop_positive_offset: float + trailing_only_offset_is_reached = False # associated ticker interval ticker_interval: str From 8d5cc42ef5a30f806fd0110e6c75c3ece12fb0de Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:46:21 +0100 Subject: [PATCH 146/457] configuration doc added --- docs/configuration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index d7e774595..59816ec4d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -26,6 +26,7 @@ Mandatory Parameters are marked as **Required**. | `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). | `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). | `trailing_stop_positive_offset` | 0 | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). +| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). | `unfilledtimeout.buy` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `bid_strategy.ask_last_balance` | 0.0 | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). @@ -319,7 +320,7 @@ section of the configuration. * `VolumePairList` * Formerly available as `--dynamic-whitelist []`. This command line option is deprecated and should no longer be used. - * It selects `number_assets` top pairs based on `sort_key`, which can be one of + * It selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. * There is a possibility to filter low-value coins that would not allow setting a stop loss (set `precision_filter` parameter to `true` for this). From 0bcf50f1b57ecc63996b8254bfd2bba0b2bdf655 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:48:30 +0100 Subject: [PATCH 147/457] added to stoploss doc --- docs/stoploss.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/stoploss.md b/docs/stoploss.md index 0726aebbc..62276e7cc 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -55,8 +55,11 @@ Both values can be configured in the main configuration file and requires `"trai ``` json "trailing_stop_positive": 0.01, "trailing_stop_positive_offset": 0.011, + "trailing_only_offset_is_reached": false ``` The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit. You should also make sure to have this value (`trailing_stop_positive_offset`) lower than your minimal ROI, otherwise minimal ROI will apply first and sell your trade. + +If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. From d423f5856679cd7e2c4e61a56e98b1629daeb6f1 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Mon, 4 Mar 2019 21:21:32 +0100 Subject: [PATCH 148/457] replace fetch_markets --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 32d952542..1fcf20ff8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -657,7 +657,7 @@ class Exchange(object): @retrier def get_markets(self) -> List[dict]: try: - return self._api.fetch_markets() + return list(self.markets.values()) except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load markets due to {e.__class__.__name__}. Message: {e}') From 3a2aa54d2afb4e3498624826ba6d33d77f7c5e89 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Mon, 4 Mar 2019 23:59:08 +0100 Subject: [PATCH 149/457] add markets property --- freqtrade/exchange/exchange.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 1fcf20ff8..b199f6051 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -106,7 +106,7 @@ class Exchange(object): logger.info('Using Exchange "%s"', self.name) - self.markets = self._load_markets() + self._load_markets() # Check if all pairs are available self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) @@ -165,6 +165,11 @@ class Exchange(object): """exchange ccxt id""" return self._api.id + @property + def markets(self) -> Dict: + """exchange ccxt markets""" + return self._api.markets + def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame: if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] From ccad883256d32080641b5eb5faade4a3e25ec27c Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 19:40:55 +0100 Subject: [PATCH 150/457] adjust get_markets --- freqtrade/exchange/exchange.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b199f6051..71c1c9a08 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -661,13 +661,7 @@ class Exchange(object): @retrier def get_markets(self) -> List[dict]: - try: - return list(self.markets.values()) - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not load markets due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(e) + return list(self.markets.values()) @retrier def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1, From 47cc04c0a315b4ed05f3e8b42aff201d37f9f2fb Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 19:44:23 +0100 Subject: [PATCH 151/457] use self.markets instead of _api.markets --- freqtrade/exchange/exchange.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 71c1c9a08..a7e10d923 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -276,8 +276,8 @@ class Exchange(object): Returns the amount to buy or sell to a precision the Exchange accepts Rounded down ''' - if self._api.markets[pair]['precision']['amount']: - symbol_prec = self._api.markets[pair]['precision']['amount'] + if self.markets[pair]['precision']['amount']: + symbol_prec = self.markets[pair]['precision']['amount'] big_amount = amount * pow(10, symbol_prec) amount = floor(big_amount) / pow(10, symbol_prec) return amount @@ -287,8 +287,8 @@ class Exchange(object): Returns the price buying or selling with to the precision the Exchange accepts Rounds up ''' - if self._api.markets[pair]['precision']['price']: - symbol_prec = self._api.markets[pair]['precision']['price'] + if self.markets[pair]['precision']['price']: + symbol_prec = self.markets[pair]['precision']['price'] big_price = price * pow(10, symbol_prec) price = ceil(big_price) / pow(10, symbol_prec) return price From b24a22b0b6033ce8a3ca1df6ff599490afb37ee3 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 19:45:10 +0100 Subject: [PATCH 152/457] use self.markets instead of get_markets --- freqtrade/freqtradebot.py | 10 ++++------ freqtrade/pairlist/IPairList.py | 6 ++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dce3136df..939904c73 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -280,12 +280,10 @@ class FreqtradeBot(object): return stake_amount def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]: - markets = self.exchange.get_markets() - markets = [m for m in markets if m['symbol'] == pair] - if not markets: - raise ValueError(f'Can\'t get market information for symbol {pair}') - - market = markets[0] + try: + market = self.exchange.markets[pair] + except KeyError: + raise ValueError(f"Can't get market information for symbol {pair}") if 'limits' not in market: return None diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 948abe113..5559c582f 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -66,12 +66,14 @@ class IPairList(ABC): black_listed """ sanitized_whitelist = whitelist - markets = self._freqtrade.exchange.get_markets() + markets = self._freqtrade.exchange.markets # Filter to markets in stake currency - markets = [m for m in markets if m['quote'] == self._config['stake_currency']] + markets = [markets[pair] for pair in markets if + markets[pair]['quote'] == self._config['stake_currency']] known_pairs = set() + # TODO: we should loop over whitelist instead of all markets for market in markets: pair = market['symbol'] # pair is not in the generated dynamic market, or in the blacklist ... ignore it From 5c840f333f8c9795a1b45f9696f84f4caad0fe7d Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 19:45:42 +0100 Subject: [PATCH 153/457] slight change to exception message --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a7e10d923..e98244656 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -226,7 +226,7 @@ class Exchange(object): f'Pair {pair} not compatible with stake_currency: {stake_cur}') if self.markets and pair not in self.markets: raise OperationalException( - f'Pair {pair} is not available at {self.name}' + f'Pair {pair} is not available on {self.name}. ' f'Please remove {pair} from your whitelist.') def validate_timeframes(self, timeframe: List[str]) -> None: From c30fb7f59088a3b2beb300d8dc0d4a9278c3cae0 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 19:45:54 +0100 Subject: [PATCH 154/457] return markets as dict --- freqtrade/tests/conftest.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 6237a27c9..4d363a173 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -37,10 +37,12 @@ def log_has_re(line, logs): def patch_exchange(mocker, api_mock=None, id='bittrex') -> None: mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) + # mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={})) if api_mock: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) @@ -225,8 +227,8 @@ def ticker_sell_down(): @pytest.fixture def markets(): - return MagicMock(return_value=[ - { + return { + 'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', @@ -251,7 +253,7 @@ def markets(): }, 'info': '', }, - { + 'TKN/BTC': { 'id': 'tknbtc', 'symbol': 'TKN/BTC', 'base': 'TKN', @@ -276,7 +278,7 @@ def markets(): }, 'info': '', }, - { + 'BLK/BTC': { 'id': 'blkbtc', 'symbol': 'BLK/BTC', 'base': 'BLK', @@ -301,7 +303,7 @@ def markets(): }, 'info': '', }, - { + 'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', @@ -326,7 +328,7 @@ def markets(): }, 'info': '', }, - { + 'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', @@ -351,7 +353,7 @@ def markets(): }, 'info': '', }, - { + 'NEO/BTC': { 'id': 'neobtc', 'symbol': 'NEO/BTC', 'base': 'NEO', @@ -376,7 +378,7 @@ def markets(): }, 'info': '', }, - { + 'BTT/BTC': { 'id': 'BTTBTC', 'symbol': 'BTT/BTC', 'base': 'BTT', @@ -404,7 +406,7 @@ def markets(): }, 'info': "", }, - { + 'ETH/USDT': { 'id': 'USDT-ETH', 'symbol': 'ETH/USDT', 'base': 'ETH', @@ -426,7 +428,7 @@ def markets(): 'active': True, 'info': "" }, - { + 'LTC/USDT': { 'id': 'USDT-LTC', 'symbol': 'LTC/USDT', 'base': 'LTC', @@ -448,7 +450,7 @@ def markets(): }, 'info': "" } - ]) + } @pytest.fixture From e234158cc90061c95889190ca6cfc35f54f152e8 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 19:46:03 +0100 Subject: [PATCH 155/457] update tests --- freqtrade/tests/exchange/test_exchange.py | 80 ++++---- freqtrade/tests/pairlist/test_pairlist.py | 14 +- freqtrade/tests/rpc/test_rpc.py | 20 +- freqtrade/tests/rpc/test_rpc_telegram.py | 24 ++- freqtrade/tests/test_freqtradebot.py | 224 ++++++++++------------ 5 files changed, 166 insertions(+), 196 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index ff36ab91c..0beea4ed3 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -152,10 +152,7 @@ def test_symbol_amount_prec(default_conf, mocker): markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 4}}}) type(api_mock).markets = markets - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - exchange = Exchange(default_conf) + exchange = get_patched_exchange(mocker, default_conf, api_mock) amount = 2.34559 pair = 'ETH/BTC' @@ -176,10 +173,7 @@ def test_symbol_price_prec(default_conf, mocker): markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 4}}}) type(api_mock).markets = markets - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - exchange = Exchange(default_conf) + exchange = get_patched_exchange(mocker, default_conf, api_mock) price = 2.34559 pair = 'ETH/BTC' @@ -198,11 +192,7 @@ def test_set_sandbox(default_conf, mocker): url_mock = PropertyMock(return_value={'test': "api-public.sandbox.gdax.com", 'api': 'https://api.gdax.com'}) type(api_mock).urls = url_mock - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - - exchange = Exchange(default_conf) + exchange = get_patched_exchange(mocker, default_conf, api_mock) liveurl = exchange._api.urls['api'] default_conf['exchange']['sandbox'] = True exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname') @@ -220,12 +210,8 @@ def test_set_sandbox_exception(default_conf, mocker): url_mock = PropertyMock(return_value={'api': 'https://api.gdax.com'}) type(api_mock).urls = url_mock - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - with pytest.raises(OperationalException, match=r'does not provide a sandbox api'): - exchange = Exchange(default_conf) + exchange = get_patched_exchange(mocker, default_conf, api_mock) default_conf['exchange']['sandbox'] = True exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname') @@ -247,29 +233,27 @@ def test__load_async_markets(default_conf, mocker, caplog): def test__load_markets(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance')) - - api_mock.load_markets = MagicMock(return_value={}) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - - expected_return = {'ETH/BTC': 'available'} - api_mock.load_markets = MagicMock(return_value=expected_return) - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - default_conf['exchange']['pair_whitelist'] = ['ETH/BTC'] - ex = Exchange(default_conf) - assert ex.markets == expected_return - api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError()) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) Exchange(default_conf) assert log_has('Unable to initialize markets. Reason: ', caplog.record_tuples) - -def test_validate_pairs(default_conf, mocker): + expected_return = {'ETH/BTC': 'available'} api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value={ + api_mock.load_markets = MagicMock(return_value=expected_return) + type(api_mock).markets = expected_return + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + default_conf['exchange']['pair_whitelist'] = ['ETH/BTC'] + ex = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + assert ex.markets == expected_return + + +def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly + api_mock = MagicMock() + type(api_mock).markets = PropertyMock(return_value={ 'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': '' }) id_mock = PropertyMock(return_value='test_exchange') @@ -283,7 +267,9 @@ def test_validate_pairs(default_conf, mocker): def test_validate_pairs_not_available(default_conf, mocker): api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value={'XRP/BTC': 'inactive'}) + type(api_mock).markets = PropertyMock(return_value={ + 'XRP/BTC': 'inactive' + }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) @@ -294,7 +280,7 @@ def test_validate_pairs_not_available(default_conf, mocker): def test_validate_pairs_not_compatible(default_conf, mocker): api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value={ + type(api_mock).markets = PropertyMock(return_value={ 'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': '' }) default_conf['stake_currency'] = 'ETH' @@ -310,15 +296,15 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance')) - api_mock.load_markets = MagicMock(return_value={}) + type(api_mock).markets = PropertyMock(return_value={}) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'): + with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'): Exchange(default_conf) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={})) Exchange(default_conf) assert log_has('Unable to validate pairs (assuming they are correct).', caplog.record_tuples) @@ -353,6 +339,7 @@ def test_validate_timeframes(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) Exchange(default_conf) @@ -369,6 +356,7 @@ def test_validate_timeframes_failed(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) with pytest.raises(OperationalException, match=r'Invalid ticker 3m, this Exchange supports.*'): Exchange(default_conf) @@ -386,6 +374,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) Exchange(default_conf) @@ -395,6 +384,7 @@ def test_validate_order_types(default_conf, mocker): type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True}) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') default_conf['order_types'] = { @@ -436,6 +426,7 @@ def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) conf = copy.deepcopy(default_conf) @@ -1314,9 +1305,9 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_markets(default_conf, mocker, markets, exchange_name): - api_mock = MagicMock() - api_mock.fetch_markets = markets - exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) ret = exchange.get_markets() assert isinstance(ret, list) assert len(ret) == 9 @@ -1324,9 +1315,6 @@ def test_get_markets(default_conf, mocker, markets, exchange_name): assert ret[0]["id"] == "ethbtc" assert ret[0]["symbol"] == "ETH/BTC" - ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - 'get_markets', 'fetch_markets') - @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_fee(default_conf, mocker, exchange_name): diff --git a/freqtrade/tests/pairlist/test_pairlist.py b/freqtrade/tests/pairlist/test_pairlist.py index dd6ebb62c..c40e16f77 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -1,6 +1,6 @@ # pragma pylint: disable=missing-docstring,C0103,protected-access -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock from freqtrade import OperationalException from freqtrade.constants import AVAILABLE_PAIRLISTS @@ -44,7 +44,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf): freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) freqtradebot.pairlists.refresh_pairlist() # List ordered by BaseVolume whitelist = ['ETH/BTC', 'TKN/BTC'] @@ -58,7 +58,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf): def test_refresh_pairlists(mocker, markets, whitelist_conf): freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) freqtradebot.pairlists.refresh_pairlist() # List ordered by BaseVolume whitelist = ['ETH/BTC', 'TKN/BTC'] @@ -73,7 +73,7 @@ def test_refresh_pairlist_dynamic(mocker, markets, tickers, whitelist_conf): } mocker.patch.multiple( 'freqtrade.exchange.Exchange', - get_markets=markets, + markets=PropertyMock(return_value=markets), get_tickers=tickers, exchange_has=MagicMock(return_value=True) ) @@ -96,7 +96,7 @@ def test_refresh_pairlist_dynamic(mocker, markets, tickers, whitelist_conf): def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.get_markets', markets_empty) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty)) # argument: use the whitelist dynamically by exchange-volume whitelist = [] @@ -111,7 +111,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) whitelist_conf['pairlist']['method'] = 'VolumePairList' mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, p, r: round(r, 8)) @@ -157,7 +157,7 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): whitelist_conf['pairlist']['method'] = pairlist - mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 2de2668e8..e1261e02e 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -2,7 +2,7 @@ # pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments from datetime import datetime -from unittest.mock import MagicMock, ANY +from unittest.mock import MagicMock, ANY, PropertyMock import pytest from numpy import isnan @@ -34,7 +34,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -90,7 +90,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -126,7 +126,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -180,7 +180,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -268,7 +268,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets, 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -424,7 +424,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: } ), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -516,7 +516,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, get_balances=MagicMock(return_value=ticker), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -552,7 +552,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: get_balances=MagicMock(return_value=ticker), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) @@ -581,7 +581,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order get_balances=MagicMock(return_value=ticker), get_ticker=ticker, get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), buy=buy_mm ) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 9964973e1..b5055d7f5 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -5,7 +5,7 @@ import re from datetime import datetime from random import randint -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock import arrow import pytest @@ -184,7 +184,7 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(markets) ) msg_mock = MagicMock() status_table = MagicMock() @@ -693,7 +693,8 @@ def test_forcesell_handle(default_conf, update, ticker, fee, _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets), + validate_pairs=MagicMock(return_value={}) ) freqtradebot = FreqtradeBot(default_conf) @@ -743,7 +744,8 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets), + validate_pairs=MagicMock(return_value={}) ) freqtradebot = FreqtradeBot(default_conf) @@ -796,7 +798,8 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets), + validate_pairs=MagicMock(return_value={}) ) freqtradebot = FreqtradeBot(default_conf) @@ -878,7 +881,8 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', _load_markets=MagicMock(return_value={}), - get_markets=markets + markets=PropertyMock(markets), + validate_pairs=MagicMock(return_value={}) ) fbuy_mock = MagicMock(return_value=None) mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) @@ -914,7 +918,8 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non mocker.patch.multiple( 'freqtrade.exchange.Exchange', _load_markets=MagicMock(return_value={}), - get_markets=markets + markets=PropertyMock(markets), + validate_pairs=MagicMock(return_value={}) ) freqtradebot = FreqtradeBot(default_conf) patch_get_signal(freqtradebot, (True, False)) @@ -941,7 +946,8 @@ def test_performance_handle(default_conf, update, ticker, fee, 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(markets), + validate_pairs=MagicMock(return_value={}) ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) freqtradebot = FreqtradeBot(default_conf) @@ -980,7 +986,7 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non 'freqtrade.exchange.Exchange', get_ticker=ticker, buy=MagicMock(return_value={'id': 'mocked_order_id'}), - get_markets=markets + markets=PropertyMock(markets) ) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) freqtradebot = FreqtradeBot(default_conf) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 2f66a5153..178081aff 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -5,7 +5,7 @@ import logging import re import time from copy import deepcopy -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock import arrow import pytest @@ -59,7 +59,8 @@ def patch_RPCManager(mocker) -> MagicMock: # Unit tests -def test_freqtradebot(mocker, default_conf) -> None: +def test_freqtradebot(mocker, default_conf, markets) -> None: + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) freqtrade = get_patched_freqtradebot(mocker, default_conf) assert freqtrade.state is State.RUNNING @@ -71,7 +72,6 @@ def test_freqtradebot(mocker, default_conf) -> None: def test_cleanup(mocker, default_conf, caplog) -> None: mock_cleanup = MagicMock() mocker.patch('freqtrade.persistence.cleanup', mock_cleanup) - freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade.cleanup() assert log_has('Cleaning up modules ...', caplog.record_tuples) @@ -171,11 +171,10 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, patch_wallet(mocker, free=default_conf['stake_amount']) mocker.patch.multiple( 'freqtrade.exchange.Exchange', - validate_pairs=MagicMock(), + markets=PropertyMock(return_value=markets), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), - get_fee=fee, - get_markets=markets + get_fee=fee ) conf = deepcopy(default_conf) @@ -253,7 +252,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets) ) ############################################# @@ -293,7 +292,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), ) ############################################# @@ -321,7 +320,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -349,131 +348,108 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: patch_exchange(mocker) freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.stoploss = -0.05 + markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} # no pair found mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC' - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) with pytest.raises(ValueError, match=r'.*get market information.*'): freqtrade._get_min_pair_stake_amount('BNB/BTC', 1) # no 'limits' section - mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC' - }]) - ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1) assert result is None # empty 'limits' section + markets["ETH/BTC"]["limits"] = {} mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': {} - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1) assert result is None # no cost Min + markets["ETH/BTC"]["limits"] = { + 'cost': {"min": None}, + 'amount': {} + } mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': { - 'cost': {"min": None}, - 'amount': {} - } - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1) assert result is None # no amount Min + markets["ETH/BTC"]["limits"] = { + 'cost': {}, + 'amount': {"min": None} + } mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': { - 'cost': {}, - 'amount': {"min": None} - } - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1) assert result is None # empty 'cost'/'amount' section + markets["ETH/BTC"]["limits"] = { + 'cost': {}, + 'amount': {} + } mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': { - 'cost': {}, - 'amount': {} - } - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1) assert result is None # min cost is set + markets["ETH/BTC"]["limits"] = { + 'cost': {'min': 2}, + 'amount': {} + } mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': { - 'cost': {'min': 2}, - 'amount': {} - } - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1) assert result == 2 / 0.9 # min amount is set + markets["ETH/BTC"]["limits"] = { + 'cost': {}, + 'amount': {'min': 2} + } mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': { - 'cost': {}, - 'amount': {'min': 2} - } - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2) assert result == 2 * 2 / 0.9 # min amount and cost are set (cost is minimal) + markets["ETH/BTC"]["limits"] = { + 'cost': {'min': 2}, + 'amount': {'min': 2} + } mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': { - 'cost': {'min': 2}, - 'amount': {'min': 2} - } - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2) assert result == min(2, 2 * 2) / 0.9 # min amount and cost are set (amount is minial) + markets["ETH/BTC"]["limits"] = { + 'cost': {'min': 8}, + 'amount': {'min': 2} + } mocker.patch( - 'freqtrade.exchange.Exchange.get_markets', - MagicMock(return_value=[{ - 'symbol': 'ETH/BTC', - 'limits': { - 'cost': {'min': 8}, - 'amount': {'min': 2} - } - }]) + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2) assert result == min(8, 2 * 2) / 0.9 @@ -487,7 +463,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocke get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) # Save state of current whitelist @@ -522,7 +498,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -541,7 +517,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, get_ticker=ticker, buy=buy_mock, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['stake_amount'] = 0.0005 freqtrade = FreqtradeBot(default_conf) @@ -562,7 +538,7 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord get_ticker=ticker, buy=buy_mock, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['stake_amount'] = 0.000000005 @@ -583,7 +559,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_balance=MagicMock(return_value=default_conf['stake_amount']), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['max_open_trades'] = 0 default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT @@ -603,7 +579,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, marke get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['exchange']['pair_whitelist'] = ["ETH/BTC"] @@ -626,7 +602,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['exchange']['pair_whitelist'] = ["ETH/BTC"] default_conf['exchange']['pair_blacklist'] = ["ETH/BTC"] @@ -665,7 +641,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, - get_markets=markets, + markets=PropertyMock(return_value=markets), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_order=MagicMock(return_value=limit_buy_order), get_fee=fee, @@ -702,7 +678,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, - get_markets=markets, + markets=PropertyMock(return_value=markets), buy=MagicMock(side_effect=TemporaryError) ) sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None) @@ -721,7 +697,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) -> mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, - get_markets=markets, + markets=PropertyMock(return_value=markets), buy=MagicMock(side_effect=OperationalException) ) freqtrade = FreqtradeBot(default_conf) @@ -742,7 +718,7 @@ def test_process_trade_handling( mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, - get_markets=markets, + markets=PropertyMock(return_value=markets), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_order=MagicMock(return_value=limit_buy_order), get_fee=fee, @@ -769,7 +745,7 @@ def test_process_trade_no_whitelist_pair( mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, - get_markets=markets, + markets=PropertyMock(return_value=markets), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_order=MagicMock(return_value=limit_buy_order), get_fee=fee, @@ -818,7 +794,7 @@ def test_process_informative_pairs_added(default_conf, ticker, markets, mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, - get_markets=markets, + markets=PropertyMock(return_value=markets), buy=MagicMock(side_effect=TemporaryError), refresh_latest_ohlcv=refresh_mock, ) @@ -886,7 +862,7 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non }), buy=buy_mm, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) pair = 'ETH/BTC' print(buy_mm.call_args_list) @@ -997,7 +973,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, buy=MagicMock(return_value={'id': limit_buy_order['id']}), sell=MagicMock(return_value={'id': limit_sell_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), stoploss_limit=stoploss_limit ) freqtrade = FreqtradeBot(default_conf) @@ -1066,7 +1042,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, buy=MagicMock(return_value={'id': limit_buy_order['id']}), sell=MagicMock(return_value={'id': limit_sell_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), stoploss_limit=stoploss_limit ) @@ -1164,7 +1140,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, buy=MagicMock(return_value={'id': limit_buy_order['id']}), sell=MagicMock(return_value={'id': limit_sell_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), stoploss_limit=stoploss_limit ) @@ -1348,7 +1324,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, buy=MagicMock(return_value={'id': limit_buy_order['id']}), sell=MagicMock(return_value={'id': limit_sell_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -1386,7 +1362,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) @@ -1442,7 +1418,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) @@ -1475,7 +1451,7 @@ def test_handle_trade_experimental( get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) @@ -1503,7 +1479,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -1846,7 +1822,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -1891,7 +1867,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -1939,7 +1915,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -1996,7 +1972,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) stoploss_limit = MagicMock(return_value={ @@ -2051,7 +2027,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) stoploss_limit = MagicMock(return_value={ @@ -2116,7 +2092,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -2162,7 +2138,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -2213,7 +2189,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['experimental'] = { 'use_sell_signal': True, @@ -2245,7 +2221,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['experimental'] = { 'use_sell_signal': True, @@ -2275,7 +2251,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['experimental'] = { 'use_sell_signal': True, @@ -2306,7 +2282,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['experimental'] = { 'use_sell_signal': True, @@ -2338,7 +2314,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['experimental'] = { 'ignore_roi_if_buy_signal': True @@ -2372,7 +2348,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), ) default_conf['trailing_stop'] = True freqtrade = FreqtradeBot(default_conf) @@ -2407,7 +2383,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), ) default_conf['trailing_stop'] = True default_conf['trailing_stop_positive'] = 0.01 @@ -2465,7 +2441,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), ) default_conf['trailing_stop'] = True @@ -2525,7 +2501,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) default_conf['experimental'] = { 'ignore_roi_if_buy_signal': False @@ -2760,7 +2736,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) # Save state of current whitelist @@ -2796,7 +2772,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) # Save state of current whitelist freqtrade = FreqtradeBot(default_conf) @@ -2816,7 +2792,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2, markets) ticker_mock = MagicMock(return_value={'ask': 0.045, 'last': 0.046}) mocker.patch.multiple( 'freqtrade.exchange.Exchange', - get_markets=markets, + markets=PropertyMock(return_value=markets), get_order_book=order_book_l2, get_ticker=ticker_mock, @@ -2841,7 +2817,7 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets) ticker_mock = MagicMock(return_value={'ask': 0.042, 'last': 0.046}) mocker.patch.multiple( 'freqtrade.exchange.Exchange', - get_markets=markets, + markets=PropertyMock(return_value=markets), get_order_book=order_book_l2, get_ticker=ticker_mock, @@ -2865,7 +2841,7 @@ def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2, markets) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', - get_markets=markets, + markets=PropertyMock(return_value=markets), get_order_book=order_book_l2 ) default_conf['telegram']['enabled'] = False @@ -2902,7 +2878,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order buy=MagicMock(return_value={'id': limit_buy_order['id']}), sell=MagicMock(return_value={'id': limit_sell_order['id']}), get_fee=fee, - get_markets=markets + markets=PropertyMock(return_value=markets) ) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) From 6b97af4a03bd1f588a7a64cb57696d336f31d0e6 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 20:18:35 +0100 Subject: [PATCH 156/457] add comment --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e98244656..7e97923b0 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -200,7 +200,7 @@ class Exchange(object): try: markets = self._api.load_markets() self._load_async_markets() - return markets + return markets # prbly not necessary to return anything anymore except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) return {} From 041e9957dd312fdefb901e4265afd5ac40068256 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 6 Mar 2019 22:48:04 +0100 Subject: [PATCH 157/457] add reload argument --- freqtrade/exchange/exchange.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7e97923b0..994e1c3a4 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -186,24 +186,23 @@ class Exchange(object): "Please check your config.json") raise OperationalException(f'Exchange {name} does not provide a sandbox api') - def _load_async_markets(self) -> None: + def _load_async_markets(self, reload=False) -> None: try: if self._api_async: - asyncio.get_event_loop().run_until_complete(self._api_async.load_markets()) + asyncio.get_event_loop().run_until_complete( + self._api_async.load_markets(reload=reload)) except ccxt.BaseError as e: logger.warning('Could not load async markets. Reason: %s', e) return - def _load_markets(self) -> Dict[str, Any]: + def _load_markets(self, reload=False) -> Dict[str, Any]: """ Initialize markets both sync and async """ try: - markets = self._api.load_markets() - self._load_async_markets() - return markets # prbly not necessary to return anything anymore + self._api.load_markets(reload=reload) + self._load_async_markets(reload=reload) except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) - return {} def validate_pairs(self, pairs: List[str]) -> None: """ From df9410cd1569957cff7edcec3bbe458eabbad4fd Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 6 Mar 2019 22:57:31 +0100 Subject: [PATCH 158/457] check if markets were loaded --- freqtrade/exchange/exchange.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 994e1c3a4..2d811e8e8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -168,6 +168,9 @@ class Exchange(object): @property def markets(self) -> Dict: """exchange ccxt markets""" + if not self._api.markets: + logger.warning("Markets were not loaded. Loading them now..") + self._load_markets() return self._api.markets def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame: From 3ad0686bc78018ccc6c19cfa6ffad72d47e62bed Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 6 Mar 2019 23:00:28 +0100 Subject: [PATCH 159/457] fix typing --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2d811e8e8..8eb74bdd5 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -199,7 +199,7 @@ class Exchange(object): logger.warning('Could not load async markets. Reason: %s', e) return - def _load_markets(self, reload=False) -> Dict[str, Any]: + def _load_markets(self, reload=False) -> None: """ Initialize markets both sync and async """ try: self._api.load_markets(reload=reload) From 0d980134e7f946aaae9894c0611657a54a8c0b2f Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 10 Mar 2019 13:30:45 +0100 Subject: [PATCH 160/457] add markets reload func --- freqtrade/exchange/exchange.py | 19 +++++++++++++++++++ freqtrade/freqtradebot.py | 3 +++ 2 files changed, 22 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8eb74bdd5..30bf9a30c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -88,6 +88,8 @@ class Exchange(object): # Holds last candle refreshed time of each pair self._pairs_last_refresh_time: Dict[Tuple[str, str], int] = {} + # Timestamp of last markets refresh + self._last_markets_refresh: int = 0 # Holds candles self._klines: Dict[Tuple[str, str], DataFrame] = {} @@ -106,7 +108,12 @@ class Exchange(object): logger.info('Using Exchange "%s"', self.name) + # Converts the interval provided in minutes in config to seconds + self.markets_refresh_interval: int = exchange_config.get( + "markets_refresh_interval", 60) * 60 + # Initial markets load self._load_markets() + # Check if all pairs are available self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) @@ -204,9 +211,21 @@ class Exchange(object): try: self._api.load_markets(reload=reload) self._load_async_markets(reload=reload) + self._last_markets_refresh = arrow.utcnow().timestamp except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) + def _reload_markets(self) -> None: + """Reload markets both sync and async, if refresh interval has passed""" + # Check whether markets have to be reloaded + if (self._last_markets_refresh > 0) and ( + self._last_markets_refresh + self.markets_refresh_interval + > arrow.utcnow().timestamp): + return None + else: + logger.debug("Performing scheduled market reload..") + self._load_markets(reload=True) + def validate_pairs(self, pairs: List[str]) -> None: """ Checks if all given pairs are tradable on the current exchange. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 939904c73..0422b0f88 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -155,6 +155,9 @@ class FreqtradeBot(object): """ state_changed = False try: + # Check whether markets have to be reloaded + self.exchange._reload_markets() + # Refresh whitelist self.pairlists.refresh_pairlist() self.active_pair_whitelist = self.pairlists.whitelist From 35c2b961be3899b1220e581143f051b00c2f03d8 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 10 Mar 2019 13:30:52 +0100 Subject: [PATCH 161/457] add config param --- config_full.json.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config_full.json.example b/config_full.json.example index 0f46a62e3..a1aabb1b8 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -77,7 +77,8 @@ "pair_blacklist": [ "DOGE/BTC" ], - "outdated_offset": 5 + "outdated_offset": 5, + "markets_refresh_interval": 60 }, "edge": { "enabled": false, From 87410178199ad60a52e2969506b093ca99beda4a Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 10 Mar 2019 15:26:55 +0100 Subject: [PATCH 162/457] remove get_markets --- freqtrade/exchange/exchange.py | 4 ---- freqtrade/tests/exchange/test_exchange.py | 13 ------------- freqtrade/tests/pairlist/test_pairlist.py | 2 +- freqtrade/tests/rpc/test_rpc_telegram.py | 8 ++++---- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 30bf9a30c..272d81e59 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -680,10 +680,6 @@ class Exchange(object): except ccxt.BaseError as e: raise OperationalException(e) - @retrier - def get_markets(self) -> List[dict]: - return list(self.markets.values()) - @retrier def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1, price=1, taker_or_maker='maker') -> float: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 0beea4ed3..e9debbfc2 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1303,19 +1303,6 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name): assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == [] -@pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_get_markets(default_conf, mocker, markets, exchange_name): - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - ret = exchange.get_markets() - assert isinstance(ret, list) - assert len(ret) == 9 - - assert ret[0]["id"] == "ethbtc" - assert ret[0]["symbol"] == "ETH/BTC" - - @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_fee(default_conf, mocker, exchange_name): api_mock = MagicMock() diff --git a/freqtrade/tests/pairlist/test_pairlist.py b/freqtrade/tests/pairlist/test_pairlist.py index c40e16f77..52f44c41b 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -33,7 +33,7 @@ def whitelist_conf(default_conf): def test_load_pairlist_noexist(mocker, markets, default_conf): freqtradebot = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.get_markets', markets) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) with pytest.raises(ImportError, match=r"Impossible to load Pairlist 'NonexistingPairList'." r" This class does not exist or contains Python code errors"): diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index b5055d7f5..02f4f4afb 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -232,7 +232,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(markets) ) msg_mock = MagicMock() status_table = MagicMock() @@ -279,7 +279,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) get_ticker=ticker, buy=MagicMock(return_value={'id': 'mocked_order_id'}), get_fee=fee, - get_markets=markets + markets=PropertyMock(markets) ) msg_mock = MagicMock() mocker.patch.multiple( @@ -334,7 +334,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(markets) ) msg_mock = MagicMock() mocker.patch.multiple( @@ -438,7 +438,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - get_markets=markets + markets=PropertyMock(markets) ) msg_mock = MagicMock() mocker.patch.multiple( From 1a92bf9e8ec0c3bd0db39ed0b6a20d52417840c0 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 10 Mar 2019 16:36:25 +0100 Subject: [PATCH 163/457] add test --- freqtrade/exchange/exchange.py | 5 ++-- freqtrade/tests/exchange/test_exchange.py | 28 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 272d81e59..f177e1d54 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -222,9 +222,8 @@ class Exchange(object): self._last_markets_refresh + self.markets_refresh_interval > arrow.utcnow().timestamp): return None - else: - logger.debug("Performing scheduled market reload..") - self._load_markets(reload=True) + logger.debug("Performing scheduled market reload..") + self._load_markets(reload=True) def validate_pairs(self, pairs: List[str]) -> None: """ diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index e9debbfc2..08e460336 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -245,12 +245,38 @@ def test__load_markets(default_conf, mocker, caplog): api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value=expected_return) type(api_mock).markets = expected_return - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) default_conf['exchange']['pair_whitelist'] = ['ETH/BTC'] ex = get_patched_exchange(mocker, default_conf, api_mock, id="binance") assert ex.markets == expected_return +def test__reload_markets(default_conf, mocker, caplog): + caplog.set_level(logging.DEBUG) + api_mock = MagicMock() + initial_markets = {'ETH/BTC': {}} + type(api_mock).markets = initial_markets + updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} + default_conf['exchange']['markets_refresh_interval'] = 10 + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange._last_markets_refresh = arrow.utcnow().timestamp + + def _load_markets(*args, **kwargs): + exchange._api.markets = updated_markets + + mocker.patch('freqtrade.exchange.Exchange._load_markets', _load_markets) + assert exchange.markets == initial_markets + + # less than 10 minutes have passed, no reload + exchange._reload_markets() + assert exchange.markets == initial_markets + + # more than 10 minutes have passed, reload is executed + exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60 + exchange._reload_markets() + assert exchange.markets == updated_markets + assert log_has('Performing scheduled market reload..', caplog.record_tuples) + + def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ From deddbda26e1e5247dc8803c7ac338645f7903b92 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 10 Mar 2019 16:40:59 +0100 Subject: [PATCH 164/457] delete markets patch from conftest --- freqtrade/tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 4d363a173..26262cb4b 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -42,7 +42,6 @@ def patch_exchange(mocker, api_mock=None, id='bittrex') -> None: mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) - # mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value={})) if api_mock: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) From 0ffefe44a7b73396e8209eadc29ee688e9f7e55e Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 10 Mar 2019 17:40:54 +0100 Subject: [PATCH 165/457] reorder vars --- freqtrade/tests/exchange/test_exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 08e460336..41ca0a884 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -252,13 +252,13 @@ def test__load_markets(default_conf, mocker, caplog): def test__reload_markets(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) - api_mock = MagicMock() initial_markets = {'ETH/BTC': {}} + api_mock = MagicMock() type(api_mock).markets = initial_markets - updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} default_conf['exchange']['markets_refresh_interval'] = 10 exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange._last_markets_refresh = arrow.utcnow().timestamp + updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} def _load_markets(*args, **kwargs): exchange._api.markets = updated_markets From 779bcdd990b098a64e4c9a67e2bd221653833228 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 12 Mar 2019 16:35:32 +0100 Subject: [PATCH 166/457] remove reload for async api --- freqtrade/exchange/exchange.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f177e1d54..6ee5b17a2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -206,11 +206,11 @@ class Exchange(object): logger.warning('Could not load async markets. Reason: %s', e) return - def _load_markets(self, reload=False) -> None: + def _load_markets(self) -> None: """ Initialize markets both sync and async """ try: - self._api.load_markets(reload=reload) - self._load_async_markets(reload=reload) + self._api.load_markets() + self._load_async_markets() self._last_markets_refresh = arrow.utcnow().timestamp except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) @@ -223,7 +223,7 @@ class Exchange(object): > arrow.utcnow().timestamp): return None logger.debug("Performing scheduled market reload..") - self._load_markets(reload=True) + self._api.load_markets(reload=True) def validate_pairs(self, pairs: List[str]) -> None: """ From 299e64017095d9165c5c31d04e3b65a109e2ab45 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 12 Mar 2019 16:39:13 +0100 Subject: [PATCH 167/457] include markets_refresh_interval in docs --- docs/configuration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index d7e774595..10fe50a27 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,6 +47,7 @@ Mandatory Parameters are marked as **Required**. | `exchange.ccxt_rate_limit` | True | DEPRECATED!! Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange. | `exchange.ccxt_config` | None | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) | `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) +| `exchange.markets_refresh_interval` | 60 | The interval in which markets are reloaded. | `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-strategy). | `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-strategy). @@ -319,7 +320,7 @@ section of the configuration. * `VolumePairList` * Formerly available as `--dynamic-whitelist []`. This command line option is deprecated and should no longer be used. - * It selects `number_assets` top pairs based on `sort_key`, which can be one of + * It selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. * There is a possibility to filter low-value coins that would not allow setting a stop loss (set `precision_filter` parameter to `true` for this). From cb9849e192205cde0178d9b7e8df7a7a9bf948a9 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 12 Mar 2019 16:54:59 +0100 Subject: [PATCH 168/457] add markets_refresh_interval to CONF_SCHEMA --- freqtrade/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4d0907d78..e3c059ae0 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -202,6 +202,7 @@ CONF_SCHEMA = { 'uniqueItems': True }, 'outdated_offset': {'type': 'integer', 'minimum': 1}, + 'markets_refresh_interval': {'type': 'integer'}, 'ccxt_config': {'type': 'object'}, 'ccxt_async_config': {'type': 'object'} }, From 7ffe65770e1b9db6c60ff5ef3e5a1ef6d666d78a Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 12 Mar 2019 17:54:16 +0100 Subject: [PATCH 169/457] fix test --- freqtrade/tests/exchange/test_exchange.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 41ca0a884..7c757df09 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -253,17 +253,18 @@ def test__load_markets(default_conf, mocker, caplog): def test__reload_markets(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) initial_markets = {'ETH/BTC': {}} + + def load_markets(*args, **kwargs): + exchange._api.markets = updated_markets + api_mock = MagicMock() + api_mock.load_markets = load_markets type(api_mock).markets = initial_markets default_conf['exchange']['markets_refresh_interval'] = 10 exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange._last_markets_refresh = arrow.utcnow().timestamp updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} - def _load_markets(*args, **kwargs): - exchange._api.markets = updated_markets - - mocker.patch('freqtrade.exchange.Exchange._load_markets', _load_markets) assert exchange.markets == initial_markets # less than 10 minutes have passed, no reload From 0293a618959969fb076bdcd778f96944e150adfa Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2019 19:37:43 +0100 Subject: [PATCH 170/457] Update documentation for minimal_roi, which is not really optional --- docs/configuration.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index d7e774595..2d5c958bb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -118,9 +118,10 @@ See the example below: }, ``` -Most of the strategy files already include the optimal `minimal_roi` -value. This parameter is optional. If you use it in the configuration file, it will take over the +Most of the strategy files already include the optimal `minimal_roi` value. +This parameter can be set in either Strategy or Configuration file. If you use it in the configuration file, it will override the `minimal_roi` value from the strategy file. +If it is not set in either Strategy or Configuration, a default of 1000% `{"0": 10}` is used, and minimal roi is disabled unless your trade generates 1000% profit. ### Understand stoploss From 94b2d48d02f6c451dfc0d0c0a6a3777d6283e8bc Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2019 19:37:58 +0100 Subject: [PATCH 171/457] Add default value for minimal_roi (1000%) fix #1633 --- freqtrade/resolvers/strategy_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index c49da9205..8e582d09c 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -46,7 +46,7 @@ class StrategyResolver(IResolver): # Set attributes # Check if we need to override configuration # (Attribute name, default, experimental) - attributes = [("minimal_roi", None, False), + attributes = [("minimal_roi", {"0": 10.0}, False), ("ticker_interval", None, False), ("stoploss", None, False), ("trailing_stop", None, False), From e2bcaa4d7586228cb95a6358f73ebd0b8c254e78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2019 21:46:35 +0100 Subject: [PATCH 172/457] Set Requested_close_rate to stoploss when stoploss_on_exchange was hit --- freqtrade/persistence.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index f603b139f..10aff72ec 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -266,6 +266,7 @@ class Trade(_DECL_BASE): logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self) elif order_type == 'stop_loss_limit': self.stoploss_order_id = None + self.close_rate_requested = self.stop_loss logger.info('STOP_LOSS_LIMIT is hit for %s.', self) self.close(order['average']) else: From 11cc33a982cbe801c564676b5082e6ec409bdf7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2019 21:49:08 +0100 Subject: [PATCH 173/457] Refactor notify_sell to rpc_manager * Call sell_notify also when stoploss_on_exchange is hit fix #1653 --- freqtrade/freqtradebot.py | 34 +++------------------------------- freqtrade/rpc/rpc_manager.py | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dce3136df..58888a4f8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -512,6 +512,7 @@ class FreqtradeBot(object): except OperationalException as exception: logger.warning("Could not update trade amount: %s", exception) + # This handles both buy and sell orders! trade.update(order) if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: @@ -657,6 +658,7 @@ class FreqtradeBot(object): if order['status'] == 'closed': trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.update(order) + self.rpc.notify_sell(trade, self.config, trade.close_rate) result = True elif self.config.get('trailing_stop', False): # if trailing stoploss is enabled we check if stoploss value has changed @@ -846,35 +848,5 @@ class FreqtradeBot(object): trade.open_order_id = order_id trade.close_rate_requested = limit trade.sell_reason = sell_reason.value - - profit_trade = trade.calc_profit(rate=limit) - current_rate = self.exchange.get_ticker(trade.pair)['bid'] - profit_percent = trade.calc_profit_percent(limit) - gain = "profit" if profit_percent > 0 else "loss" - - msg = { - 'type': RPCMessageType.SELL_NOTIFICATION, - 'exchange': trade.exchange.capitalize(), - 'pair': trade.pair, - 'gain': gain, - 'limit': limit, - 'amount': trade.amount, - 'open_rate': trade.open_rate, - 'current_rate': current_rate, - 'profit_amount': profit_trade, - 'profit_percent': profit_percent, - 'sell_reason': sell_reason.value - } - - # For regular case, when the configuration exists - if 'stake_currency' in self.config and 'fiat_display_currency' in self.config: - stake_currency = self.config['stake_currency'] - fiat_currency = self.config['fiat_display_currency'] - msg.update({ - 'stake_currency': stake_currency, - 'fiat_currency': fiat_currency, - }) - - # Send the message - self.rpc.send_msg(msg) Trade.session.flush() + self.rpc.notify_sell(trade, self.config, self.exchange.get_ticker(trade.pair)['bid']) diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index bc69c97ad..7a3971356 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -2,8 +2,9 @@ This module contains class to manage RPC communications (Telegram, Slack, ...) """ import logging -from typing import List, Dict, Any +from typing import Any, Dict, List +from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCMessageType logger = logging.getLogger(__name__) @@ -80,3 +81,35 @@ class RPCManager(object): 'status': f'Searching for {stake_currency} pairs to buy and sell ' f'based on {pairlist.short_desc()}' }) + + def notify_sell(self, trade: Trade, config, current_rate: float): + profit_trade = trade.calc_profit(rate=trade.close_rate_requested) + + profit_percent = trade.calc_profit_percent(trade.close_rate_requested) + gain = "profit" if profit_percent > 0 else "loss" + + msg = { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': trade.exchange.capitalize(), + 'pair': trade.pair, + 'gain': gain, + 'limit': trade.close_rate_requested, + 'amount': trade.amount, + 'open_rate': trade.open_rate, + 'current_rate': current_rate, + 'profit_amount': profit_trade, + 'profit_percent': profit_percent, + 'sell_reason': trade.sell_reason + } + + # For regular case, when the configuration exists + if 'stake_currency' in config and 'fiat_display_currency' in config: + stake_currency = config['stake_currency'] + fiat_currency = config['fiat_display_currency'] + msg.update({ + 'stake_currency': stake_currency, + 'fiat_currency': fiat_currency, + }) + + # Send the message + self.send_msg(msg) From 9054165e8accf6339008e75a0dec56507e0a2585 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2019 21:50:57 +0100 Subject: [PATCH 174/457] Adjust test, since rpc_message is now called on buy and sel --- freqtrade/tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 2f66a5153..bd40a063f 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2105,7 +2105,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, assert trade.is_open is False print(trade.sell_reason) assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value - assert rpc_mock.call_count == 1 + assert rpc_mock.call_count == 2 def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, From 6b948cfc7eed65da52c24037ff4e12db04849e8a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2019 22:01:19 +0100 Subject: [PATCH 175/457] Don't move notify_sell to rpc_manager - it needs exchange stuff --- freqtrade/freqtradebot.py | 39 ++++++++++++++++++++++++++++++++++-- freqtrade/rpc/rpc_manager.py | 33 ------------------------------ 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 58888a4f8..f6662e062 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -658,7 +658,7 @@ class FreqtradeBot(object): if order['status'] == 'closed': trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.update(order) - self.rpc.notify_sell(trade, self.config, trade.close_rate) + self.notify_sell(trade) result = True elif self.config.get('trailing_stop', False): # if trailing stoploss is enabled we check if stoploss value has changed @@ -849,4 +849,39 @@ class FreqtradeBot(object): trade.close_rate_requested = limit trade.sell_reason = sell_reason.value Trade.session.flush() - self.rpc.notify_sell(trade, self.config, self.exchange.get_ticker(trade.pair)['bid']) + self.notify_sell(trade) + + def notify_sell(self, trade: Trade): + """ + Sends rpc notification when a sell occured. + """ + profit_trade = trade.calc_profit(rate=trade.close_rate_requested) + current_rate = self.exchange.get_ticker(trade.pair)['bid'] + profit_percent = trade.calc_profit_percent(trade.close_rate_requested) + gain = "profit" if profit_percent > 0 else "loss" + + msg = { + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': trade.exchange.capitalize(), + 'pair': trade.pair, + 'gain': gain, + 'limit': trade.close_rate_requested, + 'amount': trade.amount, + 'open_rate': trade.open_rate, + 'current_rate': current_rate, + 'profit_amount': profit_trade, + 'profit_percent': profit_percent, + 'sell_reason': trade.sell_reason + } + + # For regular case, when the configuration exists + if 'stake_currency' in self.config and 'fiat_display_currency' in self.config: + stake_currency = self.config['stake_currency'] + fiat_currency = self.config['fiat_display_currency'] + msg.update({ + 'stake_currency': stake_currency, + 'fiat_currency': fiat_currency, + }) + + # Send the message + self.rpc.send_msg(msg) diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 7a3971356..7f0d0a5d4 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -4,7 +4,6 @@ This module contains class to manage RPC communications (Telegram, Slack, ...) import logging from typing import Any, Dict, List -from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCMessageType logger = logging.getLogger(__name__) @@ -81,35 +80,3 @@ class RPCManager(object): 'status': f'Searching for {stake_currency} pairs to buy and sell ' f'based on {pairlist.short_desc()}' }) - - def notify_sell(self, trade: Trade, config, current_rate: float): - profit_trade = trade.calc_profit(rate=trade.close_rate_requested) - - profit_percent = trade.calc_profit_percent(trade.close_rate_requested) - gain = "profit" if profit_percent > 0 else "loss" - - msg = { - 'type': RPCMessageType.SELL_NOTIFICATION, - 'exchange': trade.exchange.capitalize(), - 'pair': trade.pair, - 'gain': gain, - 'limit': trade.close_rate_requested, - 'amount': trade.amount, - 'open_rate': trade.open_rate, - 'current_rate': current_rate, - 'profit_amount': profit_trade, - 'profit_percent': profit_percent, - 'sell_reason': trade.sell_reason - } - - # For regular case, when the configuration exists - if 'stake_currency' in config and 'fiat_display_currency' in config: - stake_currency = config['stake_currency'] - fiat_currency = config['fiat_display_currency'] - msg.update({ - 'stake_currency': stake_currency, - 'fiat_currency': fiat_currency, - }) - - # Send the message - self.send_msg(msg) From 5151a4521f8f2b829d8bcc04323aee0b25dbc75d Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 13 Mar 2019 13:32:04 +0100 Subject: [PATCH 176/457] Update ccxt from 1.18.358 to 1.18.361 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6462cfd30..618de66fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.358 +ccxt==1.18.361 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 23666858e2779b21b84559678b33e66c95ad74e8 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 13 Mar 2019 13:32:06 +0100 Subject: [PATCH 177/457] Update pytest from 4.3.0 to 4.3.1 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 859d80482..68d63101a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ flake8==3.7.7 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 -pytest==4.3.0 +pytest==4.3.1 pytest-mock==1.10.1 pytest-asyncio==0.10.0 pytest-cov==2.6.1 From 2bf5a3843da848d45669bd98b764991d62d476e3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Mar 2019 19:41:58 +0100 Subject: [PATCH 178/457] Use close_rate for notification if available --- freqtrade/freqtradebot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f6662e062..2eecb5802 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -855,9 +855,10 @@ class FreqtradeBot(object): """ Sends rpc notification when a sell occured. """ - profit_trade = trade.calc_profit(rate=trade.close_rate_requested) + profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested + profit_trade = trade.calc_profit(rate=profit_rate) current_rate = self.exchange.get_ticker(trade.pair)['bid'] - profit_percent = trade.calc_profit_percent(trade.close_rate_requested) + profit_percent = trade.calc_profit_percent(profit_rate) gain = "profit" if profit_percent > 0 else "loss" msg = { From aa2d747d8fdac532d0fb3c6b6aa9a2cd500e470e Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 13 Mar 2019 20:08:51 +0100 Subject: [PATCH 179/457] update docs --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 10fe50a27..9b08f77c6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,7 +47,7 @@ Mandatory Parameters are marked as **Required**. | `exchange.ccxt_rate_limit` | True | DEPRECATED!! Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange. | `exchange.ccxt_config` | None | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) | `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) -| `exchange.markets_refresh_interval` | 60 | The interval in which markets are reloaded. +| `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded. | `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-strategy). | `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-strategy). From a1841c35aec82775cb11078bc1e9e26a89785d7e Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 13 Mar 2019 20:18:49 +0100 Subject: [PATCH 180/457] reset _last_markets_refresh --- freqtrade/exchange/exchange.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6ee5b17a2..c6b3a3796 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -224,6 +224,7 @@ class Exchange(object): return None logger.debug("Performing scheduled market reload..") self._api.load_markets(reload=True) + self._last_markets_refresh = arrow.utcnow().timestamp def validate_pairs(self, pairs: List[str]) -> None: """ From ff9231eec484bc4d12fa24e27551f243dd20875b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Mar 2019 06:42:27 +0100 Subject: [PATCH 181/457] Format attributes-table --- freqtrade/resolvers/strategy_resolver.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 8e582d09c..9d9d59a4a 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -46,18 +46,18 @@ class StrategyResolver(IResolver): # Set attributes # Check if we need to override configuration # (Attribute name, default, experimental) - attributes = [("minimal_roi", {"0": 10.0}, False), - ("ticker_interval", None, False), - ("stoploss", None, False), - ("trailing_stop", None, False), - ("trailing_stop_positive", None, False), - ("trailing_stop_positive_offset", 0.0, False), - ("process_only_new_candles", None, False), - ("order_types", None, False), - ("order_time_in_force", None, False), - ("use_sell_signal", False, True), - ("sell_profit_only", False, True), - ("ignore_roi_if_buy_signal", False, True), + attributes = [("minimal_roi", {"0": 10.0}, False), + ("ticker_interval", None, False), + ("stoploss", None, False), + ("trailing_stop", None, False), + ("trailing_stop_positive", None, False), + ("trailing_stop_positive_offset", 0.0, False), + ("process_only_new_candles", None, False), + ("order_types", None, False), + ("order_time_in_force", None, False), + ("use_sell_signal", False, True), + ("sell_profit_only", False, True), + ("ignore_roi_if_buy_signal", False, True), ] for attribute, default, experimental in attributes: if experimental: From 3c99e3b7c776d55c8de84976c13ce440cb6fa726 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:00:28 +0100 Subject: [PATCH 182/457] test adapted to new market refactoring --- freqtrade/tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 796b4411e..fc7c48663 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2504,7 +2504,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), ) default_conf['trailing_stop'] = True From 29305dd0704ade08a614fce4f528fcb224cf1244 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:01:03 +0100 Subject: [PATCH 183/457] config validation moved to configuration file --- freqtrade/configuration.py | 32 ++++++++++++++++++++- freqtrade/tests/exchange/test_exchange.py | 27 ------------------ freqtrade/tests/test_configuration.py | 34 +++++++++++++++++++++-- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index e96305993..585704b2d 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -58,6 +58,7 @@ class Configuration(object): config['internals'] = {} logger.info('Validating configuration ...') + self._validate_config_schema(config) self._validate_config(config) # Set strategy if not specified in config and or if it's non default @@ -291,7 +292,7 @@ class Configuration(object): return config - def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]: + def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]: """ Validate the configuration follow the Config Schema :param conf: Config in JSON format @@ -309,6 +310,35 @@ class Configuration(object): best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message ) + def _validate_config(self, conf: Dict[str, Any]) -> None: + """ + Validate the configuration consistency + :param conf: Config in JSON format + :return: Returns None if everything is ok, otherwise throw an exception + """ + + # validating trailing stoploss + self._validate_trailing_stoploss(conf) + + def _validate_trailing_stoploss(self, conf: Dict[str, Any]) -> None: + # Skip if trailing stoploss is not activated + if not conf.get('trailing_stop', False): + return + + tsl_positive = float(conf.get('trailing_stop_positive', 0)) + tsl_offset = float(conf.get('trailing_stop_positive_offset', 0)) + tsl_only_offset = conf.get('trailing_only_offset_is_reached', False) + + if tsl_only_offset: + if tsl_positive == 0.0: + raise OperationalException( + f'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.') + if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: + raise OperationalException( + f'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.') + def get_config(self) -> Dict[str, Any]: """ Return the config. Use this method to get the bot config diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index c71c1580f..7c757df09 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -449,33 +449,6 @@ def test_validate_order_types(default_conf, mocker): Exchange(default_conf) -def test_validate_tsl(default_conf, mocker): - api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') - default_conf['trailing_stop'] = True - default_conf['trailing_stop_positive'] = 0 - default_conf['trailing_stop_positive_offset'] = 0 - default_conf['trailing_only_offset_is_reached'] = False - - Exchange(default_conf) - - default_conf['trailing_only_offset_is_reached'] = True - with pytest.raises(OperationalException, - match=r'The config trailing_only_offset_is_reached need ' - 'trailing_stop_positive_offset to be more than 0 in your config.'): - Exchange(default_conf) - - default_conf['trailing_stop_positive_offset'] = 0.01 - default_conf['trailing_stop_positive'] = 0.015 - with pytest.raises(OperationalException, - match=r'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config.'): - Exchange(default_conf) - - def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 51098baaa..3b7b1285c 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,6 +13,7 @@ from freqtrade import OperationalException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL +from freqtrade.exchange import Exchange from freqtrade.state import RunMode from freqtrade.tests.conftest import log_has @@ -22,7 +23,7 @@ def test_load_config_invalid_pair(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*does not match.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_missing_attributes(default_conf) -> None: @@ -30,7 +31,7 @@ def test_load_config_missing_attributes(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_incorrect_stake_amount(default_conf) -> None: @@ -38,7 +39,7 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*\'fake\' does not match \'unlimited\'.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_file(default_conf, mocker, caplog) -> None: @@ -573,3 +574,30 @@ def test__create_datadir(mocker, default_conf, caplog) -> None: cfg._create_datadir(default_conf, '/foo/bar') assert md.call_args[0][0] == "/foo/bar" assert log_has('Created data directory: /foo/bar', caplog.record_tuples) + + +def test_validate_tsl(default_conf, mocker): + mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + default_conf['trailing_stop'] = True + default_conf['trailing_stop_positive'] = 0 + default_conf['trailing_stop_positive_offset'] = 0 + default_conf['trailing_only_offset_is_reached'] = False + + Exchange(default_conf) + + default_conf['trailing_only_offset_is_reached'] = True + with pytest.raises(OperationalException, + match=r'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.'): + Exchange(default_conf) + + default_conf['trailing_stop_positive_offset'] = 0.01 + default_conf['trailing_stop_positive'] = 0.015 + with pytest.raises(OperationalException, + match=r'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.'): + Exchange(default_conf) From b5034cf53579101ae9af06ad22e130cddf097ecb Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:04:41 +0100 Subject: [PATCH 184/457] TSL validator removed from exchange --- freqtrade/exchange/exchange.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b82959680..33f62f2f7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -118,7 +118,6 @@ class Exchange(object): self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {})) - self.validate_trailing_stoploss(config) if config.get('ticker_interval'): # Check if timeframe is available @@ -285,28 +284,6 @@ class Exchange(object): raise OperationalException( f'Time in force policies are not supporetd for {self.name} yet.') - def validate_trailing_stoploss(self, config) -> None: - """ - Validates the trailing stoploss configuration - """ - # Skip if trailing stoploss is not activated - if not config.get('trailing_stop', False): - return - - tsl_positive = float(config.get('trailing_stop_positive', 0)) - tsl_offset = float(config.get('trailing_stop_positive_offset', 0)) - tsl_only_offset = config.get('trailing_only_offset_is_reached', False) - - if tsl_only_offset: - if tsl_positive == 0.0: - raise OperationalException( - f'The config trailing_only_offset_is_reached need ' - 'trailing_stop_positive_offset to be more than 0 in your config.') - if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: - raise OperationalException( - f'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config.') - def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. From edf2cd0b926e51522f4b50e2d1fc22566a903e3d Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:26:31 +0100 Subject: [PATCH 185/457] configuration test fixed --- freqtrade/tests/test_configuration.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 3b7b1285c..dace9904b 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,7 +13,6 @@ from freqtrade import OperationalException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL -from freqtrade.exchange import Exchange from freqtrade.state import RunMode from freqtrade.tests.conftest import log_has @@ -576,28 +575,22 @@ def test__create_datadir(mocker, default_conf, caplog) -> None: assert log_has('Created data directory: /foo/bar', caplog.record_tuples) -def test_validate_tsl(default_conf, mocker): - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') +def test_validate_tsl(default_conf): default_conf['trailing_stop'] = True default_conf['trailing_stop_positive'] = 0 default_conf['trailing_stop_positive_offset'] = 0 - default_conf['trailing_only_offset_is_reached'] = False - - Exchange(default_conf) default_conf['trailing_only_offset_is_reached'] = True with pytest.raises(OperationalException, match=r'The config trailing_only_offset_is_reached need ' 'trailing_stop_positive_offset to be more than 0 in your config.'): - Exchange(default_conf) + configuration = Configuration(Namespace()) + configuration._validate_config(default_conf) default_conf['trailing_stop_positive_offset'] = 0.01 default_conf['trailing_stop_positive'] = 0.015 with pytest.raises(OperationalException, match=r'The config trailing_stop_positive_offset need ' 'to be greater than trailing_stop_positive_offset in your config.'): - Exchange(default_conf) + configuration = Configuration(Namespace()) + configuration._validate_config(default_conf) From 4fa16042302a19b0bb4dd97305b05e9a91a72980 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 14 Mar 2019 13:32:05 +0100 Subject: [PATCH 186/457] Update ccxt from 1.18.361 to 1.18.362 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 618de66fa..e5cd62d22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.361 +ccxt==1.18.362 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 1a83eed38f9669655199bbc7ff50fd3ed209f9ac Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 14 Mar 2019 13:32:09 +0100 Subject: [PATCH 187/457] Update pandas from 0.24.1 to 0.24.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e5cd62d22..83f89882b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ requests==2.21.0 urllib3==1.24.1 wrapt==1.11.1 numpy==1.16.2 -pandas==0.24.1 +pandas==0.24.2 scikit-learn==0.20.3 joblib==0.13.2 scipy==1.2.1 From 95a3b5c41e20886abf2d4e214ed0b0360c284158 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 14 Mar 2019 22:48:42 +0100 Subject: [PATCH 188/457] check if ticker sort key is populated --- freqtrade/pairlist/VolumePairList.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index eb03236e5..9a2e2eac4 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -69,7 +69,8 @@ class VolumePairList(IPairList): tickers = self._freqtrade.exchange.get_tickers() # check length so that we make sure that '/' is actually in the string tickers = [v for k, v in tickers.items() - if len(k.split('/')) == 2 and k.split('/')[1] == base_currency] + if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency + and v[key] is not None)] sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) # Validate whitelist to only have active market pairs valid_pairs = self._validate_whitelist([s['symbol'] for s in sorted_tickers]) From 6db6c3b2cc1da45855d58fe9adbf62bd0b4e751b Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 15 Mar 2019 13:32:05 +0100 Subject: [PATCH 189/457] Update ccxt from 1.18.362 to 1.18.367 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 83f89882b..ea74f3d39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.362 +ccxt==1.18.367 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 44acf2f4712105d72bf5fae93578dadac74a6232 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 15 Mar 2019 19:50:38 +0100 Subject: [PATCH 190/457] Catch syntaxerror on import --- freqtrade/resolvers/iresolver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 6023bc2ba..86b9a799b 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -33,9 +33,9 @@ class IResolver(object): module = importlib.util.module_from_spec(spec) try: spec.loader.exec_module(module) # type: ignore # importlib does not use typehints - except ModuleNotFoundError as err: + except (ModuleNotFoundError, SyntaxError) as err: # Catch errors in case a specific module is not installed - logger.info(f"Could not import {module_path} due to '{err}'") + logger.warning(f"Could not import {module_path} due to '{err}'") valid_objects_gen = ( obj for name, obj in inspect.getmembers(module, inspect.isclass) From d42ebab5750dc270c868717776ffee60df24c05a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 10:38:25 +0100 Subject: [PATCH 191/457] Rename function and add test --- freqtrade/configuration.py | 10 +++++----- freqtrade/tests/test_configuration.py | 13 +++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 585704b2d..d98b2ba21 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -59,7 +59,7 @@ class Configuration(object): logger.info('Validating configuration ...') self._validate_config_schema(config) - self._validate_config(config) + self._validate_config_consistency(config) # Set strategy if not specified in config and or if it's non default if self.args.strategy != constants.DEFAULT_STRATEGY or not config.get('strategy'): @@ -310,11 +310,11 @@ class Configuration(object): best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message ) - def _validate_config(self, conf: Dict[str, Any]) -> None: + def _validate_config_consistency(self, conf: Dict[str, Any]) -> None: """ Validate the configuration consistency :param conf: Config in JSON format - :return: Returns None if everything is ok, otherwise throw an exception + :return: Returns None if everything is ok, otherwise throw an OperationalException """ # validating trailing stoploss @@ -332,11 +332,11 @@ class Configuration(object): if tsl_only_offset: if tsl_positive == 0.0: raise OperationalException( - f'The config trailing_only_offset_is_reached need ' + f'The config trailing_only_offset_is_reached needs ' 'trailing_stop_positive_offset to be more than 0 in your config.') if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: raise OperationalException( - f'The config trailing_stop_positive_offset need ' + f'The config trailing_stop_positive_offset needs ' 'to be greater than trailing_stop_positive_offset in your config.') def get_config(self) -> Dict[str, Any]: diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index dace9904b..21547d205 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -582,15 +582,20 @@ def test_validate_tsl(default_conf): default_conf['trailing_only_offset_is_reached'] = True with pytest.raises(OperationalException, - match=r'The config trailing_only_offset_is_reached need ' + match=r'The config trailing_only_offset_is_reached needs ' 'trailing_stop_positive_offset to be more than 0 in your config.'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_consistency(default_conf) default_conf['trailing_stop_positive_offset'] = 0.01 default_conf['trailing_stop_positive'] = 0.015 with pytest.raises(OperationalException, - match=r'The config trailing_stop_positive_offset need ' + match=r'The config trailing_stop_positive_offset needs ' 'to be greater than trailing_stop_positive_offset in your config.'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_consistency(default_conf) + + default_conf['trailing_stop_positive'] = 0.01 + default_conf['trailing_stop_positive_offset'] = 0.015 + Configuration(Namespace()) + configuration._validate_config_consistency(default_conf) From a233a8cc820adb3d0533b77e24bd43f6ec2e6daf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 10:38:32 +0100 Subject: [PATCH 192/457] Be explicit in the documentation --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 62276e7cc..cbe4fd3c4 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -62,4 +62,4 @@ The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit. You should also make sure to have this value (`trailing_stop_positive_offset`) lower than your minimal ROI, otherwise minimal ROI will apply first and sell your trade. -If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. +If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured`stoploss`. From b9b15e5f320f8637c629020870d32471faf0fbe6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 11:04:24 +0100 Subject: [PATCH 193/457] Align help message for forcebuy --- freqtrade/rpc/telegram.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 9caa7288f..e599172e4 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -466,6 +466,8 @@ class Telegram(RPC): :param update: message update :return: None """ + forcebuy_text = "*/forcebuy []:* `Instantly buys the given pair. " \ + "Optionally takes a rate at which to buy.` \n" message = "*/start:* `Starts the trader`\n" \ "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ @@ -473,6 +475,7 @@ class Telegram(RPC): "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ "regardless of profit`\n" \ + f"{forcebuy_text if self._config.get('forcebuy_enable', False) else '' }" \ "*/performance:* `Show performance of each finished trade grouped by pair`\n" \ "*/daily :* `Shows profit or loss per day, over the last n days`\n" \ "*/count:* `Show number of trades running compared to allowed number of trades`" \ From d596a877fa70a898c5c5058d8075b573e7b9c6cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 11:07:16 +0100 Subject: [PATCH 194/457] Update docs to link to ocnfiguration piece necessary --- docs/telegram-usage.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index e01c8f9bc..db98cbb12 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -104,6 +104,8 @@ Return a summary of your profit/loss and performance. Note that for this to work, `forcebuy_enable` needs to be set to true. +[More details](configuration.md/#understand-forcebuy_enable) + ## /performance Return the performance of each crypto-currency the bot has sold. From 6bfc37309e6c14d03a747616883444736f8bf1a4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 13:24:10 +0100 Subject: [PATCH 195/457] refactor getting sell/current rate for telegram and selling fix #1658 --- freqtrade/freqtradebot.py | 24 ++++++++++++++++++++++-- freqtrade/rpc/rpc.py | 12 ++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8b9594047..6ba59bea4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -579,6 +579,25 @@ class FreqtradeBot(object): f"(from {order_amount} to {real_amount}) from Trades") return real_amount + def get_sell_rate(self, pair: str, refresh: bool) -> float: + """ + Get sell rate - either using get-ticker bid or first bid based on orderbook + The orderbook portion is only used for rpc messaging, which would otherwise fail + for BitMex (has no bid/ask in get_ticker) + or remain static in any other case since it's not updating. + :return: Bid rate + """ + config_ask_strategy = self.config.get('ask_strategy', {}) + if config_ask_strategy.get('use_order_book', False): + logger.debug('Using order book to get sell rate') + + order_book = self.exchange.get_order_book(pair, 1) + rate = order_book['asks'][0][0] + + else: + rate = self.exchange.get_ticker(pair, refresh)['bid'] + return rate + def handle_trade(self, trade: Trade) -> bool: """ Sells the current pair if the threshold is reached and updates the trade record. @@ -615,7 +634,7 @@ class FreqtradeBot(object): else: logger.debug('checking sell') - sell_rate = self.exchange.get_ticker(trade.pair)['bid'] + sell_rate = self.get_sell_rate(trade.pair, True) if self.check_sell(trade, sell_rate, buy, sell): return True @@ -858,7 +877,8 @@ class FreqtradeBot(object): """ profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested profit_trade = trade.calc_profit(rate=profit_rate) - current_rate = self.exchange.get_ticker(trade.pair)['bid'] + # Use cached ticker here - it was updated seconds ago. + current_rate = self.get_sell_rate(trade.pair, False) profit_percent = trade.calc_profit_percent(profit_rate) gain = "profit" if profit_percent > 0 else "loss" diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index af64c3d67..ffbc8d334 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -94,7 +94,7 @@ class RPC(object): order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair) # calculate profit and send message to user try: - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN current_profit = trade.calc_profit_percent(current_rate) @@ -125,7 +125,7 @@ class RPC(object): for trade in trades: # calculate profit and send message to user try: - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN trade_perc = (100 * trade.calc_profit_percent(current_rate)) @@ -213,7 +213,7 @@ class RPC(object): else: # Get current rate try: - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) except DependencyException: current_rate = NAN profit_percent = trade.calc_profit_percent(rate=current_rate) @@ -280,9 +280,9 @@ class RPC(object): else: try: if coin == 'USDT': - rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid'] + rate = 1.0 / self._freqtrade.get_sell_rate('BTC/USDT', False) else: - rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid'] + rate = self._freqtrade.get_sell_rate(coin + '/BTC', False) except (TemporaryError, DependencyException): continue est_btc: float = rate * balance['total'] @@ -356,7 +356,7 @@ class RPC(object): return # Get current rate and execute sell - current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] + current_rate = self._freqtrade.get_sell_rate(trade.pair, False) self._freqtrade.execute_sell(trade, current_rate, SellType.FORCE_SELL) # ---- EOF def _exec_forcesell ---- From 29aa1598273b7e7ea64abbbba78fc02cb8b95c43 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 13:32:26 +0100 Subject: [PATCH 196/457] Add test for get_sell_rate --- freqtrade/tests/test_freqtradebot.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index fc7c48663..950662bfb 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2962,6 +2962,31 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order assert freqtrade.handle_trade(trade) is True +def test_get_sell_rate(default_conf, mocker, ticker, order_book_l2) -> None: + + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_order_book=order_book_l2, + get_ticker=ticker, + ) + pair = "ETH/BTC" + + # Test regular mode + ft = get_patched_freqtradebot(mocker, default_conf) + rate = ft.get_sell_rate(pair, True) + assert isinstance(rate, float) + assert rate == 0.00001098 + + # Test orderbook mode + default_conf['ask_strategy']['use_order_book'] = True + default_conf['ask_strategy']['order_book_min'] = 1 + default_conf['ask_strategy']['order_book_max'] = 2 + ft = get_patched_freqtradebot(mocker, default_conf) + rate = ft.get_sell_rate(pair, True) + assert isinstance(rate, float) + assert rate == 0.043949 + + def test_startup_messages(default_conf, mocker): default_conf['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} From d7017ce1e4d792a6bfa53c66dacf7eefe83003e9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Mar 2019 21:11:14 +0100 Subject: [PATCH 197/457] Document backtest-result loading --- docs/backtesting.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/backtesting.md b/docs/backtesting.md index f6c9dd4d1..e155dce88 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -245,6 +245,26 @@ On the other hand, if you set a too high `minimal_roi` like `"0": 0.55` profit. Hence, keep in mind that your performance is a mix of your strategies, your configuration, and the crypto-currency you have set up. +### Further backtest-result analysis + +To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file). +You can then load the trades to perform further analysis. + +A good way for this is using Jupyter (notebook or lab) - which provides an interactive environment to analyze the data. + +Freqtrade provides an easy to load the backtest results, which is `load_backtest_data` - and takes a path to the backtest-results file. + +``` python +from freqtrade.data.btanalysis import load_backtest_data +df = load_backtest_data("user_data/backtest-result.json") + +# Show value-counts per pair +df.groupby("pair")["sell_reason"].value_counts() + +``` + +This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. + ## Backtesting multiple strategies To backtest multiple strategies, a list of Strategies can be provided. From e1f48c2b463322dc261b023a6721a7d6909e8f0d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Mar 2019 21:20:32 +0100 Subject: [PATCH 198/457] Add btanalysis file --- freqtrade/data/btanalysis.py | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 freqtrade/data/btanalysis.py diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py new file mode 100644 index 000000000..17e3c8c09 --- /dev/null +++ b/freqtrade/data/btanalysis.py @@ -0,0 +1,67 @@ +""" +Helpers when analyzing backtest data +""" +from pathlib import Path + +import numpy as np +import pandas as pd + +from freqtrade.misc import json_load + + +def load_backtest_data(filename) -> pd.DataFrame: + """ + Load backtest data file. + :param filename: pathlib.Path object, or string pointing to the file. + :return a dataframe with the analysis results + """ + if isinstance(filename, str): + filename = Path(filename) + + if not filename.is_file(): + raise ValueError("File {filename} does not exist.") + + with filename.open() as file: + data = json_load(file) + + # must align with columns in backtest.py + columns = ["pair", "profitperc", "open_time", "close_time", "index", "duration", + "open_rate", "close_rate", "open_at_end", "sell_reason"] + + df = pd.DataFrame(data, columns=columns) + + df['open_time'] = pd.to_datetime(df['open_time'], + unit='s', + utc=True, + infer_datetime_format=True + ) + df['close_time'] = pd.to_datetime(df['close_time'], + unit='s', + utc=True, + infer_datetime_format=True + ) + df['profitabs'] = df['close_rate'] - df['open_rate'] + df = df.sort_values("open_time").reset_index(drop=True) + return df + + +def evaluate_result_multi(results: pd.DataFrame, freq: str, max_open_trades: int) -> pd.DataFrame: + """ + Find overlapping trades by expanding each trade once per period it was open + and then counting overlaps + :param results: Results Dataframe - can be loaded + :param freq: Frequency used for the backtest + :param max_open_trades: parameter max_open_trades used during backtest run + :return: dataframe with open-counts per time-period in freq + """ + dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq)) + for row in results[['open_time', 'close_time']].iterrows()] + deltas = [len(x) for x in dates] + dates = pd.Series(pd.concat(dates).values, name='date') + df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns) + + df2 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"}) + df2 = pd.concat([dates, df2], axis=1) + df2 = df2.set_index('date') + df_final = df2.resample(freq)[['pair']].count() + return df_final[df_final['pair'] > max_open_trades] From 9f7f089d8ae8f6ef6a96f92aba33f8e229252f80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Mar 2019 21:23:53 +0100 Subject: [PATCH 199/457] adjust plot_dataframe to use btanalysis --- freqtrade/data/btanalysis.py | 10 ++++----- scripts/plot_dataframe.py | 40 ++++++++++-------------------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 17e3c8c09..6fce4361b 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -8,6 +8,10 @@ import pandas as pd from freqtrade.misc import json_load +# must align with columns in backtest.py +BT_DATA_COLUMNS = ["pair", "profitperc", "open_time", "close_time", "index", "duration", + "open_rate", "close_rate", "open_at_end", "sell_reason"] + def load_backtest_data(filename) -> pd.DataFrame: """ @@ -24,11 +28,7 @@ def load_backtest_data(filename) -> pd.DataFrame: with filename.open() as file: data = json_load(file) - # must align with columns in backtest.py - columns = ["pair", "profitperc", "open_time", "close_time", "index", "duration", - "open_rate", "close_rate", "open_at_end", "sell_reason"] - - df = pd.DataFrame(data, columns=columns) + df = pd.DataFrame(data, columns=BT_DATA_COLUMNS) df['open_time'] = pd.to_datetime(df['open_time'], unit='s', diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 6b954ac4c..581518b12 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -41,6 +41,7 @@ from plotly.offline import plot from freqtrade import persistence from freqtrade.arguments import Arguments, TimeRange from freqtrade.data import history +from freqtrade.data.btanalysis import load_backtest_data, BT_DATA_COLUMNS from freqtrade.exchange import Exchange from freqtrade.optimize.backtesting import setup_configuration from freqtrade.persistence import Trade @@ -56,7 +57,8 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram trades: pd.DataFrame = pd.DataFrame() if args.db_url: persistence.init(_CONF) - columns = ["pair", "profit", "opents", "closets", "open_rate", "close_rate", "duration"] + columns = ["pair", "profit", "open_time", "close_time", + "open_rate", "close_rate", "duration"] for x in Trade.query.all(): print("date: {}".format(x.open_date)) @@ -71,33 +73,13 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram columns=columns) elif args.exportfilename: - file = Path(args.exportfilename) - # must align with columns in backtest.py - columns = ["pair", "profit", "opents", "closets", "index", "duration", - "open_rate", "close_rate", "open_at_end", "sell_reason"] - if file.exists(): - with file.open() as f: - data = json.load(f) - trades = pd.DataFrame(data, columns=columns) - trades = trades.loc[trades["pair"] == pair] - if timerange: - if timerange.starttype == 'date': - trades = trades.loc[trades["opents"] >= timerange.startts] - if timerange.stoptype == 'date': - trades = trades.loc[trades["opents"] <= timerange.stopts] - trades['opents'] = pd.to_datetime( - trades['opents'], - unit='s', - utc=True, - infer_datetime_format=True) - trades['closets'] = pd.to_datetime( - trades['closets'], - unit='s', - utc=True, - infer_datetime_format=True) + file = Path(args.exportfilename) + if file.exists(): + load_backtest_data(file) + else: - trades = pd.DataFrame([], columns=columns) + trades = pd.DataFrame([], columns=BT_DATA_COLUMNS) return trades @@ -206,7 +188,7 @@ def extract_trades_of_period(dataframe, trades) -> pd.DataFrame: Compare trades and backtested pair DataFrames to get trades performed on backtested period :return: the DataFrame of a trades of period """ - trades = trades.loc[trades['opents'] >= dataframe.iloc[0]['date']] + trades = trades.loc[trades['open_time'] >= dataframe.iloc[0]['date']] return trades @@ -279,7 +261,7 @@ def generate_graph( ) trade_buys = go.Scattergl( - x=trades["opents"], + x=trades["open_time"], y=trades["open_rate"], mode='markers', name='trade_buy', @@ -291,7 +273,7 @@ def generate_graph( ) ) trade_sells = go.Scattergl( - x=trades["closets"], + x=trades["close_time"], y=trades["close_rate"], mode='markers', name='trade_sell', From ddb9933c91d80beb62b2fb97a32bdad53e7cd12f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Mar 2019 21:24:26 +0100 Subject: [PATCH 200/457] Remove duplicate-check from test - it's in btanalysis --- freqtrade/tests/optimize/test_backtesting.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 1d5fb1384..ea3aa95b3 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -15,6 +15,7 @@ from freqtrade import DependencyException, constants from freqtrade.arguments import Arguments, TimeRange from freqtrade.data import history from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.btanalysis import evaluate_result_multi from freqtrade.optimize import get_timeframe from freqtrade.optimize.backtesting import (Backtesting, setup_configuration, start) @@ -684,21 +685,6 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): def test_backtest_multi_pair(default_conf, fee, mocker): - def evaluate_result_multi(results, freq, max_open_trades): - # Find overlapping trades by expanding each trade once per period - # and then counting overlaps - dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq)) - for row in results[['open_time', 'close_time']].iterrows()] - deltas = [len(x) for x in dates] - dates = pd.Series(pd.concat(dates).values, name='date') - df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns) - - df2 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"}) - df2 = pd.concat([dates, df2], axis=1) - df2 = df2.set_index('date') - df_final = df2.resample(freq)[['pair']].count() - return df_final[df_final['pair'] > max_open_trades] - def _trend_alternate_hold(dataframe=None, metadata=None): """ Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit) From a123246ac93a59a1c880b29bb3f26d904934a8d2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 17:50:57 +0100 Subject: [PATCH 201/457] Add test for load_backtest_data --- freqtrade/tests/data/test_btanalysis.py | 21 ++++++++++++++++++++ freqtrade/tests/optimize/test_backtesting.py | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 freqtrade/tests/data/test_btanalysis.py diff --git a/freqtrade/tests/data/test_btanalysis.py b/freqtrade/tests/data/test_btanalysis.py new file mode 100644 index 000000000..dd7cbe0d9 --- /dev/null +++ b/freqtrade/tests/data/test_btanalysis.py @@ -0,0 +1,21 @@ +import pytest +from pandas import DataFrame + +from freqtrade.data.btanalysis import BT_DATA_COLUMNS, load_backtest_data +from freqtrade.data.history import make_testdata_path + + +def test_load_backtest_data(): + + filename = make_testdata_path(None) / "backtest-result_test.json" + bt_data = load_backtest_data(filename) + assert isinstance(bt_data, DataFrame) + assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profitabs"] + assert len(bt_data) == 179 + + # Test loading from string (must yield same result) + bt_data2 = load_backtest_data(str(filename)) + assert bt_data.equals(bt_data2) + + with pytest.raises(ValueError, match=r"File .* does not exist\."): + load_backtest_data(str("filename") + "nofile") diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index ea3aa95b3..40754cfbc 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -14,8 +14,8 @@ from arrow import Arrow from freqtrade import DependencyException, constants from freqtrade.arguments import Arguments, TimeRange from freqtrade.data import history -from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.btanalysis import evaluate_result_multi +from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.optimize import get_timeframe from freqtrade.optimize.backtesting import (Backtesting, setup_configuration, start) From e7f6df46e8b415d112bf4e5ce89cfa22a1ab8690 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 19:15:20 +0100 Subject: [PATCH 202/457] Add missing bt file --- freqtrade/tests/testdata/backtest-result_test.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 freqtrade/tests/testdata/backtest-result_test.json diff --git a/freqtrade/tests/testdata/backtest-result_test.json b/freqtrade/tests/testdata/backtest-result_test.json new file mode 100644 index 000000000..8701451dc --- /dev/null +++ b/freqtrade/tests/testdata/backtest-result_test.json @@ -0,0 +1 @@ +[["POWR/BTC",0.03990025,1515568500.0,1515568800.0,27,5,9.64e-05,0.00010074887218045112,false,"roi"],["ADA/BTC",0.03990025,1515568500.0,1515569400.0,27,15,4.756e-05,4.9705563909774425e-05,false,"roi"],["XLM/BTC",0.03990025,1515569100.0,1515569700.0,29,10,3.339e-05,3.489631578947368e-05,false,"roi"],["POWR/BTC",0.03990025,1515569100.0,1515570000.0,29,15,9.696e-05,0.00010133413533834584,false,"roi"],["ETH/BTC",-0.0,1515569700.0,1515573300.0,31,60,0.0943,0.09477268170426063,false,"roi"],["XMR/BTC",0.00997506,1515570000.0,1515571800.0,32,30,0.02719607,0.02760503345864661,false,"roi"],["ZEC/BTC",0.0,1515572100.0,1515578100.0,39,100,0.04634952,0.046581848421052625,false,"roi"],["NXT/BTC",-0.0,1515595500.0,1515599400.0,117,65,3.066e-05,3.081368421052631e-05,false,"roi"],["LTC/BTC",0.0,1515602100.0,1515604500.0,139,40,0.0168999,0.016984611278195488,false,"roi"],["ETH/BTC",-0.0,1515602400.0,1515604800.0,140,40,0.09132568,0.0917834528320802,false,"roi"],["ETH/BTC",-0.0,1515610200.0,1515613500.0,166,55,0.08898003,0.08942604518796991,false,"roi"],["ETH/BTC",0.0,1515622500.0,1515625200.0,207,45,0.08560008,0.08602915308270676,false,"roi"],["ETC/BTC",0.00997506,1515624600.0,1515626400.0,214,30,0.00249083,0.0025282860902255634,false,"roi"],["NXT/BTC",-0.0,1515626100.0,1515629700.0,219,60,3.022e-05,3.037147869674185e-05,false,"roi"],["ETC/BTC",0.01995012,1515627600.0,1515629100.0,224,25,0.002437,0.0024980776942355883,false,"roi"],["ZEC/BTC",0.00997506,1515628800.0,1515630900.0,228,35,0.04771803,0.04843559436090225,false,"roi"],["XLM/BTC",-0.10448878,1515642000.0,1515644700.0,272,45,3.651e-05,3.2859000000000005e-05,false,"stop_loss"],["ETH/BTC",0.00997506,1515642900.0,1515644700.0,275,30,0.08824105,0.08956798308270676,false,"roi"],["ETC/BTC",-0.0,1515643200.0,1515646200.0,276,50,0.00243,0.002442180451127819,false,"roi"],["ZEC/BTC",0.01995012,1515645000.0,1515646500.0,282,25,0.04545064,0.046589753784461146,false,"roi"],["XLM/BTC",0.01995012,1515645000.0,1515646200.0,282,20,3.372e-05,3.456511278195488e-05,false,"roi"],["XMR/BTC",0.01995012,1515646500.0,1515647700.0,287,20,0.02644,0.02710265664160401,false,"roi"],["ETH/BTC",-0.0,1515669600.0,1515672000.0,364,40,0.08812,0.08856170426065162,false,"roi"],["XMR/BTC",-0.0,1515670500.0,1515672900.0,367,40,0.02683577,0.026970285137844607,false,"roi"],["ADA/BTC",0.01995012,1515679200.0,1515680700.0,396,25,4.919e-05,5.04228320802005e-05,false,"roi"],["ETH/BTC",-0.0,1515698700.0,1515702900.0,461,70,0.08784896,0.08828930566416039,false,"roi"],["ADA/BTC",-0.0,1515710100.0,1515713400.0,499,55,5.105e-05,5.130588972431077e-05,false,"roi"],["XLM/BTC",0.00997506,1515711300.0,1515713100.0,503,30,3.96e-05,4.019548872180451e-05,false,"roi"],["NXT/BTC",-0.0,1515711300.0,1515713700.0,503,40,2.885e-05,2.899461152882205e-05,false,"roi"],["XMR/BTC",0.00997506,1515713400.0,1515715500.0,510,35,0.02645,0.026847744360902256,false,"roi"],["ZEC/BTC",-0.0,1515714900.0,1515719700.0,515,80,0.048,0.04824060150375939,false,"roi"],["XLM/BTC",0.01995012,1515791700.0,1515793200.0,771,25,4.692e-05,4.809593984962405e-05,false,"roi"],["ETC/BTC",-0.0,1515804900.0,1515824400.0,815,325,0.00256966,0.0025825405012531327,false,"roi"],["ADA/BTC",0.0,1515840900.0,1515843300.0,935,40,6.262e-05,6.293388471177944e-05,false,"roi"],["XLM/BTC",0.0,1515848700.0,1516025400.0,961,2945,4.73e-05,4.753709273182957e-05,false,"roi"],["ADA/BTC",-0.0,1515850200.0,1515854700.0,966,75,6.063e-05,6.0933909774436085e-05,false,"roi"],["POWR/BTC",-0.0,1515850800.0,1515886200.0,968,590,0.00011082,0.00011137548872180449,false,"roi"],["ADA/BTC",-0.0,1515856500.0,1515858900.0,987,40,5.93e-05,5.9597243107769415e-05,false,"roi"],["ZEC/BTC",-0.0,1515861000.0,1515863400.0,1002,40,0.04850003,0.04874313791979949,false,"roi"],["ETH/BTC",-0.0,1515881100.0,1515911100.0,1069,500,0.09825019,0.09874267215538847,false,"roi"],["ADA/BTC",0.0,1515889200.0,1515970500.0,1096,1355,6.018e-05,6.048165413533834e-05,false,"roi"],["ETH/BTC",-0.0,1515933900.0,1515936300.0,1245,40,0.09758999,0.0980791628822055,false,"roi"],["ETC/BTC",0.00997506,1515943800.0,1515945600.0,1278,30,0.00311,0.0031567669172932328,false,"roi"],["ETC/BTC",-0.0,1515962700.0,1515968100.0,1341,90,0.00312401,0.003139669197994987,false,"roi"],["LTC/BTC",0.0,1515972900.0,1515976200.0,1375,55,0.0174679,0.017555458395989976,false,"roi"],["DASH/BTC",-0.0,1515973500.0,1515975900.0,1377,40,0.07346846,0.07383672295739348,false,"roi"],["ETH/BTC",-0.0,1515983100.0,1515985500.0,1409,40,0.097994,0.09848519799498745,false,"roi"],["ETH/BTC",-0.0,1516000800.0,1516003200.0,1468,40,0.09659,0.09707416040100249,false,"roi"],["POWR/BTC",0.00997506,1516004400.0,1516006500.0,1480,35,9.987e-05,0.00010137180451127818,false,"roi"],["ETH/BTC",0.0,1516018200.0,1516071000.0,1526,880,0.0948969,0.09537257368421052,false,"roi"],["DASH/BTC",-0.0,1516025400.0,1516038000.0,1550,210,0.071,0.07135588972431077,false,"roi"],["ZEC/BTC",-0.0,1516026600.0,1516029000.0,1554,40,0.04600501,0.046235611553884705,false,"roi"],["POWR/BTC",-0.0,1516039800.0,1516044300.0,1598,75,9.438e-05,9.485308270676691e-05,false,"roi"],["XMR/BTC",-0.0,1516041300.0,1516043700.0,1603,40,0.03040001,0.030552391002506264,false,"roi"],["ADA/BTC",-0.10448878,1516047900.0,1516091100.0,1625,720,5.837e-05,5.2533e-05,false,"stop_loss"],["ZEC/BTC",-0.0,1516048800.0,1516053600.0,1628,80,0.046036,0.04626675689223057,false,"roi"],["ETC/BTC",-0.0,1516062600.0,1516065000.0,1674,40,0.0028685,0.0028828784461152877,false,"roi"],["DASH/BTC",0.0,1516065300.0,1516070100.0,1683,80,0.06731755,0.0676549813283208,false,"roi"],["ETH/BTC",0.0,1516088700.0,1516092000.0,1761,55,0.09217614,0.09263817578947368,false,"roi"],["LTC/BTC",0.01995012,1516091700.0,1516092900.0,1771,20,0.0165,0.016913533834586467,false,"roi"],["POWR/BTC",0.03990025,1516091700.0,1516092000.0,1771,5,7.953e-05,8.311781954887218e-05,false,"roi"],["ZEC/BTC",-0.0,1516092300.0,1516096200.0,1773,65,0.045202,0.04542857644110275,false,"roi"],["ADA/BTC",0.00997506,1516094100.0,1516095900.0,1779,30,5.248e-05,5.326917293233082e-05,false,"roi"],["XMR/BTC",0.0,1516094100.0,1516096500.0,1779,40,0.02892318,0.02906815834586466,false,"roi"],["ADA/BTC",0.01995012,1516096200.0,1516097400.0,1786,20,5.158e-05,5.287273182957392e-05,false,"roi"],["ZEC/BTC",0.00997506,1516097100.0,1516099200.0,1789,35,0.04357584,0.044231115789473675,false,"roi"],["XMR/BTC",0.00997506,1516097100.0,1516098900.0,1789,30,0.02828232,0.02870761804511278,false,"roi"],["ADA/BTC",0.00997506,1516110300.0,1516112400.0,1833,35,5.362e-05,5.4426315789473676e-05,false,"roi"],["ADA/BTC",-0.0,1516123800.0,1516127100.0,1878,55,5.302e-05,5.328576441102756e-05,false,"roi"],["ETH/BTC",0.00997506,1516126500.0,1516128300.0,1887,30,0.09129999,0.09267292218045112,false,"roi"],["XLM/BTC",0.01995012,1516126500.0,1516127700.0,1887,20,3.808e-05,3.903438596491228e-05,false,"roi"],["XMR/BTC",0.00997506,1516129200.0,1516131000.0,1896,30,0.02811012,0.028532828571428567,false,"roi"],["ETC/BTC",-0.10448878,1516137900.0,1516141500.0,1925,60,0.00258379,0.002325411,false,"stop_loss"],["NXT/BTC",-0.10448878,1516137900.0,1516142700.0,1925,80,2.559e-05,2.3031e-05,false,"stop_loss"],["POWR/BTC",-0.10448878,1516138500.0,1516141500.0,1927,50,7.62e-05,6.858e-05,false,"stop_loss"],["LTC/BTC",0.03990025,1516141800.0,1516142400.0,1938,10,0.0151,0.015781203007518795,false,"roi"],["ETC/BTC",0.03990025,1516141800.0,1516142100.0,1938,5,0.00229844,0.002402129022556391,false,"roi"],["ETC/BTC",0.03990025,1516142400.0,1516142700.0,1940,5,0.00235676,0.00246308,false,"roi"],["DASH/BTC",0.01995012,1516142700.0,1516143900.0,1941,20,0.0630692,0.06464988170426066,false,"roi"],["NXT/BTC",0.03990025,1516143000.0,1516143300.0,1942,5,2.2e-05,2.2992481203007514e-05,false,"roi"],["ADA/BTC",0.00997506,1516159800.0,1516161600.0,1998,30,4.974e-05,5.048796992481203e-05,false,"roi"],["POWR/BTC",0.01995012,1516161300.0,1516162500.0,2003,20,7.108e-05,7.28614536340852e-05,false,"roi"],["ZEC/BTC",-0.0,1516181700.0,1516184100.0,2071,40,0.04327,0.04348689223057644,false,"roi"],["ADA/BTC",-0.0,1516184400.0,1516208400.0,2080,400,4.997e-05,5.022047619047618e-05,false,"roi"],["DASH/BTC",-0.0,1516185000.0,1516188300.0,2082,55,0.06836818,0.06871087764411027,false,"roi"],["XLM/BTC",-0.0,1516185000.0,1516187400.0,2082,40,3.63e-05,3.648195488721804e-05,false,"roi"],["XMR/BTC",-0.0,1516192200.0,1516226700.0,2106,575,0.0281,0.02824085213032581,false,"roi"],["ETH/BTC",-0.0,1516192500.0,1516208100.0,2107,260,0.08651001,0.08694364413533832,false,"roi"],["ADA/BTC",-0.0,1516251600.0,1516254900.0,2304,55,5.633e-05,5.6612355889724306e-05,false,"roi"],["DASH/BTC",0.00997506,1516252800.0,1516254900.0,2308,35,0.06988494,0.07093584135338346,false,"roi"],["ADA/BTC",-0.0,1516260900.0,1516263300.0,2335,40,5.545e-05,5.572794486215538e-05,false,"roi"],["LTC/BTC",-0.0,1516266000.0,1516268400.0,2352,40,0.01633527,0.016417151052631574,false,"roi"],["ETC/BTC",-0.0,1516293600.0,1516296000.0,2444,40,0.00269734,0.0027108605012531326,false,"roi"],["XLM/BTC",0.01995012,1516298700.0,1516300200.0,2461,25,4.475e-05,4.587155388471177e-05,false,"roi"],["NXT/BTC",0.00997506,1516299900.0,1516301700.0,2465,30,2.79e-05,2.8319548872180444e-05,false,"roi"],["ZEC/BTC",0.0,1516306200.0,1516308600.0,2486,40,0.04439326,0.04461578260651629,false,"roi"],["XLM/BTC",0.0,1516311000.0,1516322100.0,2502,185,4.49e-05,4.51250626566416e-05,false,"roi"],["XMR/BTC",-0.0,1516312500.0,1516338300.0,2507,430,0.02855,0.028693107769423555,false,"roi"],["ADA/BTC",0.0,1516313400.0,1516315800.0,2510,40,5.796e-05,5.8250526315789473e-05,false,"roi"],["ZEC/BTC",0.0,1516319400.0,1516321800.0,2530,40,0.04340323,0.04362079005012531,false,"roi"],["ZEC/BTC",0.0,1516380300.0,1516383300.0,2733,50,0.04454455,0.04476783095238095,false,"roi"],["ADA/BTC",-0.0,1516382100.0,1516391700.0,2739,160,5.62e-05,5.648170426065162e-05,false,"roi"],["XLM/BTC",-0.0,1516382400.0,1516392900.0,2740,175,4.339e-05,4.360749373433584e-05,false,"roi"],["POWR/BTC",0.0,1516423500.0,1516469700.0,2877,770,0.0001009,0.00010140576441102757,false,"roi"],["ETC/BTC",-0.0,1516423800.0,1516461300.0,2878,625,0.00270505,0.002718609147869674,false,"roi"],["XMR/BTC",-0.0,1516423800.0,1516431600.0,2878,130,0.03000002,0.030150396040100245,false,"roi"],["ADA/BTC",-0.0,1516438800.0,1516441200.0,2928,40,5.46e-05,5.4873684210526304e-05,false,"roi"],["XMR/BTC",-0.10448878,1516472700.0,1516852200.0,3041,6325,0.03082222,0.027739998000000002,false,"stop_loss"],["ETH/BTC",-0.0,1516487100.0,1516490100.0,3089,50,0.08969999,0.09014961401002504,false,"roi"],["LTC/BTC",0.0,1516503000.0,1516545000.0,3142,700,0.01632501,0.01640683962406015,false,"roi"],["DASH/BTC",-0.0,1516530000.0,1516532400.0,3232,40,0.070538,0.07089157393483708,false,"roi"],["ADA/BTC",-0.0,1516549800.0,1516560300.0,3298,175,5.301e-05,5.3275714285714276e-05,false,"roi"],["XLM/BTC",0.0,1516551600.0,1516554000.0,3304,40,3.955e-05,3.9748245614035085e-05,false,"roi"],["ETC/BTC",0.00997506,1516569300.0,1516571100.0,3363,30,0.00258505,0.002623922932330827,false,"roi"],["XLM/BTC",-0.0,1516569300.0,1516571700.0,3363,40,3.903e-05,3.922563909774435e-05,false,"roi"],["ADA/BTC",-0.0,1516581300.0,1516617300.0,3403,600,5.236e-05,5.262245614035087e-05,false,"roi"],["POWR/BTC",0.0,1516584600.0,1516587000.0,3414,40,9.028e-05,9.073253132832079e-05,false,"roi"],["ETC/BTC",-0.0,1516623900.0,1516631700.0,3545,130,0.002687,0.002700468671679198,false,"roi"],["XLM/BTC",-0.0,1516626900.0,1516629300.0,3555,40,4.168e-05,4.1888922305764405e-05,false,"roi"],["POWR/BTC",0.00997506,1516629600.0,1516631400.0,3564,30,8.821e-05,8.953646616541353e-05,false,"roi"],["ADA/BTC",-0.0,1516636500.0,1516639200.0,3587,45,5.172e-05,5.1979248120300745e-05,false,"roi"],["NXT/BTC",0.01995012,1516637100.0,1516638300.0,3589,20,3.026e-05,3.101839598997494e-05,false,"roi"],["DASH/BTC",0.0,1516650600.0,1516666200.0,3634,260,0.07064,0.07099408521303258,false,"roi"],["LTC/BTC",0.0,1516656300.0,1516658700.0,3653,40,0.01644483,0.01652726022556391,false,"roi"],["XLM/BTC",0.00997506,1516665900.0,1516667700.0,3685,30,4.331e-05,4.3961278195488714e-05,false,"roi"],["NXT/BTC",0.01995012,1516672200.0,1516673700.0,3706,25,3.2e-05,3.2802005012531326e-05,false,"roi"],["ETH/BTC",0.0,1516681500.0,1516684500.0,3737,50,0.09167706,0.09213659413533835,false,"roi"],["DASH/BTC",0.0,1516692900.0,1516698000.0,3775,85,0.0692498,0.06959691679197995,false,"roi"],["NXT/BTC",0.0,1516704600.0,1516712700.0,3814,135,3.182e-05,3.197949874686716e-05,false,"roi"],["ZEC/BTC",-0.0,1516705500.0,1516723500.0,3817,300,0.04088,0.04108491228070175,false,"roi"],["ADA/BTC",-0.0,1516719300.0,1516721700.0,3863,40,5.15e-05,5.175814536340851e-05,false,"roi"],["ETH/BTC",0.0,1516725300.0,1516752300.0,3883,450,0.09071698,0.09117170170426064,false,"roi"],["NXT/BTC",-0.0,1516728300.0,1516733100.0,3893,80,3.128e-05,3.1436791979949865e-05,false,"roi"],["POWR/BTC",-0.0,1516738500.0,1516744800.0,3927,105,9.555e-05,9.602894736842104e-05,false,"roi"],["ZEC/BTC",-0.0,1516746600.0,1516749000.0,3954,40,0.04080001,0.041004521328320796,false,"roi"],["ADA/BTC",-0.0,1516751400.0,1516764900.0,3970,225,5.163e-05,5.1888796992481196e-05,false,"roi"],["ZEC/BTC",0.0,1516753200.0,1516758600.0,3976,90,0.04040781,0.04061035541353383,false,"roi"],["ADA/BTC",-0.0,1516776300.0,1516778700.0,4053,40,5.132e-05,5.157724310776942e-05,false,"roi"],["ADA/BTC",0.03990025,1516803300.0,1516803900.0,4143,10,5.198e-05,5.432496240601503e-05,false,"roi"],["NXT/BTC",-0.0,1516805400.0,1516811700.0,4150,105,3.054e-05,3.069308270676692e-05,false,"roi"],["POWR/BTC",0.0,1516806600.0,1516810500.0,4154,65,9.263e-05,9.309431077694235e-05,false,"roi"],["ADA/BTC",-0.0,1516833600.0,1516836300.0,4244,45,5.514e-05,5.5416390977443596e-05,false,"roi"],["XLM/BTC",0.0,1516841400.0,1516843800.0,4270,40,4.921e-05,4.9456666666666664e-05,false,"roi"],["ETC/BTC",0.0,1516868100.0,1516882500.0,4359,240,0.0026,0.002613032581453634,false,"roi"],["XMR/BTC",-0.0,1516875900.0,1516896900.0,4385,350,0.02799871,0.028139054411027563,false,"roi"],["ZEC/BTC",-0.0,1516878000.0,1516880700.0,4392,45,0.04078902,0.0409934762406015,false,"roi"],["NXT/BTC",-0.0,1516885500.0,1516887900.0,4417,40,2.89e-05,2.904486215538847e-05,false,"roi"],["ZEC/BTC",-0.0,1516886400.0,1516889100.0,4420,45,0.041103,0.041309030075187964,false,"roi"],["XLM/BTC",0.00997506,1516895100.0,1516896900.0,4449,30,5.428e-05,5.5096240601503756e-05,false,"roi"],["XLM/BTC",-0.0,1516902300.0,1516922100.0,4473,330,5.414e-05,5.441137844611528e-05,false,"roi"],["ZEC/BTC",-0.0,1516914900.0,1516917300.0,4515,40,0.04140777,0.0416153277443609,false,"roi"],["ETC/BTC",0.0,1516932300.0,1516934700.0,4573,40,0.00254309,0.002555837318295739,false,"roi"],["ADA/BTC",-0.0,1516935300.0,1516979400.0,4583,735,5.607e-05,5.6351052631578935e-05,false,"roi"],["ETC/BTC",0.0,1516947000.0,1516958700.0,4622,195,0.00253806,0.0025507821052631577,false,"roi"],["ZEC/BTC",-0.0,1516951500.0,1516960500.0,4637,150,0.0415,0.04170802005012531,false,"roi"],["XLM/BTC",0.00997506,1516960500.0,1516962300.0,4667,30,5.321e-05,5.401015037593984e-05,false,"roi"],["XMR/BTC",-0.0,1516982700.0,1516985100.0,4741,40,0.02772046,0.02785940967418546,false,"roi"],["ETH/BTC",0.0,1517009700.0,1517012100.0,4831,40,0.09461341,0.09508766268170425,false,"roi"],["XLM/BTC",-0.0,1517013300.0,1517016600.0,4843,55,5.615e-05,5.643145363408521e-05,false,"roi"],["ADA/BTC",-0.07877175,1517013900.0,1517287500.0,4845,4560,5.556e-05,5.144e-05,true,"force_sell"],["DASH/BTC",-0.0,1517020200.0,1517052300.0,4866,535,0.06900001,0.06934587471177944,false,"roi"],["ETH/BTC",-0.0,1517034300.0,1517036700.0,4913,40,0.09449985,0.09497353345864659,false,"roi"],["ZEC/BTC",-0.04815133,1517046000.0,1517287200.0,4952,4020,0.0410697,0.03928809,true,"force_sell"],["XMR/BTC",-0.0,1517053500.0,1517056200.0,4977,45,0.0285,0.02864285714285714,false,"roi"],["XMR/BTC",-0.0,1517056500.0,1517066700.0,4987,170,0.02866372,0.02880739779448621,false,"roi"],["ETH/BTC",-0.0,1517068200.0,1517071800.0,5026,60,0.095381,0.09585910025062655,false,"roi"],["DASH/BTC",-0.0,1517072700.0,1517075100.0,5041,40,0.06759092,0.06792972160401002,false,"roi"],["ETC/BTC",-0.0,1517096400.0,1517101500.0,5120,85,0.00258501,0.002597967443609022,false,"roi"],["DASH/BTC",-0.0,1517106300.0,1517127000.0,5153,345,0.06698502,0.0673207845112782,false,"roi"],["DASH/BTC",-0.0,1517135100.0,1517157000.0,5249,365,0.0677177,0.06805713709273183,false,"roi"],["XLM/BTC",0.0,1517171700.0,1517175300.0,5371,60,5.215e-05,5.2411403508771925e-05,false,"roi"],["ETC/BTC",0.00997506,1517176800.0,1517178600.0,5388,30,0.00273809,0.002779264285714285,false,"roi"],["ETC/BTC",0.00997506,1517184000.0,1517185800.0,5412,30,0.00274632,0.002787618045112782,false,"roi"],["LTC/BTC",0.0,1517192100.0,1517194800.0,5439,45,0.01622478,0.016306107218045113,false,"roi"],["DASH/BTC",-0.0,1517195100.0,1517197500.0,5449,40,0.069,0.06934586466165413,false,"roi"],["POWR/BTC",-0.0,1517203200.0,1517208900.0,5476,95,8.755e-05,8.798884711779448e-05,false,"roi"],["DASH/BTC",-0.0,1517209200.0,1517253900.0,5496,745,0.06825763,0.06859977350877192,false,"roi"],["DASH/BTC",-0.0,1517255100.0,1517257500.0,5649,40,0.06713892,0.06747545593984962,false,"roi"],["POWR/BTC",-0.0199116,1517268600.0,1517287500.0,5694,315,8.934e-05,8.8e-05,true,"force_sell"]] \ No newline at end of file From e632539b6164b6f9e0df4518f7c482373eaed4f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 19:51:05 +0100 Subject: [PATCH 203/457] Add 15min to documentation, fix link to "parameters in THE strategy" --- docs/configuration.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 44950e408..e7ad9c9bf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -17,16 +17,16 @@ Mandatory Parameters are marked as **Required**. | `stake_currency` | BTC | **Required.** Crypto-currency used for trading. | `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. | `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals. -| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-strategy). +| `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below. | `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode. -| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-strategy). -| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-strategy). -| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). -| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). -| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). -| `trailing_stop_positive_offset` | 0 | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). -| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy). +| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). +| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). +| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive_offset` | 0 | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `unfilledtimeout.buy` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `bid_strategy.ask_last_balance` | 0.0 | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). @@ -37,8 +37,8 @@ Mandatory Parameters are marked as **Required**. | `ask_strategy.use_order_book` | false | Allows selling of open traded pair using the rates in Order Book Asks. | `ask_strategy.order_book_min` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. | `ask_strategy.order_book_max` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. -| `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-strategy). -| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-strategy). +| `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy). +| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). | `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | `exchange.sandbox` | false | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. | `exchange.key` | key | API key to use for the exchange. Only required when you are in production mode. @@ -50,9 +50,9 @@ Mandatory Parameters are marked as **Required**. | `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) | `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded. | `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation. -| `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-strategy). -| `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-strategy). -| `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-strategy). +| `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). +| `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). +| `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). | `pairlist.method` | StaticPairList | Use Static whitelist. [More information below](#dynamic-pairlists). | `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists). | `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram. From 7166a474ae9b58259a25c356d8e16f01213b63d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 19:54:16 +0100 Subject: [PATCH 204/457] Add min_rate - always update min/max rates --- freqtrade/persistence.py | 24 ++++++++++++++---------- freqtrade/tests/test_persistence.py | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 10aff72ec..f6cdc815f 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -83,7 +83,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'stoploss_last_update'): + if not has_column(cols, 'min_rate'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -95,6 +95,7 @@ def check_migrate(engine) -> None: stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') + min_rate = get_column_def(cols, 'min_rate', '0.0') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') ticker_interval = get_column_def(cols, 'ticker_interval', 'null') @@ -113,7 +114,7 @@ def check_migrate(engine) -> None: open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, open_date, close_date, open_order_id, stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update, - max_rate, sell_reason, strategy, + max_rate, min_rate, sell_reason, strategy, ticker_interval ) select id, lower(exchange), @@ -130,7 +131,7 @@ def check_migrate(engine) -> None: stake_amount, amount, open_date, close_date, open_order_id, {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, - {max_rate} max_rate, {sell_reason} sell_reason, + {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {strategy} strategy, {ticker_interval} ticker_interval from {table_back_name} """) @@ -191,6 +192,8 @@ class Trade(_DECL_BASE): stoploss_last_update = Column(DateTime, nullable=True) # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) + # Lowest price reached + min_rate = Column(Float, nullable=True, default=0.0) sell_reason = Column(String, nullable=True) strategy = Column(String, nullable=True) ticker_interval = Column(Integer, nullable=True) @@ -201,6 +204,14 @@ class Trade(_DECL_BASE): return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' f'open_rate={self.open_rate:.8f}, open_since={open_since})') + def adjust_high_low(self, current_price): + """ + Adjust the max_rate and min_rate. + """ + logger.info("Adjusting high/low") + self.max_rate = max(current_price, self.max_rate) + self.min_rate = min(current_price, self.min_rate) + def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): """this adjusts the stop loss to it's most recently observed setting""" @@ -210,13 +221,6 @@ class Trade(_DECL_BASE): new_loss = float(current_price * (1 - abs(stoploss))) - # keeping track of the highest observed rate for this trade - if self.max_rate is None: - self.max_rate = current_price - else: - if current_price > self.max_rate: - self.max_rate = current_price - # no stop loss assigned yet if not self.stop_loss: logger.debug("assigning new stop loss") diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index a9519e693..7e809de0d 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -510,6 +510,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.pair == "ETC/BTC" assert trade.exchange == "binance" assert trade.max_rate == 0.0 + assert trade.min_rate == 0.0 assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 assert trade.sell_reason is None From 738ed932217daa6c8fc13114e710cb8ea0211506 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 19:54:34 +0100 Subject: [PATCH 205/457] call new function --- freqtrade/strategy/interface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 41dcb8c57..d65c12895 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -254,6 +254,8 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) + trade.adjust_high_low(current_rate) + stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, force_stoploss=force_stoploss) From 68a9b14eca297386b8494f44c31439ef27994099 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 20:04:39 +0100 Subject: [PATCH 206/457] Min-rate should not default to 0 --- freqtrade/persistence.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index f6cdc815f..ad85d5efd 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -95,7 +95,7 @@ def check_migrate(engine) -> None: stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') - min_rate = get_column_def(cols, 'min_rate', '0.0') + min_rate = get_column_def(cols, 'min_rate', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') ticker_interval = get_column_def(cols, 'ticker_interval', 'null') @@ -193,7 +193,7 @@ class Trade(_DECL_BASE): # absolute value of the highest reached price max_rate = Column(Float, nullable=True, default=0.0) # Lowest price reached - min_rate = Column(Float, nullable=True, default=0.0) + min_rate = Column(Float, nullable=True) sell_reason = Column(String, nullable=True) strategy = Column(String, nullable=True) ticker_interval = Column(Integer, nullable=True) @@ -204,13 +204,13 @@ class Trade(_DECL_BASE): return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' f'open_rate={self.open_rate:.8f}, open_since={open_since})') - def adjust_high_low(self, current_price): + def adjust_high_low(self, current_price: float): """ Adjust the max_rate and min_rate. """ - logger.info("Adjusting high/low") - self.max_rate = max(current_price, self.max_rate) - self.min_rate = min(current_price, self.min_rate) + logger.debug("Adjusting min/max rates") + self.max_rate = max(current_price, self.max_rate or 0.0) + self.min_rate = min(current_price, self.min_rate or 10000000.0) def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): """this adjusts the stop loss to it's most recently observed setting""" From 01733c94fa01d628fcf357fe09ca5d77d716badc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 20:04:55 +0100 Subject: [PATCH 207/457] Split up tests for adjust_stoploss and adjust_highlow --- freqtrade/tests/test_persistence.py | 87 +++++++++++++++++++---------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 7e809de0d..c4d07b3f9 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -510,7 +510,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.pair == "ETC/BTC" assert trade.exchange == "binance" assert trade.max_rate == 0.0 - assert trade.min_rate == 0.0 + assert trade.min_rate is None assert trade.stop_loss == 0.0 assert trade.initial_stop_loss == 0.0 assert trade.sell_reason is None @@ -586,7 +586,48 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): caplog.record_tuples) -def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): +def test_adjust_stop_loss(fee): + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='bittrex', + open_rate=1, + max_rate=1, + ) + + trade.adjust_stop_loss(trade.open_rate, 0.05, True) + assert trade.stop_loss == 0.95 + assert trade.initial_stop_loss == 0.95 + + # Get percent of profit with a lower rate + trade.adjust_stop_loss(0.96, 0.05) + assert trade.stop_loss == 0.95 + assert trade.initial_stop_loss == 0.95 + + # Get percent of profit with a custom rate (Higher than open rate) + trade.adjust_stop_loss(1.3, -0.1) + assert round(trade.stop_loss, 8) == 1.17 + assert trade.initial_stop_loss == 0.95 + + # current rate lower again ... should not change + trade.adjust_stop_loss(1.2, 0.1) + assert round(trade.stop_loss, 8) == 1.17 + assert trade.initial_stop_loss == 0.95 + + # current rate higher... should raise stoploss + trade.adjust_stop_loss(1.4, 0.1) + assert round(trade.stop_loss, 8) == 1.26 + assert trade.initial_stop_loss == 0.95 + + # Initial is true but stop_loss set - so doesn't do anything + trade.adjust_stop_loss(1.7, 0.1, True) + assert round(trade.stop_loss, 8) == 1.26 + assert trade.initial_stop_loss == 0.95 + + +def test_adjust_high_low(fee): trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -596,40 +637,24 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): open_rate=1, ) - trade.adjust_stop_loss(trade.open_rate, 0.05, True) - assert trade.stop_loss == 0.95 + trade.adjust_high_low(trade.open_rate) assert trade.max_rate == 1 - assert trade.initial_stop_loss == 0.95 + assert trade.min_rate == 1 - # Get percent of profit with a lowre rate - trade.adjust_stop_loss(0.96, 0.05) - assert trade.stop_loss == 0.95 + # check min adjusted, max remained + trade.adjust_high_low(0.96) assert trade.max_rate == 1 - assert trade.initial_stop_loss == 0.95 + assert trade.min_rate == 0.96 - # Get percent of profit with a custom rate (Higher than open rate) - trade.adjust_stop_loss(1.3, -0.1) - assert round(trade.stop_loss, 8) == 1.17 - assert trade.max_rate == 1.3 - assert trade.initial_stop_loss == 0.95 + # check max adjusted, min remains + trade.adjust_high_low(1.05) + assert trade.max_rate == 1.05 + assert trade.min_rate == 0.96 - # current rate lower again ... should not change - trade.adjust_stop_loss(1.2, 0.1) - assert round(trade.stop_loss, 8) == 1.17 - assert trade.max_rate == 1.3 - assert trade.initial_stop_loss == 0.95 - - # current rate higher... should raise stoploss - trade.adjust_stop_loss(1.4, 0.1) - assert round(trade.stop_loss, 8) == 1.26 - assert trade.max_rate == 1.4 - assert trade.initial_stop_loss == 0.95 - - # Initial is true but stop_loss set - so doesn't do anything - trade.adjust_stop_loss(1.7, 0.1, True) - assert round(trade.stop_loss, 8) == 1.26 - assert trade.max_rate == 1.4 - assert trade.initial_stop_loss == 0.95 + # current rate "in the middle" - no adjustment + trade.adjust_high_low(1.03) + assert trade.max_rate == 1.05 + assert trade.min_rate == 0.96 def test_get_open(default_conf, fee): From fc360608b7f6dfd7d8f1206d95ed48b6d040f5b4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 20:06:15 +0100 Subject: [PATCH 208/457] Rename function to adjust_min_max --- freqtrade/persistence.py | 2 +- freqtrade/strategy/interface.py | 2 +- freqtrade/tests/test_persistence.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ad85d5efd..ebafe7355 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -204,7 +204,7 @@ class Trade(_DECL_BASE): return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' f'open_rate={self.open_rate:.8f}, open_since={open_since})') - def adjust_high_low(self, current_price: float): + def adjust_min_max_rates(self, current_price: float): """ Adjust the max_rate and min_rate. """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d65c12895..85d4d8c13 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -254,7 +254,7 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - trade.adjust_high_low(current_rate) + trade.adjust_min_max_rates(current_rate) stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index c4d07b3f9..042237ce7 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -627,7 +627,7 @@ def test_adjust_stop_loss(fee): assert trade.initial_stop_loss == 0.95 -def test_adjust_high_low(fee): +def test_adjust_min_max_rates(fee): trade = Trade( pair='ETH/BTC', stake_amount=0.001, @@ -637,22 +637,22 @@ def test_adjust_high_low(fee): open_rate=1, ) - trade.adjust_high_low(trade.open_rate) + trade.adjust_min_max_rates(trade.open_rate) assert trade.max_rate == 1 assert trade.min_rate == 1 # check min adjusted, max remained - trade.adjust_high_low(0.96) + trade.adjust_min_max_rates(0.96) assert trade.max_rate == 1 assert trade.min_rate == 0.96 # check max adjusted, min remains - trade.adjust_high_low(1.05) + trade.adjust_min_max_rates(1.05) assert trade.max_rate == 1.05 assert trade.min_rate == 0.96 # current rate "in the middle" - no adjustment - trade.adjust_high_low(1.03) + trade.adjust_min_max_rates(1.03) assert trade.max_rate == 1.05 assert trade.min_rate == 0.96 From a0e6cd93b62595b71a2edcc8c2b6ebfd7df5229f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 11:27:01 +0100 Subject: [PATCH 209/457] Use bids, not asks for sell-rate detection --- freqtrade/freqtradebot.py | 2 +- freqtrade/tests/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6ba59bea4..cfff46fea 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -592,7 +592,7 @@ class FreqtradeBot(object): logger.debug('Using order book to get sell rate') order_book = self.exchange.get_order_book(pair, 1) - rate = order_book['asks'][0][0] + rate = order_book['bids'][0][0] else: rate = self.exchange.get_ticker(pair, refresh)['bid'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 950662bfb..e4f0415f7 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2984,7 +2984,7 @@ def test_get_sell_rate(default_conf, mocker, ticker, order_book_l2) -> None: ft = get_patched_freqtradebot(mocker, default_conf) rate = ft.get_sell_rate(pair, True) assert isinstance(rate, float) - assert rate == 0.043949 + assert rate == 0.043936 def test_startup_messages(default_conf, mocker): From 2d4a2fd10bd4517dd653a4a2754f056440009b22 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 13:12:04 +0100 Subject: [PATCH 210/457] Use oppen_rate instead of artificial defaults --- freqtrade/persistence.py | 4 ++-- freqtrade/strategy/interface.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index ebafe7355..b807e10e1 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -209,8 +209,8 @@ class Trade(_DECL_BASE): Adjust the max_rate and min_rate. """ logger.debug("Adjusting min/max rates") - self.max_rate = max(current_price, self.max_rate or 0.0) - self.min_rate = min(current_price, self.min_rate or 10000000.0) + self.max_rate = max(current_price, self.max_rate or self.open_rate) + self.min_rate = min(current_price, self.min_rate or self.open_rate) def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): """this adjusts the stop loss to it's most recently observed setting""" diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 85d4d8c13..b844c1e58 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -301,8 +301,9 @@ class IStrategy(ABC): # evaluate if the stoploss was hit if stoploss is not on exchange if ((self.stoploss is not None) and - (trade.stop_loss >= current_rate) and - (not self.order_types.get('stoploss_on_exchange'))): + (trade.stop_loss >= current_rate) and + (not self.order_types.get('stoploss_on_exchange'))): + selltype = SellType.STOP_LOSS # If Trailing stop (and max-rate did move above open rate) if trailing_stop and trade.open_rate != trade.max_rate: From 7b99daebd766d986287143ddca0d5b62cac49968 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 13:18:29 +0100 Subject: [PATCH 211/457] Update docstring for adjust_stoploss --- freqtrade/persistence.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index b807e10e1..a1ac65c04 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -213,7 +213,13 @@ class Trade(_DECL_BASE): self.min_rate = min(current_price, self.min_rate or self.open_rate) def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False): - """this adjusts the stop loss to it's most recently observed setting""" + """ + This adjusts the stop loss to it's most recently observed setting + :param current_price: Current rate the asset is traded + :param stoploss: Stoploss as factor (sample -0.05 -> -5% below current price). + :param initial: Called to initiate stop_loss. + Skips everything if self.stop_loss is already set. + """ if initial and not (self.stop_loss is None or self.stop_loss == 0): # Don't modify if called with initial and nothing to do From a77d51351342913611fb7cc7b703d098c0e15683 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 13:27:32 +0100 Subject: [PATCH 212/457] Fix backteest detail numbering ... --- .../tests/optimize/test_backtest_detail.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index e8514e76f..d6295b778 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -14,10 +14,10 @@ from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataf from freqtrade.tests.conftest import patch_exchange -# Test 0 Minus 8% Close +# Test 1 Minus 8% Close # Test with Stop-loss at 1% # TC1: Stop-Loss Triggered 1% loss -tc0 = BTContainer(data=[ +tc1 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -30,10 +30,10 @@ tc0 = BTContainer(data=[ ) -# Test 1 Minus 4% Low, minus 1% close +# Test 2 Minus 4% Low, minus 1% close # Test with Stop-Loss at 3% # TC2: Stop-Loss Triggered 3% Loss -tc1 = BTContainer(data=[ +tc2 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -53,7 +53,7 @@ tc1 = BTContainer(data=[ # Test with Stop-Loss at 2% # TC3: Trade-A: Stop-Loss Triggered 2% Loss # Trade-B: Stop-Loss Triggered 2% Loss -tc2 = BTContainer(data=[ +tc3 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -71,7 +71,7 @@ tc2 = BTContainer(data=[ # Candle Data for test 3 – Candle drops 3% Closed 15% up # Test with Stop-loss at 2% ROI 6% # TC4: Stop-Loss Triggered 2% Loss -tc3 = BTContainer(data=[ +tc4 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -83,10 +83,10 @@ tc3 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) -# Test 4 / Drops 0.5% Closes +20% +# Test 5 / Drops 0.5% Closes +20% # Set stop-loss at 1% ROI 3% # TC5: ROI triggers 3% Gain -tc4 = BTContainer(data=[ +tc5 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4980, 4987, 6172, 1, 0], [1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -102,7 +102,7 @@ tc4 = BTContainer(data=[ # Candle Data for test 6 # Set stop-loss at 2% ROI at 5% # TC6: Stop-Loss triggers 2% Loss -tc5 = BTContainer(data=[ +tc6 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) @@ -118,7 +118,7 @@ tc5 = BTContainer(data=[ # Candle Data for test 7 # Set stop-loss at 2% ROI at 3% # TC7: ROI Triggers 3% Gain -tc6 = BTContainer(data=[ +tc7 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], @@ -131,13 +131,13 @@ tc6 = BTContainer(data=[ ) TESTS = [ - tc0, tc1, tc2, tc3, tc4, tc5, tc6, + tc7, ] From 190ecb7adae743b6da71165010d854d5026f0a09 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 17 Mar 2019 13:32:05 +0100 Subject: [PATCH 213/457] Update ccxt from 1.18.367 to 1.18.368 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 96360f39d..400940d16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.367 +ccxt==1.18.368 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From a830bee9c7a84f63f1b99346db27f2564d649f55 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 15:28:04 +0100 Subject: [PATCH 214/457] Enable trailing_stop for BTContainer tests --- freqtrade/tests/optimize/__init__.py | 1 + freqtrade/tests/optimize/test_backtest_detail.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/optimize/__init__.py b/freqtrade/tests/optimize/__init__.py index 129a09f40..075938a61 100644 --- a/freqtrade/tests/optimize/__init__.py +++ b/freqtrade/tests/optimize/__init__.py @@ -28,6 +28,7 @@ class BTContainer(NamedTuple): roi: float trades: List[BTrade] profit_perc: float + trailing_stop: bool = False def _get_frame_time_from_offset(offset): diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index d6295b778..b4ca4ee26 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -148,8 +148,9 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: """ default_conf["stoploss"] = data.stop_loss default_conf["minimal_roi"] = {"0": data.roi} - default_conf['ticker_interval'] = tests_ticker_interval - mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0)) + default_conf["ticker_interval"] = tests_ticker_interval + default_conf["trailing_stop"] = data.trailing_stop + mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0)) patch_exchange(mocker) frame = _build_backtest_dataframe(data.data) backtesting = Backtesting(default_conf) @@ -157,7 +158,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: backtesting.advise_sell = lambda a, m: frame caplog.set_level(logging.DEBUG) - pair = 'UNITTEST/BTC' + pair = "UNITTEST/BTC" # Dummy data as we mock the analyze functions data_processed = {pair: DataFrame()} min_date, max_date = get_timeframe({pair: frame}) From f0e5113a7ffe919f632ec66817f7eaaf9096c48c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 15:39:05 +0100 Subject: [PATCH 215/457] Use Magicmock instead of lambda for mocking --- freqtrade/tests/test_freqtradebot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index fc7c48663..a5de283a5 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2259,9 +2259,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market } freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.stop_loss_reached = \ - lambda current_rate, trade, current_time, force_stoploss, current_profit: SellCheckTuple( - sell_flag=False, sell_type=SellType.NONE) + freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( + sell_flag=False, sell_type=SellType.NONE)) freqtrade.create_trade() trade = Trade.query.first() From 8c7e8255bb3f68c91925b68ea1229785155feb5d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:01:34 +0100 Subject: [PATCH 216/457] Add detailed test for trailing stop --- .../tests/optimize/test_backtest_detail.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index b4ca4ee26..f33f56efc 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -130,6 +130,22 @@ tc7 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] ) + +# Test 8 - trailing_stop should raise so candle 3 causes a stoploss. +# Candle Data for test 8 +# Set stop-loss at 10%, ROI at 10% (should not apply) +# TC8: Trailing stoploss - stoploss should be adjusted to 94.5 after candle 1 +tc8 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0], + [2, 5000, 5250, 4750, 4850, 6172, 0, 0], + [3, 4850, 5050, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi=0.10, profit_perc=-0.055, trailing_stop=True, + trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] +) + TESTS = [ tc1, tc2, @@ -138,6 +154,7 @@ TESTS = [ tc5, tc6, tc7, + tc8, ] From 05ab1c2e0a833bfcbaac15447bb2c5bdecedf110 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:02:13 +0100 Subject: [PATCH 217/457] Fix some comments --- freqtrade/strategy/interface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index b844c1e58..6e2f35cc8 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -247,6 +247,9 @@ class IStrategy(ABC): """ This function evaluate if on the condition required to trigger a sell has been reached if the threshold is reached and updates the trade record. + :param low: Only used during backtesting to simulate stoploss + :param high: Only used during backtesting, to simulate ROI + :param force_stoploss: Externally provided stoploss :return: True if trade should be sold, False otherwise """ @@ -263,7 +266,7 @@ class IStrategy(ABC): if stoplossflag.sell_flag: return stoplossflag - # Set current rate to low for backtesting sell + # Set current rate to high for backtesting sell current_rate = high or rate current_profit = trade.calc_profit_percent(current_rate) experimental = self.config.get('experimental', {}) From a7b60f6780e2230909400fd8437311c07a018ae2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:03:44 +0100 Subject: [PATCH 218/457] update trailing_stop with high in case of backtesting --- freqtrade/strategy/interface.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6e2f35cc8..a086139c7 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -257,11 +257,11 @@ class IStrategy(ABC): current_rate = low or rate current_profit = trade.calc_profit_percent(current_rate) - trade.adjust_min_max_rates(current_rate) + trade.adjust_min_max_rates(high or current_rate) stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, - force_stoploss=force_stoploss) + force_stoploss=force_stoploss, high=high) if stoplossflag.sell_flag: return stoplossflag @@ -291,7 +291,7 @@ class IStrategy(ABC): return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, - current_profit: float, force_stoploss: float) -> SellCheckTuple: + current_profit: float, force_stoploss: float, high) -> SellCheckTuple: """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not @@ -322,6 +322,7 @@ class IStrategy(ABC): return SellCheckTuple(sell_flag=True, sell_type=selltype) # update the stop loss afterwards, after all by definition it's supposed to be hanging + # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details if trailing_stop: # check if we have a special stop loss for positive condition @@ -342,7 +343,7 @@ class IStrategy(ABC): # we update trailing stoploss only if offset is reached. tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) if not (tsl_only_offset and current_profit < sl_offset): - trade.adjust_stop_loss(current_rate, stop_loss_value) + trade.adjust_stop_loss(high or current_rate, stop_loss_value) return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) From 39232cbcbbe440368f2389c7d03271542b6cca4f Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 5 Mar 2019 21:23:55 +0100 Subject: [PATCH 219/457] loop over whitelist only instead of all markets --- freqtrade/pairlist/IPairList.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 5559c582f..f9a66fe89 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -65,29 +65,26 @@ class IPairList(ABC): :return: the list of pairs the user wants to trade without the one unavailable or black_listed """ - sanitized_whitelist = whitelist markets = self._freqtrade.exchange.markets # Filter to markets in stake currency - markets = [markets[pair] for pair in markets if - markets[pair]['quote'] == self._config['stake_currency']] - known_pairs = set() + stake_pairs = [pair for pair in markets if + pair.endswith(self._config['stake_currency'])] - # TODO: we should loop over whitelist instead of all markets - for market in markets: - pair = market['symbol'] + sanitized_whitelist = [] + for pair in whitelist: # pair is not in the generated dynamic market, or in the blacklist ... ignore it - if pair not in whitelist or pair in self.blacklist: + if pair in self.blacklist or pair not in stake_pairs: continue - # else the pair is valid - known_pairs.add(pair) - # Market is not active + # Check if market is active + market = markets[pair] if not market['active']: - sanitized_whitelist.remove(pair) logger.info( 'Ignoring %s from whitelist. Market is not active.', pair ) + continue + sanitized_whitelist.append(pair) # We need to remove pairs that are unknown - return [x for x in sanitized_whitelist if x in known_pairs] + return sanitized_whitelist From a241e950f292a7da931ae9a04a87896acd6dfc94 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 6 Mar 2019 22:41:46 +0100 Subject: [PATCH 220/457] prune validate_pairs --- freqtrade/exchange/exchange.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 33f62f2f7..e4838b74c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -239,13 +239,9 @@ class Exchange(object): logger.warning('Unable to validate pairs (assuming they are correct).') # return - stake_cur = self._conf['stake_currency'] for pair in pairs: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs # TODO: add a support for having coins in BTC/USDT format - if not pair.endswith(stake_cur): - raise OperationalException( - f'Pair {pair} not compatible with stake_currency: {stake_cur}') if self.markets and pair not in self.markets: raise OperationalException( f'Pair {pair} is not available on {self.name}. ' From c907e80c10d5bc8c85e79b356e45333aebe79be4 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Wed, 6 Mar 2019 22:42:05 +0100 Subject: [PATCH 221/457] make sure no dups --- freqtrade/pairlist/IPairList.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index f9a66fe89..8e3b11ff4 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -68,10 +68,10 @@ class IPairList(ABC): markets = self._freqtrade.exchange.markets # Filter to markets in stake currency - stake_pairs = [pair for pair in markets if - pair.endswith(self._config['stake_currency'])] + stake_pairs = [pair for pair in markets if # pair.endswith(self._config['stake_currency']) + markets[pair]["quote"] == self._config['stake_currency']] - sanitized_whitelist = [] + sanitized_whitelist = set() for pair in whitelist: # pair is not in the generated dynamic market, or in the blacklist ... ignore it if pair in self.blacklist or pair not in stake_pairs: @@ -84,7 +84,7 @@ class IPairList(ABC): pair ) continue - sanitized_whitelist.append(pair) + sanitized_whitelist.add(pair) # We need to remove pairs that are unknown - return sanitized_whitelist + return list(sanitized_whitelist) From e38a3051a14a8faca1ef3aac0dcb368c0742b45c Mon Sep 17 00:00:00 2001 From: iuvbio Date: Mon, 11 Mar 2019 21:10:22 +0100 Subject: [PATCH 222/457] update docstring --- freqtrade/pairlist/IPairList.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 8e3b11ff4..841699251 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -60,9 +60,8 @@ class IPairList(ABC): def _validate_whitelist(self, whitelist: List[str]) -> List[str]: """ Check available markets and remove pair from whitelist if necessary - :param whitelist: the sorted list (based on BaseVolume) of pairs the user might want to - trade - :return: the list of pairs the user wants to trade without the one unavailable or + :param whitelist: the sorted list of pairs the user might want to trade + :return: the list of pairs the user wants to trade without those unavailable or black_listed """ markets = self._freqtrade.exchange.markets From d4543be8eb2f2d227291857156aba69ea318b5a4 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Mon, 11 Mar 2019 21:48:55 +0100 Subject: [PATCH 223/457] edit comment --- freqtrade/pairlist/IPairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 841699251..7960e82fc 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -66,7 +66,7 @@ class IPairList(ABC): """ markets = self._freqtrade.exchange.markets - # Filter to markets in stake currency + # keep only pairs with stake currency as quote stake_pairs = [pair for pair in markets if # pair.endswith(self._config['stake_currency']) markets[pair]["quote"] == self._config['stake_currency']] From d4d37667e1d1b912804008a5d248ba8c20b6b10a Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 12 Mar 2019 18:26:47 +0100 Subject: [PATCH 224/457] use pairname for stake cur comparison --- freqtrade/pairlist/IPairList.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 7960e82fc..fcc129814 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -67,8 +67,8 @@ class IPairList(ABC): markets = self._freqtrade.exchange.markets # keep only pairs with stake currency as quote - stake_pairs = [pair for pair in markets if # pair.endswith(self._config['stake_currency']) - markets[pair]["quote"] == self._config['stake_currency']] + stake_pairs = [pair for pair in markets if pair.endswith(self._config['stake_currency'])] + # markets[pair]["quote"] == self._config['stake_currency'] sanitized_whitelist = set() for pair in whitelist: From 7f9c76a6fcfb31591c0568adadcb86da09a49987 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 14 Mar 2019 21:02:21 +0100 Subject: [PATCH 225/457] move stake check to the same condition as the other checks --- freqtrade/pairlist/IPairList.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index fcc129814..2564c484c 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -66,14 +66,11 @@ class IPairList(ABC): """ markets = self._freqtrade.exchange.markets - # keep only pairs with stake currency as quote - stake_pairs = [pair for pair in markets if pair.endswith(self._config['stake_currency'])] - # markets[pair]["quote"] == self._config['stake_currency'] - sanitized_whitelist = set() for pair in whitelist: # pair is not in the generated dynamic market, or in the blacklist ... ignore it - if pair in self.blacklist or pair not in stake_pairs: + if (pair in self.blacklist or pair not in markets + or not pair.endswith(self._config['stake_currency'])): continue # Check if market is active market = markets[pair] From 8386496456b7d51943b7c4a5bfc710aa1312018a Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 14 Mar 2019 21:53:42 +0100 Subject: [PATCH 226/457] remove tests that are no longer applicable --- freqtrade/tests/exchange/test_exchange.py | 29 ----------------------- 1 file changed, 29 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 7c757df09..736f2298a 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -305,19 +305,6 @@ def test_validate_pairs_not_available(default_conf, mocker): Exchange(default_conf) -def test_validate_pairs_not_compatible(default_conf, mocker): - api_mock = MagicMock() - type(api_mock).markets = PropertyMock(return_value={ - 'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': '' - }) - default_conf['stake_currency'] = 'ETH' - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - with pytest.raises(OperationalException, match=r'not compatible'): - Exchange(default_conf) - - def test_validate_pairs_exception(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() @@ -337,22 +324,6 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): caplog.record_tuples) -def test_validate_pairs_stake_exception(default_conf, mocker, caplog): - caplog.set_level(logging.INFO) - default_conf['stake_currency'] = 'ETH' - api_mock = MagicMock() - api_mock.name = MagicMock(return_value='binance') - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) - - with pytest.raises( - OperationalException, - match=r'Pair ETH/BTC not compatible with stake_currency: ETH' - ): - Exchange(default_conf) - - def test_validate_timeframes(default_conf, mocker): default_conf["ticker_interval"] = "5m" api_mock = MagicMock() From 2bf7f2feae53666ab3b7fdca0109d991bc11d458 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:14:49 +0100 Subject: [PATCH 227/457] Remove duplicate backtest-result-analysi documentation --- docs/backtesting.md | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index e155dce88..932783160 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -65,35 +65,7 @@ Where `-s TestStrategy` refers to the class name within the strategy file `test_ python3 ./freqtrade/main.py backtesting --export trades ``` -The exported trades can be read using the following code for manual analysis, or can be used by the plotting script `plot_dataframe.py` in the scripts folder. - -``` python -import json -from pathlib import Path -import pandas as pd - -filename=Path('user_data/backtest_data/backtest-result.json') - -with filename.open() as file: - data = json.load(file) - -columns = ["pair", "profit", "opents", "closets", "index", "duration", - "open_rate", "close_rate", "open_at_end", "sell_reason"] -df = pd.DataFrame(data, columns=columns) - -df['opents'] = pd.to_datetime(df['opents'], - unit='s', - utc=True, - infer_datetime_format=True - ) -df['closets'] = pd.to_datetime(df['closets'], - unit='s', - utc=True, - infer_datetime_format=True - ) -``` - -If you have some ideas for interesting / helpful backtest data analysis, feel free to submit a PR so the community can benefit from it. +The exported trades can be used for [further analysis](#further-backtest-result-analysis), or can be used by the plotting script `plot_dataframe.py` in the scripts folder. #### Exporting trades to file specifying a custom filename @@ -263,7 +235,9 @@ df.groupby("pair")["sell_reason"].value_counts() ``` -This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. +This will allow you to drill deeper into your backtest results, and perform analysis which would make the regular backtest-output unreadable. + +If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a PR so the community can benefit from it. ## Backtesting multiple strategies From 8afce7e65105c07364991b4ab48ee718e10530eb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 16:26:38 +0100 Subject: [PATCH 228/457] Add testcase for Testcase 2 --- .../tests/optimize/test_backtest_detail.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtest_detail.py b/freqtrade/tests/optimize/test_backtest_detail.py index f33f56efc..b98369533 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -49,7 +49,6 @@ tc2 = BTContainer(data=[ # Test 3 Candle drops 4%, Recovers 1%. # Entry Criteria Met # Candle drops 20% -# Candle Data for test 3 # Test with Stop-Loss at 2% # TC3: Trade-A: Stop-Loss Triggered 2% Loss # Trade-B: Stop-Loss Triggered 2% Loss @@ -99,7 +98,6 @@ tc5 = BTContainer(data=[ ) # Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve -# Candle Data for test 6 # Set stop-loss at 2% ROI at 5% # TC6: Stop-Loss triggers 2% Loss tc6 = BTContainer(data=[ @@ -115,7 +113,6 @@ tc6 = BTContainer(data=[ ) # Test 7 - 6% Positive / 1% Negative / Close 1% Positve -# Candle Data for test 7 # Set stop-loss at 2% ROI at 3% # TC7: ROI Triggers 3% Gain tc7 = BTContainer(data=[ @@ -132,11 +129,10 @@ tc7 = BTContainer(data=[ # Test 8 - trailing_stop should raise so candle 3 causes a stoploss. -# Candle Data for test 8 # Set stop-loss at 10%, ROI at 10% (should not apply) -# TC8: Trailing stoploss - stoploss should be adjusted to 94.5 after candle 1 +# TC8: Trailing stoploss - stoploss should be adjusted candle 2 tc8 = BTContainer(data=[ - # D O H L C V B S + # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5050, 4950, 5000, 6172, 0, 0], [2, 5000, 5250, 4750, 4850, 6172, 0, 0], @@ -146,6 +142,22 @@ tc8 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] ) + +# Test 9 - trailing_stop should raise - high and low in same candle. +# Candle Data for test 9 +# Set stop-loss at 10%, ROI at 10% (should not apply) +# TC9: Trailing stoploss - stoploss should be adjusted candle 2 +tc9 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5000, 6172, 0, 0], + [2, 5000, 5050, 4950, 5000, 6172, 0, 0], + [3, 5000, 5200, 4550, 4850, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi=0.10, profit_perc=-0.064, trailing_stop=True, + trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] +) + TESTS = [ tc1, tc2, @@ -155,6 +167,7 @@ TESTS = [ tc6, tc7, tc8, + tc9, ] From 4de4a70be7f1b63a9eaf180c5b26e1066f83f7e6 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Mar 2019 18:18:35 +0100 Subject: [PATCH 229/457] update log messages --- freqtrade/pairlist/IPairList.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 2564c484c..d08bd2587 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -71,13 +71,15 @@ class IPairList(ABC): # pair is not in the generated dynamic market, or in the blacklist ... ignore it if (pair in self.blacklist or pair not in markets or not pair.endswith(self._config['stake_currency'])): + logger.warning(f"Pair {pair} is not compatible with exchange " + f"{self._freqtrade.exchange.name} or contained in " + f"your blacklist. Removing it from whitelist..") continue # Check if market is active market = markets[pair] if not market['active']: logger.info( - 'Ignoring %s from whitelist. Market is not active.', - pair + f"Ignoring {pair} from whitelist. Market is not active." ) continue sanitized_whitelist.add(pair) From c2076af43b00ea040cd3b7db9cdf0b013879139b Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Mar 2019 18:18:44 +0100 Subject: [PATCH 230/457] update tests --- freqtrade/tests/pairlist/test_pairlist.py | 74 +++++++++++------------ 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/freqtrade/tests/pairlist/test_pairlist.py b/freqtrade/tests/pairlist/test_pairlist.py index 52f44c41b..5fe2dfc08 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, PropertyMock from freqtrade import OperationalException from freqtrade.constants import AVAILABLE_PAIRLISTS from freqtrade.resolvers import PairListResolver -from freqtrade.tests.conftest import get_patched_freqtradebot +from freqtrade.tests.conftest import get_patched_freqtradebot, log_has import pytest # whitelist, blacklist @@ -107,7 +107,16 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): assert set(whitelist) == set(pairslist) -def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) -> None: +@pytest.mark.parametrize("precision_filter,base_currency,key,whitelist_result", [ + (False, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'BTT/BTC']), + (False, "BTC", "bidVolume", ['BTT/BTC', 'TKN/BTC', 'ETH/BTC']), + (False, "USDT", "quoteVolume", ['ETH/USDT', 'LTC/USDT']), + (False, "ETH", "quoteVolume", []), # this replaces tests that were removed from test_exchange + (True, "BTC", "quoteVolume", ["ETH/BTC", "TKN/BTC"]), + (True, "BTC", "bidVolume", ["TKN/BTC", "ETH/BTC"]) +]) +def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers, base_currency, key, + whitelist_result, precision_filter) -> None: whitelist_conf['pairlist']['method'] = 'VolumePairList' mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) @@ -115,32 +124,10 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, p, r: round(r, 8)) - # Test to retrieved BTC sorted on quoteVolume (default) - whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='quoteVolume') - assert whitelist == ['ETH/BTC', 'TKN/BTC', 'BTT/BTC'] - - # Test to retrieve BTC sorted on bidVolume - whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='bidVolume') - assert whitelist == ['BTT/BTC', 'TKN/BTC', 'ETH/BTC'] - - # Test with USDT sorted on quoteVolume (default) - freqtrade.config['stake_currency'] = 'USDT' # this has to be set, otherwise markets are removed - whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='USDT', key='quoteVolume') - assert whitelist == ['ETH/USDT', 'LTC/USDT'] - - # Test with ETH (our fixture does not have ETH, so result should be empty) - freqtrade.config['stake_currency'] = 'ETH' - whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='ETH', key='quoteVolume') - assert whitelist == [] - - freqtrade.pairlists._precision_filter = True - freqtrade.config['stake_currency'] = 'BTC' - # Retest First 2 test-cases to make sure BTT is not in it (too low priced) - whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='quoteVolume') - assert whitelist == ['ETH/BTC', 'TKN/BTC'] - - whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='bidVolume') - assert whitelist == ['TKN/BTC', 'ETH/BTC'] + freqtrade.pairlists._precision_filter = precision_filter + freqtrade.config['stake_currency'] = base_currency + whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key) + assert whitelist == whitelist_result def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: @@ -166,17 +153,24 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): assert isinstance(freqtrade.pairlists.whitelist, list) assert isinstance(freqtrade.pairlists.blacklist, list) - whitelist = ['ETH/BTC', 'TKN/BTC'] + +@pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) +@pytest.mark.parametrize("whitelist,log_message", [ + (['ETH/BTC', 'TKN/BTC'], ""), + (['ETH/BTC', 'TKN/BTC', 'TRX/ETH'], "is not compatible with exchange"), # TRX/ETH wrong stake + (['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), # BCH/BTC not available + (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "is not compatible with exchange"), # BLK/BTC in blacklist + (['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], "Market is not active") # LTC/BTC is inactive +]) +def test_validate_whitelist(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, + log_message): + whitelist_conf['pairlist']['method'] = pairlist + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) + caplog.clear() + new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist) - assert set(whitelist) == set(new_whitelist) - - whitelist = ['ETH/BTC', 'TKN/BTC', 'TRX/ETH'] - new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist) - # TRX/ETH was removed - assert set(['ETH/BTC', 'TKN/BTC']) == set(new_whitelist) - - whitelist = ['ETH/BTC', 'TKN/BTC', 'BLK/BTC'] - new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist) - # BLK/BTC is in blacklist ... - assert set(['ETH/BTC', 'TKN/BTC']) == set(new_whitelist) + assert set(new_whitelist) == set(['ETH/BTC', 'TKN/BTC']) + assert log_message in caplog.text From 937399606e98e0110d27e0e0e804128622adbde3 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Sun, 17 Mar 2019 18:24:29 +0100 Subject: [PATCH 231/457] fix flake8 --- freqtrade/tests/pairlist/test_pairlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/pairlist/test_pairlist.py b/freqtrade/tests/pairlist/test_pairlist.py index 5fe2dfc08..38a8d78c7 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, PropertyMock from freqtrade import OperationalException from freqtrade.constants import AVAILABLE_PAIRLISTS from freqtrade.resolvers import PairListResolver -from freqtrade.tests.conftest import get_patched_freqtradebot, log_has +from freqtrade.tests.conftest import get_patched_freqtradebot import pytest # whitelist, blacklist From a467d768326e4c6707d1b858e91fc3d0b3687448 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 19:35:25 +0100 Subject: [PATCH 232/457] Add /stopbuy command to telegram fixes #1607 --- freqtrade/rpc/rpc.py | 10 ++++++++++ freqtrade/rpc/telegram.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index af64c3d67..eb0269b5b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -328,6 +328,16 @@ class RPC(object): self._freqtrade.state = State.RELOAD_CONF return {'status': 'reloading config ...'} + def _rpc_stopbuy(self) -> Dict[str, str]: + """ + Handler to stop buying, but handle open trades gracefully. + """ + if self._freqtrade.state == State.RUNNING: + # Set 'max_open_trades' to 0 + self._freqtrade.config['max_open_trades'] = 0 + + return {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} + def _rpc_forcesell(self, trade_id) -> None: """ Handler for forcesell . diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e599172e4..6771ec803 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -91,6 +91,7 @@ class Telegram(RPC): CommandHandler('daily', self._daily), CommandHandler('count', self._count), CommandHandler('reload_conf', self._reload_conf), + CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), CommandHandler('help', self._help), CommandHandler('version', self._version), @@ -362,6 +363,18 @@ class Telegram(RPC): msg = self._rpc_reload_conf() self._send_msg('Status: `{status}`'.format(**msg), bot=bot) + @authorized_only + def _stopbuy(self, bot: Bot, update: Update) -> None: + """ + Handler for /stop_buy. + Sets max_open_trades to 0 and gracefully sells all open trades + :param bot: telegram bot + :param update: message update + :return: None + """ + msg = self._rpc_stopbuy() + self._send_msg('Status: `{status}`'.format(**msg), bot=bot) + @authorized_only def _forcesell(self, bot: Bot, update: Update) -> None: """ @@ -481,6 +494,7 @@ class Telegram(RPC): "*/count:* `Show number of trades running compared to allowed number of trades`" \ "\n" \ "*/balance:* `Show account balance per currency`\n" \ + "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \ "*/reload_conf:* `Reload configuration file` \n" \ "*/whitelist:* `Show current whitelist` \n" \ "*/help:* `This help message`\n" \ From 9373d0c915d14795aa832288e26fa14a5e2975bc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 19:36:02 +0100 Subject: [PATCH 233/457] Add tests for /stopbuy --- freqtrade/tests/rpc/test_rpc.py | 20 ++++++++++++++++++++ freqtrade/tests/rpc/test_rpc_telegram.py | 22 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e1261e02e..e5ac79feb 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -406,6 +406,26 @@ def test_rpc_stop(mocker, default_conf) -> None: assert freqtradebot.state == State.STOPPED +def test_rpc_stopbuy(mocker, default_conf) -> None: + patch_coinmarketcap(mocker) + patch_exchange(mocker) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock() + ) + + freqtradebot = FreqtradeBot(default_conf) + patch_get_signal(freqtradebot, (True, False)) + rpc = RPC(freqtradebot) + freqtradebot.state = State.RUNNING + + assert freqtradebot.config['max_open_trades'] != 0 + result = rpc._rpc_stopbuy() + assert {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} == result + assert freqtradebot.config['max_open_trades'] == 0 + + def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: patch_coinmarketcap(mocker) patch_exchange(mocker) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 02f4f4afb..dd8215551 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -74,7 +74,7 @@ def test_init(default_conf, mocker, caplog) -> None: message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \ "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \ "['performance'], ['daily'], ['count'], ['reload_conf'], " \ - "['whitelist'], ['help'], ['version']]" + "['stopbuy'], ['whitelist'], ['help'], ['version']]" assert log_has(message_str, caplog.record_tuples) @@ -662,6 +662,26 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: assert 'already stopped' in msg_mock.call_args_list[0][0][0] +def test_stopbuy_handle(default_conf, update, mocker) -> None: + patch_coinmarketcap(mocker) + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) + + assert freqtradebot.config['max_open_trades'] != 0 + telegram._stopbuy(bot=MagicMock(), update=update) + assert freqtradebot.config['max_open_trades'] == 0 + assert msg_mock.call_count == 1 + assert 'Setting max_open_trades to 0. Run /reload_conf to reset.' \ + in msg_mock.call_args_list[0][0][0] + + def test_reload_conf_handle(default_conf, update, mocker) -> None: patch_coinmarketcap(mocker) msg_mock = MagicMock() From 37e6b262eb06f923e7d4c8955918ca23073b7263 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Mar 2019 19:36:25 +0100 Subject: [PATCH 234/457] Update docs to include /stopbuy --- docs/telegram-usage.md | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index db98cbb12..face22404 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -16,6 +16,7 @@ official commands. You can ask at any moment for help with `/help`. |----------|---------|-------------| | `/start` | | Starts the trader | `/stop` | | Stops the trader +| `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. | `/reload_conf` | | Reloads the configuration file | `/status` | | Lists all open trades | `/status table` | | List all open trades in a table format @@ -43,7 +44,21 @@ Below, example of Telegram message you will receive for each command. > `Stopping trader ...` > **Status:** `stopped` -## /status +### /stopbuy + +> **status:** `Setting max_open_trades to 0. Run /reload_conf to reset.` + +Prevents the bot from opening new trades by temporarily setting "max_open_trades" to 0. Open trades will be handled via their regular rules (ROI / Sell-signal, stoploss, ...). + +After this, give the bot time to close off open trades (can be checked via `/status table`). +Once all positions are sold, run `/stop` to completely stop the bot. + +`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command. + +!!! warning: +The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. + +### /status For each open trade, the bot will send you the following message. @@ -58,7 +73,7 @@ For each open trade, the bot will send you the following message. > **Current Profit:** `12.95%` > **Open Order:** `None` -## /status table +### /status table Return the status of all open trades in a table format. ``` @@ -68,7 +83,7 @@ Return the status of all open trades in a table format. 123 CVC/BTC 1 h 12.95% ``` -## /count +### /count Return the number of trades used and available. ``` @@ -77,7 +92,7 @@ current max 2 10 ``` -## /profit +### /profit Return a summary of your profit/loss and performance. @@ -94,11 +109,11 @@ Return a summary of your profit/loss and performance. > **Avg. Duration:** `2:33:45` > **Best Performing:** `PAY/BTC: 50.23%` -## /forcesell +### /forcesell > **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)` -## /forcebuy +### /forcebuy > **BITTREX**: Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) @@ -106,7 +121,7 @@ Note that for this to work, `forcebuy_enable` needs to be set to true. [More details](configuration.md/#understand-forcebuy_enable) -## /performance +### /performance Return the performance of each crypto-currency the bot has sold. > Performance: @@ -117,7 +132,7 @@ Return the performance of each crypto-currency the bot has sold. > 5. `STORJ/BTC 27.24%` > ... -## /balance +### /balance Return the balance of all crypto-currency your have on the exchange. @@ -131,7 +146,7 @@ Return the balance of all crypto-currency your have on the exchange. > **Balance:** 86.64180098 > **Pending:** 0.0 -## /daily +### /daily Per default `/daily` will return the 7 last days. The example below if for `/daily 3`: @@ -145,6 +160,6 @@ Day Profit BTC Profit USD 2018-01-01 0.00269130 BTC 34.986 USD ``` -## /version +### /version > **Version:** `0.14.3` From aa698a84125bf03106338356ffaea18517c023f3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 18 Mar 2019 06:27:44 +0100 Subject: [PATCH 235/457] rename /stopbuy message --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index eb0269b5b..7ad51fa8e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -336,7 +336,7 @@ class RPC(object): # Set 'max_open_trades' to 0 self._freqtrade.config['max_open_trades'] = 0 - return {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} + return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} def _rpc_forcesell(self, trade_id) -> None: """ From 8d173efe2d48d75a07a5bcbfad085b4dc31c7179 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 18 Mar 2019 06:28:58 +0100 Subject: [PATCH 236/457] reword stopbuy message --- freqtrade/tests/rpc/test_rpc.py | 2 +- freqtrade/tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e5ac79feb..baddc0685 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -422,7 +422,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: assert freqtradebot.config['max_open_trades'] != 0 result = rpc._rpc_stopbuy() - assert {'status': 'Setting max_open_trades to 0. Run /reload_conf to reset.'} == result + assert {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} == result assert freqtradebot.config['max_open_trades'] == 0 diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index dd8215551..8e8d1f1bb 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -678,7 +678,7 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None: telegram._stopbuy(bot=MagicMock(), update=update) assert freqtradebot.config['max_open_trades'] == 0 assert msg_mock.call_count == 1 - assert 'Setting max_open_trades to 0. Run /reload_conf to reset.' \ + assert 'No more buy will occur from now. Run /reload_conf to reset.' \ in msg_mock.call_args_list[0][0][0] From 50ea4c39da3a33bdeaa579de747bc23af2e2a011 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 18 Mar 2019 13:32:05 +0100 Subject: [PATCH 237/457] Update ccxt from 1.18.368 to 1.18.372 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 400940d16..85b43b4f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.368 +ccxt==1.18.372 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 9a6106736763c89bdc8709d28b4f32953b35b30f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 19 Mar 2019 13:32:04 +0100 Subject: [PATCH 238/457] Update ccxt from 1.18.372 to 1.18.376 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 85b43b4f8..7f3fe503d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.372 +ccxt==1.18.376 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 2b09e3ca3db6490df5d633275f634618ab147057 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 19 Mar 2019 13:32:05 +0100 Subject: [PATCH 239/457] Update plotly from 3.7.0 to 3.7.1 --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index b49aad626..e582fddf6 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==3.7.0 +plotly==3.7.1 From 6c889895bdf53eb5b4a4324178de5d87b48a624a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Mar 2019 13:35:05 +0100 Subject: [PATCH 240/457] Update ccxt from 1.18.376 to 1.18.385 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7f3fe503d..3a25bb8bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.376 +ccxt==1.18.385 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From cc369f41f550e804f111b04ad316e8fe7d362c57 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 20 Mar 2019 13:35:07 +0100 Subject: [PATCH 241/457] Update coveralls from 1.6.0 to 1.7.0 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 68d63101a..e0aaf9461 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,5 +8,5 @@ pytest==4.3.1 pytest-mock==1.10.1 pytest-asyncio==0.10.0 pytest-cov==2.6.1 -coveralls==1.6.0 +coveralls==1.7.0 mypy==0.670 From 0eff324ce04d6b19e41908edf6b00065b1fcd20b Mon Sep 17 00:00:00 2001 From: Gianluca Puglia Date: Wed, 20 Mar 2019 18:38:10 +0100 Subject: [PATCH 242/457] Use dedicated index for every pair --- freqtrade/optimize/backtesting.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 031b490c8..8d0768d43 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -10,7 +10,7 @@ from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict, List, NamedTuple, Optional -from pandas import DataFrame +from pandas import DataFrame, Timestamp from tabulate import tabulate from freqtrade import optimize @@ -325,19 +325,29 @@ class Backtesting(object): pairs.append(pair) lock_pair_until: Dict = {} + indexes: Dict = {} tmp = start_date + timedelta(minutes=self.ticker_interval_mins) - index = 0 + # Loop timerange and test per pair while tmp < end_date: # print(f"time: {tmp}") + for i, pair in enumerate(ticker): + if pair not in indexes: + indexes[pair] = 0 + try: - row = ticker[pair][index] + row = ticker[pair][indexes[pair]] except IndexError: # missing Data for one pair ... # Warnings for this are shown by `validate_backtest_data` continue + if row.date > Timestamp(tmp.datetime): + continue + + indexes[pair] += 1 + if row.buy == 0 or row.sell == 1: continue # skip rows where no buy signal or that would immediately sell off @@ -351,7 +361,7 @@ class Backtesting(object): trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 - trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][index + 1:], + trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][indexes[pair]:], trade_count_lock, args) if trade_entry: @@ -359,11 +369,9 @@ class Backtesting(object): trades.append(trade_entry) else: # Set lock_pair_until to end of testing period if trade could not be closed - # This happens only if the buy-signal was with the last candle - lock_pair_until[pair] = end_date + lock_pair_until[pair] = Timestamp(end_date.datetime) tmp += timedelta(minutes=self.ticker_interval_mins) - index += 1 return DataFrame.from_records(trades, columns=BacktestResult._fields) def start(self) -> None: From 6b89e86a97de6fc81cd0212e82e3267a339fc9d0 Mon Sep 17 00:00:00 2001 From: Gianluca Puglia Date: Wed, 20 Mar 2019 19:44:59 +0100 Subject: [PATCH 243/457] Removed Timestamp cast --- freqtrade/optimize/backtesting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 8d0768d43..f54560a0e 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -10,7 +10,7 @@ from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict, List, NamedTuple, Optional -from pandas import DataFrame, Timestamp +from pandas import DataFrame from tabulate import tabulate from freqtrade import optimize @@ -343,7 +343,7 @@ class Backtesting(object): # Warnings for this are shown by `validate_backtest_data` continue - if row.date > Timestamp(tmp.datetime): + if row.date > tmp.datetime: continue indexes[pair] += 1 @@ -369,7 +369,7 @@ class Backtesting(object): trades.append(trade_entry) else: # Set lock_pair_until to end of testing period if trade could not be closed - lock_pair_until[pair] = Timestamp(end_date.datetime) + lock_pair_until[pair] = end_date.datetime tmp += timedelta(minutes=self.ticker_interval_mins) return DataFrame.from_records(trades, columns=BacktestResult._fields) From 00821036bb21c9d80265d5a5ba0e0a310f9b666b Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Wed, 20 Mar 2019 23:57:49 +0300 Subject: [PATCH 244/457] docs for dry_run_wallet --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index e7ad9c9bf..4b8d990fe 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -20,6 +20,7 @@ Mandatory Parameters are marked as **Required**. | `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below. | `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode. +| `dry_run_wallet` | 999.9 | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. | `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). | `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). | `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). From 7fdb099097c0afb373ee4233036403c095249d54 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Mar 2019 06:14:43 +0100 Subject: [PATCH 245/457] Reformat log statement --- freqtrade/pairlist/IPairList.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index d08bd2587..a112c63b4 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -78,9 +78,7 @@ class IPairList(ABC): # Check if market is active market = markets[pair] if not market['active']: - logger.info( - f"Ignoring {pair} from whitelist. Market is not active." - ) + logger.info(f"Ignoring {pair} from whitelist. Market is not active.") continue sanitized_whitelist.add(pair) From 89145a7711da5914c473146e4c5023e509261802 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 22 Mar 2019 13:35:06 +0100 Subject: [PATCH 246/457] Update ccxt from 1.18.385 to 1.18.386 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3a25bb8bc..53e81aad3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.385 +ccxt==1.18.386 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 60afba559274a13e33408c9f7fb0db24763b9594 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 22 Mar 2019 20:16:54 +0300 Subject: [PATCH 247/457] move worker stuff to main.py --- freqtrade/freqtradebot.py | 113 +++-------------------- freqtrade/main.py | 190 +++++++++++++++++++++++++++++++------- 2 files changed, 169 insertions(+), 134 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6f1fb2c99..ef00ba21d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -7,11 +7,10 @@ import logging import time import traceback from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import arrow from requests.exceptions import RequestException -import sdnotify from freqtrade import (DependencyException, OperationalException, TemporaryError, __version__, constants, persistence) @@ -24,6 +23,7 @@ from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListReso from freqtrade.state import State from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.wallets import Wallets +from freqtrade.main import Worker logger = logging.getLogger(__name__) @@ -35,26 +35,18 @@ class FreqtradeBot(object): This is from here the bot start its logic. """ - def __init__(self, config: Dict[str, Any]) -> None: + def __init__(self, config: Dict[str, Any], worker: Worker) -> None: """ Init all variables and objects the bot needs to work :param config: configuration dict, you can use Configuration.get_config() to get the config dict. """ - logger.info( - 'Starting freqtrade %s', - __version__, - ) - - # Init bot states - self.state = State.STOPPED + logger.info('Starting freqtrade %s', __version__) # Init objects self.config = config - - self._sd_notify = sdnotify.SystemdNotifier() if \ - self.config.get('internals', {}).get('sd_notify', False) else None + self._worker: Worker = worker self.strategy: IStrategy = StrategyResolver(self.config).strategy @@ -79,29 +71,16 @@ class FreqtradeBot(object): self.config.get('edge', {}).get('enabled', False) else None self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist'] - self._init_modules() - - # Tell the systemd that we completed initialization phase - if self._sd_notify: - logger.debug("sd_notify: READY=1") - self._sd_notify.notify("READY=1") - - def _init_modules(self) -> None: - """ - Initializes all modules and updates the config - :return: None - """ - # Initialize all modules persistence.init(self.config) - # Set initial application state - initial_state = self.config.get('initial_state') + @property + def state(self) -> State: + return self._worker.state - if initial_state: - self.state = State[initial_state.upper()] - else: - self.state = State.STOPPED + @state.setter + def state(self, value: State): + self._worker.state = value def cleanup(self) -> None: """ @@ -113,75 +92,7 @@ class FreqtradeBot(object): self.rpc.cleanup() persistence.cleanup() - def stopping(self) -> None: - # Tell systemd that we are exiting now - if self._sd_notify: - logger.debug("sd_notify: STOPPING=1") - self._sd_notify.notify("STOPPING=1") - - def reconfigure(self) -> None: - # Tell systemd that we initiated reconfiguring - if self._sd_notify: - logger.debug("sd_notify: RELOADING=1") - self._sd_notify.notify("RELOADING=1") - - def worker(self, old_state: State = None) -> State: - """ - Trading routine that must be run at each loop - :param old_state: the previous service state from the previous call - :return: current service state - """ - # Log state transition - state = self.state - if state != old_state: - self.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': f'{state.name.lower()}' - }) - logger.info('Changing state to: %s', state.name) - if state == State.RUNNING: - self.rpc.startup_messages(self.config, self.pairlists) - - throttle_secs = self.config.get('internals', {}).get( - 'process_throttle_secs', - constants.PROCESS_THROTTLE_SECS - ) - - if state == State.STOPPED: - # Ping systemd watchdog before sleeping in the stopped state - if self._sd_notify: - logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.") - self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.") - - time.sleep(throttle_secs) - - elif state == State.RUNNING: - # Ping systemd watchdog before throttling - if self._sd_notify: - logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.") - self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.") - - self._throttle(func=self._process, min_secs=throttle_secs) - - return state - - def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any: - """ - Throttles the given callable that it - takes at least `min_secs` to finish execution. - :param func: Any callable - :param min_secs: minimum execution time in seconds - :return: Any - """ - start = time.time() - result = func(*args, **kwargs) - end = time.time() - duration = max(min_secs - (end - start), 0.0) - logger.debug('Throttling %s for %.2f seconds', func.__name__, duration) - time.sleep(duration) - return result - - def _process(self) -> bool: + def process(self) -> bool: """ Queries the persistence layer for open trades and handles them, otherwise a new trade is created. diff --git a/freqtrade/main.py b/freqtrade/main.py index c41d54f0e..48ae17f9d 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -5,10 +5,12 @@ Read the documentation to know what cli arguments you need. """ import logging import sys +import time from argparse import Namespace -from typing import List +from typing import Any, Callable, List +import sdnotify -from freqtrade import OperationalException +from freqtrade import (constants, OperationalException, __version__) from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.freqtradebot import FreqtradeBot @@ -35,20 +37,11 @@ def main(sysargv: List[str]) -> None: args.func(args) return - freqtrade = None return_code = 1 try: - # Load and validate configuration - config = Configuration(args, None).get_config() - - # Init the bot - freqtrade = FreqtradeBot(config) - - state = None - while True: - state = freqtrade.worker(old_state=state) - if state == State.RELOAD_CONF: - freqtrade = reconfigure(freqtrade, args) + # Load and run worker + worker = Worker(args) + worker.run() except KeyboardInterrupt: logger.info('SIGINT received, aborting ...') @@ -59,32 +52,163 @@ def main(sysargv: List[str]) -> None: except BaseException: logger.exception('Fatal exception!') finally: - if freqtrade: - freqtrade.stopping() - freqtrade.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': 'process died' - }) - freqtrade.cleanup() + if worker is not None: + worker.exit() sys.exit(return_code) -def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot: +class Worker(object): """ - Cleans up current instance, reloads the configuration and returns the new instance + Freqtradebot worker class """ - freqtrade.reconfigure() - # Clean up current modules - freqtrade.cleanup() + def __init__(self, args: Namespace) -> None: + """ + Init all variables and objects the bot needs to work + """ + logger.info('Starting worker %s', __version__) - # Create new instance - freqtrade = FreqtradeBot(Configuration(args, None).get_config()) - freqtrade.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': 'config reloaded' - }) - return freqtrade + self._args = args + self._init() + + # Tell systemd that we completed initialization phase + if self._sd_notify: + logger.debug("sd_notify: READY=1") + self._sd_notify.notify("READY=1") + + def _init(self): + """ + Also called from the _reconfigure() method. + """ + # Load configuration + self._config = Configuration(self._args, None).get_config() + + # Init the instance of the bot + self.freqtrade = FreqtradeBot(self._config, self) + + # Set initial bot state + initial_state = self._config.get('initial_state') + if initial_state: + self._state = State[initial_state.upper()] + else: + self._state = State.STOPPED + + self._throttle_secs = self._config.get('internals', {}).get( + 'process_throttle_secs', + constants.PROCESS_THROTTLE_SECS + ) + + self._sd_notify = sdnotify.SystemdNotifier() if \ + self._config.get('internals', {}).get('sd_notify', False) else None + + @property + def state(self) -> State: + return self._state + + @state.setter + def state(self, value: State): + self._state = value + + def run(self): + state = None + while True: + state = self._worker(old_state=state, throttle_secs=self._throttle_secs) + if state == State.RELOAD_CONF: + self.freqtrade = self._reconfigure() + + def _worker(self, old_state: State, throttle_secs: float) -> State: + """ + Trading routine that must be run at each loop + :param old_state: the previous service state from the previous call + :return: current service state + """ + state = self._state + + # Log state transition + if state != old_state: + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': f'{state.name.lower()}' + }) + logger.info('Changing state to: %s', state.name) + if state == State.RUNNING: + self.freqtrade.rpc.startup_messages(self._config, self.freqtrade.pairlists) + + if state == State.STOPPED: + # Ping systemd watchdog before sleeping in the stopped state + if self._sd_notify: + logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.") + self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.") + + time.sleep(throttle_secs) + + elif state == State.RUNNING: + # Ping systemd watchdog before throttling + if self._sd_notify: + logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.") + self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.") + + self._throttle(func=self._process, min_secs=throttle_secs) + + return state + + def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any: + """ + Throttles the given callable that it + takes at least `min_secs` to finish execution. + :param func: Any callable + :param min_secs: minimum execution time in seconds + :return: Any + """ + start = time.time() + result = func(*args, **kwargs) + end = time.time() + duration = max(min_secs - (end - start), 0.0) + logger.debug('Throttling %s for %.2f seconds', func.__name__, duration) + time.sleep(duration) + return result + + def _process(self) -> bool: + return self.freqtrade.process() + + def _reconfigure(self): + """ + Cleans up current freqtradebot instance, reloads the configuration and + returns the new instance + """ + # Tell systemd that we initiated reconfiguration + if self._sd_notify: + logger.debug("sd_notify: RELOADING=1") + self._sd_notify.notify("RELOADING=1") + + # Clean up current freqtrade modules + self.freqtrade.cleanup() + + # Load and validate config and create new instance of the bot + self._init() + + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': 'config reloaded' + }) + + # Tell systemd that we completed reconfiguration + if self._sd_notify: + logger.debug("sd_notify: READY=1") + self._sd_notify.notify("READY=1") + + def exit(self): + # Tell systemd that we are exiting now + if self._sd_notify: + logger.debug("sd_notify: STOPPING=1") + self._sd_notify.notify("STOPPING=1") + + if self.freqtrade: + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': 'process died' + }) + self.freqtrade.cleanup() if __name__ == '__main__': From be6836b0ef912541c336e8c7ccdd29de36dce908 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 22 Mar 2019 21:49:19 +0300 Subject: [PATCH 248/457] resolve python module circular dependency --- freqtrade/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 48ae17f9d..e1b4d80fa 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -13,7 +13,6 @@ import sdnotify from freqtrade import (constants, OperationalException, __version__) from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers -from freqtrade.freqtradebot import FreqtradeBot from freqtrade.state import State from freqtrade.rpc import RPCMessageType @@ -83,6 +82,10 @@ class Worker(object): # Load configuration self._config = Configuration(self._args, None).get_config() + # Import freqtradebot here in order to avoid python circular + # dependency error, damn! + from freqtrade.freqtradebot import FreqtradeBot + # Init the instance of the bot self.freqtrade = FreqtradeBot(self._config, self) From b4488902100408a90beb33acd79bc43fe272235b Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 22 Mar 2019 22:03:15 +0300 Subject: [PATCH 249/457] test_main.py adjusted (only beginning) --- freqtrade/tests/test_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 51c95a4a9..188b9b9bd 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -8,7 +8,7 @@ import pytest from freqtrade import OperationalException from freqtrade.arguments import Arguments from freqtrade.freqtradebot import FreqtradeBot -from freqtrade.main import main, reconfigure +from freqtrade.main import main, Worker from freqtrade.state import State from freqtrade.tests.conftest import log_has, patch_exchange @@ -126,7 +126,7 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: # Raise exception as side effect to avoid endless loop reconfigure_mock = mocker.patch( - 'freqtrade.main.reconfigure', MagicMock(side_effect=Exception) + 'freqtrade.main.Worker._reconfigure', MagicMock(side_effect=Exception) ) with pytest.raises(SystemExit): From e35daf95c04571a4297769e1ac6d1c2f5965b2c3 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 22 Mar 2019 23:41:48 +0300 Subject: [PATCH 250/457] minor cleanup --- freqtrade/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index e1b4d80fa..923230b7f 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -36,6 +36,7 @@ def main(sysargv: List[str]) -> None: args.func(args) return + worker = None return_code = 1 try: # Load and run worker @@ -51,7 +52,7 @@ def main(sysargv: List[str]) -> None: except BaseException: logger.exception('Fatal exception!') finally: - if worker is not None: + if worker: worker.exit() sys.exit(return_code) From 158cb307f6268c37b711b1bc1ac5d2196bca3647 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 23 Mar 2019 00:20:20 +0300 Subject: [PATCH 251/457] further refactoring of FreqtradeBot.process() --- freqtrade/freqtradebot.py | 71 ++++++++++++++++----------------------- freqtrade/main.py | 22 ++++++++++-- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ef00ba21d..784c0b938 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -4,7 +4,6 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade() import copy import logging -import time import traceback from datetime import datetime from typing import Any, Dict, List, Optional, Tuple @@ -13,7 +12,7 @@ import arrow from requests.exceptions import RequestException from freqtrade import (DependencyException, OperationalException, - TemporaryError, __version__, constants, persistence) + __version__, constants, persistence) from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge @@ -99,55 +98,43 @@ class FreqtradeBot(object): :return: True if one or more trades has been created or closed, False otherwise """ state_changed = False - try: - # Check whether markets have to be reloaded - self.exchange._reload_markets() - # Refresh whitelist - self.pairlists.refresh_pairlist() - self.active_pair_whitelist = self.pairlists.whitelist + # Check whether markets have to be reloaded + self.exchange._reload_markets() - # Calculating Edge positioning - if self.edge: - self.edge.calculate() - self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist) + # Refresh whitelist + self.pairlists.refresh_pairlist() + self.active_pair_whitelist = self.pairlists.whitelist - # Query trades from persistence layer - trades = Trade.get_open_trades() + # Calculating Edge positioning + if self.edge: + self.edge.calculate() + self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist) - # Extend active-pair whitelist with pairs from open trades - # It ensures that tickers are downloaded for open trades - self._extend_whitelist_with_trades(self.active_pair_whitelist, trades) + # Query trades from persistence layer + trades = Trade.get_open_trades() - # Refreshing candles - self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), - self.strategy.informative_pairs()) + # Extend active-pair whitelist with pairs from open trades + # It ensures that tickers are downloaded for open trades + self._extend_whitelist_with_trades(self.active_pair_whitelist, trades) - # First process current opened trades - for trade in trades: - state_changed |= self.process_maybe_execute_sell(trade) + # Refreshing candles + self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), + self.strategy.informative_pairs()) - # Then looking for buy opportunities - if len(trades) < self.config['max_open_trades']: - state_changed = self.process_maybe_execute_buy() + # First process current opened trades + for trade in trades: + state_changed |= self.process_maybe_execute_sell(trade) - if 'unfilledtimeout' in self.config: - # Check and handle any timed out open orders - self.check_handle_timedout() - Trade.session.flush() + # Then looking for buy opportunities + if len(trades) < self.config['max_open_trades']: + state_changed = self.process_maybe_execute_buy() + + if 'unfilledtimeout' in self.config: + # Check and handle any timed out open orders + self.check_handle_timedout() + Trade.session.flush() - except TemporaryError as error: - logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...") - time.sleep(constants.RETRY_TIMEOUT) - except OperationalException: - tb = traceback.format_exc() - hint = 'Issue `/start` if you think it is safe to restart.' - self.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': f'OperationalException:\n```\n{tb}```{hint}' - }) - logger.exception('OperationalException. Stopping trader ...') - self.state = State.STOPPED return state_changed def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]): diff --git a/freqtrade/main.py b/freqtrade/main.py index 923230b7f..9331206fc 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -6,11 +6,13 @@ Read the documentation to know what cli arguments you need. import logging import sys import time +import traceback from argparse import Namespace from typing import Any, Callable, List import sdnotify -from freqtrade import (constants, OperationalException, __version__) +from freqtrade import (constants, OperationalException, TemporaryError, + __version__) from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.state import State @@ -173,7 +175,23 @@ class Worker(object): return result def _process(self) -> bool: - return self.freqtrade.process() + state_changed = False + try: + state_changed = self.freqtrade.process() + + except TemporaryError as error: + logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...") + time.sleep(constants.RETRY_TIMEOUT) + except OperationalException: + tb = traceback.format_exc() + hint = 'Issue `/start` if you think it is safe to restart.' + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': f'OperationalException:\n```\n{tb}```{hint}' + }) + logger.exception('OperationalException. Stopping trader ...') + self.state = State.STOPPED + return state_changed def _reconfigure(self): """ From 34ff946f4dc3042056a3dfa03a648b42d8e932d0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 23 Mar 2019 13:35:03 +0100 Subject: [PATCH 252/457] Update ccxt from 1.18.386 to 1.18.387 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 53e81aad3..9c083a675 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.386 +ccxt==1.18.387 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 05466d318a2f3971bff4e234ecbc7aa00e57f419 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 14:50:18 +0100 Subject: [PATCH 253/457] Modify test to check for this condition --- freqtrade/tests/optimize/test_backtesting.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 40754cfbc..531f73916 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -683,18 +683,20 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): assert len(results.loc[results.open_at_end]) == 0 -def test_backtest_multi_pair(default_conf, fee, mocker): +@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC']) +@pytest.mark.parametrize("tres", [0, 20, 30]) +def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair): def _trend_alternate_hold(dataframe=None, metadata=None): """ - Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit) + Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)flake """ - multi = 8 + if metadata['pair'] in('ETH/BTC', 'LTC/BTC'): + multi = 20 + else: + multi = 18 dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0) dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0) - if metadata['pair'] in('ETH/BTC', 'LTC/BTC'): - dataframe['buy'] = dataframe['buy'].shift(-4) - dataframe['sell'] = dataframe['sell'].shift(-4) return dataframe mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) @@ -702,6 +704,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker): pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] data = history.load_data(datadir=None, ticker_interval='5m', pairs=pairs) data = trim_dictlist(data, -500) + data[pair] = data[pair][tres:] # We need to enable sell-signal - otherwise it sells on ROI!! default_conf['experimental'] = {"use_sell_signal": True} default_conf['ticker_interval'] = '5m' From 00e6749d8bf8e7fc9c8924d0525d855acc8a72ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 15:00:07 +0100 Subject: [PATCH 254/457] Refactor backtest() to be a bit more concise --- freqtrade/optimize/backtesting.py | 47 ++++++++++++++++++------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index f54560a0e..f3661ab32 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -202,6 +202,32 @@ class Backtesting(object): logger.info('Dumping backtest results to %s', recordfilename) file_dump_json(recordfilename, records) + def _get_ticker_list(self, processed) -> Dict[str, DataFrame]: + """ + Helper function to convert a processed tickerlist into a list for performance reasons. + + Used by backtest() - so keep this optimized for performance. + """ + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] + ticker: Dict = {} + # Create ticker dict + for pair, pair_data in processed.items(): + pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run + + ticker_data = self.advise_sell( + self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() + + # to avoid using data from future, we buy/sell with signal from previous candle + ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) + ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1) + + ticker_data.drop(ticker_data.head(1).index, inplace=True) + + # Convert from Pandas to list for performance reasons + # (Looping Pandas is slow.) + ticker[pair] = [x for x in ticker_data.itertuples()] + return ticker + def _get_sell_trade_entry( self, pair: str, buy_row: DataFrame, partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]: @@ -296,7 +322,6 @@ class Backtesting(object): position_stacking: do we allow position stacking? (default: False) :return: DataFrame """ - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] processed = args['processed'] max_open_trades = args.get('max_open_trades', 0) position_stacking = args.get('position_stacking', False) @@ -304,25 +329,7 @@ class Backtesting(object): end_date = args['end_date'] trades = [] trade_count_lock: Dict = {} - ticker: Dict = {} - pairs = [] - # Create ticker dict - for pair, pair_data in processed.items(): - pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run - - ticker_data = self.advise_sell( - self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() - - # to avoid using data from future, we buy/sell with signal from previous candle - ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) - ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1) - - ticker_data.drop(ticker_data.head(1).index, inplace=True) - - # Convert from Pandas to list for performance reasons - # (Looping Pandas is slow.) - ticker[pair] = [x for x in ticker_data.itertuples()] - pairs.append(pair) + ticker: Dict = self._get_ticker_list(processed) lock_pair_until: Dict = {} indexes: Dict = {} From 40899d08dd76aca924c52ce02be418747468ef25 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 15:24:11 +0100 Subject: [PATCH 255/457] Fix failing test (all timezones are in UTC, so we should not convert to None) --- freqtrade/tests/edge/test_edge.py | 4 ++-- freqtrade/tests/optimize/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/edge/test_edge.py b/freqtrade/tests/edge/test_edge.py index c1c1b49cd..af8674188 100644 --- a/freqtrade/tests/edge/test_edge.py +++ b/freqtrade/tests/edge/test_edge.py @@ -122,8 +122,8 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None: for c, trade in enumerate(data.trades): res = results.iloc[c] assert res.exit_type == trade.sell_reason - assert res.open_time == _get_frame_time_from_offset(trade.open_tick) - assert res.close_time == _get_frame_time_from_offset(trade.close_tick) + assert arrow.get(res.open_time) == _get_frame_time_from_offset(trade.open_tick) + assert arrow.get(res.close_time) == _get_frame_time_from_offset(trade.close_tick) def test_adjust(mocker, edge_conf): diff --git a/freqtrade/tests/optimize/__init__.py b/freqtrade/tests/optimize/__init__.py index 129a09f40..b1bf55f50 100644 --- a/freqtrade/tests/optimize/__init__.py +++ b/freqtrade/tests/optimize/__init__.py @@ -32,7 +32,7 @@ class BTContainer(NamedTuple): def _get_frame_time_from_offset(offset): return ticker_start_time.shift(minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval]) - ).datetime.replace(tzinfo=None) + ).datetime def _build_backtest_dataframe(ticker_with_signals): From 7307084dfd5c0ec955278e285d464203d71a13bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:21:58 +0100 Subject: [PATCH 256/457] Move stoploss-adjustment to the top --- freqtrade/strategy/interface.py | 47 ++++++++++++++-------------- freqtrade/tests/test_freqtradebot.py | 9 ++---- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a086139c7..542b6bab0 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -302,6 +302,29 @@ class IStrategy(ABC): trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss else self.stoploss, initial=True) + # update the stop loss afterwards, after all by definition it's supposed to be hanging + # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details + if trailing_stop: + + # check if we have a special stop loss for positive condition + # and if profit is positive + stop_loss_value = force_stoploss if force_stoploss else self.stoploss + + sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 + + if 'trailing_stop_positive' in self.config and current_profit > sl_offset: + + # Ignore mypy error check in configuration that this is a float + stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore + logger.debug(f"using positive stop loss: {stop_loss_value} " + f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") + + # if trailing_only_offset_is_reached is true, + # we update trailing stoploss only if offset is reached. + tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) + if not (tsl_only_offset and current_profit < sl_offset): + trade.adjust_stop_loss(high or current_rate, stop_loss_value) + # evaluate if the stoploss was hit if stoploss is not on exchange if ((self.stoploss is not None) and (trade.stop_loss >= current_rate) and @@ -321,30 +344,6 @@ class IStrategy(ABC): logger.debug('Stop loss hit.') return SellCheckTuple(sell_flag=True, sell_type=selltype) - # update the stop loss afterwards, after all by definition it's supposed to be hanging - # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details - if trailing_stop: - - # check if we have a special stop loss for positive condition - # and if profit is positive - stop_loss_value = force_stoploss if force_stoploss else self.stoploss - - sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 - - if 'trailing_stop_positive' in self.config and current_profit > sl_offset: - - # Ignore mypy error check in configuration that this is a float - stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore - logger.debug(f"using positive stop loss mode: {stop_loss_value} " - f"with offset {sl_offset:.4g} " - f"since we have profit {current_profit:.4f}%") - - # if trailing_only_offset_is_reached is true, - # we update trailing stoploss only if offset is reached. - tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) - if not (tsl_only_offset and current_profit < sl_offset): - trade.adjust_stop_loss(high or current_rate, stop_loss_value) - return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index a5de283a5..561df7c05 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2406,8 +2406,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.01 with offset 0 ' - f'since we have profit 0.2666%', + assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000138501 @@ -2466,8 +2465,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 ' - f'since we have profit 0.2666%', + assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000138501 @@ -2546,8 +2544,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, })) assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.05 with offset 0.055 ' - f'since we have profit 0.1218%', + assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000117705 From b1fe8c532549d1c0552390e68d59f105d83a7649 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:23:32 +0100 Subject: [PATCH 257/457] Simplify stoploss_reached --- freqtrade/strategy/interface.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 542b6bab0..a9925b654 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -299,18 +299,16 @@ class IStrategy(ABC): """ trailing_stop = self.config.get('trailing_stop', False) - trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss - else self.stoploss, initial=True) + stop_loss_value = force_stoploss if force_stoploss else self.stoploss + + trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) - # update the stop loss afterwards, after all by definition it's supposed to be hanging - # TODO: Maybe this needs to be moved to the start of this function. check #1575 for details if trailing_stop: - # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = force_stoploss if force_stoploss else self.stoploss sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 + tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) if 'trailing_stop_positive' in self.config and current_profit > sl_offset: @@ -321,7 +319,6 @@ class IStrategy(ABC): # if trailing_only_offset_is_reached is true, # we update trailing stoploss only if offset is reached. - tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) if not (tsl_only_offset and current_profit < sl_offset): trade.adjust_stop_loss(high or current_rate, stop_loss_value) From c404e9ffd06e94d320b95ba2387d011cd5eab70d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:48:17 +0100 Subject: [PATCH 258/457] Simplify trailing_stop logic --- freqtrade/strategy/interface.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a9925b654..eb9ed825e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -301,6 +301,7 @@ class IStrategy(ABC): trailing_stop = self.config.get('trailing_stop', False) stop_loss_value = force_stoploss if force_stoploss else self.stoploss + # Initiate stoploss with open_rate. Does nothing if stoploss is already set. trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) if trailing_stop: @@ -310,16 +311,15 @@ class IStrategy(ABC): sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) - if 'trailing_stop_positive' in self.config and current_profit > sl_offset: - - # Ignore mypy error check in configuration that this is a float - stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore - logger.debug(f"using positive stop loss: {stop_loss_value} " - f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") - - # if trailing_only_offset_is_reached is true, - # we update trailing stoploss only if offset is reached. + # Don't update stoploss if trailing_only_offset_is_reached is true. if not (tsl_only_offset and current_profit < sl_offset): + + if 'trailing_stop_positive' in self.config and current_profit > sl_offset: + # Ignore mypy error check in configuration that this is a float + stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore + logger.debug(f"using positive stop loss: {stop_loss_value} " + f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%") + trade.adjust_stop_loss(high or current_rate, stop_loss_value) # evaluate if the stoploss was hit if stoploss is not on exchange From 9a632d9b7ca09bd0c85ad58f05e2583dc2951804 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 16:51:36 +0100 Subject: [PATCH 259/457] Formatting --- freqtrade/strategy/interface.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index eb9ed825e..fcb27d7bd 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -290,8 +290,9 @@ class IStrategy(ABC): return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) - def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, - current_profit: float, force_stoploss: float, high) -> SellCheckTuple: + def stop_loss_reached(self, current_rate: float, trade: Trade, + current_time: datetime, current_profit: float, + force_stoploss: float, high: float = None) -> SellCheckTuple: """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not @@ -305,15 +306,14 @@ class IStrategy(ABC): trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) if trailing_stop: - # check if we have a special stop loss for positive condition - # and if profit is positive + # trailing stoploss handling sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0 tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) # Don't update stoploss if trailing_only_offset_is_reached is true. if not (tsl_only_offset and current_profit < sl_offset): - + # Specific handling for trailing_stop_positive if 'trailing_stop_positive' in self.config and current_profit > sl_offset: # Ignore mypy error check in configuration that this is a float stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore From 184b13f2fba7a690919d85ca815fdfb4d48e1ee8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 19:18:10 +0100 Subject: [PATCH 260/457] Flake8 for scripts --- .travis.yml | 4 ++-- scripts/get_market_pairs.py | 2 -- scripts/plot_dataframe.py | 6 ++---- scripts/plot_profit.py | 10 ++++------ 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index d24ffcf1b..014eb00ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ jobs: script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ # Allow failure for coveralls - - coveralls || true + - coveralls || true name: pytest - script: - cp config.json.example config.json @@ -39,7 +39,7 @@ jobs: - cp config.json.example config.json - python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5 name: hyperopt - - script: flake8 freqtrade + - script: flake8 freqtrade scripts name: flake8 - script: mypy freqtrade name: mypy diff --git a/scripts/get_market_pairs.py b/scripts/get_market_pairs.py index 6ee6464d3..1a4c593f9 100644 --- a/scripts/get_market_pairs.py +++ b/scripts/get_market_pairs.py @@ -51,7 +51,6 @@ try: id = sys.argv[1] # get exchange id from command line arguments - # check if the exchange is supported by ccxt exchange_found = id in ccxt.exchanges @@ -90,4 +89,3 @@ except Exception as e: dump('[' + type(e).__name__ + ']', str(e)) dump("Usage: python " + sys.argv[0], green('id')) print_supported_exchanges() - diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 581518b12..2eccaca18 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -24,24 +24,22 @@ Example of usage: > python3 scripts/plot_dataframe.py --pairs BTC/EUR,XRP/BTC -d user_data/data/ --indicators1 sma,ema3 --indicators2 fastk,fastd """ -import json import logging import sys from argparse import Namespace from pathlib import Path -from typing import Dict, List, Any +from typing import Any, Dict, List import pandas as pd import plotly.graph_objs as go import pytz - from plotly import tools from plotly.offline import plot from freqtrade import persistence from freqtrade.arguments import Arguments, TimeRange from freqtrade.data import history -from freqtrade.data.btanalysis import load_backtest_data, BT_DATA_COLUMNS +from freqtrade.data.btanalysis import BT_DATA_COLUMNS, load_backtest_data from freqtrade.exchange import Exchange from freqtrade.optimize.backtesting import setup_configuration from freqtrade.persistence import Trade diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index e2f85932f..8361e8bf6 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -12,26 +12,24 @@ Optional Cli parameters --timerange: specify what timerange of data to use --export-filename: Specify where the backtest export is located. """ +import json import logging import sys -import json from argparse import Namespace from pathlib import Path from typing import List, Optional -import numpy as np +import numpy as np +import plotly.graph_objs as go from plotly import tools from plotly.offline import plot -import plotly.graph_objs as go +from freqtrade import constants, misc from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration -from freqtrade import constants from freqtrade.data import history from freqtrade.resolvers import StrategyResolver from freqtrade.state import RunMode -import freqtrade.misc as misc - logger = logging.getLogger(__name__) From 83a2427a61b7025d58e74c49100dc3b65d4c1088 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 19:28:06 +0100 Subject: [PATCH 261/457] Fix mypy in scripts --- .travis.yml | 2 +- docs/plotting.md | 2 +- scripts/plot_dataframe.py | 2 +- scripts/plot_profit.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 014eb00ad..71432ea0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,7 @@ jobs: name: hyperopt - script: flake8 freqtrade scripts name: flake8 - - script: mypy freqtrade + - script: mypy freqtrade scripts name: mypy - stage: docker diff --git a/docs/plotting.md b/docs/plotting.md index a9b191e75..60c642ab3 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -84,5 +84,5 @@ The `-p` pair argument, can be used to plot a single pair Example ``` -python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p BTC_LTC +python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC ``` diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 2eccaca18..14d57265e 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -146,7 +146,7 @@ def get_tickers_data(strategy, exchange, pairs: List[str], args): tickers[pair] = exchange.klines((pair, tick_interval)) else: tickers = history.load_data( - datadir=Path(_CONF.get("datadir")), + datadir=Path(str(_CONF.get("datadir"))), pairs=pairs, ticker_interval=tick_interval, refresh_pairs=_CONF.get('refresh_pairs', False), diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 8361e8bf6..394f02116 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -37,7 +37,7 @@ logger = logging.getLogger(__name__) # data:: [ pair, profit-%, enter, exit, time, duration] # data:: ["ETH/BTC", 0.0023975, "1515598200", "1515602100", "2018-01-10 07:30:00+00:00", 65] def make_profit_array(data: List, px: int, min_date: int, - interval: int, + interval: str, filter_pairs: Optional[List] = None) -> np.ndarray: pg = np.zeros(px) filter_pairs = filter_pairs or [] @@ -120,7 +120,7 @@ def plot_profit(args: Namespace) -> None: logger.info('Filter, keep pairs %s' % pairs) tickers = history.load_data( - datadir=Path(config.get('datadir')), + datadir=Path(str(config.get('datadir'))), pairs=pairs, ticker_interval=tick_interval, refresh_pairs=False, @@ -178,7 +178,7 @@ def plot_profit(args: Namespace) -> None: fig.append_trace(profit, 2, 1) for pair in pairs: - pg = make_profit_array(data, num_iterations, min_date, tick_interval, pair) + pg = make_profit_array(data, num_iterations, min_date, tick_interval, [pair]) pair_profit = go.Scattergl( x=dates, y=pg, From a95f30ce452b06d16a854ccf04e182d1f350c2b4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 19:40:52 +0100 Subject: [PATCH 262/457] Fix custom boxes on documentation --- docs/bot-optimization.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 8592f6cca..00ed470f5 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -250,17 +250,17 @@ class Awesomestrategy(IStrategy): self.cust_info[metadata["pair"]["crosstime"] = 1 ``` -!!! Warning: +!!! Warning The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash. -!!! Note: +!!! Note If the data is pair-specific, make sure to use pair as one of the keys in the dictionary. ### Additional data (DataProvider) The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy. -!!!Note: +!!! Note The DataProvier is currently not available during backtesting / hyperopt, but this is planned for the future. All methods return `None` in case of failure (do not raise an exception). @@ -288,7 +288,7 @@ if self.dp: ticker_interval='1h') ``` -!!! Warning: Warning about backtesting +!!! Warning Warning about backtesting Be carefull when using dataprovider in backtesting. `historic_ohlcv()` provides the full time-range in one go, so please be aware of it and make sure to not "look into the future" to avoid surprises when running in dry/live mode). @@ -317,7 +317,7 @@ def informative_pairs(self): ] ``` -!!! Warning: +!!! Warning As these pairs will be refreshed as part of the regular whitelist refresh, it's best to keep this list short. All intervals and all pairs can be specified as long as they are available (and active) on the used exchange. It is however better to use resampling to longer time-intervals when possible @@ -327,7 +327,7 @@ def informative_pairs(self): The strategy provides access to the `Wallets` object. This contains the current balances on the exchange. -!!!NOTE: +!!! Note Wallets is not available during backtesting / hyperopt. Please always check if `Wallets` is available to avoid failures during backtesting. From 0dc96210b629af0157136f2e31d1763c45cfde61 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 19:43:23 +0100 Subject: [PATCH 263/457] Fix formatting of boxes 2 --- docs/bot-optimization.md | 2 +- docs/hyperopt.md | 4 ++-- docs/installation.md | 6 +++--- docs/sql_cheatsheet.md | 4 ++-- docs/telegram-usage.md | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 00ed470f5..d1ab08635 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -47,7 +47,7 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy **For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py) file as reference.** -!!! Note: Strategies and Backtesting +!!! Note Strategies and Backtesting To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware that during backtesting the full time-interval is passed to the `populate_*()` methods at once. It is therefore best to use vectorized operations (across the whole dataframe, not loops) and diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 6e52be47f..14021a341 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -293,8 +293,8 @@ You can overwrite position stacking in the configuration by explicitly setting ` Enabling the market-position for hyperopt is currently not possible. -!!! Note: -Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality. +!!! Note + Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality. ## Next Step diff --git a/docs/installation.md b/docs/installation.md index bd6c50c5a..02fdf5b8f 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -437,9 +437,9 @@ when it changes. The `freqtrade.service.watchdog` file contains an example of the service unit configuration file which uses systemd as the watchdog. -!!! Note: -The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a -Docker container. +!!! Note + The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a + Docker container. ------ diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index e85aceec8..54f9b8213 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -65,11 +65,11 @@ SELECT * FROM trades; ## Fix trade still open after a manual sell on the exchange -!!! Warning: +!!! Warning Manually selling on the exchange should not be done by default, since the bot does not detect this and will try to sell anyway. /foresell should accomplish the same thing. -!!! Note: +!!! Note This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. ```sql diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index face22404..92d60c7ed 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -55,8 +55,8 @@ Once all positions are sold, run `/stop` to completely stop the bot. `/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command. -!!! warning: -The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. +!!! warning + The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. ### /status From 06f4e627fcf8ba33ed58dd4ed390b98a3c5428a7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Mar 2019 20:40:07 +0100 Subject: [PATCH 264/457] Add stake_currency to strategy, fix documentation typo --- docs/bot-optimization.md | 10 +++++----- freqtrade/resolvers/strategy_resolver.py | 2 ++ freqtrade/tests/strategy/test_strategy.py | 4 +++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 8592f6cca..89d35848e 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -278,13 +278,13 @@ Please always check if the `DataProvider` is available to avoid failures during ``` python if self.dp: - if dp.runmode == 'live': - if ('ETH/BTC', ticker_interval) in self.dp.available_pairs: - data_eth = self.dp.ohlcv(pair='ETH/BTC', - ticker_interval=ticker_interval) + if self.dp.runmode == 'live': + if (f'{self.stake_currency}/BTC', self.ticker_interval) in self.dp.available_pairs: + data_eth = self.dp.ohlcv(pair='{self.stake_currency}/BTC', + ticker_interval=self.ticker_interval) else: # Get historic ohlcv data (cached on disk). - history_eth = self.dp.historic_ohlcv(pair='ETH/BTC', + history_eth = self.dp.historic_ohlcv(pair='{self.stake_currency}/BTC', ticker_interval='1h') ``` diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index cece5a5d1..44cc3fe76 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -56,6 +56,8 @@ class StrategyResolver(IResolver): ("process_only_new_candles", None, False), ("order_types", None, False), ("order_time_in_force", None, False), + ("stake_currency", None, False), + ("stake_amount", None, False), ("use_sell_signal", False, True), ("sell_profit_only", False, True), ("ignore_roi_if_buy_signal", False, True), diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 602ea5dbe..b63180d1e 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -194,11 +194,13 @@ def test_strategy_override_ticker_interval(caplog): config = { 'strategy': 'DefaultStrategy', - 'ticker_interval': 60 + 'ticker_interval': 60, + 'stake_currency': 'ETH' } resolver = StrategyResolver(config) assert resolver.strategy.ticker_interval == 60 + assert resolver.strategy.stake_currency == 'ETH' assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'ticker_interval' with value in config file: 60." From e644493e02b4877e160eb95a638dfc9c58a82715 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 24 Mar 2019 13:35:03 +0100 Subject: [PATCH 265/457] Update ccxt from 1.18.387 to 1.18.395 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9c083a675..73c65aff6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.387 +ccxt==1.18.395 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From e60d1788b28a92f75959a4d096cceb58440d3e57 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 15:06:17 +0100 Subject: [PATCH 266/457] Add new options to docu --- docs/configuration.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4b8d990fe..11b941220 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,8 +14,8 @@ Mandatory Parameters are marked as **Required**. | Command | Default | Description | |----------|---------|-------------| | `max_open_trades` | 3 | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) -| `stake_currency` | BTC | **Required.** Crypto-currency used for trading. -| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. +| `stake_currency` | BTC | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). +| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. [Strategy Override](#parameters-in-the-strategy). | `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals. | `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below. @@ -77,8 +77,10 @@ Mandatory Parameters are marked as **Required**. The following parameters can be set in either configuration file or strategy. Values set in the configuration file always overwrite values set in the strategy. -* `minimal_roi` +* `stake_currency` +* `stake_amount` * `ticker_interval` +* `minimal_roi` * `stoploss` * `trailing_stop` * `trailing_stop_positive` From f7fc9adc638367cde602e3d2222530dd46030101 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 15:13:03 +0100 Subject: [PATCH 267/457] Run travis with freqtrade, not main.py --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d24ffcf1b..a711290ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,15 +29,15 @@ jobs: script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ # Allow failure for coveralls - - coveralls || true + - coveralls || true name: pytest - script: - cp config.json.example config.json - - python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting + - python freqtrade --datadir freqtrade/tests/testdata backtesting name: backtest - script: - cp config.json.example config.json - - python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5 + - python freqtrade --datadir freqtrade/tests/testdata hyperopt -e 5 name: hyperopt - script: flake8 freqtrade name: flake8 From 1bba9fcc53bed652cca3e55d6596f51b5a0791f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 15:13:17 +0100 Subject: [PATCH 268/457] Update documentation to use freqtrade, not freqtrade/main.py fixes #1521 --- docs/backtesting.md | 18 +++++++++--------- docs/bot-optimization.md | 6 +++--- docs/bot-usage.md | 12 ++++++------ docs/deprecated.md | 4 ++-- docs/edge.md | 22 +++++++++++++++++----- docs/faq.md | 10 ++++++---- docs/hyperopt.md | 4 ++-- docs/installation.md | 2 +- setup.sh | 2 +- 9 files changed, 47 insertions(+), 33 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 932783160..a25d3c1d5 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -24,37 +24,37 @@ The backtesting is very easy with freqtrade. #### With 5 min tickers (Per default) ```bash -python3 ./freqtrade/main.py backtesting +python3 freqtrade backtesting ``` #### With 1 min tickers ```bash -python3 ./freqtrade/main.py backtesting --ticker-interval 1m +python3 freqtrade backtesting --ticker-interval 1m ``` #### Update cached pairs with the latest data ```bash -python3 ./freqtrade/main.py backtesting --refresh-pairs-cached +python3 freqtrade backtesting --refresh-pairs-cached ``` #### With live data (do not alter your testdata files) ```bash -python3 ./freqtrade/main.py backtesting --live +python3 freqtrade backtesting --live ``` #### Using a different on-disk ticker-data source ```bash -python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101 +python3 freqtrade backtesting --datadir freqtrade/tests/testdata-20180101 ``` #### With a (custom) strategy file ```bash -python3 ./freqtrade/main.py -s TestStrategy backtesting +python3 freqtrade -s TestStrategy backtesting ``` Where `-s TestStrategy` refers to the class name within the strategy file `test_strategy.py` found in the `freqtrade/user_data/strategies` directory @@ -62,7 +62,7 @@ Where `-s TestStrategy` refers to the class name within the strategy file `test_ #### Exporting trades to file ```bash -python3 ./freqtrade/main.py backtesting --export trades +python3 freqtrade backtesting --export trades ``` The exported trades can be used for [further analysis](#further-backtest-result-analysis), or can be used by the plotting script `plot_dataframe.py` in the scripts folder. @@ -70,7 +70,7 @@ The exported trades can be used for [further analysis](#further-backtest-result- #### Exporting trades to file specifying a custom filename ```bash -python3 ./freqtrade/main.py backtesting --export trades --export-filename=backtest_teststrategy.json +python3 freqtrade backtesting --export trades --export-filename=backtest_teststrategy.json ``` #### Running backtest with smaller testset @@ -81,7 +81,7 @@ you want to use. The last N ticks/timeframes will be used. Example: ```bash -python3 ./freqtrade/main.py backtesting --timerange=-200 +python3 freqtrade backtesting --timerange=-200 ``` #### Advanced use of timerange diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index d1ab08635..511be18fa 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -14,7 +14,7 @@ Let assume you have a class called `AwesomeStrategy` in the file `awesome-strate 2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name) ```bash -python3 ./freqtrade/main.py --strategy AwesomeStrategy +python3 freqtrade --strategy AwesomeStrategy ``` ## Change your strategy @@ -41,7 +41,7 @@ The bot also include a sample strategy called `TestStrategy` you can update: `us You can test it with the parameter: `--strategy TestStrategy` ```bash -python3 ./freqtrade/main.py --strategy AwesomeStrategy +python3 freqtrade --strategy AwesomeStrategy ``` **For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py) @@ -355,7 +355,7 @@ The default buy strategy is located in the file If you want to use a strategy from a different folder you can pass `--strategy-path` ```bash -python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder +python3 freqtrade --strategy AwesomeStrategy --strategy-path /some/folder ``` ### Further strategy ideas diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 739a89c07..35e4a776d 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -47,7 +47,7 @@ The bot allows you to select which configuration file you want to use. Per default, the bot will load the file `./config.json` ```bash -python3 ./freqtrade/main.py -c path/far/far/away/config.json +python3 freqtrade -c path/far/far/away/config.json ``` ### How to use multiple configuration files? @@ -63,13 +63,13 @@ empty key and secrete values while running in the Dry Mode (which does not actua require them): ```bash -python3 ./freqtrade/main.py -c ./config.json +python3 freqtrade -c ./config.json ``` and specify both configuration files when running in the normal Live Trade Mode: ```bash -python3 ./freqtrade/main.py -c ./config.json -c path/to/secrets/keys.config.json +python3 freqtrade -c ./config.json -c path/to/secrets/keys.config.json ``` This could help you hide your private Exchange key and Exchange secrete on you local machine @@ -95,7 +95,7 @@ In `user_data/strategies` you have a file `my_awesome_strategy.py` which has a strategy class called `AwesomeStrategy` to load it: ```bash -python3 ./freqtrade/main.py --strategy AwesomeStrategy +python3 freqtrade --strategy AwesomeStrategy ``` If the bot does not find your strategy file, it will display in an error @@ -109,7 +109,7 @@ Learn more about strategy file in This parameter allows you to add an additional strategy lookup path, which gets checked before the default locations (The passed path must be a folder!): ```bash -python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder +python3 freqtrade --strategy AwesomeStrategy --strategy-path /some/folder ``` #### How to install a strategy? @@ -136,7 +136,7 @@ using `--db-url`. This can also be used to specify a custom database in production mode. Example command: ```bash -python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite +python3 freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite ``` ## Backtesting commands diff --git a/docs/deprecated.md b/docs/deprecated.md index 25043d981..c218bd360 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -13,14 +13,14 @@ on BaseVolume. This value can be changed when you run the script. Get the 20 currencies based on BaseVolume. ```bash -python3 ./freqtrade/main.py --dynamic-whitelist +python3 freqtrade --dynamic-whitelist ``` **Customize the number of currencies to retrieve** Get the 30 currencies based on BaseVolume. ```bash -python3 ./freqtrade/main.py --dynamic-whitelist 30 +python3 freqtrade --dynamic-whitelist 30 ``` **Exception** diff --git a/docs/edge.md b/docs/edge.md index 7372e3373..b0e0b2d42 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -146,16 +146,19 @@ Percentage of allowed risk per trade. (defaults to 0.01 so 1%) #### stoploss_range_min + Minimum stoploss. (defaults to -0.01) #### stoploss_range_max + Maximum stoploss. (defaults to -0.10) #### stoploss_range_step + As an example if this is set to -0.01 then Edge will test the strategy for \[-0.01, -0,02, -0,03 ..., -0.09, -0.10\] ranges. Note than having a smaller step means having a bigger range which could lead to slow calculation. @@ -164,6 +167,7 @@ If you set this parameter to -0.001, you then slow down the Edge calculation by (defaults to -0.01) #### minimum_winrate + It filters out pairs which don't have at least minimum_winrate. This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. @@ -171,6 +175,7 @@ This comes handy if you want to be conservative and don't comprise win rate in f (defaults to 0.60) #### minimum_expectancy + It filters out pairs which have the expectancy lower than this number. Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return. @@ -178,6 +183,7 @@ Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ re (defaults to 0.20) #### min_trade_number + When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. @@ -185,6 +191,7 @@ Having a win rate of 100% on a single trade doesn't mean anything at all. But ha (defaults to 10, it is highly recommended not to decrease this number) #### max_trade_duration_minute + Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign. **NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.). @@ -192,15 +199,17 @@ Edge will filter out trades with long duration. If a trade is profitable after 1 (defaults to 1 day, i.e. to 60 * 24 = 1440 minutes) #### remove_pumps + Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off. (defaults to false) - ## Running Edge independently + You can run Edge independently in order to see in details the result. Here is an example: + ```bash -python3 ./freqtrade/main.py edge +python3 freqtrade edge ``` An example of its output: @@ -224,18 +233,21 @@ An example of its output: | NEBL/BTC | -0.03 | 0.63 | 1.29 | 0.58 | 0.44 | 19 | 59 | ### Update cached pairs with the latest data + ```bash -python3 ./freqtrade/main.py edge --refresh-pairs-cached +python3 freqtrade edge --refresh-pairs-cached ``` ### Precising stoploss range + ```bash -python3 ./freqtrade/main.py edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step +python3 freqtrade edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step ``` ### Advanced use of timerange + ```bash -python3 ./freqtrade/main.py edge --timerange=20181110-20181113 +python3 freqtrade edge --timerange=20181110-20181113 ``` Doing `--timerange=-200` will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop. diff --git a/docs/faq.md b/docs/faq.md index 127f69e9f..60c1509e0 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -46,22 +46,24 @@ have to run it for 10.000 or more. But it will take an eternity to compute. We recommend you to run it at least 10.000 epochs: + ```bash -python3 ./freqtrade/main.py hyperopt -e 10000 +python3 freqtrade hyperopt -e 10000 ``` or if you want intermediate result to see + ```bash -for i in {1..100}; do python3 ./freqtrade/main.py hyperopt -e 100; done +for i in {1..100}; do python3 freqtrade hyperopt -e 100; done ``` #### Why it is so long to run hyperopt? + Finding a great Hyperopt results takes time. If you wonder why it takes a while to find great hyperopt results -This answer was written during the under the release 0.15.1, when we had -: +This answer was written during the under the release 0.15.1, when we had: - 8 triggers - 9 guards: let's say we evaluate even 10 values from each - 1 stoploss calculation: let's say we want 10 values from that too to diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 14021a341..e25f35c35 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -152,7 +152,7 @@ Because hyperopt tries a lot of combinations to find the best parameters it will We strongly recommend to use `screen` or `tmux` to prevent any connection loss. ```bash -python3 ./freqtrade/main.py -c config.json hyperopt --customhyperopt -e 5000 --spaces all +python3 freqtrade -c config.json hyperopt --customhyperopt -e 5000 --spaces all ``` Use `` as the name of the custom hyperopt used. @@ -178,7 +178,7 @@ you want to use. The last N ticks/timeframes will be used. Example: ```bash -python3 ./freqtrade/main.py hyperopt --timerange -200 +python3 freqtrade hyperopt --timerange -200 ``` ### Running Hyperopt with Smaller Search Space diff --git a/docs/installation.md b/docs/installation.md index 02fdf5b8f..995bc561b 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -407,7 +407,7 @@ pip3 install -e . If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins. ```bash -python3.6 ./freqtrade/main.py -c config.json +python3.6 freqtrade -c config.json ``` *Note*: If you run the bot on a server, you should consider using [Docker](#automatic-installation---docker) a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout. diff --git a/setup.sh b/setup.sh index 66d449037..11df6820e 100755 --- a/setup.sh +++ b/setup.sh @@ -235,7 +235,7 @@ function install() { echo "-------------------------" echo "Run the bot !" echo "-------------------------" - echo "You can now use the bot by executing 'source .env/bin/activate; python freqtrade/main.py'." + echo "You can now use the bot by executing 'source .env/bin/activate; python freqtrade'." } function plot() { From 3a8b69d69becf6aa7711a29d1dc7ce6c2c3791b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 15:37:58 +0100 Subject: [PATCH 269/457] also support dry_run --- docs/bot-optimization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 89d35848e..276c992f1 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -278,7 +278,7 @@ Please always check if the `DataProvider` is available to avoid failures during ``` python if self.dp: - if self.dp.runmode == 'live': + if self.dp.runmode in ('live', 'dry_run'): if (f'{self.stake_currency}/BTC', self.ticker_interval) in self.dp.available_pairs: data_eth = self.dp.ohlcv(pair='{self.stake_currency}/BTC', ticker_interval=self.ticker_interval) From 684727b32ea11c8f1b4a4e8a120db215bb32fa7e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:08:48 +0100 Subject: [PATCH 270/457] Add black blacklist handler (ro) --- freqtrade/rpc/rpc.py | 10 +++++++++- freqtrade/rpc/telegram.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index a0ffff107..687ee9375 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -456,7 +456,15 @@ class RPC(object): def _rpc_whitelist(self) -> Dict: """ Returns the currently active whitelist""" res = {'method': self._freqtrade.pairlists.name, - 'length': len(self._freqtrade.pairlists.whitelist), + 'length': len(self._freqtrade.active_pair_whitelist), 'whitelist': self._freqtrade.active_pair_whitelist } return res + + def _rpc_blacklist(self) -> Dict: + """ Returns the currently active blacklist""" + res = {'method': self._freqtrade.pairlists.name, + 'length': len(self._freqtrade.pairlists.blacklist), + 'blacklist': self._freqtrade.pairlists.blacklist + } + return res diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 6771ec803..903efe7d1 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -93,6 +93,7 @@ class Telegram(RPC): CommandHandler('reload_conf', self._reload_conf), CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), + CommandHandler('blacklist', self._blacklist), CommandHandler('help', self._help), CommandHandler('version', self._version), ] @@ -470,6 +471,23 @@ class Telegram(RPC): except RPCException as e: self._send_msg(str(e), bot=bot) + @authorized_only + def _blacklist(self, bot: Bot, update: Update) -> None: + """ + Handler for /blacklist + Shows the currently active blacklist + """ + try: + blacklist = self._rpc_blacklist() + + message = f"Using blacklist `{blacklist['method']}` with {blacklist['length']} pairs\n" + message += f"`{', '.join(blacklist['blacklist'])}`" + + logger.debug(message) + self._send_msg(message) + except RPCException as e: + self._send_msg(str(e), bot=bot) + @authorized_only def _help(self, bot: Bot, update: Update) -> None: """ @@ -497,6 +515,7 @@ class Telegram(RPC): "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \ "*/reload_conf:* `Reload configuration file` \n" \ "*/whitelist:* `Show current whitelist` \n" \ + "*/blacklist:* `Show current blacklist` \n" \ "*/help:* `This help message`\n" \ "*/version:* `Show version`" From ffdca7eea7d62969e9c580fe9a4599453241e541 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:09:04 +0100 Subject: [PATCH 271/457] Add blacklist to default_config --- freqtrade/tests/conftest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 26262cb4b..c0f8e49b7 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -171,6 +171,10 @@ def default_conf(): "LTC/BTC", "XRP/BTC", "NEO/BTC" + ], + "pair_blacklist": [ + "DOGE/BTC", + "HOT/BTC", ] }, "telegram": { From 8b2174d249135b285932892cbd417af5c470747a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:09:20 +0100 Subject: [PATCH 272/457] Add tests for /blacklist handler --- freqtrade/tests/rpc/test_rpc.py | 12 ++++++++++++ freqtrade/tests/rpc/test_rpc_telegram.py | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index baddc0685..b1fbd27ff 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -693,3 +693,15 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: assert ret['method'] == 'VolumePairList' assert ret['length'] == 4 assert ret['whitelist'] == default_conf['exchange']['pair_whitelist'] + + +def test_rpc_blacklist(mocker, default_conf) -> None: + patch_coinmarketcap(mocker) + patch_exchange(mocker) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + + freqtradebot = FreqtradeBot(default_conf) + rpc = RPC(freqtradebot) + ret = rpc._rpc_blacklist() + assert ret['method'] == 'StaticPairList' + assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 8e8d1f1bb..fec2e508c 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -74,7 +74,7 @@ def test_init(default_conf, mocker, caplog) -> None: message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \ "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \ "['performance'], ['daily'], ['count'], ['reload_conf'], " \ - "['stopbuy'], ['whitelist'], ['help'], ['version']]" + "['stopbuy'], ['whitelist'], ['blacklist'], ['help'], ['version']]" assert log_has(message_str, caplog.record_tuples) @@ -1074,6 +1074,24 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None: in msg_mock.call_args_list[0][0][0]) +def test_blacklist_static(default_conf, update, mocker) -> None: + patch_coinmarketcap(mocker) + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + + telegram = Telegram(freqtradebot) + + telegram._blacklist(bot=MagicMock(), update=update) + assert msg_mock.call_count == 1 + assert ('Using blacklist `StaticPairList` with 2 pairs\n`DOGE/BTC, HOT/BTC`' + in msg_mock.call_args_list[0][0][0]) + + def test_help_handle(default_conf, update, mocker) -> None: patch_coinmarketcap(mocker) msg_mock = MagicMock() From 7b99d5ebcbb742d724232c606ca5a87691e9bcdb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:16:39 +0100 Subject: [PATCH 273/457] Add blacklist and whitelist commands to telegram docs --- docs/telegram-usage.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 92d60c7ed..671229242 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -28,6 +28,8 @@ official commands. You can ask at any moment for help with `/help`. | `/performance` | | Show performance of each finished trade grouped by pair | `/balance` | | Show account balance per currency | `/daily ` | 7 | Shows profit or loss per day, over the last n days +| `/whitelist` | | Show the current whitelist +| `/blacklist` | | Show the current blacklist | `/help` | | Show help message | `/version` | | Show version @@ -160,6 +162,21 @@ Day Profit BTC Profit USD 2018-01-01 0.00269130 BTC 34.986 USD ``` +### /whitelist + +Shows the current whitelist + +> Using whitelist `StaticPairList` with 22 pairs +> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC` + + +### /blacklist + +Shows the current blacklist + +> Using blacklist `StaticPairList` with 2 pairs +>`DODGE/BTC`, `HOT/BTC`. + ### /version > **Version:** `0.14.3` From 9d6f629f6a50f29d8f497b2baaf8623995f87dcd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:28:14 +0100 Subject: [PATCH 274/457] Support adding pairs to blacklist --- freqtrade/rpc/rpc.py | 5 ++++- freqtrade/rpc/telegram.py | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 687ee9375..cacca4e3c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -461,8 +461,11 @@ class RPC(object): } return res - def _rpc_blacklist(self) -> Dict: + def _rpc_blacklist(self, add: List[str]) -> Dict: """ Returns the currently active blacklist""" + if add: + self._freqtrade.pairlists.blacklist.extend(add) + res = {'method': self._freqtrade.pairlists.name, 'length': len(self._freqtrade.pairlists.blacklist), 'blacklist': self._freqtrade.pairlists.blacklist diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 903efe7d1..92108ded9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -4,7 +4,7 @@ This module manage Telegram communication """ import logging -from typing import Any, Callable, Dict +from typing import Any, Callable, Dict, List from tabulate import tabulate from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update @@ -93,7 +93,7 @@ class Telegram(RPC): CommandHandler('reload_conf', self._reload_conf), CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), - CommandHandler('blacklist', self._blacklist), + CommandHandler('blacklist', self._blacklist, pass_args=True), CommandHandler('help', self._help), CommandHandler('version', self._version), ] @@ -472,15 +472,16 @@ class Telegram(RPC): self._send_msg(str(e), bot=bot) @authorized_only - def _blacklist(self, bot: Bot, update: Update) -> None: + def _blacklist(self, bot: Bot, update: Update, args: List[str]) -> None: """ Handler for /blacklist Shows the currently active blacklist """ try: - blacklist = self._rpc_blacklist() - message = f"Using blacklist `{blacklist['method']}` with {blacklist['length']} pairs\n" + blacklist = self._rpc_blacklist(args) + + message = f"Blacklist contains {blacklist['length']} pairs\n" message += f"`{', '.join(blacklist['blacklist'])}`" logger.debug(message) From f0d3901b6b77976836a0fb300b7980b7f3b9fdfc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:29:58 +0100 Subject: [PATCH 275/457] Add blacklist-pair to documentation --- docs/telegram-usage.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 671229242..d0ee2849d 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -29,7 +29,7 @@ official commands. You can ask at any moment for help with `/help`. | `/balance` | | Show account balance per currency | `/daily ` | 7 | Shows profit or loss per day, over the last n days | `/whitelist` | | Show the current whitelist -| `/blacklist` | | Show the current blacklist +| `/blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist. | `/help` | | Show help message | `/version` | | Show version @@ -169,10 +169,11 @@ Shows the current whitelist > Using whitelist `StaticPairList` with 22 pairs > `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC` +### /blacklist [pair] -### /blacklist - -Shows the current blacklist +Shows the current blacklist. +If Pair is set, then this pair will be added to the pairlist. +Use `/reload_conf` to reset the blacklist. > Using blacklist `StaticPairList` with 2 pairs >`DODGE/BTC`, `HOT/BTC`. From 042354d00f2ede1a9052ceb935f6c3edf2dffb73 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:30:11 +0100 Subject: [PATCH 276/457] Test blacklist-adding --- freqtrade/tests/rpc/test_rpc_telegram.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index fec2e508c..6adcda77b 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -1086,11 +1086,19 @@ def test_blacklist_static(default_conf, update, mocker) -> None: telegram = Telegram(freqtradebot) - telegram._blacklist(bot=MagicMock(), update=update) + telegram._blacklist(bot=MagicMock(), update=update, args=[]) assert msg_mock.call_count == 1 - assert ('Using blacklist `StaticPairList` with 2 pairs\n`DOGE/BTC, HOT/BTC`' + assert ("Blacklist contains 2 pairs\n`DOGE/BTC, HOT/BTC`" in msg_mock.call_args_list[0][0][0]) + msg_mock.reset_mock() + telegram._blacklist(bot=MagicMock(), update=update, args=["ETH/BTC"]) + assert msg_mock.call_count == 1 + assert ("Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`" + in msg_mock.call_args_list[0][0][0]) + assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] + + def test_help_handle(default_conf, update, mocker) -> None: patch_coinmarketcap(mocker) From 49559f1a1a4b553476ae986bc6bd677dc7f49890 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 16:32:56 +0100 Subject: [PATCH 277/457] Improve documentation and help message --- docs/telegram-usage.md | 1 + freqtrade/rpc/telegram.py | 3 ++- freqtrade/tests/rpc/test_rpc_telegram.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index d0ee2849d..2f7e9ada8 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -173,6 +173,7 @@ Shows the current whitelist Shows the current blacklist. If Pair is set, then this pair will be added to the pairlist. +Also supports multiple pairs, seperated by a space. Use `/reload_conf` to reset the blacklist. > Using blacklist `StaticPairList` with 2 pairs diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 92108ded9..553ac4ed9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -516,7 +516,8 @@ class Telegram(RPC): "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \ "*/reload_conf:* `Reload configuration file` \n" \ "*/whitelist:* `Show current whitelist` \n" \ - "*/blacklist:* `Show current blacklist` \n" \ + "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " \ + "to the blacklist.` \n" \ "*/help:* `This help message`\n" \ "*/version:* `Show version`" diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 6adcda77b..dd49b0000 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -1099,7 +1099,6 @@ def test_blacklist_static(default_conf, update, mocker) -> None: assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] - def test_help_handle(default_conf, update, mocker) -> None: patch_coinmarketcap(mocker) msg_mock = MagicMock() From 14167f826ba8924c629438461294ab2fb4179690 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 19:44:52 +0100 Subject: [PATCH 278/457] Fix typehints --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 553ac4ed9..2c419e417 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) logger.debug('Included module rpc.telegram ...') -def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Callable[..., Any]: +def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: """ Decorator to check if the message comes from the correct chat_id :param command_handler: Telegram CommandHandler From 29b9bb96f3bc745a0ee88b83b39db7584e732175 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 19:49:49 +0100 Subject: [PATCH 279/457] Fix test to support adding things to pairlist --- freqtrade/tests/rpc/test_rpc.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index b1fbd27ff..e6f7ea41e 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -702,6 +702,14 @@ def test_rpc_blacklist(mocker, default_conf) -> None: freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) - ret = rpc._rpc_blacklist() + ret = rpc._rpc_blacklist(None) assert ret['method'] == 'StaticPairList' + assert len(ret['blacklist']) == 2 assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] + assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC'] + + ret = rpc._rpc_blacklist(["ETH/BTC"]) + assert ret['method'] == 'StaticPairList' + assert len(ret['blacklist']) == 3 + assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] + assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] From 1dfbf6eed646b6fe3575d8703f0f101053f3aad9 Mon Sep 17 00:00:00 2001 From: Misagh Date: Sun, 24 Mar 2019 22:36:33 +0100 Subject: [PATCH 280/457] darfting edge rpc messages --- freqtrade/rpc/rpc.py | 15 +++++++++++++++ freqtrade/rpc/telegram.py | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index a0ffff107..3c0fbeba9 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -460,3 +460,18 @@ class RPC(object): 'whitelist': self._freqtrade.active_pair_whitelist } return res + + def _rpc_edge(self) -> Dict: + """ Returns information related to Edge """ + if not self._freqtrade.edge: + raise RPCException(f'Edge is not enabled.') + + for pair in self._freqtrade.edge._cached_pairs: + res = { + 'pair': pair, + 'winrate': self._freqtrade.edge._cached_pairs[pair].winrate, + 'expectancy': self._freqtrade.edge._cached_pairs[pair].expectancy, + 'stoploss': self._freqtrade.edge._cached_pairs[pair].stoploss, + } + + return res diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 6771ec803..edc380f92 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -470,6 +470,29 @@ class Telegram(RPC): except RPCException as e: self._send_msg(str(e), bot=bot) + @authorized_only + def _edge(self, bot: Bot, update: Update) -> None: + """ + Handler for /edge + Shows informaton related to Edge + """ + try: + edge_pairs = self._rpc_edge() + edge_pairs_tab = tabulate(edge_pairs, + headers=[ + 'Pair', + f'Winrate', + f'Expectancy', + f'Stoploss' + ], + tablefmt='simple') + + message = f'Edge only validated following pairs:\n
{edge_pairs_tab}
' + self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML) + except RPCException as e: + self._send_msg(str(e), bot=bot) + + @authorized_only def _help(self, bot: Bot, update: Update) -> None: """ From a8be277ca022ddf9f41e32a904e3490101e9b6c2 Mon Sep 17 00:00:00 2001 From: Misagh Date: Sun, 24 Mar 2019 22:56:42 +0100 Subject: [PATCH 281/457] cached pairs iteration fixed + help added --- freqtrade/rpc/rpc.py | 16 ++++++++-------- freqtrade/rpc/telegram.py | 13 ++++--------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3c0fbeba9..fb06c7d86 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -466,12 +466,12 @@ class RPC(object): if not self._freqtrade.edge: raise RPCException(f'Edge is not enabled.') - for pair in self._freqtrade.edge._cached_pairs: - res = { - 'pair': pair, - 'winrate': self._freqtrade.edge._cached_pairs[pair].winrate, - 'expectancy': self._freqtrade.edge._cached_pairs[pair].expectancy, - 'stoploss': self._freqtrade.edge._cached_pairs[pair].stoploss, + return [ + { + 'Pair': pair, + 'Winrate': self._freqtrade.edge._cached_pairs[pair].winrate, + 'Expectancy': self._freqtrade.edge._cached_pairs[pair].expectancy, + 'Stoploss': self._freqtrade.edge._cached_pairs[pair].stoploss, } - - return res + for pair in self._freqtrade.edge._cached_pairs + ] diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index edc380f92..64c35078d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -93,6 +93,7 @@ class Telegram(RPC): CommandHandler('reload_conf', self._reload_conf), CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), + CommandHandler('edge', self._edge), CommandHandler('help', self._help), CommandHandler('version', self._version), ] @@ -478,15 +479,8 @@ class Telegram(RPC): """ try: edge_pairs = self._rpc_edge() - edge_pairs_tab = tabulate(edge_pairs, - headers=[ - 'Pair', - f'Winrate', - f'Expectancy', - f'Stoploss' - ], - tablefmt='simple') - + print(edge_pairs) + edge_pairs_tab = tabulate(edge_pairs, headers='keys', tablefmt='simple') message = f'Edge only validated following pairs:\n
{edge_pairs_tab}
' self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML) except RPCException as e: @@ -520,6 +514,7 @@ class Telegram(RPC): "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \ "*/reload_conf:* `Reload configuration file` \n" \ "*/whitelist:* `Show current whitelist` \n" \ + "*/edge:* `Shows validated pairs by Edge if it is enabeld` \n" \ "*/help:* `This help message`\n" \ "*/version:* `Show version`" From fd7278517dd7613c9ce7f0f4ba72b24ad4ed21c9 Mon Sep 17 00:00:00 2001 From: Misagh Date: Mon, 25 Mar 2019 09:48:41 +0100 Subject: [PATCH 282/457] using items() --- freqtrade/rpc/rpc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index fb06c7d86..6416a97eb 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -468,10 +468,10 @@ class RPC(object): return [ { - 'Pair': pair, - 'Winrate': self._freqtrade.edge._cached_pairs[pair].winrate, - 'Expectancy': self._freqtrade.edge._cached_pairs[pair].expectancy, - 'Stoploss': self._freqtrade.edge._cached_pairs[pair].stoploss, + 'Pair': k, + 'Winrate': v.winrate, + 'Expectancy': v.expectancy, + 'Stoploss': v.stoploss, } - for pair in self._freqtrade.edge._cached_pairs + for k, v in self._freqtrade.edge._cached_pairs.items() ] From 66f1e0f4cd82597305bfa5ff74d8224973fd1338 Mon Sep 17 00:00:00 2001 From: Misagh Date: Mon, 25 Mar 2019 10:25:07 +0100 Subject: [PATCH 283/457] help added --- docs/telegram-usage.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 2f7e9ada8..6788bec90 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -30,6 +30,7 @@ official commands. You can ask at any moment for help with `/help`. | `/daily ` | 7 | Shows profit or loss per day, over the last n days | `/whitelist` | | Show the current whitelist | `/blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist. +| `/edge` | | Show validated pairs by Edge if it is enabled. | `/help` | | Show help message | `/version` | | Show version @@ -55,7 +56,7 @@ Prevents the bot from opening new trades by temporarily setting "max_open_trades After this, give the bot time to close off open trades (can be checked via `/status table`). Once all positions are sold, run `/stop` to completely stop the bot. -`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command. +`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command. !!! warning The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. @@ -166,7 +167,7 @@ Day Profit BTC Profit USD Shows the current whitelist -> Using whitelist `StaticPairList` with 22 pairs +> Using whitelist `StaticPairList` with 22 pairs > `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC` ### /blacklist [pair] @@ -176,7 +177,7 @@ If Pair is set, then this pair will be added to the pairlist. Also supports multiple pairs, seperated by a space. Use `/reload_conf` to reset the blacklist. -> Using blacklist `StaticPairList` with 2 pairs +> Using blacklist `StaticPairList` with 2 pairs >`DODGE/BTC`, `HOT/BTC`. ### /version From 904b3008a97eda34648809ef1906eca7d7887adf Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 25 Mar 2019 13:36:04 +0100 Subject: [PATCH 284/457] Update ccxt from 1.18.395 to 1.18.398 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 73c65aff6..feed42b52 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.395 +ccxt==1.18.398 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From fe9322ecd53eea54ec7a97fbe31f82df4fc96a14 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 25 Mar 2019 13:36:06 +0100 Subject: [PATCH 285/457] Update pytest-mock from 1.10.1 to 1.10.2 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e0aaf9461..56d7964c3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ flake8==3.7.7 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 pytest==4.3.1 -pytest-mock==1.10.1 +pytest-mock==1.10.2 pytest-asyncio==0.10.0 pytest-cov==2.6.1 coveralls==1.7.0 From c8b0c9af0a5e28b18d7a8f604a5f3d3db836e992 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 25 Mar 2019 17:45:03 +0300 Subject: [PATCH 286/457] Worker moved to new worker.py --- freqtrade/freqtradebot.py | 2 +- freqtrade/main.py | 190 ++----------------------------------- freqtrade/worker.py | 192 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 185 deletions(-) create mode 100755 freqtrade/worker.py diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 784c0b938..94c1bca8a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -22,7 +22,7 @@ from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListReso from freqtrade.state import State from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.wallets import Wallets -from freqtrade.main import Worker +from freqtrade.worker import Worker logger = logging.getLogger(__name__) diff --git a/freqtrade/main.py b/freqtrade/main.py index 9331206fc..877e2921d 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -5,18 +5,14 @@ Read the documentation to know what cli arguments you need. """ import logging import sys -import time -import traceback from argparse import Namespace -from typing import Any, Callable, List -import sdnotify +from typing import List -from freqtrade import (constants, OperationalException, TemporaryError, - __version__) +from freqtrade import OperationalException from freqtrade.arguments import Arguments -from freqtrade.configuration import Configuration, set_loggers -from freqtrade.state import State -from freqtrade.rpc import RPCMessageType +from freqtrade.configuration import set_loggers +from freqtrade.worker import Worker + logger = logging.getLogger('freqtrade') @@ -30,7 +26,7 @@ def main(sysargv: List[str]) -> None: sysargv, 'Free, open source crypto trading bot' ) - args = arguments.get_parsed_arg() + args: Namespace = arguments.get_parsed_arg() # A subcommand has been issued. # Means if Backtesting or Hyperopt have been called we exit the bot @@ -59,180 +55,6 @@ def main(sysargv: List[str]) -> None: sys.exit(return_code) -class Worker(object): - """ - Freqtradebot worker class - """ - - def __init__(self, args: Namespace) -> None: - """ - Init all variables and objects the bot needs to work - """ - logger.info('Starting worker %s', __version__) - - self._args = args - self._init() - - # Tell systemd that we completed initialization phase - if self._sd_notify: - logger.debug("sd_notify: READY=1") - self._sd_notify.notify("READY=1") - - def _init(self): - """ - Also called from the _reconfigure() method. - """ - # Load configuration - self._config = Configuration(self._args, None).get_config() - - # Import freqtradebot here in order to avoid python circular - # dependency error, damn! - from freqtrade.freqtradebot import FreqtradeBot - - # Init the instance of the bot - self.freqtrade = FreqtradeBot(self._config, self) - - # Set initial bot state - initial_state = self._config.get('initial_state') - if initial_state: - self._state = State[initial_state.upper()] - else: - self._state = State.STOPPED - - self._throttle_secs = self._config.get('internals', {}).get( - 'process_throttle_secs', - constants.PROCESS_THROTTLE_SECS - ) - - self._sd_notify = sdnotify.SystemdNotifier() if \ - self._config.get('internals', {}).get('sd_notify', False) else None - - @property - def state(self) -> State: - return self._state - - @state.setter - def state(self, value: State): - self._state = value - - def run(self): - state = None - while True: - state = self._worker(old_state=state, throttle_secs=self._throttle_secs) - if state == State.RELOAD_CONF: - self.freqtrade = self._reconfigure() - - def _worker(self, old_state: State, throttle_secs: float) -> State: - """ - Trading routine that must be run at each loop - :param old_state: the previous service state from the previous call - :return: current service state - """ - state = self._state - - # Log state transition - if state != old_state: - self.freqtrade.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': f'{state.name.lower()}' - }) - logger.info('Changing state to: %s', state.name) - if state == State.RUNNING: - self.freqtrade.rpc.startup_messages(self._config, self.freqtrade.pairlists) - - if state == State.STOPPED: - # Ping systemd watchdog before sleeping in the stopped state - if self._sd_notify: - logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.") - self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.") - - time.sleep(throttle_secs) - - elif state == State.RUNNING: - # Ping systemd watchdog before throttling - if self._sd_notify: - logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.") - self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.") - - self._throttle(func=self._process, min_secs=throttle_secs) - - return state - - def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any: - """ - Throttles the given callable that it - takes at least `min_secs` to finish execution. - :param func: Any callable - :param min_secs: minimum execution time in seconds - :return: Any - """ - start = time.time() - result = func(*args, **kwargs) - end = time.time() - duration = max(min_secs - (end - start), 0.0) - logger.debug('Throttling %s for %.2f seconds', func.__name__, duration) - time.sleep(duration) - return result - - def _process(self) -> bool: - state_changed = False - try: - state_changed = self.freqtrade.process() - - except TemporaryError as error: - logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...") - time.sleep(constants.RETRY_TIMEOUT) - except OperationalException: - tb = traceback.format_exc() - hint = 'Issue `/start` if you think it is safe to restart.' - self.freqtrade.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': f'OperationalException:\n```\n{tb}```{hint}' - }) - logger.exception('OperationalException. Stopping trader ...') - self.state = State.STOPPED - return state_changed - - def _reconfigure(self): - """ - Cleans up current freqtradebot instance, reloads the configuration and - returns the new instance - """ - # Tell systemd that we initiated reconfiguration - if self._sd_notify: - logger.debug("sd_notify: RELOADING=1") - self._sd_notify.notify("RELOADING=1") - - # Clean up current freqtrade modules - self.freqtrade.cleanup() - - # Load and validate config and create new instance of the bot - self._init() - - self.freqtrade.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': 'config reloaded' - }) - - # Tell systemd that we completed reconfiguration - if self._sd_notify: - logger.debug("sd_notify: READY=1") - self._sd_notify.notify("READY=1") - - def exit(self): - # Tell systemd that we are exiting now - if self._sd_notify: - logger.debug("sd_notify: STOPPING=1") - self._sd_notify.notify("STOPPING=1") - - if self.freqtrade: - self.freqtrade.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': 'process died' - }) - self.freqtrade.cleanup() - - if __name__ == '__main__': set_loggers() main(sys.argv[1:]) diff --git a/freqtrade/worker.py b/freqtrade/worker.py new file mode 100755 index 000000000..9a7e67424 --- /dev/null +++ b/freqtrade/worker.py @@ -0,0 +1,192 @@ +""" +Main Freqtrade worker class. +""" +import logging +import time +import traceback +from argparse import Namespace +from typing import Any, Callable +import sdnotify + +from freqtrade import (constants, OperationalException, TemporaryError, + __version__) +from freqtrade.configuration import Configuration +from freqtrade.state import State +from freqtrade.rpc import RPCMessageType + + +logger = logging.getLogger(__name__) + + +class Worker(object): + """ + Freqtradebot worker class + """ + + def __init__(self, args: Namespace) -> None: + """ + Init all variables and objects the bot needs to work + """ + logger.info('Starting worker %s', __version__) + + self._args = args + self._init() + + # Tell systemd that we completed initialization phase + if self._sd_notify: + logger.debug("sd_notify: READY=1") + self._sd_notify.notify("READY=1") + + def _init(self): + """ + Also called from the _reconfigure() method. + """ + # Load configuration + self._config = Configuration(self._args, None).get_config() + + # Import freqtradebot here in order to avoid python circular + # dependency error, damn! + from freqtrade.freqtradebot import FreqtradeBot + + # Init the instance of the bot + self.freqtrade = FreqtradeBot(self._config, self) + + # Set initial bot state + initial_state = self._config.get('initial_state') + if initial_state: + self._state = State[initial_state.upper()] + else: + self._state = State.STOPPED + + self._throttle_secs = self._config.get('internals', {}).get( + 'process_throttle_secs', + constants.PROCESS_THROTTLE_SECS + ) + + self._sd_notify = sdnotify.SystemdNotifier() if \ + self._config.get('internals', {}).get('sd_notify', False) else None + + @property + def state(self) -> State: + return self._state + + @state.setter + def state(self, value: State): + self._state = value + + def run(self): + state = None + while True: + state = self._worker(old_state=state, throttle_secs=self._throttle_secs) + if state == State.RELOAD_CONF: + self.freqtrade = self._reconfigure() + + def _worker(self, old_state: State, throttle_secs: float) -> State: + """ + Trading routine that must be run at each loop + :param old_state: the previous service state from the previous call + :return: current service state + """ + state = self._state + + # Log state transition + if state != old_state: + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': f'{state.name.lower()}' + }) + logger.info('Changing state to: %s', state.name) + if state == State.RUNNING: + self.freqtrade.rpc.startup_messages(self._config, self.freqtrade.pairlists) + + if state == State.STOPPED: + # Ping systemd watchdog before sleeping in the stopped state + if self._sd_notify: + logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.") + self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.") + + time.sleep(throttle_secs) + + elif state == State.RUNNING: + # Ping systemd watchdog before throttling + if self._sd_notify: + logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.") + self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.") + + self._throttle(func=self._process, min_secs=throttle_secs) + + return state + + def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any: + """ + Throttles the given callable that it + takes at least `min_secs` to finish execution. + :param func: Any callable + :param min_secs: minimum execution time in seconds + :return: Any + """ + start = time.time() + result = func(*args, **kwargs) + end = time.time() + duration = max(min_secs - (end - start), 0.0) + logger.debug('Throttling %s for %.2f seconds', func.__name__, duration) + time.sleep(duration) + return result + + def _process(self) -> bool: + state_changed = False + try: + state_changed = self.freqtrade.process() + + except TemporaryError as error: + logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...") + time.sleep(constants.RETRY_TIMEOUT) + except OperationalException: + tb = traceback.format_exc() + hint = 'Issue `/start` if you think it is safe to restart.' + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': f'OperationalException:\n```\n{tb}```{hint}' + }) + logger.exception('OperationalException. Stopping trader ...') + self.state = State.STOPPED + return state_changed + + def _reconfigure(self): + """ + Cleans up current freqtradebot instance, reloads the configuration and + returns the new instance + """ + # Tell systemd that we initiated reconfiguration + if self._sd_notify: + logger.debug("sd_notify: RELOADING=1") + self._sd_notify.notify("RELOADING=1") + + # Clean up current freqtrade modules + self.freqtrade.cleanup() + + # Load and validate config and create new instance of the bot + self._init() + + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': 'config reloaded' + }) + + # Tell systemd that we completed reconfiguration + if self._sd_notify: + logger.debug("sd_notify: READY=1") + self._sd_notify.notify("READY=1") + + def exit(self): + # Tell systemd that we are exiting now + if self._sd_notify: + logger.debug("sd_notify: STOPPING=1") + self._sd_notify.notify("STOPPING=1") + + if self.freqtrade: + self.freqtrade.rpc.send_msg({ + 'type': RPCMessageType.STATUS_NOTIFICATION, + 'status': 'process died' + }) + self.freqtrade.cleanup() From bd29b7d03136b3905152b079f872d030284e7a3c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 15:21:05 +0100 Subject: [PATCH 287/457] Test that dataprovider is loaded to strategy --- freqtrade/tests/test_freqtradebot.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index e4f0415f7..fa50b36e6 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -13,12 +13,14 @@ import requests from freqtrade import (DependencyException, OperationalException, TemporaryError, constants) +from freqtrade.data.dataprovider import DataProvider from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType from freqtrade.state import State -from freqtrade.strategy.interface import SellType, SellCheckTuple -from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange, patch_edge, patch_wallet +from freqtrade.strategy.interface import SellCheckTuple, SellType +from freqtrade.tests.conftest import (log_has, log_has_re, patch_edge, + patch_exchange, patch_wallet) # Functions for recurrent object patching @@ -88,6 +90,10 @@ def test_worker_running(mocker, default_conf, caplog) -> None: assert state is State.RUNNING assert log_has('Changing state to: RUNNING', caplog.record_tuples) assert mock_throttle.call_count == 1 + # Check strategy is loaded, and received a dataprovider object + assert freqtrade.strategy + assert freqtrade.strategy.dp + assert isinstance(freqtrade.strategy.dp, DataProvider) def test_worker_stopped(mocker, default_conf, caplog) -> None: From 226fc3d99b97b9a62a2c2a8246c60d8753c367e6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 15:23:14 +0100 Subject: [PATCH 288/457] Check that dataprovider is part of strategy --- freqtrade/tests/optimize/test_backtesting.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 40754cfbc..d0b21b8f4 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -16,6 +16,7 @@ from freqtrade.arguments import Arguments, TimeRange from freqtrade.data import history from freqtrade.data.btanalysis import evaluate_result_multi from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.data.dataprovider import DataProvider from freqtrade.optimize import get_timeframe from freqtrade.optimize.backtesting import (Backtesting, setup_configuration, start) @@ -346,6 +347,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: assert callable(backtesting.strategy.tickerdata_to_dataframe) assert callable(backtesting.advise_buy) assert callable(backtesting.advise_sell) + assert isinstance(backtesting.strategy.dp, DataProvider) get_fee.assert_called() assert backtesting.fee == 0.5 assert not backtesting.strategy.order_types["stoploss_on_exchange"] From 0ae81d41156f84828d9bab0ec22f0686fb4d75a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Mar 2019 15:24:47 +0100 Subject: [PATCH 289/457] Provide dataprovider access during backtesting --- freqtrade/optimize/backtesting.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 031b490c8..7c98bd3a7 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -18,6 +18,7 @@ from freqtrade import DependencyException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.data import history +from freqtrade.data.dataprovider import DataProvider from freqtrade.misc import file_dump_json from freqtrade.persistence import Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver @@ -64,6 +65,13 @@ class Backtesting(object): self.config['exchange']['uid'] = '' self.config['dry_run'] = True self.strategylist: List[IStrategy] = [] + + exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() + self.exchange = ExchangeResolver(exchange_name, self.config).exchange + self.fee = self.exchange.get_fee() + self.dataprovider = DataProvider(self.config, self.exchange) + IStrategy.dp = self.dataprovider + if self.config.get('strategy_list', None): # Force one interval self.ticker_interval = str(self.config.get('ticker_interval')) @@ -78,15 +86,13 @@ class Backtesting(object): self.strategylist.append(StrategyResolver(self.config).strategy) # Load one strategy self._set_strategy(self.strategylist[0]) - exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() - self.exchange = ExchangeResolver(exchange_name, self.config).exchange - self.fee = self.exchange.get_fee() def _set_strategy(self, strategy): """ Load strategy into backtesting """ self.strategy = strategy + self.ticker_interval = self.config.get('ticker_interval') self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval] self.tickerdata_to_dataframe = strategy.tickerdata_to_dataframe From 4cf7282027e56277f79a317a8d8a7aa3092f17be Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Mar 2019 19:31:10 +0100 Subject: [PATCH 290/457] Update dataprovider docs --- docs/bot-optimization.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index c01a0a03d..ae6377bf5 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -260,12 +260,9 @@ class Awesomestrategy(IStrategy): The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy. -!!! Note - The DataProvier is currently not available during backtesting / hyperopt, but this is planned for the future. - All methods return `None` in case of failure (do not raise an exception). -Please always check if the `DataProvider` is available to avoid failures during backtesting. +Please always the mode of operation to select the correct method to get data (samples see below). #### Possible options for DataProvider @@ -292,6 +289,9 @@ if self.dp: Be carefull when using dataprovider in backtesting. `historic_ohlcv()` provides the full time-range in one go, so please be aware of it and make sure to not "look into the future" to avoid surprises when running in dry/live mode). +!!! Warning Warning in hyperopt + This option should only be used in `populate_indicators()` - since it pulls the historic data from disk each time, which would be a huge performance penalty during hyperopt. + #### Available Pairs ``` python From f26ed1c8c1503457293dbdae6f0fb53c8251a317 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Mar 2019 19:40:21 +0100 Subject: [PATCH 291/457] Check if added pair has correct stake-currency --- freqtrade/rpc/rpc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index cacca4e3c..a5601a502 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -464,10 +464,14 @@ class RPC(object): def _rpc_blacklist(self, add: List[str]) -> Dict: """ Returns the currently active blacklist""" if add: - self._freqtrade.pairlists.blacklist.extend(add) + stake_currency = self._freqtrade.config.get('stake_currency') + for pair in add: + if (pair.endswith(stake_currency) + and pair not in self._freqtrade.pairlists.blacklist): + self._freqtrade.pairlists.blacklist.append(pair) res = {'method': self._freqtrade.pairlists.name, 'length': len(self._freqtrade.pairlists.blacklist), - 'blacklist': self._freqtrade.pairlists.blacklist + 'blacklist': self._freqtrade.pairlists.blacklist, } return res From e085fd9e9538c18a6e4e6ea51c0dd151eb31c15e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Mar 2019 19:49:58 +0100 Subject: [PATCH 292/457] Disable dataprovider from hyperopt. Dataprovider uses weak links to initialize, which cannot be pickled, and therefore cannot be used during hyperopt. --- docs/bot-optimization.md | 2 +- freqtrade/optimize/backtesting.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index ae6377bf5..f018d92a7 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -290,7 +290,7 @@ if self.dp: so please be aware of it and make sure to not "look into the future" to avoid surprises when running in dry/live mode). !!! Warning Warning in hyperopt - This option should only be used in `populate_indicators()` - since it pulls the historic data from disk each time, which would be a huge performance penalty during hyperopt. + This option cannot currently be used during hyperopt. #### Available Pairs diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7c98bd3a7..293511fc0 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -69,8 +69,10 @@ class Backtesting(object): exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title() self.exchange = ExchangeResolver(exchange_name, self.config).exchange self.fee = self.exchange.get_fee() - self.dataprovider = DataProvider(self.config, self.exchange) - IStrategy.dp = self.dataprovider + + if self.config.get('runmode') != RunMode.HYPEROPT: + self.dataprovider = DataProvider(self.config, self.exchange) + IStrategy.dp = self.dataprovider if self.config.get('strategy_list', None): # Force one interval From 85ac99aee021522e60f2b1823b2c6a2998d2a144 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 21 Mar 2019 19:11:47 +0100 Subject: [PATCH 293/457] move exchange urls to constants --- freqtrade/constants.py | 6 ++++++ freqtrade/exchange/exchange.py | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 02062acc4..bd021c327 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -22,6 +22,12 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] DRY_RUN_WALLET = 999.9 +# Urls to exchange markets, insert quote and base with .format() +_EXCHANGE_URLS = { + "Bittrex": '/Market/Index?MarketName={quote}-{base}', + "Binance": '/tradeDetail.html?symbol={base}_{quote}', +} + TICKER_INTERVAL_MINUTES = { '1m': 1, '3m': 3, diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ea8bcfac1..7781c85e4 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -21,13 +21,6 @@ logger = logging.getLogger(__name__) API_RETRY_COUNT = 4 -# Urls to exchange markets, insert quote and base with .format() -_EXCHANGE_URLS = { - ccxt.bittrex.__name__: '/Market/Index?MarketName={quote}-{base}', - ccxt.binance.__name__: '/tradeDetail.html?symbol={base}_{quote}', -} - - def retrier_async(f): async def wrapper(*args, **kwargs): count = kwargs.pop('count', API_RETRY_COUNT) From 4005b8d1d254f94840cc00034521e2c7421c17a5 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 21 Mar 2019 19:12:15 +0100 Subject: [PATCH 294/457] remove the if condition for binance --- freqtrade/exchange/binance.py | 6 ++++++ freqtrade/exchange/exchange.py | 7 +++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 127f4e916..1d633be2b 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -24,3 +24,9 @@ class Binance(Exchange): limit = min(list(filter(lambda x: limit <= x, limit_range))) return super().get_order_book(pair, limit) + + def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: + """ + Checks if order time in force configured in strategy/config are supported + """ + pass diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7781c85e4..d422e2bbf 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -13,7 +13,7 @@ import ccxt import ccxt.async_support as ccxt_async from pandas import DataFrame -from freqtrade import constants, OperationalException, DependencyException, TemporaryError +from freqtrade import constants, DependencyException, OperationalException, TemporaryError from freqtrade.data.converter import parse_ticker_dataframe logger = logging.getLogger(__name__) @@ -269,9 +269,8 @@ class Exchange(object): Checks if order time in force configured in strategy/config are supported """ if any(v != 'gtc' for k, v in order_time_in_force.items()): - if self.name != 'Binance': - raise OperationalException( - f'Time in force policies are not supporetd for {self.name} yet.') + raise OperationalException( + f'Time in force policies are not supporetd for {self.name} yet.') def exchange_has(self, endpoint: str) -> bool: """ From 8dea640e9a671b315675f27557361e46e5eee084 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Mon, 25 Mar 2019 23:58:02 +0100 Subject: [PATCH 295/457] remove exchange urls --- freqtrade/constants.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index bd021c327..02062acc4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -22,12 +22,6 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] DRY_RUN_WALLET = 999.9 -# Urls to exchange markets, insert quote and base with .format() -_EXCHANGE_URLS = { - "Bittrex": '/Market/Index?MarketName={quote}-{base}', - "Binance": '/tradeDetail.html?symbol={base}_{quote}', -} - TICKER_INTERVAL_MINUTES = { '1m': 1, '3m': 3, From e15f2ef11ad07c4f71e5086d0edaad1c22c80e11 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Tue, 26 Mar 2019 00:49:39 +0100 Subject: [PATCH 296/457] add order_time_in_force in _ft_has and revert binance --- freqtrade/exchange/binance.py | 7 +------ freqtrade/exchange/exchange.py | 6 ++++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 1d633be2b..18e754e3f 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -11,6 +11,7 @@ class Binance(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, + "order_time_in_force": ['gtc', 'fok', 'ioc'], } def get_order_book(self, pair: str, limit: int = 100) -> dict: @@ -24,9 +25,3 @@ class Binance(Exchange): limit = min(list(filter(lambda x: limit <= x, limit_range))) return super().get_order_book(pair, limit) - - def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: - """ - Checks if order time in force configured in strategy/config are supported - """ - pass diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d422e2bbf..2ec5c0e25 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -65,8 +65,9 @@ class Exchange(object): # Dict to specify which options each exchange implements # TODO: this should be merged with attributes from subclasses # To avoid having to copy/paste this to all subclasses. - _ft_has = { + _ft_has: Dict = { "stoploss_on_exchange": False, + "order_time_in_force": ["gtc"], } def __init__(self, config: dict) -> None: @@ -268,7 +269,8 @@ class Exchange(object): """ Checks if order time in force configured in strategy/config are supported """ - if any(v != 'gtc' for k, v in order_time_in_force.items()): + if any(v not in self._ft_has["order_time_in_force"] + for k, v in order_time_in_force.items()): raise OperationalException( f'Time in force policies are not supporetd for {self.name} yet.') From 5161e1abb3fd2fa5d5ea69751447d14cd3c58007 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Mar 2019 11:07:02 +0300 Subject: [PATCH 297/457] Allow to pass config into worker, as it's used in the tests --- freqtrade/freqtradebot.py | 8 ++++++-- freqtrade/worker.py | 24 ++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 94c1bca8a..7c92ac29a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -34,7 +34,7 @@ class FreqtradeBot(object): This is from here the bot start its logic. """ - def __init__(self, config: Dict[str, Any], worker: Worker) -> None: + def __init__(self, config: Dict[str, Any], worker: Optional[Worker] = None) -> None: """ Init all variables and objects the bot needs to work :param config: configuration dict, you can use Configuration.get_config() @@ -45,7 +45,7 @@ class FreqtradeBot(object): # Init objects self.config = config - self._worker: Worker = worker + self._worker = worker self.strategy: IStrategy = StrategyResolver(self.config).strategy @@ -75,10 +75,14 @@ class FreqtradeBot(object): @property def state(self) -> State: + if self._worker is None: + raise DependencyException("No Worker is available") return self._worker.state @state.setter def state(self, value: State): + if self._worker is None: + raise DependencyException("No Worker is available") self._worker.state = value def cleanup(self) -> None: diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 9a7e67424..a6fba1460 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -5,7 +5,7 @@ import logging import time import traceback from argparse import Namespace -from typing import Any, Callable +from typing import Any, Callable, Optional import sdnotify from freqtrade import (constants, OperationalException, TemporaryError, @@ -23,26 +23,28 @@ class Worker(object): Freqtradebot worker class """ - def __init__(self, args: Namespace) -> None: + def __init__(self, args: Optional[Namespace] = None, config = None) -> None: """ Init all variables and objects the bot needs to work """ logger.info('Starting worker %s', __version__) self._args = args - self._init() + self._config = config + self._init(False) # Tell systemd that we completed initialization phase if self._sd_notify: logger.debug("sd_notify: READY=1") self._sd_notify.notify("READY=1") - def _init(self): + def _init(self, reconfig: bool): """ - Also called from the _reconfigure() method. + Also called from the _reconfigure() method (with reconfig=True). """ - # Load configuration - self._config = Configuration(self._args, None).get_config() + if reconfig or self._config is None: + # Load configuration + self._config = Configuration(self._args, None).get_config() # Import freqtradebot here in order to avoid python circular # dependency error, damn! @@ -77,17 +79,19 @@ class Worker(object): def run(self): state = None while True: - state = self._worker(old_state=state, throttle_secs=self._throttle_secs) + state = self._worker(old_state=state) if state == State.RELOAD_CONF: self.freqtrade = self._reconfigure() - def _worker(self, old_state: State, throttle_secs: float) -> State: + def _worker(self, old_state: State, throttle_secs: Optional[float] = None) -> State: """ Trading routine that must be run at each loop :param old_state: the previous service state from the previous call :return: current service state """ state = self._state + if throttle_secs is None: + throttle_secs = self._throttle_secs # Log state transition if state != old_state: @@ -166,7 +170,7 @@ class Worker(object): self.freqtrade.cleanup() # Load and validate config and create new instance of the bot - self._init() + self._init(True) self.freqtrade.rpc.send_msg({ 'type': RPCMessageType.STATUS_NOTIFICATION, From 5ccd618189f2bc3a303148a487f114d4b5df66be Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Mar 2019 11:07:24 +0300 Subject: [PATCH 298/457] tests adjusted --- freqtrade/tests/conftest.py | 11 +- freqtrade/tests/rpc/test_rpc.py | 74 ++++++++++--- freqtrade/tests/rpc/test_rpc_telegram.py | 134 +++++++++++++++-------- freqtrade/tests/test_freqtradebot.py | 93 ++++++++++------ freqtrade/tests/test_main.py | 45 +++----- 5 files changed, 228 insertions(+), 129 deletions(-) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 26262cb4b..772602f6d 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -15,6 +15,7 @@ from freqtrade import constants from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.exchange import Exchange from freqtrade.edge import Edge, PairInfo +from freqtrade.worker import Worker from freqtrade.freqtradebot import FreqtradeBot from freqtrade.resolvers import ExchangeResolver @@ -88,7 +89,7 @@ def get_patched_edge(mocker, config) -> Edge: # Functions for recurrent object patching -def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: +def patch_freqtradebot(mocker, config) -> None: """ This function patch _init_modules() to not call dependencies :param mocker: a Mocker object to apply patches @@ -102,9 +103,17 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock()) + +def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: + patch_freqtradebot(mocker, config) return FreqtradeBot(config) +def get_patched_worker(mocker, config) -> Worker: + patch_freqtradebot(mocker, config) + return Worker(args=None, config=config) + + def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None: """ Mocker to coinmarketcap to speed up tests diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index baddc0685..69b428693 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -8,6 +8,7 @@ import pytest from numpy import isnan from freqtrade import TemporaryError, DependencyException +from freqtrade.worker import Worker from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCException @@ -37,7 +38,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) @@ -93,7 +96,9 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) @@ -129,7 +134,9 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -183,7 +190,9 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -271,7 +280,9 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets, markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] @@ -340,7 +351,9 @@ def test_rpc_balance_handle(default_conf, mocker): get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() @@ -368,7 +381,9 @@ def test_rpc_start(mocker, default_conf) -> None: get_ticker=MagicMock() ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED @@ -391,7 +406,9 @@ def test_rpc_stop(mocker, default_conf) -> None: get_ticker=MagicMock() ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -415,7 +432,9 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: get_ticker=MagicMock() ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING @@ -447,7 +466,9 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) @@ -539,7 +560,9 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) @@ -575,7 +598,9 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: markets=PropertyMock(return_value=markets) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) @@ -605,7 +630,9 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order buy=buy_mm ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) pair = 'ETH/BTC' @@ -630,7 +657,10 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order # Test not buying default_conf['stake_amount'] = 0.0000001 - freqtradebot = FreqtradeBot(default_conf) + + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) pair = 'TKN/BTC' @@ -645,7 +675,9 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None: patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) pair = 'ETH/BTC' @@ -658,7 +690,9 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None: patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) pair = 'ETH/BTC' @@ -671,7 +705,9 @@ def test_rpc_whitelist(mocker, default_conf) -> None: patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + rpc = RPC(freqtradebot) ret = rpc._rpc_whitelist() assert ret['method'] == 'StaticPairList' @@ -687,7 +723,9 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + rpc = RPC(freqtradebot) ret = rpc._rpc_whitelist() assert ret['method'] == 'VolumePairList' diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 8e8d1f1bb..1452267ea 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -13,13 +13,14 @@ from telegram import Chat, Message, Update from telegram.error import NetworkError from freqtrade import __version__ +from freqtrade.worker import Worker from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.strategy.interface import SellType from freqtrade.state import State -from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, +from freqtrade.tests.conftest import (get_patched_freqtradebot, get_patched_worker, log_has, patch_exchange) from freqtrade.tests.test_freqtradebot import patch_get_signal from freqtrade.tests.conftest import patch_coinmarketcap @@ -98,7 +99,10 @@ def test_authorized_only(default_conf, mocker, caplog) -> None: update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) default_conf['telegram']['enabled'] = False - bot = FreqtradeBot(default_conf) + + worker = Worker(args=None, config=default_conf) + bot = worker.freqtrade + patch_get_signal(bot, (True, False)) dummy = DummyCls(bot) dummy.dummy_handler(bot=MagicMock(), update=update) @@ -125,7 +129,10 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) default_conf['telegram']['enabled'] = False - bot = FreqtradeBot(default_conf) + + worker = Worker(args=None, config=default_conf) + bot = worker.freqtrade + patch_get_signal(bot, (True, False)) dummy = DummyCls(bot) dummy.dummy_handler(bot=MagicMock(), update=update) @@ -153,7 +160,9 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None: default_conf['telegram']['enabled'] = False - bot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + bot = worker.freqtrade + patch_get_signal(bot, (True, False)) dummy = DummyCls(bot) @@ -208,7 +217,9 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -244,19 +255,21 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) - freqtradebot.state = State.STOPPED + worker.state = State.STOPPED # Status is also enabled when stopped telegram._status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'no active trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() - freqtradebot.state = State.RUNNING + worker.state = State.RUNNING telegram._status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'no active trade' in msg_mock.call_args_list[0][0][0] @@ -290,19 +303,22 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) default_conf['stake_amount'] = 15.0 - freqtradebot = FreqtradeBot(default_conf) + + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) - freqtradebot.state = State.STOPPED + worker.state = State.STOPPED # Status table is also enabled when stopped telegram._status_table(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'no active order' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() - freqtradebot.state = State.RUNNING + worker.state = State.RUNNING telegram._status_table(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'no active order' in msg_mock.call_args_list[0][0][0] @@ -344,7 +360,9 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -409,13 +427,15 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) # Try invalid data msg_mock.reset_mock() - freqtradebot.state = State.RUNNING + worker.state = State.RUNNING update.message.text = '/daily -2' telegram._daily(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 @@ -423,7 +443,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: # Try invalid data msg_mock.reset_mock() - freqtradebot.state = State.RUNNING + worker.state = State.RUNNING update.message.text = '/daily today' telegram._daily(bot=MagicMock(), update=update) assert str('Daily Profit over the last 7 days') in msg_mock.call_args_list[0][0][0] @@ -448,7 +468,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -593,13 +615,14 @@ def test_start_handle(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) + freqtradebot = worker.freqtrade telegram = Telegram(freqtradebot) - freqtradebot.state = State.STOPPED - assert freqtradebot.state == State.STOPPED + worker.state = State.STOPPED + assert worker.state == State.STOPPED telegram._start(bot=MagicMock(), update=update) - assert freqtradebot.state == State.RUNNING + assert worker.state == State.RUNNING assert msg_mock.call_count == 1 @@ -611,13 +634,14 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) + freqtradebot = worker.freqtrade telegram = Telegram(freqtradebot) - freqtradebot.state = State.RUNNING - assert freqtradebot.state == State.RUNNING + worker.state = State.RUNNING + assert worker.state == State.RUNNING telegram._start(bot=MagicMock(), update=update) - assert freqtradebot.state == State.RUNNING + assert worker.state == State.RUNNING assert msg_mock.call_count == 1 assert 'already running' in msg_mock.call_args_list[0][0][0] @@ -631,13 +655,14 @@ def test_stop_handle(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) + freqtradebot = worker.freqtrade telegram = Telegram(freqtradebot) - freqtradebot.state = State.RUNNING - assert freqtradebot.state == State.RUNNING + worker.state = State.RUNNING + assert worker.state == State.RUNNING telegram._stop(bot=MagicMock(), update=update) - assert freqtradebot.state == State.STOPPED + assert worker.state == State.STOPPED assert msg_mock.call_count == 1 assert 'stopping trader' in msg_mock.call_args_list[0][0][0] @@ -651,13 +676,14 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) + freqtradebot = worker.freqtrade telegram = Telegram(freqtradebot) - freqtradebot.state = State.STOPPED - assert freqtradebot.state == State.STOPPED + worker.state = State.STOPPED + assert worker.state == State.STOPPED telegram._stop(bot=MagicMock(), update=update) - assert freqtradebot.state == State.STOPPED + assert worker.state == State.STOPPED assert msg_mock.call_count == 1 assert 'already stopped' in msg_mock.call_args_list[0][0][0] @@ -691,13 +717,14 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) + freqtradebot = worker.freqtrade telegram = Telegram(freqtradebot) - freqtradebot.state = State.RUNNING - assert freqtradebot.state == State.RUNNING + worker.state = State.RUNNING + assert worker.state == State.RUNNING telegram._reload_conf(bot=MagicMock(), update=update) - assert freqtradebot.state == State.RELOAD_CONF + assert worker.state == State.RELOAD_CONF assert msg_mock.call_count == 1 assert 'reloading config' in msg_mock.call_args_list[0][0][0] @@ -717,7 +744,9 @@ def test_forcesell_handle(default_conf, update, ticker, fee, validate_pairs=MagicMock(return_value={}) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -768,7 +797,9 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, validate_pairs=MagicMock(return_value={}) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -822,7 +853,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker validate_pairs=MagicMock(return_value={}) ) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -865,7 +898,9 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: ) patch_exchange(mocker) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -907,7 +942,9 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None: fbuy_mock = MagicMock(return_value=None) mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock) - freqtradebot = FreqtradeBot(default_conf) + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -941,7 +978,10 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non markets=PropertyMock(markets), validate_pairs=MagicMock(return_value={}) ) - freqtradebot = FreqtradeBot(default_conf) + + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -970,7 +1010,10 @@ def test_performance_handle(default_conf, update, ticker, fee, validate_pairs=MagicMock(return_value={}) ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) - freqtradebot = FreqtradeBot(default_conf) + + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) @@ -1009,7 +1052,10 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non markets=PropertyMock(markets) ) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) - freqtradebot = FreqtradeBot(default_conf) + + worker = Worker(args=None, config=default_conf) + freqtradebot = worker.freqtrade + patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index e4f0415f7..276cdc0b9 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -13,6 +13,7 @@ import requests from freqtrade import (DependencyException, OperationalException, TemporaryError, constants) +from freqtrade.worker import Worker from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType @@ -22,9 +23,9 @@ from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange, patch_ # Functions for recurrent object patching -def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: +def patch_freqtradebot(mocker, config) -> None: """ - This function patch _init_modules() to not call dependencies + This function patches _init_modules() to not call dependencies :param mocker: a Mocker object to apply patches :param config: Config to pass to the bot :return: None @@ -33,9 +34,29 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) patch_exchange(mocker) + +def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: + """ + This function patches _init_modules() to not call dependencies + :param mocker: a Mocker object to apply patches + :param config: Config to pass to the bot + :return: FreqtradeBot + """ + patch_freqtradebot(mocker, config) return FreqtradeBot(config) +def get_patched_worker(mocker, config) -> Worker: + """ + This function patches _init_modules() to not call dependencies + :param mocker: a Mocker object to apply patches + :param config: Config to pass to the bot + :return: FreqtradeBot + """ + patch_freqtradebot(mocker, config) + return Worker(args=None, config=config) + + def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: """ :param mocker: mocker to patch IStrategy class @@ -61,12 +82,12 @@ def patch_RPCManager(mocker) -> MagicMock: def test_freqtradebot(mocker, default_conf, markets) -> None: mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) - freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert freqtrade.state is State.RUNNING + worker = get_patched_worker(mocker, default_conf) + assert worker.state is State.RUNNING default_conf.pop('initial_state') - freqtrade = FreqtradeBot(default_conf) - assert freqtrade.state is State.STOPPED + worker = Worker(args=None, config=default_conf) + assert worker.state is State.STOPPED def test_cleanup(mocker, default_conf, caplog) -> None: @@ -80,11 +101,11 @@ def test_cleanup(mocker, default_conf, caplog) -> None: def test_worker_running(mocker, default_conf, caplog) -> None: mock_throttle = MagicMock() - mocker.patch('freqtrade.freqtradebot.FreqtradeBot._throttle', mock_throttle) + mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle) - freqtrade = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) - state = freqtrade.worker(old_state=None) + state = worker._worker(old_state=None) assert state is State.RUNNING assert log_has('Changing state to: RUNNING', caplog.record_tuples) assert mock_throttle.call_count == 1 @@ -92,12 +113,12 @@ def test_worker_running(mocker, default_conf, caplog) -> None: def test_worker_stopped(mocker, default_conf, caplog) -> None: mock_throttle = MagicMock() - mocker.patch('freqtrade.freqtradebot.FreqtradeBot._throttle', mock_throttle) + mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle) mock_sleep = mocker.patch('time.sleep', return_value=None) - freqtrade = get_patched_freqtradebot(mocker, default_conf) - freqtrade.state = State.STOPPED - state = freqtrade.worker(old_state=State.RUNNING) + worker = get_patched_worker(mocker, default_conf) + worker.state = State.STOPPED + state = worker._worker(old_state=State.RUNNING) assert state is State.STOPPED assert log_has('Changing state to: STOPPED', caplog.record_tuples) assert mock_throttle.call_count == 0 @@ -109,17 +130,17 @@ def test_throttle(mocker, default_conf, caplog) -> None: return 42 caplog.set_level(logging.DEBUG) - freqtrade = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) start = time.time() - result = freqtrade._throttle(throttled_func, min_secs=0.1) + result = worker._throttle(throttled_func, min_secs=0.1) end = time.time() assert result == 42 assert end - start > 0.1 assert log_has('Throttling throttled_func for 0.10 seconds', caplog.record_tuples) - result = freqtrade._throttle(throttled_func, min_secs=-1) + result = worker._throttle(throttled_func, min_secs=-1) assert result == 42 @@ -127,12 +148,12 @@ def test_throttle_with_assets(mocker, default_conf) -> None: def throttled_func(nb_assets=-1): return nb_assets - freqtrade = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) - result = freqtrade._throttle(throttled_func, min_secs=0.1, nb_assets=666) + result = worker._throttle(throttled_func, min_secs=0.1, nb_assets=666) assert result == 666 - result = freqtrade._throttle(throttled_func, min_secs=0.1) + result = worker._throttle(throttled_func, min_secs=0.1) assert result == -1 @@ -218,7 +239,7 @@ def test_edge_called_in_process(mocker, edge_conf) -> None: freqtrade = FreqtradeBot(edge_conf) freqtrade.pairlists._validate_whitelist = _refresh_whitelist patch_get_signal(freqtrade) - freqtrade._process() + freqtrade.process() assert freqtrade.active_pair_whitelist == ['NEO/BTC', 'LTC/BTC'] @@ -652,7 +673,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, trades = Trade.query.filter(Trade.is_open.is_(True)).all() assert not trades - result = freqtrade._process() + result = freqtrade.process() assert result is True trades = Trade.query.filter(Trade.is_open.is_(True)).all() @@ -683,10 +704,10 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non ) sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None) - freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade) + worker = get_patched_worker(mocker, default_conf) + patch_get_signal(worker.freqtrade) - result = freqtrade._process() + result = worker.freqtrade.process() assert result is False assert sleep_mock.has_calls() @@ -700,14 +721,14 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) -> markets=PropertyMock(return_value=markets), buy=MagicMock(side_effect=OperationalException) ) - freqtrade = FreqtradeBot(default_conf) - patch_get_signal(freqtrade) + worker = get_patched_worker(mocker, default_conf) + patch_get_signal(worker.freqtrade) - assert freqtrade.state == State.RUNNING + assert worker.state == State.RUNNING - result = freqtrade._process() + result = worker.freqtrade.process() assert result is False - assert freqtrade.state == State.STOPPED + assert worker.state == State.STOPPED assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status'] @@ -728,18 +749,18 @@ def test_process_trade_handling( trades = Trade.query.filter(Trade.is_open.is_(True)).all() assert not trades - result = freqtrade._process() + result = freqtrade.process() assert result is True trades = Trade.query.filter(Trade.is_open.is_(True)).all() assert len(trades) == 1 - result = freqtrade._process() + result = freqtrade.process() assert result is False def test_process_trade_no_whitelist_pair( default_conf, ticker, limit_buy_order, markets, fee, mocker) -> None: - """ Test _process with trade not in pair list """ + """ Test process with trade not in pair list """ patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -776,7 +797,7 @@ def test_process_trade_no_whitelist_pair( )) assert pair not in freqtrade.active_pair_whitelist - result = freqtrade._process() + result = freqtrade.process() assert pair in freqtrade.active_pair_whitelist # Make sure each pair is only in the list once assert len(freqtrade.active_pair_whitelist) == len(set(freqtrade.active_pair_whitelist)) @@ -806,7 +827,7 @@ def test_process_informative_pairs_added(default_conf, ticker, markets, mocker) freqtrade.strategy.informative_pairs = inf_pairs # patch_get_signal(freqtrade) - freqtrade._process() + freqtrade.process() assert inf_pairs.call_count == 1 assert refresh_mock.call_count == 1 assert ("BTC/ETH", "1m") in refresh_mock.call_args[0][0] @@ -2992,5 +3013,5 @@ def test_startup_messages(default_conf, mocker): 'config': {'number_assets': 20} } mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert freqtrade.state is State.RUNNING + worker = get_patched_worker(mocker, default_conf) + assert worker.state is State.RUNNING diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 188b9b9bd..cce02bf8b 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -43,17 +43,14 @@ def test_main_start_hyperopt(mocker) -> None: def test_main_fatal_exception(mocker, default_conf, caplog) -> None: patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', - _init_modules=MagicMock(), - worker=MagicMock(side_effect=Exception), - cleanup=MagicMock(), - ) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) + mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=Exception)) mocker.patch( 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) + mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) args = ['-c', 'config.json.example'] @@ -66,17 +63,14 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None: def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', - _init_modules=MagicMock(), - worker=MagicMock(side_effect=KeyboardInterrupt), - cleanup=MagicMock(), - ) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) + mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=KeyboardInterrupt)) mocker.patch( 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) + mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) args = ['-c', 'config.json.example'] @@ -89,17 +83,14 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: def test_main_operational_exception(mocker, default_conf, caplog) -> None: patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', - _init_modules=MagicMock(), - worker=MagicMock(side_effect=OperationalException('Oh snap!')), - cleanup=MagicMock(), - ) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) + mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=OperationalException('Oh snap!'))) mocker.patch( 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) + mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) args = ['-c', 'config.json.example'] @@ -112,17 +103,14 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: def test_main_reload_conf(mocker, default_conf, caplog) -> None: patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', - _init_modules=MagicMock(), - worker=MagicMock(return_value=State.RELOAD_CONF), - cleanup=MagicMock(), - ) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) + mocker.patch('freqtrade.worker.Worker._worker', MagicMock(return_value=State.RELOAD_CONF)) mocker.patch( 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) + mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) # Raise exception as side effect to avoid endless loop reconfigure_mock = mocker.patch( @@ -138,17 +126,14 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: def test_reconfigure(mocker, default_conf) -> None: patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.freqtradebot.FreqtradeBot', - _init_modules=MagicMock(), - worker=MagicMock(side_effect=OperationalException('Oh snap!')), - cleanup=MagicMock(), - ) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) + mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=OperationalException('Oh snap!'))) mocker.patch( 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) + mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) freqtrade = FreqtradeBot(default_conf) From 8aee009a0acce1d86cd4be84f3696cdb6e654d19 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Mar 2019 12:42:19 +0300 Subject: [PATCH 299/457] test _reconfigure() adjusted --- freqtrade/tests/test_main.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index cce02bf8b..96cf2834d 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -7,8 +7,9 @@ import pytest from freqtrade import OperationalException from freqtrade.arguments import Arguments +from freqtrade.worker import Worker from freqtrade.freqtradebot import FreqtradeBot -from freqtrade.main import main, Worker +from freqtrade.main import main from freqtrade.state import State from freqtrade.tests.conftest import log_has, patch_exchange @@ -135,7 +136,9 @@ def test_reconfigure(mocker, default_conf) -> None: mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - freqtrade = FreqtradeBot(default_conf) + args = Arguments(['-c', 'config.json.example'], '').get_parsed_arg() + worker = Worker(args=args, config=default_conf) + freqtrade = worker.freqtrade # Renew mock to return modified data conf = deepcopy(default_conf) @@ -145,11 +148,10 @@ def test_reconfigure(mocker, default_conf) -> None: lambda *args, **kwargs: conf ) + worker._config = conf # reconfigure should return a new instance - freqtrade2 = reconfigure( - freqtrade, - Arguments(['-c', 'config.json.example'], '').get_parsed_arg() - ) + worker._reconfigure() + freqtrade2 = worker.freqtrade # Verify we have a new instance with the new config assert freqtrade is not freqtrade2 From c6d2c1e52095dc3d4fbfaed5daa067b63131ede7 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Mar 2019 12:45:19 +0300 Subject: [PATCH 300/457] rest of telegram tests adjusted --- freqtrade/tests/rpc/test_rpc_telegram.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 1452267ea..578e45e24 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -697,7 +697,9 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None: _send_msg=msg_mock ) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + worker = get_patched_worker(mocker, default_conf) + freqtradebot = worker.freqtrade + telegram = Telegram(freqtradebot) assert freqtradebot.config['max_open_trades'] != 0 From 1f50bc79bc0ee098f6974ab40c7bfd530ceca914 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 26 Mar 2019 13:37:03 +0100 Subject: [PATCH 301/457] Update ccxt from 1.18.398 to 1.18.400 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index feed42b52..345d32c0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.398 +ccxt==1.18.400 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From f5744cc9bf3d7b0febc35dade539ba1f7b546bab Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Mar 2019 18:34:50 +0300 Subject: [PATCH 302/457] fix in the tests --- freqtrade/tests/test_freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 276cdc0b9..ece07f6fd 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -51,7 +51,7 @@ def get_patched_worker(mocker, config) -> Worker: This function patches _init_modules() to not call dependencies :param mocker: a Mocker object to apply patches :param config: Config to pass to the bot - :return: FreqtradeBot + :return: Worker """ patch_freqtradebot(mocker, config) return Worker(args=None, config=config) @@ -707,7 +707,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non worker = get_patched_worker(mocker, default_conf) patch_get_signal(worker.freqtrade) - result = worker.freqtrade.process() + result = worker._process() assert result is False assert sleep_mock.has_calls() @@ -726,7 +726,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) -> assert worker.state == State.RUNNING - result = worker.freqtrade.process() + result = worker._process() assert result is False assert worker.state == State.STOPPED assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status'] From b2c2b42408753c0d2a976fbeb91aac094fbeef0f Mon Sep 17 00:00:00 2001 From: Gianluca Puglia Date: Tue, 26 Mar 2019 18:53:16 +0100 Subject: [PATCH 303/457] Removed unwanted comment --- freqtrade/tests/optimize/test_backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 531f73916..64a33fae2 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -689,7 +689,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair): def _trend_alternate_hold(dataframe=None, metadata=None): """ - Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)flake + Buy every xth candle - sell every other xth -2 (hold on to pairs a bit) """ if metadata['pair'] in('ETH/BTC', 'LTC/BTC'): multi = 20 From 3bdc7b9a8880a23cf9ae0925a0194f373e027c27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Mar 2019 10:51:13 +0100 Subject: [PATCH 304/457] add missed "check" in docs --- docs/bot-optimization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index f018d92a7..9e754c213 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -262,7 +262,7 @@ The strategy provides access to the `DataProvider`. This allows you to get addit All methods return `None` in case of failure (do not raise an exception). -Please always the mode of operation to select the correct method to get data (samples see below). +Please always check the mode of operation to select the correct method to get data (samples see below). #### Possible options for DataProvider From 4e57969e4e2d3e1d49967f46844e744f2f6e2b8d Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 12:54:00 +0100 Subject: [PATCH 305/457] documentation added --- docs/telegram-usage.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 6788bec90..fa10969db 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -180,6 +180,21 @@ Use `/reload_conf` to reset the blacklist. > Using blacklist `StaticPairList` with 2 pairs >`DODGE/BTC`, `HOT/BTC`. +### /edge + +Shows pairs accepted by pais along with their corresponding winrate, expectancy and stoploss values. + +> **Edge only validated following pairs:** +``` +Pair Winrate Expectancy Stoploss +-------- --------- ------------ ---------- +DOCK/ETH 0.522727 0.881821 -0.03 +PHX/ETH 0.677419 0.560488 -0.03 +HOT/ETH 0.733333 0.490492 -0.03 +HC/ETH 0.588235 0.280988 -0.02 +ARDR/ETH 0.366667 0.143059 -0.01 +``` + ### /version > **Version:** `0.14.3` From 955e2d28262fd8572cd8983196a0a0bcbdd099f6 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 12:59:59 +0100 Subject: [PATCH 306/457] Update test_rpc_telegram.py telegram test_init fixed --- freqtrade/tests/rpc/test_rpc_telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index dd49b0000..24e99b1c5 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -74,7 +74,7 @@ def test_init(default_conf, mocker, caplog) -> None: message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \ "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \ "['performance'], ['daily'], ['count'], ['reload_conf'], " \ - "['stopbuy'], ['whitelist'], ['blacklist'], ['help'], ['version']]" + "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]" assert log_has(message_str, caplog.record_tuples) From cc32566c92f046dfe943e80dcd36fdcdcffa2e6d Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 27 Mar 2019 12:38:05 +0000 Subject: [PATCH 307/457] Update ccxt from 1.18.400 to 1.18.406 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 345d32c0d..2bf329bd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.400 +ccxt==1.18.406 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 8641da13b908747d49b0b5f39610e8cd104ecdfd Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 14:02:37 +0100 Subject: [PATCH 308/457] added RPC tests in case of edge enabled/disabled --- freqtrade/tests/rpc/test_rpc.py | 36 ++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e6f7ea41e..a5ee4a26c 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -2,19 +2,20 @@ # pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments from datetime import datetime -from unittest.mock import MagicMock, ANY, PropertyMock +from unittest.mock import ANY, MagicMock, PropertyMock import pytest from numpy import isnan -from freqtrade import TemporaryError, DependencyException +from freqtrade import DependencyException, TemporaryError +from freqtrade.edge import PairInfo from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.state import State -from freqtrade.tests.test_freqtradebot import patch_get_signal from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange +from freqtrade.tests.test_freqtradebot import patch_get_signal # Functions for recurrent object patching @@ -713,3 +714,32 @@ def test_rpc_blacklist(mocker, default_conf) -> None: assert len(ret['blacklist']) == 3 assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] + +def test_rpc_edge_disabled(mocker, default_conf) -> None: + patch_coinmarketcap(mocker) + patch_exchange(mocker) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + freqtradebot = FreqtradeBot(default_conf) + rpc = RPC(freqtradebot) + with pytest.raises(RPCException, match=r'Edge is not enabled.'): + ret = rpc._rpc_edge() + +def test_rpc_edge_enabled(mocker, edge_conf) -> None: + patch_coinmarketcap(mocker) + patch_exchange(mocker) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( + return_value={ + 'E/F': PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60), + } + )) + freqtradebot = FreqtradeBot(edge_conf) + + rpc = RPC(freqtradebot) + ret = rpc._rpc_edge() + + assert len(ret) == 1 + assert ret[0]['Pair'] == 'E/F' + assert ret[0]['Winrate'] == 0.66 + assert ret[0]['Expectancy'] == 1.71 + assert ret[0]['Stoploss'] == -0.02 \ No newline at end of file From 0687051ffbd86b821cfdccb228680f6d51df1bd7 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 14:04:33 +0100 Subject: [PATCH 309/457] Update test_rpc.py flake8 --- freqtrade/tests/rpc/test_rpc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index a5ee4a26c..8e8e7fc50 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -715,6 +715,7 @@ def test_rpc_blacklist(mocker, default_conf) -> None: assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] + def test_rpc_edge_disabled(mocker, default_conf) -> None: patch_coinmarketcap(mocker) patch_exchange(mocker) @@ -722,7 +723,8 @@ def test_rpc_edge_disabled(mocker, default_conf) -> None: freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) with pytest.raises(RPCException, match=r'Edge is not enabled.'): - ret = rpc._rpc_edge() + rpc._rpc_edge() + def test_rpc_edge_enabled(mocker, edge_conf) -> None: patch_coinmarketcap(mocker) @@ -742,4 +744,4 @@ def test_rpc_edge_enabled(mocker, edge_conf) -> None: assert ret[0]['Pair'] == 'E/F' assert ret[0]['Winrate'] == 0.66 assert ret[0]['Expectancy'] == 1.71 - assert ret[0]['Stoploss'] == -0.02 \ No newline at end of file + assert ret[0]['Stoploss'] == -0.02 From 4038cdf70a25deae3ab1f50399dbd7cbd94d34ce Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 27 Mar 2019 16:04:05 +0100 Subject: [PATCH 310/457] "Edge" test for rpc telegram --- freqtrade/tests/rpc/test_rpc_telegram.py | 47 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 24e99b1c5..8118266cc 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -13,16 +13,16 @@ from telegram import Chat, Message, Update from telegram.error import NetworkError from freqtrade import __version__ +from freqtrade.edge import PairInfo from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType from freqtrade.rpc.telegram import Telegram, authorized_only -from freqtrade.strategy.interface import SellType from freqtrade.state import State +from freqtrade.strategy.interface import SellType from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, - patch_exchange) + patch_coinmarketcap, patch_exchange) from freqtrade.tests.test_freqtradebot import patch_get_signal -from freqtrade.tests.conftest import patch_coinmarketcap class DummyCls(Telegram): @@ -1099,6 +1099,47 @@ def test_blacklist_static(default_conf, update, mocker) -> None: assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] +def test_edge_disabled(default_conf, update, mocker) -> None: + patch_coinmarketcap(mocker) + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + + telegram = Telegram(freqtradebot) + + telegram._edge(bot=MagicMock(), update=update) + assert msg_mock.call_count == 1 + assert "Edge is not enabled." in msg_mock.call_args_list[0][0][0] + + +def test_edge_enabled(edge_conf, update, mocker) -> None: + patch_coinmarketcap(mocker) + msg_mock = MagicMock() + mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( + return_value={ + 'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), + } + )) + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + + freqtradebot = get_patched_freqtradebot(mocker, edge_conf) + + telegram = Telegram(freqtradebot) + + telegram._edge(bot=MagicMock(), update=update) + assert msg_mock.call_count == 1 + assert 'Edge only validated following pairs:\n
' in msg_mock.call_args_list[0][0][0]
+    assert 'Pair      Winrate    Expectancy    Stoploss' in msg_mock.call_args_list[0][0][0]
+
 def test_help_handle(default_conf, update, mocker) -> None:
     patch_coinmarketcap(mocker)
     msg_mock = MagicMock()

From 1e37d8ccb3266be217847865e4fa9154d3d83583 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 16:58:53 +0100
Subject: [PATCH 311/457] flake8

---
 freqtrade/tests/rpc/test_rpc_telegram.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index 8118266cc..c6a5fff54 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -1140,6 +1140,7 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
     assert 'Edge only validated following pairs:\n
' in msg_mock.call_args_list[0][0][0]
     assert 'Pair      Winrate    Expectancy    Stoploss' in msg_mock.call_args_list[0][0][0]
 
+
 def test_help_handle(default_conf, update, mocker) -> None:
     patch_coinmarketcap(mocker)
     msg_mock = MagicMock()

From 753b03d581d2f435f70c44ca96fcbcd4589a45a7 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 18:19:42 +0100
Subject: [PATCH 312/457] rolback on removing MD whitespaces

---
 docs/telegram-usage.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index fa10969db..d68183537 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -56,7 +56,7 @@ Prevents the bot from opening new trades by temporarily setting "max_open_trades
 After this, give the bot time to close off open trades (can be checked via `/status table`).
 Once all positions are sold, run `/stop` to completely stop the bot.
 
-`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command.
+`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command. 
 
 !!! warning
    The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
@@ -167,7 +167,7 @@ Day         Profit BTC      Profit USD
 
 Shows the current whitelist
 
-> Using whitelist `StaticPairList` with 22 pairs
+> Using whitelist `StaticPairList` with 22 pairs  
 > `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
 
 ### /blacklist [pair]
@@ -177,7 +177,7 @@ If Pair is set, then this pair will be added to the pairlist.
 Also supports multiple pairs, seperated by a space.
 Use `/reload_conf` to reset the blacklist.
 
-> Using blacklist `StaticPairList` with 2 pairs
+> Using blacklist `StaticPairList` with 2 pairs  
 >`DODGE/BTC`, `HOT/BTC`.
 
 ### /edge

From 9b22d5cab135d743d7c5b3841427a24636de11ee Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 27 Mar 2019 20:51:55 +0100
Subject: [PATCH 313/457] Fix typo, add test for validate_order_tif

---
 freqtrade/exchange/exchange.py            |  2 +-
 freqtrade/tests/exchange/test_exchange.py | 22 ++++++++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 2ec5c0e25..011be58e5 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -272,7 +272,7 @@ class Exchange(object):
         if any(v not in self._ft_has["order_time_in_force"]
                for k, v in order_time_in_force.items()):
             raise OperationalException(
-                f'Time in force policies are not supporetd for  {self.name} yet.')
+                f'Time in force policies are not supported for {self.name} yet.')
 
     def exchange_has(self, endpoint: str) -> bool:
         """
diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py
index 736f2298a..eed16d39b 100644
--- a/freqtrade/tests/exchange/test_exchange.py
+++ b/freqtrade/tests/exchange/test_exchange.py
@@ -139,6 +139,28 @@ def test_exchange_resolver(default_conf, mocker, caplog):
                           caplog.record_tuples)
 
 
+def test_validate_order_time_in_force(default_conf, mocker, caplog):
+    caplog.set_level(logging.INFO)
+    # explicitly test bittrex, exchanges implementing other policies need seperate tests
+    ex = get_patched_exchange(mocker, default_conf, id="bittrex")
+    tif = {
+        "buy": "gtc",
+        "sell": "gtc",
+    }
+
+    ex.validate_order_time_in_force(tif)
+    tif2 = {
+        "buy": "fok",
+        "sell": "ioc",
+    }
+    with pytest.raises(OperationalException, match=r"Time in force.*not supported for .*"):
+        ex.validate_order_time_in_force(tif2)
+
+    # Patch to see if this will pass if the values are in the ft dict
+    ex._ft_has.update({"order_time_in_force": ["gtc", "fok", "ioc"]})
+    ex.validate_order_time_in_force(tif2)
+
+
 def test_symbol_amount_prec(default_conf, mocker):
     '''
     Test rounds down to 4 Decimal places

From 6045f07a9c4fafe7d730c0d3e3d57b3e761933e5 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 21:12:57 +0100
Subject: [PATCH 314/457] telegram message concatenation refactored

---
 freqtrade/rpc/telegram.py                | 30 ++++++++++++++----------
 freqtrade/tests/rpc/test_rpc_telegram.py |  6 +++++
 2 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 2c419e417..1e1436631 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -193,21 +193,25 @@ class Telegram(RPC):
             for result in results:
                 result['date'] = result['date'].humanize()
 
-            messages = [
-                "*Trade ID:* `{trade_id}`\n"
-                "*Current Pair:* {pair}\n"
-                "*Open Since:* `{date}`\n"
-                "*Amount:* `{amount}`\n"
-                "*Open Rate:* `{open_rate:.8f}`\n"
-                "*Close Rate:* `{close_rate}`\n"
-                "*Current Rate:* `{current_rate:.8f}`\n"
-                "*Close Profit:* `{close_profit}`\n"
-                "*Current Profit:* `{current_profit:.2f}%`\n"
-                "*Open Order:* `{open_order}`".format(**result)
-                for result in results
-            ]
+            messages = []
+            for r in results:
+                lines = [
+                    "*Trade ID:* `{trade_id}`",
+                    "*Current Pair:* {pair}",
+                    "*Open Since:* `{date}`",
+                    "*Amount:* `{amount}`",
+                    "*Open Rate:* `{open_rate:.8f}`",
+                    "*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
+                    "*Current Rate:* `{current_rate:.8f}`",
+                    "*Close Profit:* `{close_profit}`",
+                    "*Current Profit:* `{current_profit:.2f}%`",
+                    "*Open Order:* `{open_order}`"
+                ]
+                messages.append("\n".join(filter(None,lines)).format(**r))
+
             for msg in messages:
                 self._send_msg(msg, bot=bot)
+
         except RPCException as e:
             self._send_msg(str(e), bot=bot)
 
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index dd49b0000..39973d5db 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -267,6 +267,12 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
     # Trigger status while we have a fulfilled order for the open trade
     telegram._status(bot=MagicMock(), update=update)
 
+    # close_rate should not be included in the message as the trade is not closed
+    # and no line should be empty
+    lines = msg_mock.call_args_list[0][0][0].split('\n')
+    assert '' not in lines
+    assert 'Close Rate' not in ''.join(lines)
+
     assert msg_mock.call_count == 1
     assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
 

From 4d9ca71c82c08270d8fcb8c102c1c52670c60f9e Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 21:20:09 +0100
Subject: [PATCH 315/457] shifting edge help message a line lower

---
 freqtrade/rpc/telegram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 553f1a1fb..919c45757 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -533,8 +533,8 @@ class Telegram(RPC):
                   "*/reload_conf:* `Reload configuration file` \n" \
                   "*/whitelist:* `Show current whitelist` \n" \
                   "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " \
-                  "*/edge:* `Shows validated pairs by Edge if it is enabeld` \n" \
                   "to the blacklist.` \n" \
+                  "*/edge:* `Shows validated pairs by Edge if it is enabeld` \n" \
                   "*/help:* `This help message`\n" \
                   "*/version:* `Show version`"
 

From e5406ed3cfe41d75a8381ab5112c80451023bf03 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 21:22:25 +0100
Subject: [PATCH 316/457] typo in docs and comments

---
 docs/telegram-usage.md    | 2 +-
 freqtrade/rpc/telegram.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index d68183537..1ca61e54a 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -182,7 +182,7 @@ Use `/reload_conf` to reset the blacklist.
 
 ### /edge
 
-Shows pairs accepted by pais along with their corresponding winrate, expectancy and stoploss values.
+Shows pairs validated by Edge along with their corresponding winrate, expectancy and stoploss values.
 
 > **Edge only validated following pairs:**
 ```
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 919c45757..06bf5efe9 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -494,7 +494,7 @@ class Telegram(RPC):
     def _edge(self, bot: Bot, update: Update) -> None:
         """
         Handler for /edge
-        Shows informaton related to Edge
+        Shows information related to Edge
         """
         try:
             edge_pairs = self._rpc_edge()

From 1678a039aee6cd06981152205b833bd0202ab512 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 21:32:56 +0100
Subject: [PATCH 317/457] removing close profit is trade is open

---
 freqtrade/rpc/telegram.py                | 2 +-
 freqtrade/tests/rpc/test_rpc_telegram.py | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 1e1436631..0870e660b 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -203,7 +203,7 @@ class Telegram(RPC):
                     "*Open Rate:* `{open_rate:.8f}`",
                     "*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
                     "*Current Rate:* `{current_rate:.8f}`",
-                    "*Close Profit:* `{close_profit}`",
+                    "*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
                     "*Current Profit:* `{current_profit:.2f}%`",
                     "*Open Order:* `{open_order}`"
                 ]
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index 39973d5db..d1a068100 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -272,6 +272,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
     lines = msg_mock.call_args_list[0][0][0].split('\n')
     assert '' not in lines
     assert 'Close Rate' not in ''.join(lines)
+    assert 'Close Profit' not in ''.join(lines)
 
     assert msg_mock.call_count == 1
     assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]

From 0ca3a38ba6b6de63574c23ea8355863c858d0da9 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 21:39:17 +0100
Subject: [PATCH 318/457] moved date to top and show open order only if it is
 not none

---
 freqtrade/rpc/telegram.py | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 0870e660b..2ba431b07 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -196,18 +196,17 @@ class Telegram(RPC):
             messages = []
             for r in results:
                 lines = [
-                    "*Trade ID:* `{trade_id}`",
+                    "*Trade ID:* `{trade_id}` (since `{date}`)",
                     "*Current Pair:* {pair}",
-                    "*Open Since:* `{date}`",
                     "*Amount:* `{amount}`",
                     "*Open Rate:* `{open_rate:.8f}`",
                     "*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
                     "*Current Rate:* `{current_rate:.8f}`",
                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
                     "*Current Profit:* `{current_profit:.2f}%`",
-                    "*Open Order:* `{open_order}`"
+                    "*Open Order:* `{open_order}`" if r['open_order'] else "",
                 ]
-                messages.append("\n".join(filter(None,lines)).format(**r))
+                messages.append("\n".join(filter(None ,lines)).format(**r))
 
             for msg in messages:
                 self._send_msg(msg, bot=bot)

From 941921dd0f9f741d81da890941d9a3f6294ba81c Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 27 Mar 2019 22:00:46 +0100
Subject: [PATCH 319/457] initial SL and SL added to RPC

---
 freqtrade/rpc/rpc.py            | 2 ++
 freqtrade/tests/rpc/test_rpc.py | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index a5601a502..3272059a9 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -110,6 +110,8 @@ class RPC(object):
                     amount=round(trade.amount, 8),
                     close_profit=fmt_close_profit,
                     current_profit=round(current_profit * 100, 2),
+                    initial_stoploss=trade.initial_stop_loss,
+                    stoploss=trade.stop_loss,
                     open_order='({} {} rem={:.8f})'.format(
                       order['type'], order['side'], order['remaining']
                     ) if order else None,
diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py
index e6f7ea41e..f9862c9ca 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -58,6 +58,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         'amount': 90.99181074,
         'close_profit': None,
         'current_profit': -0.59,
+        'initial_stoploss': 0.0,
+        'stoploss': 0.0,
         'open_order': '(limit buy rem=0.00000000)'
     } == results[0]
 
@@ -78,6 +80,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         'amount': 90.99181074,
         'close_profit': None,
         'current_profit': ANY,
+        'initial_stoploss': 0.0,
+        'stoploss': 0.0,
         'open_order': '(limit buy rem=0.00000000)'
     } == results[0]
 

From 0e5b0ebda6fa26aab71a877a6c62616110a46d7d Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Thu, 28 Mar 2019 12:09:07 +0100
Subject: [PATCH 320/457] adding SL and SL percentage to telegram msg

---
 freqtrade/rpc/rpc.py                     | 8 ++++++--
 freqtrade/rpc/telegram.py                | 3 ++-
 freqtrade/tests/rpc/test_rpc_telegram.py | 2 ++
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 3272059a9..553e66d85 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -97,9 +97,13 @@ class RPC(object):
                     current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
                 except DependencyException:
                     current_rate = NAN
+
                 current_profit = trade.calc_profit_percent(current_rate)
                 fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
                                     if trade.close_profit else None)
+                sl_percentage = round(((trade.stop_loss - current_rate) / current_rate) * 100, 2)
+                txt_sl_percentage = f'{sl_percentage}%'
+
                 results.append(dict(
                     trade_id=trade.id,
                     pair=trade.pair,
@@ -110,8 +114,8 @@ class RPC(object):
                     amount=round(trade.amount, 8),
                     close_profit=fmt_close_profit,
                     current_profit=round(current_profit * 100, 2),
-                    initial_stoploss=trade.initial_stop_loss,
-                    stoploss=trade.stop_loss,
+                    stop_loss=trade.stop_loss,
+                    stop_loss_percentage=txt_sl_percentage,
                     open_order='({} {} rem={:.8f})'.format(
                       order['type'], order['side'], order['remaining']
                     ) if order else None,
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 2ba431b07..8bec8e6cd 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -204,9 +204,10 @@ class Telegram(RPC):
                     "*Current Rate:* `{current_rate:.8f}`",
                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
                     "*Current Profit:* `{current_profit:.2f}%`",
+                    "*Stoploss:* `{stop_loss:.8f}` ({stop_loss_percentage})",
                     "*Open Order:* `{open_order}`" if r['open_order'] else "",
                 ]
-                messages.append("\n".join(filter(None ,lines)).format(**r))
+                messages.append("\n".join(filter(None, lines)).format(**r))
 
             for msg in messages:
                 self._send_msg(msg, bot=bot)
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index d1a068100..edd099dc4 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -201,6 +201,8 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
             'amount': 90.99181074,
             'close_profit': None,
             'current_profit': -0.59,
+            'stop_loss': 1.099e-05,
+            'stop_loss_percentage': '-2%',
             'open_order': '(limit buy rem=0.00000000)'
         }]),
         _status_table=status_table,

From daeb172ba1d481f0c46dda83576c6b5032c34c27 Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Thu, 28 Mar 2019 12:38:05 +0000
Subject: [PATCH 321/457] Update ccxt from 1.18.406 to 1.18.407

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 2bf329bd8..0fff9dacf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-ccxt==1.18.406
+ccxt==1.18.407
 SQLAlchemy==1.3.1
 python-telegram-bot==11.1.0
 arrow==0.13.1

From e11eb4775e875533c21f21a7c2a8d63eaf506b55 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Thu, 28 Mar 2019 16:21:49 +0100
Subject: [PATCH 322/457] stoploss precentage in telegram msg removed

---
 freqtrade/rpc/rpc.py            | 3 ---
 freqtrade/rpc/telegram.py       | 2 +-
 freqtrade/tests/rpc/test_rpc.py | 6 ++----
 3 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 553e66d85..20bfe41ba 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -101,8 +101,6 @@ class RPC(object):
                 current_profit = trade.calc_profit_percent(current_rate)
                 fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
                                     if trade.close_profit else None)
-                sl_percentage = round(((trade.stop_loss - current_rate) / current_rate) * 100, 2)
-                txt_sl_percentage = f'{sl_percentage}%'
 
                 results.append(dict(
                     trade_id=trade.id,
@@ -115,7 +113,6 @@ class RPC(object):
                     close_profit=fmt_close_profit,
                     current_profit=round(current_profit * 100, 2),
                     stop_loss=trade.stop_loss,
-                    stop_loss_percentage=txt_sl_percentage,
                     open_order='({} {} rem={:.8f})'.format(
                       order['type'], order['side'], order['remaining']
                     ) if order else None,
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 8bec8e6cd..d3f3521aa 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -204,7 +204,7 @@ class Telegram(RPC):
                     "*Current Rate:* `{current_rate:.8f}`",
                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
                     "*Current Profit:* `{current_profit:.2f}%`",
-                    "*Stoploss:* `{stop_loss:.8f}` ({stop_loss_percentage})",
+                    "*Stoploss:* `{stop_loss:.8f}`",
                     "*Open Order:* `{open_order}`" if r['open_order'] else "",
                 ]
                 messages.append("\n".join(filter(None, lines)).format(**r))
diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py
index f9862c9ca..529bf31f3 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -58,8 +58,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         'amount': 90.99181074,
         'close_profit': None,
         'current_profit': -0.59,
-        'initial_stoploss': 0.0,
-        'stoploss': 0.0,
+        'stop_loss': 0.0,
         'open_order': '(limit buy rem=0.00000000)'
     } == results[0]
 
@@ -80,8 +79,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         'amount': 90.99181074,
         'close_profit': None,
         'current_profit': ANY,
-        'initial_stoploss': 0.0,
-        'stoploss': 0.0,
+        'stop_loss': 0.0,
         'open_order': '(limit buy rem=0.00000000)'
     } == results[0]
 

From 2f3f5f19cdc82971525990918c08fb5d17a173f8 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Thu, 28 Mar 2019 16:26:59 +0100
Subject: [PATCH 323/457] sl percentage removed form rpc test

---
 freqtrade/rpc/rpc.py                     | 2 --
 freqtrade/tests/rpc/test_rpc_telegram.py | 1 -
 2 files changed, 3 deletions(-)

diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 20bfe41ba..55f9a302c 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -97,11 +97,9 @@ class RPC(object):
                     current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
                 except DependencyException:
                     current_rate = NAN
-
                 current_profit = trade.calc_profit_percent(current_rate)
                 fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
                                     if trade.close_profit else None)
-
                 results.append(dict(
                     trade_id=trade.id,
                     pair=trade.pair,
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index edd099dc4..b66e29143 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -202,7 +202,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
             'close_profit': None,
             'current_profit': -0.59,
             'stop_loss': 1.099e-05,
-            'stop_loss_percentage': '-2%',
             'open_order': '(limit buy rem=0.00000000)'
         }]),
         _status_table=status_table,

From a87fc5f8634d656faa0d9de020e5f0beb0bda40c Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Thu, 28 Mar 2019 19:37:50 +0100
Subject: [PATCH 324/457] Fix tests - freqtrade should not be patched in this
 case

---
 freqtrade/tests/test_freqtradebot.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index ceeae6865..8bc071071 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -112,9 +112,9 @@ def test_worker_running(mocker, default_conf, caplog) -> None:
     assert log_has('Changing state to: RUNNING', caplog.record_tuples)
     assert mock_throttle.call_count == 1
     # Check strategy is loaded, and received a dataprovider object
-    assert freqtrade.strategy
-    assert freqtrade.strategy.dp
-    assert isinstance(freqtrade.strategy.dp, DataProvider)
+    assert worker.freqtrade.strategy
+    assert worker.freqtrade.strategy.dp
+    assert isinstance(worker.freqtrade.strategy.dp, DataProvider)
 
 
 def test_worker_stopped(mocker, default_conf, caplog) -> None:
@@ -710,7 +710,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non
     )
     sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
 
-    worker = get_patched_worker(mocker, default_conf)
+    worker = Worker(args=None, config=default_conf)
     patch_get_signal(worker.freqtrade)
 
     result = worker._process()
@@ -727,7 +727,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
         markets=PropertyMock(return_value=markets),
         buy=MagicMock(side_effect=OperationalException)
     )
-    worker = get_patched_worker(mocker, default_conf)
+    worker = Worker(args=None, config=default_conf)
     patch_get_signal(worker.freqtrade)
 
     assert worker.state == State.RUNNING

From 50fc63251ecf33edc8afebfe38cc2ce8d479b6a2 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Thu, 28 Mar 2019 21:18:26 +0100
Subject: [PATCH 325/457] added SL pct to DB

---
 freqtrade/persistence.py | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py
index a1ac65c04..0b46768c1 100644
--- a/freqtrade/persistence.py
+++ b/freqtrade/persistence.py
@@ -83,7 +83,7 @@ def check_migrate(engine) -> None:
         logger.debug(f'trying {table_back_name}')
 
     # Check for latest column
-    if not has_column(cols, 'min_rate'):
+    if not has_column(cols, 'stop_loss_pct'):
         logger.info(f'Running database migration - backup available as {table_back_name}')
 
         fee_open = get_column_def(cols, 'fee_open', 'fee')
@@ -91,7 +91,9 @@ def check_migrate(engine) -> None:
         open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
         close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
         stop_loss = get_column_def(cols, 'stop_loss', '0.0')
+        stop_loss_pct = get_column_def(cols, 'stop_loss_pct', '0.0')
         initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
+        initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', '0.0')
         stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
         stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
         max_rate = get_column_def(cols, 'max_rate', '0.0')
@@ -113,7 +115,8 @@ def check_migrate(engine) -> None:
                 (id, exchange, pair, is_open, fee_open, fee_close, open_rate,
                 open_rate_requested, close_rate, close_rate_requested, close_profit,
                 stake_amount, amount, open_date, close_date, open_order_id,
-                stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update,
+                stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
+                stoploss_order_id, stoploss_last_update,
                 max_rate, min_rate, sell_reason, strategy,
                 ticker_interval
                 )
@@ -129,7 +132,9 @@ def check_migrate(engine) -> None:
                 open_rate, {open_rate_requested} open_rate_requested, close_rate,
                 {close_rate_requested} close_rate_requested, close_profit,
                 stake_amount, amount, open_date, close_date, open_order_id,
-                {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss,
+                {stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct,
+                {initial_stop_loss} initial_stop_loss,
+                {initial_stop_loss_pct} initial_stop_loss_pct,
                 {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
                 {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
                 {strategy} strategy, {ticker_interval} ticker_interval
@@ -184,8 +189,12 @@ class Trade(_DECL_BASE):
     open_order_id = Column(String)
     # absolute value of the stop loss
     stop_loss = Column(Float, nullable=True, default=0.0)
+    # percentage value of the stop loss
+    stop_loss_pct = Column(Float, nullable=True, default=0.0)
     # absolute value of the initial stop loss
     initial_stop_loss = Column(Float, nullable=True, default=0.0)
+    # percentage value of the initial stop loss
+    initial_stop_loss_pct = Column(Float, nullable=True, default=0.0)
     # stoploss order id which is on exchange
     stoploss_order_id = Column(String, nullable=True, index=True)
     # last update time of the stoploss order on exchange
@@ -231,13 +240,16 @@ class Trade(_DECL_BASE):
         if not self.stop_loss:
             logger.debug("assigning new stop loss")
             self.stop_loss = new_loss
+            self.stop_loss_pct = stoploss
             self.initial_stop_loss = new_loss
+            self.initial_stop_loss_pct = stoploss
             self.stoploss_last_update = datetime.utcnow()
 
         # evaluate if the stop loss needs to be updated
         else:
             if new_loss > self.stop_loss:  # stop losses only walk up, never down!
                 self.stop_loss = new_loss
+                self.stop_loss_pct = stoploss
                 self.stoploss_last_update = datetime.utcnow()
                 logger.debug("adjusted stop loss")
             else:

From f2599ffe9029acea5a0a08137376c6e894a8fa2b Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Fri, 29 Mar 2019 08:08:29 +0100
Subject: [PATCH 326/457] pct default to None

---
 freqtrade/persistence.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py
index 0b46768c1..5be61393e 100644
--- a/freqtrade/persistence.py
+++ b/freqtrade/persistence.py
@@ -91,9 +91,9 @@ def check_migrate(engine) -> None:
         open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
         close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
         stop_loss = get_column_def(cols, 'stop_loss', '0.0')
-        stop_loss_pct = get_column_def(cols, 'stop_loss_pct', '0.0')
+        stop_loss_pct = get_column_def(cols, 'stop_loss_pct', 'null')
         initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
-        initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', '0.0')
+        initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null')
         stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
         stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
         max_rate = get_column_def(cols, 'max_rate', '0.0')
@@ -190,11 +190,11 @@ class Trade(_DECL_BASE):
     # absolute value of the stop loss
     stop_loss = Column(Float, nullable=True, default=0.0)
     # percentage value of the stop loss
-    stop_loss_pct = Column(Float, nullable=True, default=0.0)
+    stop_loss_pct = Column(Float, nullable=True)
     # absolute value of the initial stop loss
     initial_stop_loss = Column(Float, nullable=True, default=0.0)
     # percentage value of the initial stop loss
-    initial_stop_loss_pct = Column(Float, nullable=True, default=0.0)
+    initial_stop_loss_pct = Column(Float, nullable=True)
     # stoploss order id which is on exchange
     stoploss_order_id = Column(String, nullable=True, index=True)
     # last update time of the stoploss order on exchange

From 82b344db1bd93dcda60c48cbf65c8b2936f4d935 Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Fri, 29 Mar 2019 12:38:05 +0000
Subject: [PATCH 327/457] Update ccxt from 1.18.407 to 1.18.412

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 0fff9dacf..22d3ae485 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-ccxt==1.18.407
+ccxt==1.18.412
 SQLAlchemy==1.3.1
 python-telegram-bot==11.1.0
 arrow==0.13.1

From bb5a310aec09fd9c2f961f32a78fa86bb97bb411 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 29 Mar 2019 20:12:44 +0100
Subject: [PATCH 328/457] Add --logfile argument

---
 freqtrade/arguments.py     |  7 +++++++
 freqtrade/configuration.py | 17 ++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py
index 604386426..8d7dac4bc 100644
--- a/freqtrade/arguments.py
+++ b/freqtrade/arguments.py
@@ -71,6 +71,13 @@ class Arguments(object):
             dest='loglevel',
             default=0,
         )
+        self.parser.add_argument(
+            '--logfile',
+            help='Log to the file specified',
+            dest='logfile',
+            type=str,
+            metavar='FILE'
+        )
         self.parser.add_argument(
             '--version',
             action='version',
diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py
index ba7a0e200..adaf7a59b 100644
--- a/freqtrade/configuration.py
+++ b/freqtrade/configuration.py
@@ -4,7 +4,9 @@ This module contains the configuration class
 import json
 import logging
 import os
+import sys
 from argparse import Namespace
+from logging.handlers import RotatingFileHandler
 from typing import Any, Dict, Optional
 
 import ccxt
@@ -12,8 +14,8 @@ from jsonschema import Draft4Validator, validate
 from jsonschema.exceptions import ValidationError, best_match
 
 from freqtrade import OperationalException, constants
-from freqtrade.state import RunMode
 from freqtrade.misc import deep_merge_dicts
+from freqtrade.state import RunMode
 
 logger = logging.getLogger(__name__)
 
@@ -116,9 +118,22 @@ class Configuration(object):
             config.update({'verbosity': self.args.loglevel})
         else:
             config.update({'verbosity': 0})
+
+        # Log to stdout, not stderr
+        log_handlers = [logging.StreamHandler(sys.stdout)]
+        if 'logfile' in self.args and self.args.logfile:
+            config.update({'logfile': self.args.logfile})
+
+        # Allow setting this as either configuration or argument
+        if 'logfile' in config:
+            log_handlers.append(RotatingFileHandler(config['logfile'],
+                                                    maxBytes=1024 * 1024,  # 1Mb
+                                                    backupCount=10))
+
         logging.basicConfig(
             level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG,
             format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+            handlers=log_handlers
         )
         set_loggers(config['verbosity'])
         logger.info('Verbosity set to %s', config['verbosity'])

From d4ffdaffc2a19d4a5f54a0b1cee6d00a8534e258 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 29 Mar 2019 20:16:41 +0100
Subject: [PATCH 329/457] Correctly add types

---
 freqtrade/configuration.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py
index adaf7a59b..fdd71f2f5 100644
--- a/freqtrade/configuration.py
+++ b/freqtrade/configuration.py
@@ -7,7 +7,7 @@ import os
 import sys
 from argparse import Namespace
 from logging.handlers import RotatingFileHandler
-from typing import Any, Dict, Optional
+from typing import Any, Dict, List, Optional
 
 import ccxt
 from jsonschema import Draft4Validator, validate
@@ -120,7 +120,7 @@ class Configuration(object):
             config.update({'verbosity': 0})
 
         # Log to stdout, not stderr
-        log_handlers = [logging.StreamHandler(sys.stdout)]
+        log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)]
         if 'logfile' in self.args and self.args.logfile:
             config.update({'logfile': self.args.logfile})
 

From e5008fbf9347d5d8234a70afc2f65c1049b695ab Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 29 Mar 2019 20:16:52 +0100
Subject: [PATCH 330/457] Add test for logfile attribute

---
 freqtrade/tests/test_configuration.py | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py
index 21547d205..45e539c2f 100644
--- a/freqtrade/tests/test_configuration.py
+++ b/freqtrade/tests/test_configuration.py
@@ -5,6 +5,7 @@ import logging
 from argparse import Namespace
 from copy import deepcopy
 from unittest.mock import MagicMock
+from pathlib import Path
 
 import pytest
 from jsonschema import Draft4Validator, ValidationError, validate
@@ -547,6 +548,23 @@ def test_set_loggers() -> None:
     assert logging.getLogger('telegram').level is logging.INFO
 
 
+def test_set_logfile(default_conf, mocker):
+    mocker.patch('freqtrade.configuration.open',
+                 mocker.mock_open(read_data=json.dumps(default_conf)))
+
+    arglist = [
+        '--logfile', 'test_file.log',
+    ]
+    args = Arguments(arglist, '').get_parsed_arg()
+    configuration = Configuration(args)
+    validated_conf = configuration.load_config()
+
+    assert validated_conf['logfile'] == "test_file.log"
+    f = Path("test_file.log")
+    assert f.is_file()
+    f.unlink()
+
+
 def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None:
     default_conf['forcebuy_enable'] = True
     mocker.patch('freqtrade.configuration.open', mocker.mock_open(

From 12066411db802e942bd990cb3c461280ed4adb51 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 29 Mar 2019 20:19:40 +0100
Subject: [PATCH 331/457] Update docs with logfile methods

---
 README.md             | 8 +++++---
 docs/bot-usage.md     | 7 ++++---
 docs/configuration.md | 1 +
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index ade62ce94..8f7578561 100644
--- a/README.md
+++ b/README.md
@@ -68,9 +68,9 @@ For any other type of installation please refer to [Installation doc](https://ww
 ### Bot commands
 
 ```
-usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
-                 [--strategy-path PATH] [--dynamic-whitelist [INT]]
-                 [--db-url PATH]
+usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
+                 [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
+                 [--db-url PATH] [--sd-notify]
                  {backtesting,edge,hyperopt} ...
 
 Free, open source crypto trading bot
@@ -84,6 +84,7 @@ positional arguments:
 optional arguments:
   -h, --help            show this help message and exit
   -v, --verbose         Verbose mode (-vv for more, -vvv to get all messages).
+  --logfile FILE        Log to the file specified
   --version             show program's version number and exit
   -c PATH, --config PATH
                         Specify configuration file (default: None). Multiple
@@ -100,6 +101,7 @@ optional arguments:
   --db-url PATH         Override trades database URL, this is useful if
                         dry_run is enabled or in custom deployments (default:
                         None).
+  --sd-notify           Notify systemd service manager.
 ```
 
 ### Telegram RPC commands
diff --git a/docs/bot-usage.md b/docs/bot-usage.md
index 35e4a776d..5ec390d5c 100644
--- a/docs/bot-usage.md
+++ b/docs/bot-usage.md
@@ -6,9 +6,9 @@ This page explains the different parameters of the bot and how to run it.
 ## Bot commands
 
 ```
-usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
-                 [--strategy-path PATH] [--dynamic-whitelist [INT]]
-                 [--db-url PATH]
+usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
+                 [-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
+                 [--db-url PATH] [--sd-notify]
                  {backtesting,edge,hyperopt} ...
 
 Free, open source crypto trading bot
@@ -22,6 +22,7 @@ positional arguments:
 optional arguments:
   -h, --help            show this help message and exit
   -v, --verbose         Verbose mode (-vv for more, -vvv to get all messages).
+  --logfile FILE        Log to the file specified
   --version             show program's version number and exit
   -c PATH, --config PATH
                         Specify configuration file (default: None). Multiple
diff --git a/docs/configuration.md b/docs/configuration.md
index 11b941220..75843ef4a 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -70,6 +70,7 @@ Mandatory Parameters are marked as **Required**.
 | `strategy` | DefaultStrategy | Defines Strategy class to use.
 | `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
 | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
+| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file.
 | `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
 
 ### Parameters in the strategy

From 208832e8471faed4427155c5b6817a6f8295aab8 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Sat, 30 Mar 2019 02:19:43 +0300
Subject: [PATCH 332/457] flake8, mypy resolved

---
 freqtrade/tests/rpc/test_rpc_telegram.py |  1 -
 freqtrade/tests/test_main.py             | 11 ++++++++---
 freqtrade/worker.py                      |  2 +-
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index b44c19688..384cdcfa0 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -14,7 +14,6 @@ from telegram.error import NetworkError
 
 from freqtrade import __version__
 from freqtrade.edge import PairInfo
-from freqtrade.freqtradebot import FreqtradeBot
 from freqtrade.persistence import Trade
 from freqtrade.rpc import RPCMessageType
 from freqtrade.rpc.telegram import Telegram, authorized_only
diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py
index 96cf2834d..fc5d2e378 100644
--- a/freqtrade/tests/test_main.py
+++ b/freqtrade/tests/test_main.py
@@ -8,7 +8,6 @@ import pytest
 from freqtrade import OperationalException
 from freqtrade.arguments import Arguments
 from freqtrade.worker import Worker
-from freqtrade.freqtradebot import FreqtradeBot
 from freqtrade.main import main
 from freqtrade.state import State
 from freqtrade.tests.conftest import log_has, patch_exchange
@@ -85,7 +84,10 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
 def test_main_operational_exception(mocker, default_conf, caplog) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())
-    mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=OperationalException('Oh snap!')))
+    mocker.patch(
+        'freqtrade.worker.Worker._worker',
+        MagicMock(side_effect=OperationalException('Oh snap!'))
+    )
     mocker.patch(
         'freqtrade.configuration.Configuration._load_config_file',
         lambda *args, **kwargs: default_conf
@@ -128,7 +130,10 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None:
 def test_reconfigure(mocker, default_conf) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())
-    mocker.patch('freqtrade.worker.Worker._worker', MagicMock(side_effect=OperationalException('Oh snap!')))
+    mocker.patch(
+        'freqtrade.worker.Worker._worker',
+        MagicMock(side_effect=OperationalException('Oh snap!'))
+    )
     mocker.patch(
         'freqtrade.configuration.Configuration._load_config_file',
         lambda *args, **kwargs: default_conf
diff --git a/freqtrade/worker.py b/freqtrade/worker.py
index a6fba1460..f32e6ff49 100755
--- a/freqtrade/worker.py
+++ b/freqtrade/worker.py
@@ -23,7 +23,7 @@ class Worker(object):
     Freqtradebot worker class
     """
 
-    def __init__(self, args: Optional[Namespace] = None, config = None) -> None:
+    def __init__(self, args: Namespace, config=None) -> None:
         """
         Init all variables and objects the bot needs to work
         """

From 44142706c3cb73cb51b5dd821daf505269b802c8 Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Sat, 30 Mar 2019 12:38:03 +0000
Subject: [PATCH 333/457] Update ccxt from 1.18.412 to 1.18.415

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 22d3ae485..b2dee3542 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-ccxt==1.18.412
+ccxt==1.18.415
 SQLAlchemy==1.3.1
 python-telegram-bot==11.1.0
 arrow==0.13.1

From 40c0b4ef2e4315df1d8fc5baa0937c66c929e147 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 30 Mar 2019 13:47:09 +0100
Subject: [PATCH 334/457] Autopatch coinmarketcap

---
 freqtrade/tests/conftest.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py
index c0f8e49b7..6478706a4 100644
--- a/freqtrade/tests/conftest.py
+++ b/freqtrade/tests/conftest.py
@@ -95,7 +95,6 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
     :param config: Config to pass to the bot
     :return: None
     """
-    patch_coinmarketcap(mocker, {'price_usd': 12345.0})
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
     mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
     patch_exchange(mocker, None)
@@ -105,7 +104,8 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
     return FreqtradeBot(config)
 
 
-def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
+@pytest.fixture(autouse=True)
+def patch_coinmarketcap(mocker) -> None:
     """
     Mocker to coinmarketcap to speed up tests
     :param mocker: mocker to patch coinmarketcap class

From e98c0621d33988c354bf2400052f1daf1ee7ac2a Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 30 Mar 2019 13:47:30 +0100
Subject: [PATCH 335/457] We don't need to call patch_coinmarketcap each time.

---
 freqtrade/tests/rpc/test_fiat_convert.py | 14 +-------------
 1 file changed, 1 insertion(+), 13 deletions(-)

diff --git a/freqtrade/tests/rpc/test_fiat_convert.py b/freqtrade/tests/rpc/test_fiat_convert.py
index fbc942432..66870efcc 100644
--- a/freqtrade/tests/rpc/test_fiat_convert.py
+++ b/freqtrade/tests/rpc/test_fiat_convert.py
@@ -8,7 +8,7 @@ import pytest
 from requests.exceptions import RequestException
 
 from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
-from freqtrade.tests.conftest import log_has, patch_coinmarketcap
+from freqtrade.tests.conftest import log_has
 
 
 def test_pair_convertion_object():
@@ -40,7 +40,6 @@ def test_pair_convertion_object():
 
 
 def test_fiat_convert_is_supported(mocker):
-    patch_coinmarketcap(mocker)
     fiat_convert = CryptoToFiatConverter()
     assert fiat_convert._is_supported_fiat(fiat='USD') is True
     assert fiat_convert._is_supported_fiat(fiat='usd') is True
@@ -49,7 +48,6 @@ def test_fiat_convert_is_supported(mocker):
 
 
 def test_fiat_convert_add_pair(mocker):
-    patch_coinmarketcap(mocker)
 
     fiat_convert = CryptoToFiatConverter()
 
@@ -72,8 +70,6 @@ def test_fiat_convert_add_pair(mocker):
 
 
 def test_fiat_convert_find_price(mocker):
-    patch_coinmarketcap(mocker)
-
     fiat_convert = CryptoToFiatConverter()
 
     with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
@@ -93,15 +89,12 @@ def test_fiat_convert_find_price(mocker):
 
 def test_fiat_convert_unsupported_crypto(mocker, caplog):
     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
-    patch_coinmarketcap(mocker)
     fiat_convert = CryptoToFiatConverter()
     assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
     assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
 
 
 def test_fiat_convert_get_price(mocker):
-    patch_coinmarketcap(mocker)
-
     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
                  return_value=28000.0)
 
@@ -134,21 +127,18 @@ def test_fiat_convert_get_price(mocker):
 
 
 def test_fiat_convert_same_currencies(mocker):
-    patch_coinmarketcap(mocker)
     fiat_convert = CryptoToFiatConverter()
 
     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0
 
 
 def test_fiat_convert_two_FIAT(mocker):
-    patch_coinmarketcap(mocker)
     fiat_convert = CryptoToFiatConverter()
 
     assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0
 
 
 def test_loadcryptomap(mocker):
-    patch_coinmarketcap(mocker)
 
     fiat_convert = CryptoToFiatConverter()
     assert len(fiat_convert._cryptomap) == 2
@@ -174,7 +164,6 @@ def test_fiat_init_network_exception(mocker):
 
 def test_fiat_convert_without_network(mocker):
     # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
-    patch_coinmarketcap(mocker)
 
     fiat_convert = CryptoToFiatConverter()
 
@@ -205,7 +194,6 @@ def test_fiat_invalid_response(mocker, caplog):
 
 
 def test_convert_amount(mocker):
-    patch_coinmarketcap(mocker)
     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
 
     fiat_convert = CryptoToFiatConverter()

From 87a296f728487c9bcef763bd98a46e760b07d954 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 30 Mar 2019 13:47:47 +0100
Subject: [PATCH 336/457] No need to call patch_coinmarketcap each tim

---
 freqtrade/tests/rpc/test_rpc.py          | 21 +--------------
 freqtrade/tests/rpc/test_rpc_telegram.py | 34 +-----------------------
 2 files changed, 2 insertions(+), 53 deletions(-)

diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py
index df89c03d4..627768ec2 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -14,7 +14,7 @@ from freqtrade.persistence import Trade
 from freqtrade.rpc import RPC, RPCException
 from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
 from freqtrade.state import State
-from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange
+from freqtrade.tests.conftest import patch_exchange
 from freqtrade.tests.test_freqtradebot import patch_get_signal
 
 
@@ -28,7 +28,6 @@ def prec_satoshi(a, b) -> float:
 
 # Unit tests
 def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
-    patch_coinmarketcap(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -86,7 +85,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
 
 
 def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
@@ -122,7 +120,6 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
 
 def test_rpc_daily_profit(default_conf, update, ticker, fee,
                           limit_buy_order, limit_sell_order, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
@@ -175,7 +172,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
         'freqtrade.rpc.fiat_convert.Market',
         ticker=MagicMock(return_value={'price_usd': 15000.0}),
     )
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -333,7 +329,6 @@ def test_rpc_balance_handle(default_conf, mocker):
         'freqtrade.rpc.fiat_convert.Market',
         ticker=MagicMock(return_value={'price_usd': 15000.0}),
     )
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -363,7 +358,6 @@ def test_rpc_balance_handle(default_conf, mocker):
 
 
 def test_rpc_start(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
@@ -386,7 +380,6 @@ def test_rpc_start(mocker, default_conf) -> None:
 
 
 def test_rpc_stop(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
@@ -410,7 +403,6 @@ def test_rpc_stop(mocker, default_conf) -> None:
 
 
 def test_rpc_stopbuy(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
@@ -430,7 +422,6 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
 
 
 def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
@@ -531,7 +522,6 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
 
 def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
                             limit_sell_order, markets, mocker) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
@@ -567,7 +557,6 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
 
 
 def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch.multiple(
@@ -595,7 +584,6 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
 
 def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None:
     default_conf['forcebuy_enable'] = True
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
@@ -644,7 +632,6 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
 def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
     default_conf['forcebuy_enable'] = True
     default_conf['initial_state'] = 'stopped'
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
@@ -657,7 +644,6 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
 
 
 def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
@@ -670,7 +656,6 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
 
 
 def test_rpc_whitelist(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
@@ -682,7 +667,6 @@ def test_rpc_whitelist(mocker, default_conf) -> None:
 
 
 def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     default_conf['pairlist'] = {'method': 'VolumePairList',
                                 'config': {'number_assets': 4}
@@ -699,7 +683,6 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
 
 
 def test_rpc_blacklist(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
@@ -719,7 +702,6 @@ def test_rpc_blacklist(mocker, default_conf) -> None:
 
 
 def test_rpc_edge_disabled(mocker, default_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     freqtradebot = FreqtradeBot(default_conf)
@@ -729,7 +711,6 @@ def test_rpc_edge_disabled(mocker, default_conf) -> None:
 
 
 def test_rpc_edge_enabled(mocker, edge_conf) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index 03a6442e5..fb2d71d4f 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -20,8 +20,7 @@ from freqtrade.rpc import RPCMessageType
 from freqtrade.rpc.telegram import Telegram, authorized_only
 from freqtrade.state import State
 from freqtrade.strategy.interface import SellType
-from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
-                                      patch_coinmarketcap, patch_exchange)
+from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange)
 from freqtrade.tests.test_freqtradebot import patch_get_signal
 
 
@@ -90,7 +89,6 @@ def test_cleanup(default_conf, mocker) -> None:
 
 
 def test_authorized_only(default_conf, mocker, caplog) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker, None)
 
     chat = Chat(0, 0)
@@ -118,7 +116,6 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
 
 
 def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker, None)
     chat = Chat(0xdeadbeef, 0)
     update = Update(randint(1, 100))
@@ -145,7 +142,6 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
 
 
 def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
 
     update = Update(randint(1, 100))
@@ -178,7 +174,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
     default_conf['telegram']['enabled'] = False
     default_conf['telegram']['chat_id'] = 123
 
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -227,7 +222,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
 
 
 def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -280,7 +274,6 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
 
 
 def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -332,7 +325,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
 
 def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
                       limit_sell_order, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     patch_exchange(mocker)
     mocker.patch(
         'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
@@ -403,7 +395,6 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
 
 
 def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -439,7 +430,6 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
 
 def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
                        limit_buy_order, limit_sell_order, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
     mocker.patch.multiple(
@@ -544,7 +534,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
             'last': 0.1,
         }
 
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance)
     mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
 
@@ -631,7 +620,6 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
 
 
 def test_stop_handle(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -651,7 +639,6 @@ def test_stop_handle(default_conf, update, mocker) -> None:
 
 
 def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -671,7 +658,6 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
 
 
 def test_stopbuy_handle(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -691,7 +677,6 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None:
 
 
 def test_reload_conf_handle(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -712,7 +697,6 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None:
 
 def test_forcesell_handle(default_conf, update, ticker, fee,
                           ticker_sell_up, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
@@ -762,7 +746,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
 
 def test_forcesell_down_handle(default_conf, update, ticker, fee,
                                ticker_sell_down, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
                  return_value=15000.0)
     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
@@ -816,7 +799,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
 
 
 def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
                  return_value=15000.0)
@@ -862,7 +844,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
 
 
 def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
                  return_value=15000.0)
     msg_mock = MagicMock()
@@ -902,7 +883,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
 
 
 def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
     mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock())
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
@@ -939,7 +919,6 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
 
 
 def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> None:
-    patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
     mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
     rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock())
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
@@ -962,7 +941,6 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non
 
 def test_performance_handle(default_conf, update, ticker, fee,
                             limit_buy_order, limit_sell_order, markets, mocker) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
@@ -1002,7 +980,6 @@ def test_performance_handle(default_conf, update, ticker, fee,
 
 
 def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
-    patch_coinmarketcap(mocker)
     patch_exchange(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
@@ -1043,7 +1020,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
 
 
 def test_whitelist_static(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -1061,7 +1037,6 @@ def test_whitelist_static(default_conf, update, mocker) -> None:
 
 
 def test_whitelist_dynamic(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -1083,7 +1058,6 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None:
 
 
 def test_blacklist_static(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -1108,7 +1082,6 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
 
 
 def test_edge_disabled(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -1126,7 +1099,6 @@ def test_edge_disabled(default_conf, update, mocker) -> None:
 
 
 def test_edge_enabled(edge_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
         return_value={
@@ -1150,7 +1122,6 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
 
 
 def test_help_handle(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -1167,7 +1138,6 @@ def test_help_handle(default_conf, update, mocker) -> None:
 
 
 def test_version_handle(default_conf, update, mocker) -> None:
-    patch_coinmarketcap(mocker)
     msg_mock = MagicMock()
     mocker.patch.multiple(
         'freqtrade.rpc.telegram.Telegram',
@@ -1394,7 +1364,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
 
 
 def test__send_msg(default_conf, mocker) -> None:
-    patch_coinmarketcap(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()
     freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@@ -1406,7 +1375,6 @@ def test__send_msg(default_conf, mocker) -> None:
 
 
 def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
-    patch_coinmarketcap(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()
     bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))

From 1a61bf7bff0003b7632ef20609aff3e83e99bb87 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 30 Mar 2019 13:48:30 +0100
Subject: [PATCH 337/457] sort imports

---
 freqtrade/tests/conftest.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py
index 6478706a4..cba754199 100644
--- a/freqtrade/tests/conftest.py
+++ b/freqtrade/tests/conftest.py
@@ -4,7 +4,6 @@ import logging
 import re
 from datetime import datetime
 from functools import reduce
-from typing import Dict, Optional
 from unittest.mock import MagicMock, PropertyMock
 
 import arrow
@@ -13,8 +12,8 @@ from telegram import Chat, Message, Update
 
 from freqtrade import constants
 from freqtrade.data.converter import parse_ticker_dataframe
-from freqtrade.exchange import Exchange
 from freqtrade.edge import Edge, PairInfo
+from freqtrade.exchange import Exchange
 from freqtrade.freqtradebot import FreqtradeBot
 from freqtrade.resolvers import ExchangeResolver
 

From 06144a1fc4ea15f107ecbe21bca9382d1e8193c6 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Sat, 30 Mar 2019 23:33:52 +0300
Subject: [PATCH 338/457] Wording in a comment

---
 freqtrade/worker.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/freqtrade/worker.py b/freqtrade/worker.py
index f32e6ff49..e5ca36035 100755
--- a/freqtrade/worker.py
+++ b/freqtrade/worker.py
@@ -159,7 +159,7 @@ class Worker(object):
     def _reconfigure(self):
         """
         Cleans up current freqtradebot instance, reloads the configuration and
-        returns the new instance
+        replaces it with the new instance
         """
         # Tell systemd that we initiated reconfiguration
         if self._sd_notify:

From 9b38c04579d22bc371a6ff417a21397ae4de05ac Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Sun, 31 Mar 2019 13:15:35 +0200
Subject: [PATCH 339/457] negating SL pct and adding tests

---
 freqtrade/persistence.py            |  6 +++---
 freqtrade/tests/test_persistence.py | 10 ++++++++++
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py
index 5be61393e..5a18a922a 100644
--- a/freqtrade/persistence.py
+++ b/freqtrade/persistence.py
@@ -240,16 +240,16 @@ class Trade(_DECL_BASE):
         if not self.stop_loss:
             logger.debug("assigning new stop loss")
             self.stop_loss = new_loss
-            self.stop_loss_pct = stoploss
+            self.stop_loss_pct = -1 * abs(stoploss)
             self.initial_stop_loss = new_loss
-            self.initial_stop_loss_pct = stoploss
+            self.initial_stop_loss_pct = -1 * abs(stoploss)
             self.stoploss_last_update = datetime.utcnow()
 
         # evaluate if the stop loss needs to be updated
         else:
             if new_loss > self.stop_loss:  # stop losses only walk up, never down!
                 self.stop_loss = new_loss
-                self.stop_loss_pct = stoploss
+                self.stop_loss_pct = -1 * abs(stoploss)
                 self.stoploss_last_update = datetime.utcnow()
                 logger.debug("adjusted stop loss")
             else:
diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py
index 042237ce7..f57a466e3 100644
--- a/freqtrade/tests/test_persistence.py
+++ b/freqtrade/tests/test_persistence.py
@@ -599,32 +599,42 @@ def test_adjust_stop_loss(fee):
 
     trade.adjust_stop_loss(trade.open_rate, 0.05, True)
     assert trade.stop_loss == 0.95
+    assert trade.stop_loss_pct == -0.05
     assert trade.initial_stop_loss == 0.95
+    assert trade.initial_stop_loss_pct == -0.05
 
     # Get percent of profit with a lower rate
     trade.adjust_stop_loss(0.96, 0.05)
     assert trade.stop_loss == 0.95
+    assert trade.stop_loss_pct == -0.05
     assert trade.initial_stop_loss == 0.95
+    assert trade.initial_stop_loss_pct == -0.05
 
     # Get percent of profit with a custom rate (Higher than open rate)
     trade.adjust_stop_loss(1.3, -0.1)
     assert round(trade.stop_loss, 8) == 1.17
+    assert trade.stop_loss_pct == -0.1
     assert trade.initial_stop_loss == 0.95
+    assert trade.initial_stop_loss_pct == -0.05
 
     # current rate lower again ... should not change
     trade.adjust_stop_loss(1.2, 0.1)
     assert round(trade.stop_loss, 8) == 1.17
     assert trade.initial_stop_loss == 0.95
+    assert trade.initial_stop_loss_pct == -0.05
 
     # current rate higher... should raise stoploss
     trade.adjust_stop_loss(1.4, 0.1)
     assert round(trade.stop_loss, 8) == 1.26
     assert trade.initial_stop_loss == 0.95
+    assert trade.initial_stop_loss_pct == -0.05
 
     #  Initial is true but stop_loss set - so doesn't do anything
     trade.adjust_stop_loss(1.7, 0.1, True)
     assert round(trade.stop_loss, 8) == 1.26
     assert trade.initial_stop_loss == 0.95
+    assert trade.initial_stop_loss_pct == -0.05
+    assert trade.stop_loss_pct == -0.1
 
 
 def test_adjust_min_max_rates(fee):

From 707a5fca919d1561413733f3182357f7cffdd8d8 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 13:30:22 +0200
Subject: [PATCH 340/457] ifix typos in full_json_example

---
 config_full.json.example | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/config_full.json.example b/config_full.json.example
index 357a8f525..58d3d3ac6 100644
--- a/config_full.json.example
+++ b/config_full.json.example
@@ -39,12 +39,12 @@
         "buy": "limit",
         "sell": "limit",
         "stoploss": "market",
-        "stoploss_on_exchange": "false",
+        "stoploss_on_exchange": false,
         "stoploss_on_exchange_interval": 60
     },
     "order_time_in_force": {
         "buy": "gtc",
-        "sell": "gtc",
+        "sell": "gtc"
     },
     "pairlist": {
         "method": "VolumePairList",

From 93229fc54b840fd7f6643b59072270004ae607ac Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Sun, 31 Mar 2019 12:38:03 +0000
Subject: [PATCH 341/457] Update ccxt from 1.18.415 to 1.18.418

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index b2dee3542..6e21e879d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-ccxt==1.18.415
+ccxt==1.18.418
 SQLAlchemy==1.3.1
 python-telegram-bot==11.1.0
 arrow==0.13.1

From c28a0374f15e6fbd2660a1dc1a08daebf8f91d76 Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Sun, 31 Mar 2019 12:38:04 +0000
Subject: [PATCH 342/457] Update pytest-mock from 1.10.2 to 1.10.3

---
 requirements-dev.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-dev.txt b/requirements-dev.txt
index 56d7964c3..0592b99e8 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -5,7 +5,7 @@ flake8==3.7.7
 flake8-type-annotations==0.1.0
 flake8-tidy-imports==2.0.0
 pytest==4.3.1
-pytest-mock==1.10.2
+pytest-mock==1.10.3
 pytest-asyncio==0.10.0
 pytest-cov==2.6.1
 coveralls==1.7.0

From 4fa736114c1321e274803a4235d08c7e684f0043 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 15:30:22 +0200
Subject: [PATCH 343/457] Don't set order_id to none here - it's used in
 "update_open_order".

should fix bugs observed in #1371 connected to stoploss
---
 freqtrade/freqtradebot.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 6f1fb2c99..291b74913 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -472,7 +472,6 @@ class FreqtradeBot(object):
             stake_amount = order['cost']
             amount = order['amount']
             buy_limit_filled_price = order['price']
-            order_id = None
 
         self.rpc.send_msg({
             'type': RPCMessageType.BUY_NOTIFICATION,

From 8f4cca47e9a31d08015bc0093d252b4a202f80e5 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 15:39:41 +0200
Subject: [PATCH 344/457] Refactor update_open_order into it's own function

---
 freqtrade/freqtradebot.py | 41 ++++++++++++++++++++++-----------------
 1 file changed, 23 insertions(+), 18 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 291b74913..f78a9078e 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -530,24 +530,7 @@ class FreqtradeBot(object):
         :return: True if executed
         """
         try:
-            # Get order details for actual price per unit
-            if trade.open_order_id:
-                # Update trade with order values
-                logger.info('Found open order for %s', trade)
-                order = self.exchange.get_order(trade.open_order_id, trade.pair)
-                # Try update amount (binance-fix)
-                try:
-                    new_amount = self.get_real_amount(trade, order)
-                    if order['amount'] != new_amount:
-                        order['amount'] = new_amount
-                        # Fee was applied, so set to 0
-                        trade.fee_open = 0
-
-                except OperationalException as exception:
-                    logger.warning("Could not update trade amount: %s", exception)
-
-                # This handles both buy and sell orders!
-                trade.update(order)
+            self.update_open_order(trade)
 
             if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open:
                 result = self.handle_stoploss_on_exchange(trade)
@@ -612,6 +595,28 @@ class FreqtradeBot(object):
                         f"(from {order_amount} to {real_amount}) from Trades")
         return real_amount
 
+    def update_open_order(self, trade, action_order: dict = None):
+        """
+        Checks trades with open orders and updates the amount if necessary
+        """
+        # Get order details for actual price per unit
+        if trade.open_order_id:
+            # Update trade with order values
+            logger.info('Found open order for %s', trade)
+            order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair)
+            # Try update amount (binance-fix)
+            try:
+                new_amount = self.get_real_amount(trade, order)
+                if order['amount'] != new_amount:
+                    order['amount'] = new_amount
+                    # Fee was applied, so set to 0
+                    trade.fee_open = 0
+
+            except OperationalException as exception:
+                logger.warning("Could not update trade amount: %s", exception)
+
+            trade.update(order)
+
     def get_sell_rate(self, pair: str, refresh: bool) -> float:
         """
         Get sell rate - either using get-ticker bid or first bid based on orderbook

From f11a1b01228801dd5ea5a6877c2f819b2564d236 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 15:40:16 +0200
Subject: [PATCH 345/457] Call update_open_order inline with buy

captures FOK / market orders
---
 freqtrade/freqtradebot.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index f78a9078e..74c74324f 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -500,6 +500,10 @@ class FreqtradeBot(object):
             ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']]
         )
 
+        # Update fees if order is closed already.
+        if order_status == 'closed':
+            self.update_open_order(trade, order)
+
         Trade.session.add(trade)
         Trade.session.flush()
 

From 5c8fbe2c6ff44d36322f3268deabdbe29c6e8779 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 15:41:10 +0200
Subject: [PATCH 346/457] Handle exception for stoploss independently of sell
 order

---
 freqtrade/freqtradebot.py | 67 ++++++++++++++++++++-------------------
 1 file changed, 34 insertions(+), 33 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 74c74324f..a06e146f9 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -691,43 +691,44 @@ class FreqtradeBot(object):
         """
 
         result = False
+        try:
+            # If trade is open and the buy order is fulfilled but there is no stoploss,
+            # then we add a stoploss on exchange
+            if not trade.open_order_id and not trade.stoploss_order_id:
+                if self.edge:
+                    stoploss = self.edge.stoploss(pair=trade.pair)
+                else:
+                    stoploss = self.strategy.stoploss
 
-        # If trade is open and the buy order is fulfilled but there is no stoploss,
-        # then we add a stoploss on exchange
-        if not trade.open_order_id and not trade.stoploss_order_id:
-            if self.edge:
-                stoploss = self.edge.stoploss(pair=trade.pair)
-            else:
-                stoploss = self.strategy.stoploss
+                stop_price = trade.open_rate * (1 + stoploss)
 
-            stop_price = trade.open_rate * (1 + stoploss)
+                # limit price should be less than stop price.
+                # 0.99 is arbitrary here.
+                limit_price = stop_price * 0.99
 
-            # limit price should be less than stop price.
-            # 0.99 is arbitrary here.
-            limit_price = stop_price * 0.99
-
-            stoploss_order_id = self.exchange.stoploss_limit(
-                pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
-            )['id']
-            trade.stoploss_order_id = str(stoploss_order_id)
-            trade.stoploss_last_update = datetime.now()
-
-        # Or the trade open and there is already a stoploss on exchange.
-        # so we check if it is hit ...
-        elif trade.stoploss_order_id:
-            logger.debug('Handling stoploss on exchange %s ...', trade)
-            order = self.exchange.get_order(trade.stoploss_order_id, trade.pair)
-            if order['status'] == 'closed':
-                trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
-                trade.update(order)
-                self.notify_sell(trade)
-                result = True
-            elif self.config.get('trailing_stop', False):
-                # if trailing stoploss is enabled we check if stoploss value has changed
-                # in which case we cancel stoploss order and put another one with new
-                # value immediately
-                self.handle_trailing_stoploss_on_exchange(trade, order)
+                stoploss_order_id = self.exchange.stoploss_limit(
+                    pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
+                )['id']
+                trade.stoploss_order_id = str(stoploss_order_id)
+                trade.stoploss_last_update = datetime.now()
 
+            # Or the trade open and there is already a stoploss on exchange.
+            # so we check if it is hit ...
+            elif trade.stoploss_order_id:
+                logger.debug('Handling stoploss on exchange %s ...', trade)
+                order = self.exchange.get_order(trade.stoploss_order_id, trade.pair)
+                if order['status'] == 'closed':
+                    trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
+                    trade.update(order)
+                    self.notify_sell(trade)
+                    result = True
+                elif self.config.get('trailing_stop', False):
+                    # if trailing stoploss is enabled we check if stoploss value has changed
+                    # in which case we cancel stoploss order and put another one with new
+                    # value immediately
+                    self.handle_trailing_stoploss_on_exchange(trade, order)
+        except DependencyException as exception:
+            logger.warning('Unable to create stoploss order: %s', exception)
         return result
 
     def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):

From e46dac3fbd2846d7a22d1c54ca9ff1a64368d2e8 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 15:45:22 +0200
Subject: [PATCH 347/457] Test stoploss does not raise dependencyexception

---
 freqtrade/tests/test_freqtradebot.py | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index 5bf0bfcbb..e53d26793 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -1031,6 +1031,13 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
     assert trade.stoploss_order_id is None
     assert trade.is_open is False
 
+    mocker.patch(
+        'freqtrade.exchange.Exchange.stoploss_limit',
+        side_effect=DependencyException()
+    )
+    freqtrade.handle_stoploss_on_exchange(trade)
+    assert log_has('Unable to create stoploss order: ', caplog.record_tuples)
+
 
 def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
                                               markets, limit_buy_order, limit_sell_order) -> None:

From b2ad402df4ec8b6a4e35a2abaf368b09fce1bda8 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 15:51:45 +0200
Subject: [PATCH 348/457] Split tests for update-open_order

---
 freqtrade/tests/test_freqtradebot.py | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index e53d26793..38a6df695 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -1306,22 +1306,32 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
     trade.open_order_id = '123'
     trade.open_fee = 0.001
 
+    # Test raise of DependencyException exception
+    mocker.patch(
+        'freqtrade.freqtradebot.FreqtradeBot.update_open_order',
+        side_effect=DependencyException()
+    )
+    freqtrade.process_maybe_execute_sell(trade)
+    assert log_has('Unable to sell trade: ', caplog.record_tuples)
+
+
+def test_update_open_order_exception(mocker, default_conf,
+                                     limit_buy_order, caplog) -> None:
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+    mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
+
+    trade = MagicMock()
+    trade.open_order_id = '123'
+    trade.open_fee = 0.001
+
     # Test raise of OperationalException exception
     mocker.patch(
         'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
         side_effect=OperationalException()
     )
-    freqtrade.process_maybe_execute_sell(trade)
+    freqtrade.update_open_order(trade)
     assert log_has('Could not update trade amount: ', caplog.record_tuples)
 
-    # Test raise of DependencyException exception
-    mocker.patch(
-        'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
-        side_effect=DependencyException()
-    )
-    freqtrade.process_maybe_execute_sell(trade)
-    assert log_has('Unable to sell trade: ', caplog.record_tuples)
-
 
 def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
                       fee, markets, mocker) -> None:

From 0ddafeeabf1322fa679a1c56f77ed709cd3ac4bf Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 16:05:00 +0200
Subject: [PATCH 349/457] Split test for open_orders from maybe_sell

---
 freqtrade/tests/test_freqtradebot.py | 39 +++++++++++++++++++++++++++-
 1 file changed, 38 insertions(+), 1 deletion(-)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index 38a6df695..a3b01c542 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -1291,7 +1291,7 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
     trade.is_open = True
     trade.open_order_id = None
     # Assert we call handle_trade() if trade is feasible for execution
-    assert freqtrade.process_maybe_execute_sell(trade)
+    freqtrade.update_open_order(trade)
 
     regexp = re.compile('Found open order for.*')
     assert filter(regexp.match, caplog.record_tuples)
@@ -1315,6 +1315,43 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
     assert log_has('Unable to sell trade: ', caplog.record_tuples)
 
 
+def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> None:
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
+    mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
+    mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
+                 return_value=limit_buy_order['amount'])
+
+    trade = Trade()
+    # Mock session away
+    Trade.session = MagicMock()
+    trade.open_order_id = '123'
+    trade.open_fee = 0.001
+    freqtrade.update_open_order(trade)
+    # Test amount not modified by fee-logic
+    assert not log_has_re(r'Applying fee to .*', caplog.record_tuples)
+    assert trade.open_order_id is None
+    assert trade.amount == limit_buy_order['amount']
+
+    trade.open_order_id = '123'
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
+    assert trade.amount != 90.81
+    # test amount modified by fee-logic
+    freqtrade.update_open_order(trade)
+    assert trade.amount == 90.81
+    assert trade.open_order_id is None
+
+    trade.is_open = True
+    trade.open_order_id = None
+    # Assert we call handle_trade() if trade is feasible for execution
+    freqtrade.update_open_order(trade)
+
+    regexp = re.compile('Found open order for.*')
+    assert filter(regexp.match, caplog.record_tuples)
+
+
 def test_update_open_order_exception(mocker, default_conf,
                                      limit_buy_order, caplog) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf)

From 19d3a0cbacd21b58589abc4c5100fe0b335bb69f Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 19:41:17 +0200
Subject: [PATCH 350/457] Update comment

---
 freqtrade/freqtradebot.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index a06e146f9..5b1e5625d 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -500,7 +500,7 @@ class FreqtradeBot(object):
             ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']]
         )
 
-        # Update fees if order is closed already.
+        # Update fees if order is closed
         if order_status == 'closed':
             self.update_open_order(trade, order)
 

From 7be90f71d379b235d2cbfa507b5d528bf42756fc Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 31 Mar 2019 19:56:01 +0200
Subject: [PATCH 351/457] Add test as called from execute_buy

---
 freqtrade/tests/test_freqtradebot.py | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index a3b01c542..af8fc3b09 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -1352,6 +1352,26 @@ def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> Non
     assert filter(regexp.match, caplog.record_tuples)
 
 
+def test_update_open_order_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker):
+    mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
+    # get_order should not be called!!
+    mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError))
+    patch_exchange(mocker)
+    Trade.session = MagicMock()
+    amount = sum(x['amount'] for x in trades_for_order)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+    trade = Trade(
+        pair='LTC/ETH',
+        amount=amount,
+        exchange='binance',
+        open_rate=0.245441,
+        open_order_id="123456"
+    )
+    freqtrade.update_open_order(trade, limit_buy_order)
+    assert trade.amount != amount
+    assert trade.amount == limit_buy_order['amount']
+
+
 def test_update_open_order_exception(mocker, default_conf,
                                      limit_buy_order, caplog) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf)

From 7251e5bd625817e823dc4d90f14ecd04f84a4461 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Sun, 31 Mar 2019 23:39:55 +0300
Subject: [PATCH 352/457] bot state moved back to freqtradebot from worker

---
 freqtrade/freqtradebot.py | 21 +++++++--------------
 freqtrade/worker.py       | 24 +++++++-----------------
 2 files changed, 14 insertions(+), 31 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 7c92ac29a..6345dae56 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -22,7 +22,6 @@ from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListReso
 from freqtrade.state import State
 from freqtrade.strategy.interface import SellType, IStrategy
 from freqtrade.wallets import Wallets
-from freqtrade.worker import Worker
 
 
 logger = logging.getLogger(__name__)
@@ -34,7 +33,7 @@ class FreqtradeBot(object):
     This is from here the bot start its logic.
     """
 
-    def __init__(self, config: Dict[str, Any], worker: Optional[Worker] = None) -> None:
+    def __init__(self, config: Dict[str, Any]) -> None:
         """
         Init all variables and objects the bot needs to work
         :param config: configuration dict, you can use Configuration.get_config()
@@ -43,9 +42,11 @@ class FreqtradeBot(object):
 
         logger.info('Starting freqtrade %s', __version__)
 
+        # Init bot state
+        self.state = State.STOPPED
+
         # Init objects
         self.config = config
-        self._worker = worker
 
         self.strategy: IStrategy = StrategyResolver(self.config).strategy
 
@@ -73,17 +74,9 @@ class FreqtradeBot(object):
 
         persistence.init(self.config)
 
-    @property
-    def state(self) -> State:
-        if self._worker is None:
-            raise DependencyException("No Worker is available")
-        return self._worker.state
-
-    @state.setter
-    def state(self, value: State):
-        if self._worker is None:
-            raise DependencyException("No Worker is available")
-        self._worker.state = value
+        # Set initial bot state from config
+        initial_state = self.config.get('initial_state')
+        self.state = State[initial_state.upper()] if initial_state else State.STOPPED
 
     def cleanup(self) -> None:
         """
diff --git a/freqtrade/worker.py b/freqtrade/worker.py
index e5ca36035..2440e7320 100755
--- a/freqtrade/worker.py
+++ b/freqtrade/worker.py
@@ -11,6 +11,7 @@ import sdnotify
 from freqtrade import (constants, OperationalException, TemporaryError,
                        __version__)
 from freqtrade.configuration import Configuration
+from freqtrade.freqtradebot import FreqtradeBot
 from freqtrade.state import State
 from freqtrade.rpc import RPCMessageType
 
@@ -46,19 +47,8 @@ class Worker(object):
             # Load configuration
             self._config = Configuration(self._args, None).get_config()
 
-        # Import freqtradebot here in order to avoid python circular
-        # dependency error, damn!
-        from freqtrade.freqtradebot import FreqtradeBot
-
         # Init the instance of the bot
-        self.freqtrade = FreqtradeBot(self._config, self)
-
-        # Set initial bot state
-        initial_state = self._config.get('initial_state')
-        if initial_state:
-            self._state = State[initial_state.upper()]
-        else:
-            self._state = State.STOPPED
+        self.freqtrade = FreqtradeBot(self._config)
 
         self._throttle_secs = self._config.get('internals', {}).get(
             'process_throttle_secs',
@@ -70,11 +60,11 @@ class Worker(object):
 
     @property
     def state(self) -> State:
-        return self._state
+        return self.freqtrade.state
 
     @state.setter
     def state(self, value: State):
-        self._state = value
+        self.freqtrade.state = value
 
     def run(self):
         state = None
@@ -89,7 +79,7 @@ class Worker(object):
         :param old_state: the previous service state from the previous call
         :return: current service state
         """
-        state = self._state
+        state = self.freqtrade.state
         if throttle_secs is None:
             throttle_secs = self._throttle_secs
 
@@ -141,7 +131,6 @@ class Worker(object):
         state_changed = False
         try:
             state_changed = self.freqtrade.process()
-
         except TemporaryError as error:
             logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...")
             time.sleep(constants.RETRY_TIMEOUT)
@@ -153,7 +142,8 @@ class Worker(object):
                 'status': f'OperationalException:\n```\n{tb}```{hint}'
             })
             logger.exception('OperationalException. Stopping trader ...')
-            self.state = State.STOPPED
+            self.freqtrade.state = State.STOPPED
+###            state_changed = True
         return state_changed
 
     def _reconfigure(self):

From f0b2798c37b7ad9dbda736cf92217a060ab514d9 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Mon, 1 Apr 2019 14:08:03 +0300
Subject: [PATCH 353/457] fix #1704

---
 freqtrade/freqtradebot.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 6f1fb2c99..c14f0d31a 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -363,7 +363,8 @@ class FreqtradeBot(object):
                 logger.debug('Ignoring %s in pair whitelist', trade.pair)
 
         if not whitelist:
-            raise DependencyException('No currency pairs in whitelist')
+            logger.info("No currency pairs left in whitelist, no trades can be created.")
+            return False
 
         # running get_signal on historical data fetched
         for _pair in whitelist:

From 77d2479c7584954fdd30568756453051d34ac87b Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Mon, 1 Apr 2019 14:08:41 +0300
Subject: [PATCH 354/457] tests adjusted

---
 freqtrade/tests/test_freqtradebot.py | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index e4f0415f7..4e145c354 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -545,8 +545,7 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord
     freqtrade = FreqtradeBot(default_conf)
     patch_get_signal(freqtrade)
 
-    result = freqtrade.create_trade()
-    assert result is False
+    assert not freqtrade.create_trade()
 
 
 def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
@@ -567,7 +566,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
     freqtrade = FreqtradeBot(default_conf)
     patch_get_signal(freqtrade)
 
-    assert freqtrade.create_trade() is False
+    assert not freqtrade.create_trade()
     assert freqtrade._get_trade_stake_amount('ETH/BTC') is None
 
 
@@ -588,9 +587,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, marke
     patch_get_signal(freqtrade)
 
     freqtrade.create_trade()
-
-    with pytest.raises(DependencyException, match=r'.*No currency pairs in whitelist.*'):
-        freqtrade.create_trade()
+    assert not freqtrade.create_trade()
 
 
 def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
@@ -610,9 +607,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
     patch_get_signal(freqtrade)
 
     freqtrade.create_trade()
-
-    with pytest.raises(DependencyException, match=r'.*No currency pairs in whitelist.*'):
-        freqtrade.create_trade()
+    assert not freqtrade.create_trade()
 
 
 def test_create_trade_no_signal(default_conf, fee, mocker) -> None:

From 97b31352c2640909a4e05b330354599767732f1c Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Mon, 1 Apr 2019 12:38:06 +0000
Subject: [PATCH 355/457] Update ccxt from 1.18.418 to 1.18.420

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 6e21e879d..bdad28181 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-ccxt==1.18.418
+ccxt==1.18.420
 SQLAlchemy==1.3.1
 python-telegram-bot==11.1.0
 arrow==0.13.1

From 061f91ba4173d5e4d329c0a5623690a2ceccb00c Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Mon, 1 Apr 2019 12:38:07 +0000
Subject: [PATCH 356/457] Update pytest from 4.3.1 to 4.4.0

---
 requirements-dev.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements-dev.txt b/requirements-dev.txt
index 0592b99e8..69082587a 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -4,7 +4,7 @@
 flake8==3.7.7
 flake8-type-annotations==0.1.0
 flake8-tidy-imports==2.0.0
-pytest==4.3.1
+pytest==4.4.0
 pytest-mock==1.10.3
 pytest-asyncio==0.10.0
 pytest-cov==2.6.1

From ab579587f29e3802f1c038e804214a228a4b1b26 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Mon, 1 Apr 2019 19:13:45 +0200
Subject: [PATCH 357/457] adding percentage to telegram status messages

---
 docs/telegram-usage.md                   | 6 ++----
 freqtrade/rpc/rpc.py                     | 3 +++
 freqtrade/rpc/telegram.py                | 6 ++++--
 freqtrade/tests/rpc/test_rpc.py          | 6 ++++++
 freqtrade/tests/rpc/test_rpc_telegram.py | 3 +++
 5 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index 1ca61e54a..381a47ae9 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -65,16 +65,14 @@ Once all positions are sold, run `/stop` to completely stop the bot.
 
 For each open trade, the bot will send you the following message.
 
-> **Trade ID:** `123`
+> **Trade ID:** `123` `(since 1 days ago)`
 > **Current Pair:** CVC/BTC
 > **Open Since:** `1 days ago`
 > **Amount:** `26.64180098`
 > **Open Rate:** `0.00007489`
-> **Close Rate:** `None`
 > **Current Rate:** `0.00007489`
-> **Close Profit:** `None`
 > **Current Profit:** `12.95%`
-> **Open Order:** `None`
+> **Stoploss:** `0.00007389 (-0.02%)`
 
 ### /status table
 
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 79db08fd3..5308c9d51 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -111,6 +111,9 @@ class RPC(object):
                     close_profit=fmt_close_profit,
                     current_profit=round(current_profit * 100, 2),
                     stop_loss=trade.stop_loss,
+                    stop_loss_pct=trade.stop_loss_pct,
+                    initial_stop_loss=trade.initial_stop_loss,
+                    initial_stop_loss_pct=trade.initial_stop_loss_pct,
                     open_order='({} {} rem={:.8f})'.format(
                       order['type'], order['side'], order['remaining']
                     ) if order else None,
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 7b36e8a1f..ca9b376ec 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -197,7 +197,7 @@ class Telegram(RPC):
             messages = []
             for r in results:
                 lines = [
-                    "*Trade ID:* `{trade_id}` (since `{date}`)",
+                    "*Trade ID:* `{trade_id}` `(since {date})`",
                     "*Current Pair:* {pair}",
                     "*Amount:* `{amount}`",
                     "*Open Rate:* `{open_rate:.8f}`",
@@ -205,7 +205,9 @@ class Telegram(RPC):
                     "*Current Rate:* `{current_rate:.8f}`",
                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
                     "*Current Profit:* `{current_profit:.2f}%`",
-                    "*Stoploss:* `{stop_loss:.8f}`",
+                    "*Initial Stoploss:* `{initial_stop_loss:.8f}` `({initial_stop_loss_pct}%)`"
+                    if r['stop_loss'] != r['initial_stop_loss'] else "",
+                    "*Stoploss:* `{stop_loss:.8f}` `({stop_loss_pct}%)`",
                     "*Open Order:* `{open_order}`" if r['open_order'] else "",
                 ]
                 messages.append("\n".join(filter(None, lines)).format(**r))
diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py
index 627768ec2..b454f9cd8 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -59,6 +59,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         'close_profit': None,
         'current_profit': -0.59,
         'stop_loss': 0.0,
+        'initial_stop_loss': 0.0,
+        'initial_stop_loss_pct': None,
+        'stop_loss_pct': None,
         'open_order': '(limit buy rem=0.00000000)'
     } == results[0]
 
@@ -80,6 +83,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         'close_profit': None,
         'current_profit': ANY,
         'stop_loss': 0.0,
+        'initial_stop_loss': 0.0,
+        'initial_stop_loss_pct': None,
+        'stop_loss_pct': None,
         'open_order': '(limit buy rem=0.00000000)'
     } == results[0]
 
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index fb2d71d4f..b6d12fe41 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -196,7 +196,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
             'amount': 90.99181074,
             'close_profit': None,
             'current_profit': -0.59,
+            'initial_stop_loss': 1.098e-05,
             'stop_loss': 1.099e-05,
+            'initial_stop_loss_pct': -0.05,
+            'stop_loss_pct': -0.01,
             'open_order': '(limit buy rem=0.00000000)'
         }]),
         _status_table=status_table,

From 8546db9dfdccd813fc3e0400fb8cb6aff84bcf1a Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Mon, 1 Apr 2019 20:23:13 +0300
Subject: [PATCH 358/457] wording in the log message

---
 freqtrade/freqtradebot.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index c14f0d31a..e9b8b1956 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -363,7 +363,7 @@ class FreqtradeBot(object):
                 logger.debug('Ignoring %s in pair whitelist', trade.pair)
 
         if not whitelist:
-            logger.info("No currency pairs left in whitelist, no trades can be created.")
+            logger.info("No currency pair left in whitelist, no more trade can be created.")
             return False
 
         # running get_signal on historical data fetched

From a3b0135557bf221760f2f19fe9db323e9f7b2191 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Mon, 1 Apr 2019 19:25:13 +0200
Subject: [PATCH 359/457] documentation added for telegram

---
 docs/bot-usage.md      |  2 +-
 docs/telegram-usage.md | 68 +++++++++++++++++++++---------------------
 2 files changed, 35 insertions(+), 35 deletions(-)

diff --git a/docs/bot-usage.md b/docs/bot-usage.md
index 5ec390d5c..55988985a 100644
--- a/docs/bot-usage.md
+++ b/docs/bot-usage.md
@@ -79,7 +79,7 @@ prevent unintended disclosure of sensitive private data when you publish example
 of your configuration in the project issues or in the Internet.
 
 See more details on this technique with examples in the documentation page on
-[configuration](bot-configuration.md).
+[configuration](configuration.md).
 
 ### How to use **--strategy**?
 
diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index 381a47ae9..4cc8eaa5c 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -65,14 +65,14 @@ Once all positions are sold, run `/stop` to completely stop the bot.
 
 For each open trade, the bot will send you the following message.
 
-> **Trade ID:** `123` `(since 1 days ago)`
-> **Current Pair:** CVC/BTC
-> **Open Since:** `1 days ago`
-> **Amount:** `26.64180098`
-> **Open Rate:** `0.00007489`
-> **Current Rate:** `0.00007489`
-> **Current Profit:** `12.95%`
-> **Stoploss:** `0.00007389 (-0.02%)`
+> **Trade ID:** `123` `(since 1 days ago)`  
+> **Current Pair:** CVC/BTC  
+> **Open Since:** `1 days ago`  
+> **Amount:** `26.64180098`  
+> **Open Rate:** `0.00007489`  
+> **Current Rate:** `0.00007489`  
+> **Current Profit:** `12.95%`  
+> **Stoploss:** `0.00007389 (-0.02%)`  
 
 ### /status table
 
@@ -97,18 +97,18 @@ current    max
 
 Return a summary of your profit/loss and performance.
 
-> **ROI:** Close trades
->   ∙ `0.00485701 BTC (258.45%)`
->   ∙ `62.968 USD`
-> **ROI:** All trades
->   ∙ `0.00255280 BTC (143.43%)`
->   ∙ `33.095 EUR`
->
-> **Total Trade Count:** `138`
-> **First Trade opened:** `3 days ago`
-> **Latest Trade opened:** `2 minutes ago`
-> **Avg. Duration:** `2:33:45`
-> **Best Performing:** `PAY/BTC: 50.23%`
+> **ROI:** Close trades  
+>   ∙ `0.00485701 BTC (258.45%)`  
+>   ∙ `62.968 USD`  
+> **ROI:** All trades  
+>   ∙ `0.00255280 BTC (143.43%)`  
+>   ∙ `33.095 EUR`  
+>  
+> **Total Trade Count:** `138`  
+> **First Trade opened:** `3 days ago`  
+> **Latest Trade opened:** `2 minutes ago`  
+> **Avg. Duration:** `2:33:45`  
+> **Best Performing:** `PAY/BTC: 50.23%`  
 
 ### /forcesell 
 
@@ -126,26 +126,26 @@ Note that for this to work, `forcebuy_enable` needs to be set to true.
 
 Return the performance of each crypto-currency the bot has sold.
 > Performance:
-> 1. `RCN/BTC 57.77%`
-> 2. `PAY/BTC 56.91%`
-> 3. `VIB/BTC 47.07%`
-> 4. `SALT/BTC 30.24%`
-> 5. `STORJ/BTC 27.24%`
-> ...
+> 1. `RCN/BTC 57.77%`  
+> 2. `PAY/BTC 56.91%`  
+> 3. `VIB/BTC 47.07%`  
+> 4. `SALT/BTC 30.24%`  
+> 5. `STORJ/BTC 27.24%`  
+> ...  
 
 ### /balance
 
 Return the balance of all crypto-currency your have on the exchange.
 
-> **Currency:** BTC
-> **Available:** 3.05890234
-> **Balance:** 3.05890234
-> **Pending:** 0.0
+> **Currency:** BTC  
+> **Available:** 3.05890234  
+> **Balance:** 3.05890234  
+> **Pending:** 0.0  
 
-> **Currency:** CVC
-> **Available:** 86.64180098
-> **Balance:** 86.64180098
-> **Pending:** 0.0
+> **Currency:** CVC  
+> **Available:** 86.64180098  
+> **Balance:** 86.64180098  
+> **Pending:** 0.0  
 
 ### /daily 
 

From 34b40500c33dbddbd60f45ae9e8dcf4e619b0b18 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Mon, 1 Apr 2019 20:45:59 +0300
Subject: [PATCH 360/457] Check whitelist fetched from config for emptiness

---
 freqtrade/freqtradebot.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index e9b8b1956..42013c85f 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -79,6 +79,9 @@ class FreqtradeBot(object):
             self.config.get('edge', {}).get('enabled', False) else None
 
         self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist']
+        if not self.active_pair_whitelist:
+            raise DependencyException('Whitelist is empty.')
+
         self._init_modules()
 
         # Tell the systemd that we completed initialization phase

From ab0e657d7711bdb4a1ff7b60652a4e2030b8cbc3 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Mon, 1 Apr 2019 21:36:53 +0300
Subject: [PATCH 361/457] Check for empty whitelist moved to _process()

---
 freqtrade/freqtradebot.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 42013c85f..e5400c1d0 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -79,8 +79,6 @@ class FreqtradeBot(object):
             self.config.get('edge', {}).get('enabled', False) else None
 
         self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist']
-        if not self.active_pair_whitelist:
-            raise DependencyException('Whitelist is empty.')
 
         self._init_modules()
 
@@ -198,6 +196,9 @@ class FreqtradeBot(object):
             # Refresh whitelist
             self.pairlists.refresh_pairlist()
             self.active_pair_whitelist = self.pairlists.whitelist
+            if not self.active_pair_whitelist:
+                logger.warning('Whitelist is empty.')
+                return False
 
             # Calculating Edge positioning
             if self.edge:

From 0cfdce0d5e69a8b79c95bbac839ed37583dcd9b8 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Tue, 2 Apr 2019 07:12:48 +0200
Subject: [PATCH 362/457] Update function name from update_open_order to
 update_trade_state

---
 freqtrade/freqtradebot.py            |  6 +++---
 freqtrade/tests/test_freqtradebot.py | 28 ++++++++++------------------
 2 files changed, 13 insertions(+), 21 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 5b1e5625d..55ef6f611 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -502,7 +502,7 @@ class FreqtradeBot(object):
 
         # Update fees if order is closed
         if order_status == 'closed':
-            self.update_open_order(trade, order)
+            self.update_trade_state(trade, order)
 
         Trade.session.add(trade)
         Trade.session.flush()
@@ -534,7 +534,7 @@ class FreqtradeBot(object):
         :return: True if executed
         """
         try:
-            self.update_open_order(trade)
+            self.update_trade_state(trade)
 
             if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open:
                 result = self.handle_stoploss_on_exchange(trade)
@@ -599,7 +599,7 @@ class FreqtradeBot(object):
                         f"(from {order_amount} to {real_amount}) from Trades")
         return real_amount
 
-    def update_open_order(self, trade, action_order: dict = None):
+    def update_trade_state(self, trade, action_order: dict = None):
         """
         Checks trades with open orders and updates the amount if necessary
         """
diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index af8fc3b09..416a085ad 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -1288,14 +1288,6 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
     # test amount modified by fee-logic
     assert not freqtrade.process_maybe_execute_sell(trade)
 
-    trade.is_open = True
-    trade.open_order_id = None
-    # Assert we call handle_trade() if trade is feasible for execution
-    freqtrade.update_open_order(trade)
-
-    regexp = re.compile('Found open order for.*')
-    assert filter(regexp.match, caplog.record_tuples)
-
 
 def test_process_maybe_execute_sell_exception(mocker, default_conf,
                                               limit_buy_order, caplog) -> None:
@@ -1308,14 +1300,14 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
 
     # Test raise of DependencyException exception
     mocker.patch(
-        'freqtrade.freqtradebot.FreqtradeBot.update_open_order',
+        'freqtrade.freqtradebot.FreqtradeBot.update_trade_state',
         side_effect=DependencyException()
     )
     freqtrade.process_maybe_execute_sell(trade)
     assert log_has('Unable to sell trade: ', caplog.record_tuples)
 
 
-def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> None:
+def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
@@ -1329,7 +1321,7 @@ def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> Non
     Trade.session = MagicMock()
     trade.open_order_id = '123'
     trade.open_fee = 0.001
-    freqtrade.update_open_order(trade)
+    freqtrade.update_trade_state(trade)
     # Test amount not modified by fee-logic
     assert not log_has_re(r'Applying fee to .*', caplog.record_tuples)
     assert trade.open_order_id is None
@@ -1339,20 +1331,20 @@ def test_update_open_order(mocker, default_conf, limit_buy_order, caplog) -> Non
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
     assert trade.amount != 90.81
     # test amount modified by fee-logic
-    freqtrade.update_open_order(trade)
+    freqtrade.update_trade_state(trade)
     assert trade.amount == 90.81
     assert trade.open_order_id is None
 
     trade.is_open = True
     trade.open_order_id = None
     # Assert we call handle_trade() if trade is feasible for execution
-    freqtrade.update_open_order(trade)
+    freqtrade.update_trade_state(trade)
 
     regexp = re.compile('Found open order for.*')
     assert filter(regexp.match, caplog.record_tuples)
 
 
-def test_update_open_order_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker):
+def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, mocker):
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     # get_order should not be called!!
     mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError))
@@ -1367,13 +1359,13 @@ def test_update_open_order_withorderdict(default_conf, trades_for_order, limit_b
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade.update_open_order(trade, limit_buy_order)
+    freqtrade.update_trade_state(trade, limit_buy_order)
     assert trade.amount != amount
     assert trade.amount == limit_buy_order['amount']
 
 
-def test_update_open_order_exception(mocker, default_conf,
-                                     limit_buy_order, caplog) -> None:
+def test_update_trade_state_exception(mocker, default_conf,
+                                      limit_buy_order, caplog) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf)
     mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
 
@@ -1386,7 +1378,7 @@ def test_update_open_order_exception(mocker, default_conf,
         'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
         side_effect=OperationalException()
     )
-    freqtrade.update_open_order(trade)
+    freqtrade.update_trade_state(trade)
     assert log_has('Could not update trade amount: ', caplog.record_tuples)
 
 

From b9b76977b69c3e846944517eb86d57d72023f041 Mon Sep 17 00:00:00 2001
From: pyup-bot 
Date: Tue, 2 Apr 2019 12:38:06 +0000
Subject: [PATCH 363/457] Update ccxt from 1.18.420 to 1.18.425

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index bdad28181..83d77b693 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-ccxt==1.18.420
+ccxt==1.18.425
 SQLAlchemy==1.3.1
 python-telegram-bot==11.1.0
 arrow==0.13.1

From 389feda65f6b25eb1344a3f37217793fc2a00129 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Tue, 2 Apr 2019 18:25:17 +0200
Subject: [PATCH 364/457] Invalid order exception added

---
 freqtrade/__init__.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py
index 0d1ae9c26..30fed8c53 100644
--- a/freqtrade/__init__.py
+++ b/freqtrade/__init__.py
@@ -17,6 +17,14 @@ class OperationalException(BaseException):
     """
 
 
+class InvalidOrder(BaseException):
+    """
+    This is returned when the order is not valid. Example:
+    If stoploss on exchange order is hit, then trying to cancel the order
+    should return this exception.
+    """
+
+
 class TemporaryError(BaseException):
     """
     Temporary network or exchange related error.

From 99d256422e24fe39c7b3f0642dcec074b100daf3 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Tue, 2 Apr 2019 18:31:03 +0200
Subject: [PATCH 365/457] adding InvalidOrder to exchange

---
 freqtrade/exchange/exchange.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 011be58e5..2b7e41847 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -13,7 +13,8 @@ import ccxt
 import ccxt.async_support as ccxt_async
 from pandas import DataFrame
 
-from freqtrade import constants, DependencyException, OperationalException, TemporaryError
+from freqtrade import (constants, DependencyException, OperationalException,
+                       TemporaryError, InvalidOrder)
 from freqtrade.data.converter import parse_ticker_dataframe
 
 logger = logging.getLogger(__name__)

From 40df0dcf3d5653dc8aba3cf68f724480885b2a34 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Tue, 2 Apr 2019 18:45:18 +0200
Subject: [PATCH 366/457] tests fixed

---
 freqtrade/__init__.py                     | 2 +-
 freqtrade/exchange/exchange.py            | 4 ++--
 freqtrade/tests/exchange/test_exchange.py | 7 ++++---
 3 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py
index 30fed8c53..292613297 100644
--- a/freqtrade/__init__.py
+++ b/freqtrade/__init__.py
@@ -17,7 +17,7 @@ class OperationalException(BaseException):
     """
 
 
-class InvalidOrder(BaseException):
+class InvalidOrderException(BaseException):
     """
     This is returned when the order is not valid. Example:
     If stoploss on exchange order is hit, then trying to cancel the order
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 2b7e41847..7f4ab062c 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -14,7 +14,7 @@ import ccxt.async_support as ccxt_async
 from pandas import DataFrame
 
 from freqtrade import (constants, DependencyException, OperationalException,
-                       TemporaryError, InvalidOrder)
+                       TemporaryError, InvalidOrderException)
 from freqtrade.data.converter import parse_ticker_dataframe
 
 logger = logging.getLogger(__name__)
@@ -608,7 +608,7 @@ class Exchange(object):
         try:
             return self._api.cancel_order(order_id, pair)
         except ccxt.InvalidOrder as e:
-            raise DependencyException(
+            raise InvalidOrderException(
                 f'Could not cancel order. Message: {e}')
         except (ccxt.NetworkError, ccxt.ExchangeError) as e:
             raise TemporaryError(
diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py
index eed16d39b..4deb74c67 100644
--- a/freqtrade/tests/exchange/test_exchange.py
+++ b/freqtrade/tests/exchange/test_exchange.py
@@ -11,7 +11,8 @@ import ccxt
 import pytest
 from pandas import DataFrame
 
-from freqtrade import DependencyException, OperationalException, TemporaryError
+from freqtrade import (DependencyException, OperationalException,
+                       TemporaryError, InvalidOrderException)
 from freqtrade.exchange import Binance, Exchange, Kraken
 from freqtrade.exchange.exchange import API_RETRY_COUNT
 from freqtrade.resolvers.exchange_resolver import ExchangeResolver
@@ -1233,11 +1234,11 @@ def test_cancel_order(default_conf, mocker, exchange_name):
     exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
     assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
 
-    with pytest.raises(DependencyException):
+    with pytest.raises(InvalidOrderException):
         api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
         exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
         exchange.cancel_order(order_id='_', pair='TKN/BTC')
-    assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
+    assert api_mock.cancel_order.call_count == 1
 
     ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
                            "cancel_order", "cancel_order",

From 54f11ad60388d5c9e3da5c90424174a914f1c00c Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Tue, 2 Apr 2019 18:57:06 +0200
Subject: [PATCH 367/457] enriching TSL log

---
 freqtrade/freqtradebot.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 55ef6f611..fca3e346d 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -745,8 +745,8 @@ class FreqtradeBot(object):
             update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
             if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat:
                 # cancelling the current stoploss on exchange first
-                logger.info('Trailing stoploss: cancelling current stoploss on exchange '
-                            'in order to add another one ...')
+                logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
+                            'in order to add another one ...', order['id'])
                 if self.exchange.cancel_order(order['id'], trade.pair):
                     # creating the new one
                     stoploss_order_id = self.exchange.stoploss_limit(

From a6daf0d991469e5db365a3106bd848a22fb1f5a7 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Tue, 2 Apr 2019 20:00:58 +0200
Subject: [PATCH 368/457] formatting pct

---
 freqtrade/rpc/telegram.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index ca9b376ec..9d1f9f206 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -205,9 +205,11 @@ class Telegram(RPC):
                     "*Current Rate:* `{current_rate:.8f}`",
                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
                     "*Current Profit:* `{current_profit:.2f}%`",
-                    "*Initial Stoploss:* `{initial_stop_loss:.8f}` `({initial_stop_loss_pct}%)`"
+                    "*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
+                    ("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "")
                     if r['stop_loss'] != r['initial_stop_loss'] else "",
-                    "*Stoploss:* `{stop_loss:.8f}` `({stop_loss_pct}%)`",
+                    "*Stoploss:* `{stop_loss:.8f}` " +
+                    ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""),
                     "*Open Order:* `{open_order}`" if r['open_order'] else "",
                 ]
                 messages.append("\n".join(filter(None, lines)).format(**r))

From 7b39a3084fc15406cd56fc17ea4eb8a539d47d13 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Tue, 2 Apr 2019 20:08:10 +0200
Subject: [PATCH 369/457] formatting and readability

---
 freqtrade/rpc/telegram.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 9d1f9f206..2d822820f 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -205,12 +205,17 @@ class Telegram(RPC):
                     "*Current Rate:* `{current_rate:.8f}`",
                     "*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
                     "*Current Profit:* `{current_profit:.2f}%`",
+
+                    # Adding initial stoploss only if it is different from stoploss
                     "*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
                     ("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "")
                     if r['stop_loss'] != r['initial_stop_loss'] else "",
+
+                    # Adding stoploss and stoploss percentage only if it is not None
                     "*Stoploss:* `{stop_loss:.8f}` " +
                     ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""),
-                    "*Open Order:* `{open_order}`" if r['open_order'] else "",
+
+                    "*Open Order:* `{open_order}`" if r['open_order'] else ""
                 ]
                 messages.append("\n".join(filter(None, lines)).format(**r))
 

From 62141d3d2790c55d779b296528e224754d1fae7f Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Tue, 2 Apr 2019 21:57:52 +0300
Subject: [PATCH 370/457] test cloned, separate tests for worker and freqtrade
 states

---
 freqtrade/tests/test_freqtradebot.py | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index 8bc071071..250e43c3b 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -82,7 +82,17 @@ def patch_RPCManager(mocker) -> MagicMock:
 
 # Unit tests
 
-def test_freqtradebot(mocker, default_conf, markets) -> None:
+def test_freqtradebot_state(mocker, default_conf, markets) -> None:
+    mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+    assert freqtrade.state is State.RUNNING
+
+    default_conf.pop('initial_state')
+    freqtrade = FreqtradeBot(config=default_conf)
+    assert freqtrade.state is State.STOPPED
+
+
+def test_worker_state(mocker, default_conf, markets) -> None:
     mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
     worker = get_patched_worker(mocker, default_conf)
     assert worker.state is State.RUNNING

From b0ddb33acc5fb2eb3d5d30068853456e44af6c81 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Tue, 2 Apr 2019 22:36:30 +0300
Subject: [PATCH 371/457] tests cleanup: Worker --> FreqtradeBot where the
 Worker object is not really needed

---
 freqtrade/tests/rpc/test_rpc.py          |  55 ++++-------
 freqtrade/tests/rpc/test_rpc_telegram.py | 116 +++++++++--------------
 2 files changed, 64 insertions(+), 107 deletions(-)

diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py
index 46033ec23..b7b041695 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -16,7 +16,6 @@ from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
 from freqtrade.state import State
 from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange
 from freqtrade.tests.test_freqtradebot import patch_get_signal
-from freqtrade.worker import Worker
 
 
 # Functions for recurrent object patching
@@ -39,8 +38,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -97,8 +95,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -135,8 +132,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     stake_currency = default_conf['stake_currency']
@@ -191,8 +187,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     stake_currency = default_conf['stake_currency']
@@ -281,8 +276,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     stake_currency = default_conf['stake_currency']
@@ -352,8 +346,7 @@ def test_rpc_balance_handle(default_conf, mocker):
         get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -382,8 +375,7 @@ def test_rpc_start(mocker, default_conf) -> None:
         get_ticker=MagicMock()
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -407,8 +399,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
         get_ticker=MagicMock()
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -433,8 +424,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
         get_ticker=MagicMock()
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -467,8 +457,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -561,8 +550,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -599,8 +587,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -631,8 +618,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
         buy=buy_mm
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -659,8 +645,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
     # Test not buying
     default_conf['stake_amount'] = 0.0000001
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -676,8 +661,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -691,8 +675,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
@@ -706,8 +689,7 @@ def test_rpc_whitelist(mocker, default_conf) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     rpc = RPC(freqtradebot)
     ret = rpc._rpc_whitelist()
@@ -724,8 +706,7 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
     mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     rpc = RPC(freqtradebot)
     ret = rpc._rpc_whitelist()
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index 384cdcfa0..c0b77076f 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -14,15 +14,15 @@ from telegram.error import NetworkError
 
 from freqtrade import __version__
 from freqtrade.edge import PairInfo
+from freqtrade.freqtradebot import FreqtradeBot
 from freqtrade.persistence import Trade
 from freqtrade.rpc import RPCMessageType
 from freqtrade.rpc.telegram import Telegram, authorized_only
 from freqtrade.state import State
 from freqtrade.strategy.interface import SellType
-from freqtrade.tests.conftest import (get_patched_freqtradebot, get_patched_worker,
+from freqtrade.tests.conftest import (get_patched_freqtradebot,
                                       log_has, patch_coinmarketcap, patch_exchange)
 from freqtrade.tests.test_freqtradebot import patch_get_signal
-from freqtrade.worker import Worker
 
 
 class DummyCls(Telegram):
@@ -99,8 +99,7 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
 
     default_conf['telegram']['enabled'] = False
 
-    worker = Worker(args=None, config=default_conf)
-    bot = worker.freqtrade
+    bot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(bot, (True, False))
     dummy = DummyCls(bot)
@@ -129,8 +128,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
 
     default_conf['telegram']['enabled'] = False
 
-    worker = Worker(args=None, config=default_conf)
-    bot = worker.freqtrade
+    bot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(bot, (True, False))
     dummy = DummyCls(bot)
@@ -159,8 +157,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
 
     default_conf['telegram']['enabled'] = False
 
-    worker = Worker(args=None, config=default_conf)
-    bot = worker.freqtrade
+    bot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(bot, (True, False))
     dummy = DummyCls(bot)
@@ -216,8 +213,7 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -254,21 +250,20 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
 
     telegram = Telegram(freqtradebot)
 
-    worker.state = State.STOPPED
+    freqtradebot.state = State.STOPPED
     # Status is also enabled when stopped
     telegram._status(bot=MagicMock(), update=update)
     assert msg_mock.call_count == 1
     assert 'no active trade' in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
 
-    worker.state = State.RUNNING
+    freqtradebot.state = State.RUNNING
     telegram._status(bot=MagicMock(), update=update)
     assert msg_mock.call_count == 1
     assert 'no active trade' in msg_mock.call_args_list[0][0][0]
@@ -303,21 +298,20 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
 
     default_conf['stake_amount'] = 15.0
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
 
     telegram = Telegram(freqtradebot)
 
-    worker.state = State.STOPPED
+    freqtradebot.state = State.STOPPED
     # Status table is also enabled when stopped
     telegram._status_table(bot=MagicMock(), update=update)
     assert msg_mock.call_count == 1
     assert 'no active order' in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
 
-    worker.state = State.RUNNING
+    freqtradebot.state = State.RUNNING
     telegram._status_table(bot=MagicMock(), update=update)
     assert msg_mock.call_count == 1
     assert 'no active order' in msg_mock.call_args_list[0][0][0]
@@ -359,8 +353,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -426,15 +419,14 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
     # Try invalid data
     msg_mock.reset_mock()
-    worker.state = State.RUNNING
+    freqtradebot.state = State.RUNNING
     update.message.text = '/daily -2'
     telegram._daily(bot=MagicMock(), update=update)
     assert msg_mock.call_count == 1
@@ -442,7 +434,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
 
     # Try invalid data
     msg_mock.reset_mock()
-    worker.state = State.RUNNING
+    freqtradebot.state = State.RUNNING
     update.message.text = '/daily today'
     telegram._daily(bot=MagicMock(), update=update)
     assert str('Daily Profit over the last 7 days') in msg_mock.call_args_list[0][0][0]
@@ -467,8 +459,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -614,14 +605,13 @@ def test_start_handle(default_conf, update, mocker) -> None:
         _send_msg=msg_mock
     )
 
-    worker = get_patched_worker(mocker, default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     telegram = Telegram(freqtradebot)
 
-    worker.state = State.STOPPED
-    assert worker.state == State.STOPPED
+    freqtradebot.state = State.STOPPED
+    assert freqtradebot.state == State.STOPPED
     telegram._start(bot=MagicMock(), update=update)
-    assert worker.state == State.RUNNING
+    assert freqtradebot.state == State.RUNNING
     assert msg_mock.call_count == 1
 
 
@@ -633,14 +623,13 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
         _send_msg=msg_mock
     )
 
-    worker = get_patched_worker(mocker, default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     telegram = Telegram(freqtradebot)
 
-    worker.state = State.RUNNING
-    assert worker.state == State.RUNNING
+    freqtradebot.state = State.RUNNING
+    assert freqtradebot.state == State.RUNNING
     telegram._start(bot=MagicMock(), update=update)
-    assert worker.state == State.RUNNING
+    assert freqtradebot.state == State.RUNNING
     assert msg_mock.call_count == 1
     assert 'already running' in msg_mock.call_args_list[0][0][0]
 
@@ -654,14 +643,13 @@ def test_stop_handle(default_conf, update, mocker) -> None:
         _send_msg=msg_mock
     )
 
-    worker = get_patched_worker(mocker, default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     telegram = Telegram(freqtradebot)
 
-    worker.state = State.RUNNING
-    assert worker.state == State.RUNNING
+    freqtradebot.state = State.RUNNING
+    assert freqtradebot.state == State.RUNNING
     telegram._stop(bot=MagicMock(), update=update)
-    assert worker.state == State.STOPPED
+    assert freqtradebot.state == State.STOPPED
     assert msg_mock.call_count == 1
     assert 'stopping trader' in msg_mock.call_args_list[0][0][0]
 
@@ -675,14 +663,13 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
         _send_msg=msg_mock
     )
 
-    worker = get_patched_worker(mocker, default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     telegram = Telegram(freqtradebot)
 
-    worker.state = State.STOPPED
-    assert worker.state == State.STOPPED
+    freqtradebot.state = State.STOPPED
+    assert freqtradebot.state == State.STOPPED
     telegram._stop(bot=MagicMock(), update=update)
-    assert worker.state == State.STOPPED
+    assert freqtradebot.state == State.STOPPED
     assert msg_mock.call_count == 1
     assert 'already stopped' in msg_mock.call_args_list[0][0][0]
 
@@ -696,9 +683,7 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None:
         _send_msg=msg_mock
     )
 
-    worker = get_patched_worker(mocker, default_conf)
-    freqtradebot = worker.freqtrade
-
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     telegram = Telegram(freqtradebot)
 
     assert freqtradebot.config['max_open_trades'] != 0
@@ -718,14 +703,13 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None:
         _send_msg=msg_mock
     )
 
-    worker = get_patched_worker(mocker, default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     telegram = Telegram(freqtradebot)
 
-    worker.state = State.RUNNING
-    assert worker.state == State.RUNNING
+    freqtradebot.state = State.RUNNING
+    assert freqtradebot.state == State.RUNNING
     telegram._reload_conf(bot=MagicMock(), update=update)
-    assert worker.state == State.RELOAD_CONF
+    assert freqtradebot.state == State.RELOAD_CONF
     assert msg_mock.call_count == 1
     assert 'reloading config' in msg_mock.call_args_list[0][0][0]
 
@@ -745,8 +729,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
         validate_pairs=MagicMock(return_value={})
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -798,8 +781,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
         validate_pairs=MagicMock(return_value={})
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -854,8 +836,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
         validate_pairs=MagicMock(return_value={})
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -899,8 +880,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
     )
     patch_exchange(mocker)
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -943,8 +923,7 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
     fbuy_mock = MagicMock(return_value=None)
     mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -980,8 +959,7 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non
         validate_pairs=MagicMock(return_value={})
     )
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -1012,8 +990,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
@@ -1054,8 +1031,7 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
     )
     mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
 
-    worker = Worker(args=None, config=default_conf)
-    freqtradebot = worker.freqtrade
+    freqtradebot = FreqtradeBot(config=default_conf)
 
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)

From d54acca53abdc2d8cc6752a687f84c53c9390c73 Mon Sep 17 00:00:00 2001
From: hroff-1902 
Date: Wed, 3 Apr 2019 00:55:59 +0300
Subject: [PATCH 372/457] move tests back to original codebase to minimize
 changes

---
 freqtrade/tests/rpc/test_rpc.py          | 55 ++++++++---------------
 freqtrade/tests/rpc/test_rpc_telegram.py | 57 +++++++-----------------
 freqtrade/tests/test_freqtradebot.py     |  2 +-
 3 files changed, 36 insertions(+), 78 deletions(-)

diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py
index 4a3cafbd2..b454f9cd8 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -37,8 +37,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
 
@@ -101,8 +100,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
 
@@ -137,8 +135,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     stake_currency = default_conf['stake_currency']
     fiat_display_currency = default_conf['fiat_display_currency']
@@ -191,8 +188,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     stake_currency = default_conf['stake_currency']
     fiat_display_currency = default_conf['fiat_display_currency']
@@ -280,8 +276,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     stake_currency = default_conf['stake_currency']
     fiat_display_currency = default_conf['fiat_display_currency']
@@ -349,8 +344,7 @@ def test_rpc_balance_handle(default_conf, mocker):
         get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     rpc._fiat_converter = CryptoToFiatConverter()
@@ -377,8 +371,7 @@ def test_rpc_start(mocker, default_conf) -> None:
         get_ticker=MagicMock()
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     freqtradebot.state = State.STOPPED
@@ -400,8 +393,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
         get_ticker=MagicMock()
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     freqtradebot.state = State.RUNNING
@@ -424,8 +416,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
         get_ticker=MagicMock()
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     freqtradebot.state = State.RUNNING
@@ -456,8 +447,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
 
@@ -548,8 +538,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
 
@@ -584,8 +573,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
         markets=PropertyMock(return_value=markets)
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
 
@@ -614,8 +602,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
         buy=buy_mm
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     pair = 'ETH/BTC'
@@ -640,9 +627,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
 
     # Test not buying
     default_conf['stake_amount'] = 0.0000001
-
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     pair = 'TKN/BTC'
@@ -656,8 +641,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     pair = 'ETH/BTC'
@@ -669,8 +653,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     rpc = RPC(freqtradebot)
     pair = 'ETH/BTC'
@@ -682,8 +665,7 @@ def test_rpc_whitelist(mocker, default_conf) -> None:
     patch_exchange(mocker)
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     rpc = RPC(freqtradebot)
     ret = rpc._rpc_whitelist()
     assert ret['method'] == 'StaticPairList'
@@ -698,8 +680,7 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
     mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
     mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     rpc = RPC(freqtradebot)
     ret = rpc._rpc_whitelist()
     assert ret['method'] == 'VolumePairList'
diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py
index c3ab5064c..b6d12fe41 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -96,9 +96,7 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
     update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
 
     default_conf['telegram']['enabled'] = False
-
-    bot = FreqtradeBot(config=default_conf)
-
+    bot = FreqtradeBot(default_conf)
     patch_get_signal(bot, (True, False))
     dummy = DummyCls(bot)
     dummy.dummy_handler(bot=MagicMock(), update=update)
@@ -124,9 +122,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
     update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
 
     default_conf['telegram']['enabled'] = False
-
-    bot = FreqtradeBot(config=default_conf)
-
+    bot = FreqtradeBot(default_conf)
     patch_get_signal(bot, (True, False))
     dummy = DummyCls(bot)
     dummy.dummy_handler(bot=MagicMock(), update=update)
@@ -153,8 +149,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
 
     default_conf['telegram']['enabled'] = False
 
-    bot = FreqtradeBot(config=default_conf)
-
+    bot = FreqtradeBot(default_conf)
     patch_get_signal(bot, (True, False))
     dummy = DummyCls(bot)
 
@@ -212,8 +207,7 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -248,8 +242,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
 
     telegram = Telegram(freqtradebot)
@@ -301,9 +294,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
     default_conf['stake_amount'] = 15.0
-
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
 
     telegram = Telegram(freqtradebot)
@@ -356,8 +347,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -421,8 +411,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -460,8 +449,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -724,8 +712,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
         validate_pairs=MagicMock(return_value={})
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -775,8 +762,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
         validate_pairs=MagicMock(return_value={})
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -829,8 +815,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
         validate_pairs=MagicMock(return_value={})
     )
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -872,8 +857,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
     )
     patch_exchange(mocker)
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -914,8 +898,7 @@ def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
     fbuy_mock = MagicMock(return_value=None)
     mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
 
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -948,9 +931,7 @@ def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> Non
         markets=PropertyMock(markets),
         validate_pairs=MagicMock(return_value={})
     )
-
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -978,9 +959,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
         validate_pairs=MagicMock(return_value={})
     )
     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
-
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
@@ -1018,9 +997,7 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
         markets=PropertyMock(markets)
     )
     mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
-
-    freqtradebot = FreqtradeBot(config=default_conf)
-
+    freqtradebot = FreqtradeBot(default_conf)
     patch_get_signal(freqtradebot, (True, False))
     telegram = Telegram(freqtradebot)
 
diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index 6790f513c..211683d64 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -88,7 +88,7 @@ def test_freqtradebot_state(mocker, default_conf, markets) -> None:
     assert freqtrade.state is State.RUNNING
 
     default_conf.pop('initial_state')
-    freqtrade = FreqtradeBot(config=default_conf)
+    freqtrade = FreqtradeBot(default_conf)
     assert freqtrade.state is State.STOPPED
 
 

From 53eaf85969b31b282b5bf5196d7acde51145d5f7 Mon Sep 17 00:00:00 2001
From: Misagh 
Date: Wed, 3 Apr 2019 14:03:28 +0200
Subject: [PATCH 373/457] filtering edge pairs for RPC

---
 freqtrade/edge/__init__.py | 18 ++++++++++++++++++
 freqtrade/rpc/rpc.py       | 11 +----------
 freqtrade/rpc/telegram.py  |  1 -
 3 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py
index b4dfa5624..0f70df43b 100644
--- a/freqtrade/edge/__init__.py
+++ b/freqtrade/edge/__init__.py
@@ -203,6 +203,24 @@ class Edge():
 
         return self._final_pairs
 
+    def accepted_pairs(self) -> list:
+        """
+        return a list of accepted pairs along with their winrate, expectancy and stoploss
+        ex:
+        #[{'Pair': 'ADX/ETH', 'Winrate': 0.08333333333333333, 'Expectancy': -0.8105153934775888, 'Stoploss': -0.02}]
+        """
+        final = []
+        for pair, info in self._cached_pairs.items():
+            if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
+                info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)):
+                final.append({
+                    'Pair': pair,
+                    'Winrate': info.winrate,
+                    'Expectancy': info.expectancy,
+                    'Stoploss': info.stoploss,
+                })
+        return final
+
     def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
         """
         The result frame contains a number of columns that are calculable
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 5308c9d51..79bfffb1d 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -484,13 +484,4 @@ class RPC(object):
         """ Returns information related to Edge """
         if not self._freqtrade.edge:
             raise RPCException(f'Edge is not enabled.')
-
-        return [
-            {
-                'Pair': k,
-                'Winrate': v.winrate,
-                'Expectancy': v.expectancy,
-                'Stoploss': v.stoploss,
-            }
-            for k, v in self._freqtrade.edge._cached_pairs.items()
-        ]
+        return self._freqtrade.edge.accepted_pairs()
\ No newline at end of file
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 2d822820f..8ff59d759 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -511,7 +511,6 @@ class Telegram(RPC):
         """
         try:
             edge_pairs = self._rpc_edge()
-            print(edge_pairs)
             edge_pairs_tab = tabulate(edge_pairs, headers='keys', tablefmt='simple')
             message = f'Edge only validated following pairs:\n
{edge_pairs_tab}
' self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML) From 5f38d5ee6309aba3852ee832b6ecbff6ce50b5fe Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 3 Apr 2019 14:07:33 +0200 Subject: [PATCH 374/457] removing % sign as it is already a pct --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2d822820f..81f9dc4f0 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -208,12 +208,12 @@ class Telegram(RPC): # Adding initial stoploss only if it is different from stoploss "*Initial Stoploss:* `{initial_stop_loss:.8f}` " + - ("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "") + ("`({initial_stop_loss_pct:.2f})`" if r['initial_stop_loss_pct'] else "") if r['stop_loss'] != r['initial_stop_loss'] else "", # Adding stoploss and stoploss percentage only if it is not None "*Stoploss:* `{stop_loss:.8f}` " + - ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""), + ("`({stop_loss_pct:.2f})`" if r['stop_loss_pct'] else ""), "*Open Order:* `{open_order}`" if r['open_order'] else "" ] From a3835b1279d0d0b7fd7df2b34d9a74d6b2db0cf8 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 3 Apr 2019 14:14:47 +0200 Subject: [PATCH 375/457] flake8 --- freqtrade/edge/__init__.py | 16 +++++++--------- freqtrade/rpc/rpc.py | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 0f70df43b..7bda942d2 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -206,19 +206,17 @@ class Edge(): def accepted_pairs(self) -> list: """ return a list of accepted pairs along with their winrate, expectancy and stoploss - ex: - #[{'Pair': 'ADX/ETH', 'Winrate': 0.08333333333333333, 'Expectancy': -0.8105153934775888, 'Stoploss': -0.02}] """ final = [] for pair, info in self._cached_pairs.items(): if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ - info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): - final.append({ - 'Pair': pair, - 'Winrate': info.winrate, - 'Expectancy': info.expectancy, - 'Stoploss': info.stoploss, - }) + info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): + final.append({ + 'Pair': pair, + 'Winrate': info.winrate, + 'Expectancy': info.expectancy, + 'Stoploss': info.stoploss, + }) return final def _fill_calculable_fields(self, result: DataFrame) -> DataFrame: diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 79bfffb1d..ceab00373 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -484,4 +484,4 @@ class RPC(object): """ Returns information related to Edge """ if not self._freqtrade.edge: raise RPCException(f'Edge is not enabled.') - return self._freqtrade.edge.accepted_pairs() \ No newline at end of file + return self._freqtrade.edge.accepted_pairs() From 67eeb145e189b9cc6a1adec0003334c795a6aac8 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 3 Apr 2019 14:31:00 +0200 Subject: [PATCH 376/457] flake8 --- freqtrade/edge/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 7bda942d2..4801c6cb3 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -211,12 +211,12 @@ class Edge(): for pair, info in self._cached_pairs.items(): if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): - final.append({ - 'Pair': pair, - 'Winrate': info.winrate, - 'Expectancy': info.expectancy, - 'Stoploss': info.stoploss, - }) + final.append({ + 'Pair': pair, + 'Winrate': info.winrate, + 'Expectancy': info.expectancy, + 'Stoploss': info.stoploss, + }) return final def _fill_calculable_fields(self, result: DataFrame) -> DataFrame: From eb610441b574eaac2f8603ad9f4df59a0286b932 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 3 Apr 2019 12:38:06 +0000 Subject: [PATCH 377/457] Update ccxt from 1.18.425 to 1.18.430 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 83d77b693..914c8e644 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.425 +ccxt==1.18.430 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 92dc3c89afb525d5276c31f9e631799f45574c3f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 3 Apr 2019 12:38:07 +0000 Subject: [PATCH 378/457] Update sqlalchemy from 1.3.1 to 1.3.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 914c8e644..afb422e4b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ccxt==1.18.430 -SQLAlchemy==1.3.1 +SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 cachetools==3.1.0 From a3fe5f57579b730705390c4e6e0e2e6f4f98ac9b Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 3 Apr 2019 16:28:44 +0200 Subject: [PATCH 379/457] adding stake amount to telegram message --- freqtrade/rpc/rpc.py | 2 ++ freqtrade/rpc/telegram.py | 2 +- freqtrade/tests/rpc/test_rpc.py | 4 ++++ freqtrade/tests/rpc/test_rpc_telegram.py | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 5308c9d51..ec6c1feed 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -103,11 +103,13 @@ class RPC(object): results.append(dict( trade_id=trade.id, pair=trade.pair, + base_currency=self._freqtrade.config['stake_currency'], date=arrow.get(trade.open_date), open_rate=trade.open_rate, close_rate=trade.close_rate, current_rate=current_rate, amount=round(trade.amount, 8), + stake_amount=round(trade.amount, 8), close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), stop_loss=trade.stop_loss, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2d822820f..9a64f5197 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -199,7 +199,7 @@ class Telegram(RPC): lines = [ "*Trade ID:* `{trade_id}` `(since {date})`", "*Current Pair:* {pair}", - "*Amount:* `{amount}`", + "*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Open Rate:* `{open_rate:.8f}`", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Current Rate:* `{current_rate:.8f}`", diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index b454f9cd8..981d3edfb 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -51,11 +51,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: assert { 'trade_id': 1, 'pair': 'ETH/BTC', + 'base_currency': 'BTC', 'date': ANY, 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': 1.098e-05, 'amount': 90.99181074, + 'stake_amount': 90.99181074, 'close_profit': None, 'current_profit': -0.59, 'stop_loss': 0.0, @@ -75,11 +77,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: assert { 'trade_id': 1, 'pair': 'ETH/BTC', + 'base_currency': 'BTC', 'date': ANY, 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': ANY, 'amount': 90.99181074, + 'stake_amount': 90.99181074, 'close_profit': None, 'current_profit': ANY, 'stop_loss': 0.0, diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index b6d12fe41..8f43d7ed0 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -189,11 +189,13 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: _rpc_trade_status=MagicMock(return_value=[{ 'trade_id': 1, 'pair': 'ETH/BTC', + 'base_currency': 'BTC', 'date': arrow.utcnow(), 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': 1.098e-05, 'amount': 90.99181074, + 'stake_amount': 90.99181074, 'close_profit': None, 'current_profit': -0.59, 'initial_stop_loss': 1.098e-05, From d5498c87123e21cddb7d65e3a186eeb19e62aec0 Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 3 Apr 2019 19:29:44 +0200 Subject: [PATCH 380/457] adding % --- freqtrade/rpc/rpc.py | 4 ++-- freqtrade/rpc/telegram.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 5308c9d51..0a6a2388a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -111,9 +111,9 @@ class RPC(object): close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), stop_loss=trade.stop_loss, - stop_loss_pct=trade.stop_loss_pct, + stop_loss_pct=(trade.stop_loss_pct * 100), initial_stop_loss=trade.initial_stop_loss, - initial_stop_loss_pct=trade.initial_stop_loss_pct, + initial_stop_loss_pct=(trade.initial_stop_loss_pct * 100), open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 81f9dc4f0..2d822820f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -208,12 +208,12 @@ class Telegram(RPC): # Adding initial stoploss only if it is different from stoploss "*Initial Stoploss:* `{initial_stop_loss:.8f}` " + - ("`({initial_stop_loss_pct:.2f})`" if r['initial_stop_loss_pct'] else "") + ("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "") if r['stop_loss'] != r['initial_stop_loss'] else "", # Adding stoploss and stoploss percentage only if it is not None "*Stoploss:* `{stop_loss:.8f}` " + - ("`({stop_loss_pct:.2f})`" if r['stop_loss_pct'] else ""), + ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""), "*Open Order:* `{open_order}`" if r['open_order'] else "" ] From 3c399fbe3fe1edd1ea1dab04898201edcf1026a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Apr 2019 19:51:46 +0200 Subject: [PATCH 381/457] Improve whitelist wordings --- freqtrade/freqtradebot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e5400c1d0..bf445e56d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -79,7 +79,6 @@ class FreqtradeBot(object): self.config.get('edge', {}).get('enabled', False) else None self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist'] - self._init_modules() # Tell the systemd that we completed initialization phase @@ -196,9 +195,6 @@ class FreqtradeBot(object): # Refresh whitelist self.pairlists.refresh_pairlist() self.active_pair_whitelist = self.pairlists.whitelist - if not self.active_pair_whitelist: - logger.warning('Whitelist is empty.') - return False # Calculating Edge positioning if self.edge: @@ -360,6 +356,10 @@ class FreqtradeBot(object): interval = self.strategy.ticker_interval whitelist = copy.deepcopy(self.active_pair_whitelist) + if not whitelist: + logger.warning("Whitelist is empty.") + return False + # Remove currently opened and latest pairs from whitelist for trade in Trade.get_open_trades(): if trade.pair in whitelist: @@ -367,7 +367,7 @@ class FreqtradeBot(object): logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: - logger.info("No currency pair left in whitelist, no more trade can be created.") + logger.info("No currency pair in whitelist, but checking to sell open trades.") return False # running get_signal on historical data fetched From 1a5b0969b952603fa54a6dedd446ad93fcaf2caf Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Apr 2019 19:52:06 +0200 Subject: [PATCH 382/457] Fix tests (both tests where testing the same thing) --- freqtrade/tests/test_freqtradebot.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4e145c354..fc13bc0f1 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -570,7 +570,8 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order, assert freqtrade._get_trade_stake_amount('ETH/BTC') is None -def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None: +def test_create_trade_no_pairs_let(default_conf, ticker, limit_buy_order, fee, + markets, mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -582,16 +583,17 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, marke ) default_conf['exchange']['pair_whitelist'] = ["ETH/BTC"] - default_conf['exchange']['pair_blacklist'] = ["ETH/BTC"] freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() + assert freqtrade.create_trade() assert not freqtrade.create_trade() + assert log_has("No currency pair in whitelist, but checking to sell open trades.", + caplog.record_tuples) -def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, - limit_buy_order, fee, markets, mocker) -> None: +def test_create_trade_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee, + markets, mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -601,13 +603,12 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, get_fee=fee, markets=PropertyMock(return_value=markets) ) - default_conf['exchange']['pair_whitelist'] = ["ETH/BTC"] - default_conf['exchange']['pair_blacklist'] = ["ETH/BTC"] + default_conf['exchange']['pair_whitelist'] = [] freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.create_trade() assert not freqtrade.create_trade() + assert log_has("Whitelist is empty.", caplog.record_tuples) def test_create_trade_no_signal(default_conf, fee, mocker) -> None: From 0307ba7883c55eb7b2a763feda5373df39f9210c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Apr 2019 20:04:04 +0200 Subject: [PATCH 383/457] Remove one branch - python does lazy evaluation --- freqtrade/optimize/backtesting.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index f3661ab32..0fcfd11f1 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -337,7 +337,6 @@ class Backtesting(object): # Loop timerange and test per pair while tmp < end_date: - # print(f"time: {tmp}") for i, pair in enumerate(ticker): if pair not in indexes: @@ -358,9 +357,9 @@ class Backtesting(object): if row.buy == 0 or row.sell == 1: continue # skip rows where no buy signal or that would immediately sell off - if not position_stacking: - if pair in lock_pair_until and row.date <= lock_pair_until[pair]: - continue + if (not position_stacking and pair in lock_pair_until + and row.date <= lock_pair_until[pair]): + continue if max_open_trades > 0: # Check if max_open_trades has already been reached for the given date if not trade_count_lock.get(row.date, 0) < max_open_trades: From 9ee1dd99eb0e7989abb55dbbeb549ea156d4396e Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 3 Apr 2019 20:28:03 +0200 Subject: [PATCH 384/457] tests fixed --- freqtrade/rpc/rpc.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 0a6a2388a..090eb295f 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -111,11 +111,13 @@ class RPC(object): close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), stop_loss=trade.stop_loss, - stop_loss_pct=(trade.stop_loss_pct * 100), + stop_loss_pct=(trade.stop_loss_pct * 100) \ + if trade.stop_loss_pct else None, initial_stop_loss=trade.initial_stop_loss, - initial_stop_loss_pct=(trade.initial_stop_loss_pct * 100), + initial_stop_loss_pct=(trade.initial_stop_loss_pct * 100) \ + if trade.initial_stop_loss_pct else None, open_order='({} {} rem={:.8f})'.format( - order['type'], order['side'], order['remaining'] + order['type'], order['side'], order['remaining'] ) if order else None, )) return results From 5488c66f536672d22d529450905767c637f5cc5a Mon Sep 17 00:00:00 2001 From: Misagh Date: Wed, 3 Apr 2019 20:35:37 +0200 Subject: [PATCH 385/457] flake8 --- freqtrade/rpc/rpc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 090eb295f..e59fb73f3 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -111,11 +111,11 @@ class RPC(object): close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), stop_loss=trade.stop_loss, - stop_loss_pct=(trade.stop_loss_pct * 100) \ - if trade.stop_loss_pct else None, + stop_loss_pct=(trade.stop_loss_pct * 100) + if trade.stop_loss_pct else None, initial_stop_loss=trade.initial_stop_loss, - initial_stop_loss_pct=(trade.initial_stop_loss_pct * 100) \ - if trade.initial_stop_loss_pct else None, + initial_stop_loss_pct=(trade.initial_stop_loss_pct * 100) + if trade.initial_stop_loss_pct else None, open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, From 65350ad55235d8fd12356dacf9ddc1dccbf56d61 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 3 Apr 2019 22:14:42 +0300 Subject: [PATCH 386/457] final flake happy --- freqtrade/worker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 2440e7320..c7afe5c97 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -143,7 +143,9 @@ class Worker(object): }) logger.exception('OperationalException. Stopping trader ...') self.freqtrade.state = State.STOPPED -### state_changed = True + # TODO: The return value of _process() is not used apart tests + # and should (could) be eliminated later. See PR #1689. +# state_changed = True return state_changed def _reconfigure(self): From 0cdbe714d23356375c21aa7d69184e1c7e5a0a32 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 4 Apr 2019 12:06:45 +0200 Subject: [PATCH 387/457] stake amount not amount --- freqtrade/rpc/rpc.py | 2 +- freqtrade/tests/rpc/test_rpc.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index e3276c53f..88ade0c27 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -109,7 +109,7 @@ class RPC(object): close_rate=trade.close_rate, current_rate=current_rate, amount=round(trade.amount, 8), - stake_amount=round(trade.amount, 8), + stake_amount=round(trade.stake_amount, 8), close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), stop_loss=trade.stop_loss, diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 981d3edfb..8b10a1314 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -57,7 +57,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'close_rate': None, 'current_rate': 1.098e-05, 'amount': 90.99181074, - 'stake_amount': 90.99181074, + 'stake_amount': 0.001, 'close_profit': None, 'current_profit': -0.59, 'stop_loss': 0.0, @@ -83,7 +83,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: 'close_rate': None, 'current_rate': ANY, 'amount': 90.99181074, - 'stake_amount': 90.99181074, + 'stake_amount': 0.001, 'close_profit': None, 'current_profit': ANY, 'stop_loss': 0.0, From 6afe232c4d56f840266a7298ea963fbbd4337a5a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 4 Apr 2019 12:38:05 +0000 Subject: [PATCH 388/457] Update ccxt from 1.18.430 to 1.18.432 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afb422e4b..eb5d564fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.430 +ccxt==1.18.432 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From ebeaf64fbbd45aa7f7e64f25286ad10aa8625de9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 4 Apr 2019 12:38:06 +0000 Subject: [PATCH 389/457] Update mypy from 0.670 to 0.700 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 69082587a..c59923ed2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,4 +9,4 @@ pytest-mock==1.10.3 pytest-asyncio==0.10.0 pytest-cov==2.6.1 coveralls==1.7.0 -mypy==0.670 +mypy==0.700 From a363d443bfd38de6934e5d1d3beab020a9771d76 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 4 Apr 2019 17:13:54 +0200 Subject: [PATCH 390/457] stoploss on exchange canceled handled --- freqtrade/freqtradebot.py | 92 +++++++++++++++++++--------- freqtrade/tests/test_freqtradebot.py | 2 +- 2 files changed, 63 insertions(+), 31 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fca3e346d..eeefaa6d8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -690,46 +690,78 @@ class FreqtradeBot(object): is enabled. """ - result = False + logger.debug('Handling stoploss on exchange %s ...', trade) + try: - # If trade is open and the buy order is fulfilled but there is no stoploss, - # then we add a stoploss on exchange - if not trade.open_order_id and not trade.stoploss_order_id: - if self.edge: - stoploss = self.edge.stoploss(pair=trade.pair) - else: - stoploss = self.strategy.stoploss + # First we check if there is already a stoploss on exchange + stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \ + if trade.stoploss_order_id else None + except DependencyException as exception: + logger.warning('Unable to fetch stoploss order: %s', exception) - stop_price = trade.open_rate * (1 + stoploss) - # limit price should be less than stop price. - # 0.99 is arbitrary here. - limit_price = stop_price * 0.99 + # If there open order id does not exist, + # it means buy order is fulfilled + buy_order_fulfilled = not trade.open_order_id + # Limit price threshold + # This is the limit price percentage below which you don't want to sell + # 0.99 is arbitrary here. + # As limit price should always be below price + limit_price_pct = 0.99 + + # If buy order is fulfilled but there is no stoploss, + # then we add a stoploss on exchange + if (buy_order_fulfilled and not stoploss_order): + if self.edge: + stoploss = self.edge.stoploss(pair=trade.pair) + else: + stoploss = self.strategy.stoploss + + stop_price = trade.open_rate * (1 + stoploss) + + # limit price should be less than stop price. + limit_price = stop_price * limit_price_pct + + try: stoploss_order_id = self.exchange.stoploss_limit( pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price )['id'] trade.stoploss_order_id = str(stoploss_order_id) trade.stoploss_last_update = datetime.now() + return False - # Or the trade open and there is already a stoploss on exchange. - # so we check if it is hit ... - elif trade.stoploss_order_id: - logger.debug('Handling stoploss on exchange %s ...', trade) - order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) - if order['status'] == 'closed': - trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value - trade.update(order) - self.notify_sell(trade) - result = True - elif self.config.get('trailing_stop', False): - # if trailing stoploss is enabled we check if stoploss value has changed - # in which case we cancel stoploss order and put another one with new - # value immediately - self.handle_trailing_stoploss_on_exchange(trade, order) - except DependencyException as exception: - logger.warning('Unable to create stoploss order: %s', exception) - return result + except DependencyException as exception: + logger.warning('Unable to place a stoploss order on exchange: %s', exception) + + + # If stoploss order is canceled for some reason we add it + if stoploss_order and stoploss_order['status'] == 'canceled': + try: + stoploss_order_id = self.exchange.stoploss_limit( + pair=trade.pair, amount=trade.amount, + stop_price=trade.stop_loss, rate=trade.stop_loss * limit_price_pct + )['id'] + trade.stoploss_order_id = str(stoploss_order_id) + return False + except DependencyException as exception: + logger.warning('Stoploss order was cancelled, but unable to recreate one: %s', exception) + + # We check if stoploss order is fulfilled + if stoploss_order and stoploss_order['status'] == 'closed': + trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value + trade.update(stoploss_order) + self.notify_sell(trade) + return True + + # Finally we check if stoploss on exchange should be moved up because of trailing. + if stoploss_order and self.config.get('trailing_stop', False): + # if trailing stoploss is enabled we check if stoploss value has changed + # in which case we cancel stoploss order and put another one with new + # value immediately + self.handle_trailing_stoploss_on_exchange(trade, stoploss_order) + + return False def handle_trailing_stoploss_on_exchange(self, trade: Trade, order): """ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 416a085ad..3e8bdb001 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1036,7 +1036,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, side_effect=DependencyException() ) freqtrade.handle_stoploss_on_exchange(trade) - assert log_has('Unable to create stoploss order: ', caplog.record_tuples) + assert log_has('Unable to place a stoploss order on exchange: ', caplog.record_tuples) def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, From 31fa857319e73fff1beb48c123f8f8a65edb25d0 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 4 Apr 2019 17:15:51 +0200 Subject: [PATCH 391/457] typo --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index eeefaa6d8..acff0761b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -700,7 +700,7 @@ class FreqtradeBot(object): logger.warning('Unable to fetch stoploss order: %s', exception) - # If there open order id does not exist, + # If trade open order id does not exist, # it means buy order is fulfilled buy_order_fulfilled = not trade.open_order_id From 647534a4f81b42c0e5b944fc0aa992aea53b51c3 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 4 Apr 2019 17:17:21 +0200 Subject: [PATCH 392/457] flake8 --- freqtrade/freqtradebot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index acff0761b..9bd3384fa 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -699,7 +699,6 @@ class FreqtradeBot(object): except DependencyException as exception: logger.warning('Unable to fetch stoploss order: %s', exception) - # If trade open order id does not exist, # it means buy order is fulfilled buy_order_fulfilled = not trade.open_order_id @@ -734,7 +733,6 @@ class FreqtradeBot(object): except DependencyException as exception: logger.warning('Unable to place a stoploss order on exchange: %s', exception) - # If stoploss order is canceled for some reason we add it if stoploss_order and stoploss_order['status'] == 'canceled': try: @@ -745,7 +743,8 @@ class FreqtradeBot(object): trade.stoploss_order_id = str(stoploss_order_id) return False except DependencyException as exception: - logger.warning('Stoploss order was cancelled, but unable to recreate one: %s', exception) + logger.warning('Stoploss order was cancelled, ' + 'but unable to recreate one: %s', exception) # We check if stoploss order is fulfilled if stoploss_order and stoploss_order['status'] == 'closed': From 7f4fd6168a272f190c4dcbf7ec65aae9a6e1ea98 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 4 Apr 2019 17:23:21 +0200 Subject: [PATCH 393/457] test for canceled SL on exchange added --- freqtrade/tests/test_freqtradebot.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 3e8bdb001..c854d99e8 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1009,7 +1009,21 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id == 100 - # Third case: when stoploss is set and it is hit + # Third case: when stoploss was set but it was canceled for some reason + # should set a stoploss immediately and return False + trade.is_open = True + trade.open_order_id = None + trade.stoploss_order_id = 100 + + canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'}) + mocker.patch('freqtrade.exchange.Exchange.get_order', canceled_stoploss_order) + stoploss_limit.reset_mock() + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert stoploss_limit.call_count == 1 + assert trade.stoploss_order_id == "13434334" + + # Fourth case: when stoploss is set and it is hit # should unset stoploss_order_id and return true # as a trade actually happened freqtrade.create_trade() From 32cbb714f9bfe415a737f8c908e41a802c4ac666 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 4 Apr 2019 19:44:03 +0200 Subject: [PATCH 394/457] Improve commenting on backtsting and backtest_multi_tst --- freqtrade/optimize/backtesting.py | 9 +++++++-- freqtrade/tests/optimize/test_backtesting.py | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 0fcfd11f1..fc007ce2b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -329,6 +329,8 @@ class Backtesting(object): end_date = args['end_date'] trades = [] trade_count_lock: Dict = {} + + # Dict of ticker-lists for performance (looping lists is a lot faster than dataframes) ticker: Dict = self._get_ticker_list(processed) lock_pair_until: Dict = {} @@ -345,10 +347,11 @@ class Backtesting(object): try: row = ticker[pair][indexes[pair]] except IndexError: - # missing Data for one pair ... + # missing Data for one pair at the end. # Warnings for this are shown by `validate_backtest_data` continue + # Waits until the time-counter reaches the start of the data for this pair. if row.date > tmp.datetime: continue @@ -359,12 +362,13 @@ class Backtesting(object): if (not position_stacking and pair in lock_pair_until and row.date <= lock_pair_until[pair]): + # without positionstacking, we can only have one open trade per pair. continue + if max_open_trades > 0: # Check if max_open_trades has already been reached for the given date if not trade_count_lock.get(row.date, 0) < max_open_trades: continue - trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][indexes[pair]:], @@ -377,6 +381,7 @@ class Backtesting(object): # Set lock_pair_until to end of testing period if trade could not be closed lock_pair_until[pair] = end_date.datetime + # Move time one configured time_interval ahead. tmp += timedelta(minutes=self.ticker_interval_mins) return DataFrame.from_records(trades, columns=BacktestResult._fields) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 64a33fae2..edfe02225 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -701,9 +701,13 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair): mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) patch_exchange(mocker) + pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] data = history.load_data(datadir=None, ticker_interval='5m', pairs=pairs) + # Only use 500 lines to increase performance data = trim_dictlist(data, -500) + + # Remove data for one pair from the beginning of the data data[pair] = data[pair][tres:] # We need to enable sell-signal - otherwise it sells on ROI!! default_conf['experimental'] = {"use_sell_signal": True} From 2aa1b43f013a29ee2a398b660cd23709eb57eed1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 4 Apr 2019 20:56:40 +0300 Subject: [PATCH 395/457] get rid of TICKER_INTERVAL_MINUTES dict, use ccxt's parse_timeframe() instead --- freqtrade/constants.py | 25 +++++++---------------- freqtrade/data/converter.py | 6 +++--- freqtrade/data/history.py | 8 +++++--- freqtrade/exchange/exchange.py | 9 +++++--- freqtrade/freqtradebot.py | 3 ++- freqtrade/misc.py | 13 ++++++++++++ freqtrade/optimize/backtesting.py | 8 ++++---- freqtrade/strategy/interface.py | 4 ++-- freqtrade/tests/optimize/__init__.py | 6 +++--- freqtrade/tests/optimize/test_optimize.py | 7 ++++--- scripts/plot_profit.py | 5 +++-- 11 files changed, 52 insertions(+), 42 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 02062acc4..5243eeb4a 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -6,7 +6,7 @@ bot constants DEFAULT_CONFIG = 'config.json' DYNAMIC_WHITELIST = 20 # pairs PROCESS_THROTTLE_SECS = 5 # sec -TICKER_INTERVAL = 5 # min +DEFAULT_TICKER_INTERVAL = 5 # min HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec DEFAULT_STRATEGY = 'DefaultStrategy' @@ -22,22 +22,11 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] DRY_RUN_WALLET = 999.9 -TICKER_INTERVAL_MINUTES = { - '1m': 1, - '3m': 3, - '5m': 5, - '15m': 15, - '30m': 30, - '1h': 60, - '2h': 120, - '4h': 240, - '6h': 360, - '8h': 480, - '12h': 720, - '1d': 1440, - '3d': 4320, - '1w': 10080, -} +TICKER_INTERVALS = [ + '1m', '3m', '5m', '15m', '30m', + '1h', '2h', '4h', '6h', '8h', '12h', + '1d', '3d', '1w', +] SUPPORTED_FIAT = [ "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", @@ -52,7 +41,7 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'max_open_trades': {'type': 'integer', 'minimum': -1}, - 'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())}, + 'ticker_interval': {'type': 'string', 'enum': TICKER_INTERVALS}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']}, 'stake_amount': { "type": ["number", "string"], diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index c32338bbe..28749293b 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -4,8 +4,8 @@ Functions to convert data from one format to another import logging import pandas as pd from pandas import DataFrame, to_datetime +from freqtrade.misc import timeframe_to_minutes -from freqtrade.constants import TICKER_INTERVAL_MINUTES logger = logging.getLogger(__name__) @@ -65,9 +65,9 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str) -> Da 'close': 'last', 'volume': 'sum' } - tick_mins = TICKER_INTERVAL_MINUTES[ticker_interval] + ticker_minutes = timeframe_to_minutes(ticker_interval) # Resample to create "NAN" values - df = dataframe.resample(f'{tick_mins}min', on='date').agg(ohlc_dict) + df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict) # Forwardfill close for missing columns df['close'] = df['close'].fillna(method='ffill') diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 7d89f7ad6..0ecc632e4 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -12,10 +12,12 @@ from typing import Optional, List, Dict, Tuple, Any import arrow from pandas import DataFrame -from freqtrade import misc, constants, OperationalException +from freqtrade import misc, OperationalException +from freqtrade.arguments import TimeRange from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.exchange import Exchange -from freqtrade.arguments import TimeRange +from freqtrade.misc import timeframe_to_minutes + logger = logging.getLogger(__name__) @@ -163,7 +165,7 @@ def load_cached_data_for_updating(filename: Path, tick_interval: str, if timerange.starttype == 'date': since_ms = timerange.startts * 1000 elif timerange.stoptype == 'line': - num_minutes = timerange.stopts * constants.TICKER_INTERVAL_MINUTES[tick_interval] + num_minutes = timerange.stopts * timeframe_to_minutes(tick_interval) since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000 # read the cached file diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 011be58e5..f18a71a50 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -15,9 +15,12 @@ from pandas import DataFrame from freqtrade import constants, DependencyException, OperationalException, TemporaryError from freqtrade.data.converter import parse_ticker_dataframe +from freqtrade.misc import timeframe_to_seconds, timeframe_to_msecs + logger = logging.getLogger(__name__) + API_RETRY_COUNT = 4 @@ -502,8 +505,8 @@ class Exchange(object): # Assume exchange returns 500 candles _LIMIT = 500 - one_call = constants.TICKER_INTERVAL_MINUTES[tick_interval] * 60 * _LIMIT * 1000 - logger.debug("one_call: %s", one_call) + one_call = timeframe_to_msecs(tick_interval) * _LIMIT + logger.debug("one_call: %s msecs", one_call) input_coroutines = [self._async_get_candle_history( pair, tick_interval, since) for since in range(since_ms, arrow.utcnow().timestamp * 1000, one_call)] @@ -557,7 +560,7 @@ class Exchange(object): 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 + interval_in_sec = timeframe_to_seconds(ticker_interval) return not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0) + interval_in_sec) >= arrow.utcnow().timestamp) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index acf47b065..e9f636ec7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -16,6 +16,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.misc import timeframe_to_minutes from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver @@ -397,7 +398,7 @@ class FreqtradeBot(object): exchange=self.exchange.id, open_order_id=order_id, strategy=self.strategy.get_strategy_name(), - ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] + ticker_interval=timeframe_to_minutes(self.config['ticker_interval']) ) # Update fees if order is closed diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 38f758669..9aeaef069 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -8,6 +8,7 @@ import re from datetime import datetime from typing import Dict +from ccxt import Exchange import numpy as np from pandas import DataFrame import rapidjson @@ -131,3 +132,15 @@ def deep_merge_dicts(source, destination): destination[key] = value return destination + + +def timeframe_to_seconds(ticker_interval: str) -> int: + return Exchange.parse_timeframe(ticker_interval) + + +def timeframe_to_minutes(ticker_interval: str) -> int: + return Exchange.parse_timeframe(ticker_interval) // 60 + + +def timeframe_to_msecs(ticker_interval: str) -> int: + return Exchange.parse_timeframe(ticker_interval) * 1000 diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 293511fc0..e0360cdef 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -19,7 +19,7 @@ from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.data import history from freqtrade.data.dataprovider import DataProvider -from freqtrade.misc import file_dump_json +from freqtrade.misc import file_dump_json, timeframe_to_minutes from freqtrade.persistence import Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.state import RunMode @@ -77,7 +77,7 @@ class Backtesting(object): if self.config.get('strategy_list', None): # Force one interval self.ticker_interval = str(self.config.get('ticker_interval')) - self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval] + self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval) for strat in list(self.config['strategy_list']): stratconf = deepcopy(self.config) stratconf['strategy'] = strat @@ -96,7 +96,7 @@ class Backtesting(object): self.strategy = strategy self.ticker_interval = self.config.get('ticker_interval') - self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval] + self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval) self.tickerdata_to_dataframe = strategy.tickerdata_to_dataframe self.advise_buy = strategy.advise_buy self.advise_sell = strategy.advise_sell @@ -421,7 +421,7 @@ class Backtesting(object): min_date, max_date = optimize.get_timeframe(data) # Validate dataframe for missing values (mainly at start and end, as fillup is called) optimize.validate_backtest_data(data, min_date, max_date, - constants.TICKER_INTERVAL_MINUTES[self.ticker_interval]) + timeframe_to_minutes(self.ticker_interval)) logger.info( 'Measuring data from %s up to %s (%s days)..', min_date.isoformat(), diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index fcb27d7bd..646bd2a94 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -12,8 +12,8 @@ import warnings import arrow from pandas import DataFrame -from freqtrade import constants from freqtrade.data.dataprovider import DataProvider +from freqtrade.misc import timeframe_to_minutes from freqtrade.persistence import Trade from freqtrade.wallets import Wallets @@ -221,7 +221,7 @@ class IStrategy(ABC): # Check if dataframe is out of date signal_date = arrow.get(latest['date']) - interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval] + interval_minutes = timeframe_to_minutes(interval) offset = self.config.get('exchange', {}).get('outdated_offset', 5) if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + offset))): logger.warning( diff --git a/freqtrade/tests/optimize/__init__.py b/freqtrade/tests/optimize/__init__.py index 075938a61..9203ec19c 100644 --- a/freqtrade/tests/optimize/__init__.py +++ b/freqtrade/tests/optimize/__init__.py @@ -3,11 +3,11 @@ from typing import NamedTuple, List import arrow from pandas import DataFrame +from freqtrade.misc import timeframe_to_minutes from freqtrade.strategy.interface import SellType -from freqtrade.constants import TICKER_INTERVAL_MINUTES ticker_start_time = arrow.get(2018, 10, 3) -tests_ticker_interval = "1h" +tests_ticker_interval = '1h' class BTrade(NamedTuple): @@ -32,7 +32,7 @@ class BTContainer(NamedTuple): def _get_frame_time_from_offset(offset): - return ticker_start_time.shift(minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval]) + return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval)) ).datetime.replace(tzinfo=None) diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index 99cd24c26..088743038 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -1,7 +1,8 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 -from freqtrade import optimize, constants +from freqtrade import optimize from freqtrade.arguments import TimeRange from freqtrade.data import history +from freqtrade.misc import timeframe_to_minutes from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.tests.conftest import log_has, patch_exchange @@ -37,7 +38,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog) -> None: min_date, max_date = optimize.get_timeframe(data) caplog.clear() assert optimize.validate_backtest_data(data, min_date, max_date, - constants.TICKER_INTERVAL_MINUTES["1m"]) + timeframe_to_minutes('1m')) assert len(caplog.record_tuples) == 1 assert log_has( "UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values", @@ -61,5 +62,5 @@ def test_validate_backtest_data(default_conf, mocker, caplog) -> None: min_date, max_date = optimize.get_timeframe(data) caplog.clear() assert not optimize.validate_backtest_data(data, min_date, max_date, - constants.TICKER_INTERVAL_MINUTES["5m"]) + timeframe_to_minutes('5m')) assert len(caplog.record_tuples) == 0 diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 394f02116..8330e2a88 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -28,6 +28,7 @@ from freqtrade import constants, misc from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.data import history +from freqtrade.misc import timeframe_to_seconds from freqtrade.resolvers import StrategyResolver from freqtrade.state import RunMode @@ -193,8 +194,8 @@ def define_index(min_date: int, max_date: int, interval: str) -> int: """ Return the index of a specific date """ - interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval] - return int((max_date - min_date) / (interval_minutes * 60)) + interval_seconds = timeframe_to_seconds(interval) + return int((max_date - min_date) / interval_seconds) def plot_parse_args(args: List[str]) -> Namespace: From 7010c835d24bdcd5cfa3420d56d68906b2cdb60e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 4 Apr 2019 20:23:10 +0200 Subject: [PATCH 396/457] Improve commentign --- freqtrade/optimize/backtesting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index fc007ce2b..bbbda9b1f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -334,10 +334,11 @@ class Backtesting(object): ticker: Dict = self._get_ticker_list(processed) lock_pair_until: Dict = {} + # Indexes per pair, so some pairs are allowed to have a missing start. indexes: Dict = {} tmp = start_date + timedelta(minutes=self.ticker_interval_mins) - # Loop timerange and test per pair + # Loop timerange and get candle for each pair at that point in time while tmp < end_date: for i, pair in enumerate(ticker): From 6913bce6a1e84968bc4added173e82011dc75721 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 4 Apr 2019 21:30:08 +0300 Subject: [PATCH 397/457] flake8, import in script/plot_profit.py --- scripts/plot_profit.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 8330e2a88..2b6360bec 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -24,11 +24,10 @@ import plotly.graph_objs as go from plotly import tools from plotly.offline import plot -from freqtrade import constants, misc from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.data import history -from freqtrade.misc import timeframe_to_seconds +from freqtrade.misc import common_datearray, timeframe_to_seconds from freqtrade.resolvers import StrategyResolver from freqtrade.state import RunMode @@ -132,7 +131,7 @@ def plot_profit(args: Namespace) -> None: # NOTE: the dataframes are of unequal length, # 'dates' is an merged date array of them all. - dates = misc.common_datearray(dataframes) + dates = common_datearray(dataframes) min_date = int(min(dates).timestamp()) max_date = int(max(dates).timestamp()) num_iterations = define_index(min_date, max_date, tick_interval) + 1 From e3cdc0a05bcdc9ffd3901b73c64c54abf438de44 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 4 Apr 2019 20:53:28 +0200 Subject: [PATCH 398/457] typos and visual fixes --- docs/sql_cheatsheet.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 54f9b8213..803a2b48a 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -1,5 +1,5 @@ # SQL Helper -This page constains some help if you want to edit your sqlite db. +This page contains some help if you want to edit your sqlite db. ## Install sqlite3 **Ubuntu/Debian installation** @@ -66,11 +66,13 @@ SELECT * FROM trades; ## Fix trade still open after a manual sell on the exchange !!! Warning - Manually selling on the exchange should not be done by default, since the bot does not detect this and will try to sell anyway. - /foresell should accomplish the same thing. + Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. + Whenever possible, /forcesell should be used to accomplish the same thing. + + It is strongly advised to backup your database file before making any manual changes. !!! Note - This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. + This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. ```sql UPDATE trades From 7486cb7c64d6b50b5b934ffb2fa39fb3c215a8f3 Mon Sep 17 00:00:00 2001 From: iuvbio Date: Thu, 4 Apr 2019 21:05:26 +0200 Subject: [PATCH 399/457] fix admonitions --- docs/hyperopt.md | 4 ++-- docs/sql_cheatsheet.md | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index e25f35c35..b4e42de16 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -62,7 +62,7 @@ If you have updated the buy strategy, ie. changed the contents of #### Sell optimization -Similar to the buy-signal above, sell-signals can also be optimized. +Similar to the buy-signal above, sell-signals can also be optimized. Place the corresponding settings into the following methods * Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing. @@ -163,7 +163,7 @@ running at least several thousand evaluations. The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below. !!! Warning -When switching parameters or changing configuration options, the file `user_data/hyperopt_results.pickle` should be removed. It's used to be able to continue interrupted calculations, but does not detect changes to settings or the hyperopt file. + When switching parameters or changing configuration options, the file `user_data/hyperopt_results.pickle` should be removed. It's used to be able to continue interrupted calculations, but does not detect changes to settings or the hyperopt file. ### Execute Hyperopt with Different Ticker-Data Source diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 803a2b48a..f41520bd9 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -66,9 +66,7 @@ SELECT * FROM trades; ## Fix trade still open after a manual sell on the exchange !!! Warning - Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. - Whenever possible, /forcesell should be used to accomplish the same thing. - + Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forcesell should be used to accomplish the same thing. It is strongly advised to backup your database file before making any manual changes. !!! Note From dbb1bbf1019657969d7bc68ff541f6c9ea128c0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 5 Apr 2019 06:47:03 +0200 Subject: [PATCH 400/457] Fix webhook documentation --- docs/webhook-config.md | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index 2b5365e32..43b036c74 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -39,32 +39,30 @@ Different payloads can be configured for different events. Not all fields are ne The fields in `webhook.webhookbuy` are filled when the bot executes a buy. Parameters are filled using string.format. Possible parameters are: -* exchange -* pair -* limit -* stake_amount -* stake_amount_fiat -* stake_currency -* fiat_currency +* `exchange` +* `pair` +* `limit` +* `stake_amount` +* `stake_currency` +* `fiat_currency` ### Webhooksell The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format. Possible parameters are: -* exchange -* pair -* gain -* limit -* amount -* open_rate -* current_rate -* profit_amount -* profit_percent -* profit_fiat -* stake_currency -* fiat_currency -* sell_reason +* `exchange` +* `pair` +* `gain` +* `limit` +* `amount` +* `open_rate` +* `current_rate` +* `profit_amount` +* `profit_percent` +* `stake_currency` +* `fiat_currency` +* `sell_reason` ### Webhookstatus From ac1964edb16dd0bf13fd1bef6bc59ce9519b229b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 5 Apr 2019 06:49:15 +0200 Subject: [PATCH 401/457] Remove unnecessary comment --- docs/telegram-usage.md | 4 ++-- docs/webhook-config.md | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 4cc8eaa5c..9d6877318 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -1,13 +1,13 @@ # Telegram usage -This page explains how to command your bot with Telegram. - ## Prerequisite + To control your bot with Telegram, you need first to [set up a Telegram bot](installation.md) and add your Telegram API keys into your config file. ## Telegram commands + Per default, the Telegram bot shows predefined commands. Some commands are only available by sending them to the bot. The table below list the official commands. You can ask at any moment for help with `/help`. diff --git a/docs/webhook-config.md b/docs/webhook-config.md index 43b036c74..811b57f9b 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -1,7 +1,5 @@ # Webhook usage -This page explains how to configure your bot to talk to webhooks. - ## Configuration Enable webhooks by adding a webhook-section to your configuration file, and setting `webhook.enabled` to `true`. From 13e8f25ca97843baf47b4be38515ae830f3a7a39 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 5 Apr 2019 06:51:16 +0200 Subject: [PATCH 402/457] Improve docs layout --- mkdocs.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 9a6fec851..ecac265c1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,11 +3,12 @@ nav: - About: index.md - Installation: installation.md - Configuration: configuration.md - - Start the bot: bot-usage.md - - Stoploss: stoploss.md - Custom Strategy: bot-optimization.md - - Telegram: telegram-usage.md - - Web Hook: webhook-config.md + - Stoploss: stoploss.md + - Start the bot: bot-usage.md + - Control the bot: + - Telegram: telegram-usage.md + - Web Hook: webhook-config.md - Backtesting: backtesting.md - Hyperopt: hyperopt.md - Edge positioning: edge.md From 4c5432be6f51911327a8697a357d3b7c04996207 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 5 Apr 2019 16:48:14 +0300 Subject: [PATCH 403/457] Added command line options in backtesting to override max_open_trades and stake_amount --- freqtrade/arguments.py | 16 ++++++++++++++++ freqtrade/configuration.py | 10 +++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 8d7dac4bc..b0acb4122 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -247,6 +247,22 @@ class Arguments(object): dest='timerange', ) + parser.add_argument( + '--max_open_trades', + help='Specify max_open_trades to use.', + default=None, + type=int, + dest='max_open_trades', + ) + + parser.add_argument( + '--stake_amount', + help='Specify stake_amount.', + default=None, + type=float, + dest='stake_amount', + ) + @staticmethod def hyperopt_options(parser: argparse.ArgumentParser) -> None: """ diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index fdd71f2f5..e7441c18d 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -217,14 +217,22 @@ class Configuration(object): config.update({'position_stacking': True}) logger.info('Parameter --enable-position-stacking detected ...') - # If --disable-max-market-positions is used we add it to the configuration + # If --disable-max-market-positions or --max_open_trades is used we update configuration if 'use_max_market_positions' in self.args and not self.args.use_max_market_positions: config.update({'use_max_market_positions': False}) logger.info('Parameter --disable-max-market-positions detected ...') logger.info('max_open_trades set to unlimited ...') + elif 'max_open_trades' in self.args and self.args.max_open_trades: + config.update({'max_open_trades': self.args.max_open_trades}) + logger.info('Parameter --max_open_trades detected, overriding max_open_trades to: %s ...', config.get('max_open_trades')) else: logger.info('Using max_open_trades: %s ...', config.get('max_open_trades')) + # If --stake_amount is used we update configuration + if 'stake_amount' in self.args and self.args.stake_amount: + config.update({'stake_amount': self.args.stake_amount}) + logger.info('Parameter --stake_amount detected, overriding stake_amount to: %s ...', config.get('stake_amount')) + # If --timerange is used we add it to the configuration if 'timerange' in self.args and self.args.timerange: config.update({'timerange': self.args.timerange}) From 2b49a11b2add88895a3bd3f2f99189bd499b84a5 Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 5 Apr 2019 19:46:43 +0200 Subject: [PATCH 404/457] returning InvalidOrder exception for get_order --- freqtrade/exchange/exchange.py | 4 ++-- freqtrade/tests/exchange/test_exchange.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7f4ab062c..7880ce464 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -624,8 +624,8 @@ class Exchange(object): try: return self._api.fetch_order(order_id, pair) except ccxt.InvalidOrder as e: - raise DependencyException( - f'Could not get order. Message: {e}') + raise InvalidOrderException( + f'Tried to get an invalid order. Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get order due to {e.__class__.__name__}. Message: {e}') diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 4deb74c67..66bc47405 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -1261,11 +1261,11 @@ def test_get_order(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.get_order('X', 'TKN/BTC') == 456 - with pytest.raises(DependencyException): + with pytest.raises(InvalidOrderException): api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.get_order(order_id='_', pair='TKN/BTC') - assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1 + assert api_mock.fetch_order.call_count == 1 ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_order', 'fetch_order', From 9712fb2d57669217523e797ec5ffd81796a02981 Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 5 Apr 2019 19:49:02 +0200 Subject: [PATCH 405/457] removing unnecessary comment --- freqtrade/freqtradebot.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9bd3384fa..a0725ad08 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -703,10 +703,7 @@ class FreqtradeBot(object): # it means buy order is fulfilled buy_order_fulfilled = not trade.open_order_id - # Limit price threshold - # This is the limit price percentage below which you don't want to sell - # 0.99 is arbitrary here. - # As limit price should always be below price + # Limit price threshold: As limit price should always be below price limit_price_pct = 0.99 # If buy order is fulfilled but there is no stoploss, From 25d8e93a90de924758d6298455b59e6facfb9e13 Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 5 Apr 2019 19:53:15 +0200 Subject: [PATCH 406/457] remove unnecessary comment --- freqtrade/freqtradebot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a0725ad08..f04a39610 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -699,8 +699,7 @@ class FreqtradeBot(object): except DependencyException as exception: logger.warning('Unable to fetch stoploss order: %s', exception) - # If trade open order id does not exist, - # it means buy order is fulfilled + # If trade open order id does not exist: buy order is fulfilled buy_order_fulfilled = not trade.open_order_id # Limit price threshold: As limit price should always be below price From 54d068de44ce6a4bbb21a4ca08502d6dd1f77868 Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 5 Apr 2019 20:20:16 +0200 Subject: [PATCH 407/457] missing test added --- freqtrade/freqtradebot.py | 9 +++++---- freqtrade/tests/test_freqtradebot.py | 11 ++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f04a39610..3bab1758d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -13,7 +13,7 @@ import arrow from requests.exceptions import RequestException import sdnotify -from freqtrade import (DependencyException, OperationalException, +from freqtrade import (DependencyException, OperationalException, InvalidOrderException, TemporaryError, __version__, constants, persistence) from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider @@ -692,11 +692,13 @@ class FreqtradeBot(object): logger.debug('Handling stoploss on exchange %s ...', trade) + stoploss_order = None + try: # First we check if there is already a stoploss on exchange stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \ if trade.stoploss_order_id else None - except DependencyException as exception: + except InvalidOrderException as exception: logger.warning('Unable to fetch stoploss order: %s', exception) # If trade open order id does not exist: buy order is fulfilled @@ -705,8 +707,7 @@ class FreqtradeBot(object): # Limit price threshold: As limit price should always be below price limit_price_pct = 0.99 - # If buy order is fulfilled but there is no stoploss, - # then we add a stoploss on exchange + # If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange if (buy_order_fulfilled and not stoploss_order): if self.edge: stoploss = self.edge.stoploss(pair=trade.pair) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index c854d99e8..5896e7cf9 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -12,7 +12,7 @@ import pytest import requests from freqtrade import (DependencyException, OperationalException, - TemporaryError, constants) + TemporaryError, InvalidOrderException, constants) from freqtrade.data.dataprovider import DataProvider from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade @@ -1052,6 +1052,15 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, freqtrade.handle_stoploss_on_exchange(trade) assert log_has('Unable to place a stoploss order on exchange: ', caplog.record_tuples) + #Fifth case: get_order returns InvalidOrder + # It should try to add stoploss order + trade.stoploss_order_id = 100 + stoploss_limit.reset_mock() + mocker.patch('freqtrade.exchange.Exchange.get_order', side_effect=InvalidOrderException()) + mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) + freqtrade.handle_stoploss_on_exchange(trade) + assert stoploss_limit.call_count == 1 + def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, markets, limit_buy_order, limit_sell_order) -> None: From a505826ec92650af7d19a1d5a05f7fd09c82b956 Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 5 Apr 2019 20:20:41 +0200 Subject: [PATCH 408/457] flake8 --- freqtrade/tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 5896e7cf9..9687fe903 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1052,7 +1052,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, freqtrade.handle_stoploss_on_exchange(trade) assert log_has('Unable to place a stoploss order on exchange: ', caplog.record_tuples) - #Fifth case: get_order returns InvalidOrder + # Fifth case: get_order returns InvalidOrder # It should try to add stoploss order trade.stoploss_order_id = 100 stoploss_limit.reset_mock() From acb99a03e3f3e936865f16adb56657aeb8183ae1 Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 5 Apr 2019 20:30:54 +0200 Subject: [PATCH 409/457] adding stoploss on exchange manual cancel note --- docs/configuration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 75843ef4a..f7e2a07f3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -212,6 +212,10 @@ The below is the default which is used if this is not configured in either strat unsure of what you are doing. For more information about how stoploss works please read [the stoploss documentation](stoploss.md). +!!! Note + In case of stoploss on exchange if the stoploss is cancelled manually then + the bot would recreate one. + ### Understand order_time_in_force The `order_time_in_force` configuration parameter defines the policy by which the order is executed on the exchange. Three commonly used time in force are: From 41ff2a927650347a78f386abc57e387dadf15185 Mon Sep 17 00:00:00 2001 From: Misagh Date: Fri, 5 Apr 2019 20:40:44 +0200 Subject: [PATCH 410/457] TemporaryError removed --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 346bcfce3..009e039b3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -12,7 +12,7 @@ import arrow from requests.exceptions import RequestException from freqtrade import (DependencyException, OperationalException, InvalidOrderException, - TemporaryError, __version__, constants, persistence) + __version__, constants, persistence) from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge From 481df98f58025a428f61b9c375155701b9068310 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 6 Apr 2019 12:38:04 +0000 Subject: [PATCH 411/457] Update ccxt from 1.18.432 to 1.18.435 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eb5d564fa..7006c52c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.432 +ccxt==1.18.435 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From 7a598f32dcc0f45f326148ed98c4ea83278dce42 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 6 Apr 2019 19:58:45 +0200 Subject: [PATCH 412/457] Move rpc-count calculation to _rpc class --- freqtrade/rpc/rpc.py | 7 ++++++- freqtrade/rpc/telegram.py | 10 ++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 88ade0c27..d3a6dc0db 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -459,7 +459,12 @@ class RPC(object): if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') - return Trade.get_open_trades() + trades = Trade.get_open_trades() + return { + 'current': len(trades), + 'max': self._config['max_open_trades'], + 'total stake': sum((trade.open_rate * trade.amount) for trade in trades) + } def _rpc_whitelist(self) -> Dict: """ Returns the currently active whitelist""" diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e17f73502..ca108b17e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -456,12 +456,10 @@ class Telegram(RPC): :return: None """ try: - trades = self._rpc_count() - message = tabulate({ - 'current': [len(trades)], - 'max': [self._config['max_open_trades']], - 'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)] - }, headers=['current', 'max', 'total stake'], tablefmt='simple') + counts = self._rpc_count() + message = tabulate({k: [v] for k, v in counts.items()}, + headers=['current', 'max', 'total stake'], + tablefmt='simple') message = "
{}
".format(message) logger.debug(message) self._send_msg(message, parse_mode=ParseMode.HTML) From 4eb0ed9f2fd3296643e45da462c7867c3a7a2bba Mon Sep 17 00:00:00 2001 From: TL Nguyen Date: Sat, 6 Apr 2019 21:11:14 +0300 Subject: [PATCH 413/457] Add Dockerfile.pi for building docker image for raspberry pi --- Dockerfile.pi | 40 ++++++++++++++++++++++++++++++++++++++++ requirements-pi.txt | 23 +++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 Dockerfile.pi create mode 100644 requirements-pi.txt diff --git a/Dockerfile.pi b/Dockerfile.pi new file mode 100644 index 000000000..5184e2d37 --- /dev/null +++ b/Dockerfile.pi @@ -0,0 +1,40 @@ +FROM balenalib/raspberrypi3-debian:stretch + +RUN [ "cross-build-start" ] + +RUN apt-get update \ + && apt-get -y install wget curl build-essential libssl-dev libffi-dev \ + && apt-get clean + +# Prepare environment +RUN mkdir /freqtrade +WORKDIR /freqtrade + +# Install TA-lib +COPY build_helpers/ta-lib-0.4.0-src.tar.gz /freqtrade/ +RUN tar -xzf /freqtrade/ta-lib-0.4.0-src.tar.gz \ + && cd /freqtrade/ta-lib/ \ + && ./configure \ + && make \ + && make install \ + && rm /freqtrade/ta-lib-0.4.0-src.tar.gz + +ENV LD_LIBRARY_PATH /usr/local/lib + +# Install berryconda +RUN wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryconda3-2.0.0-Linux-armv7l.sh \ + && bash ./Berryconda3-2.0.0-Linux-armv7l.sh -b \ + && rm Berryconda3-2.0.0-Linux-armv7l.sh + +# Install dependencies +COPY requirements-pi.txt /freqtrade/ +RUN ~/berryconda3/bin/conda install -y numpy pandas scipy \ + && ~/berryconda3/bin/pip install -r requirements-pi.txt --no-cache-dir + +# Install and execute +COPY . /freqtrade/ +RUN ~/berryconda3/bin/pip install -e . --no-cache-dir + +RUN [ "cross-build-end" ] + +ENTRYPOINT ["/root/berryconda3/bin/python","./freqtrade/main.py"] diff --git a/requirements-pi.txt b/requirements-pi.txt new file mode 100644 index 000000000..575107e2d --- /dev/null +++ b/requirements-pi.txt @@ -0,0 +1,23 @@ +ccxt==1.18.270 +SQLAlchemy==1.2.18 +python-telegram-bot==11.1.0 +arrow==0.13.1 +cachetools==3.1.0 +requests==2.21.0 +urllib3==1.24.1 +wrapt==1.11.1 +scikit-learn==0.20.2 +joblib==0.13.2 +jsonschema==2.6.0 +TA-Lib==0.4.17 +tabulate==0.8.3 +coinmarketcap==5.0.3 + +# Required for hyperopt +scikit-optimize==0.5.2 + +# find first, C search in arrays +py_find_1st==1.1.3 + +#Load ticker files 30% faster +python-rapidjson==0.7.0 From f13917813637ca9bc64e764cc92a9876c3850e00 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 6 Apr 2019 20:01:29 +0200 Subject: [PATCH 414/457] rpc_counts should be in .rpc --- freqtrade/rpc/rpc.py | 6 +++--- freqtrade/tests/rpc/test_rpc.py | 10 ++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index d3a6dc0db..aac419fe1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -454,7 +454,7 @@ class RPC(object): for pair, rate, count in pair_rates ] - def _rpc_count(self) -> List[Trade]: + def _rpc_count(self) -> Dict[str, float]: """ Returns the number of trades running """ if self._freqtrade.state != State.RUNNING: raise RPCException('trader is not running') @@ -462,8 +462,8 @@ class RPC(object): trades = Trade.get_open_trades() return { 'current': len(trades), - 'max': self._config['max_open_trades'], - 'total stake': sum((trade.open_rate * trade.amount) for trade in trades) + 'max': float(self._freqtrade.config['max_open_trades']), + 'total_stake': sum((trade.open_rate * trade.amount) for trade in trades) } def _rpc_whitelist(self) -> Dict: diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 8b10a1314..25d1109b2 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -581,15 +581,13 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) - trades = rpc._rpc_count() - nb_trades = len(trades) - assert nb_trades == 0 + counts = rpc._rpc_count() + assert counts["current"] == 0 # Create some test data freqtradebot.create_trade() - trades = rpc._rpc_count() - nb_trades = len(trades) - assert nb_trades == 1 + counts = rpc._rpc_count() + assert counts["current"] == 1 def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None: From d294cab933ddaa49a00ae47ac33ae87d32754648 Mon Sep 17 00:00:00 2001 From: Misagh Date: Sat, 6 Apr 2019 20:27:03 +0200 Subject: [PATCH 415/457] adding order id to invalidorder exception message --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7880ce464..275b2123f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -625,7 +625,7 @@ class Exchange(object): return self._api.fetch_order(order_id, pair) except ccxt.InvalidOrder as e: raise InvalidOrderException( - f'Tried to get an invalid order. Message: {e}') + f'Tried to get an invalid order (id: {order_id}). Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get order due to {e.__class__.__name__}. Message: {e}') From dc1968b9685270d738646df3ed26145a4b96b84a Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 6 Apr 2019 23:36:55 +0300 Subject: [PATCH 416/457] docstrings added --- freqtrade/misc.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 9aeaef069..57a5673fd 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -135,12 +135,27 @@ def deep_merge_dicts(source, destination): def timeframe_to_seconds(ticker_interval: str) -> int: + """ + This function uses ccxt to parse the timeframe interval value written in the human readable + form ('1m', '5m', '1h', '1d', '1w', etc.) and returns the number + of seconds for one timeframe interval. + """ return Exchange.parse_timeframe(ticker_interval) def timeframe_to_minutes(ticker_interval: str) -> int: + """ + This function uses ccxt to parse the timeframe interval value written in the human readable + form ('1m', '5m', '1h', '1d', '1w', etc.) and returns the number + of minutes for one timeframe interval. + """ return Exchange.parse_timeframe(ticker_interval) // 60 def timeframe_to_msecs(ticker_interval: str) -> int: + """ + This function uses ccxt to parse the timeframe interval value written in the human readable + form ('1m', '5m', '1h', '1d', '1w', etc.) and returns the number + of milliseconds for one timeframe interval. + """ return Exchange.parse_timeframe(ticker_interval) * 1000 From d6d16b4696268c891efa99862309f969a35a4a1d Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 7 Apr 2019 00:22:02 +0300 Subject: [PATCH 417/457] docstrings improved --- freqtrade/misc.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 57a5673fd..d066878be 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -136,8 +136,8 @@ def deep_merge_dicts(source, destination): def timeframe_to_seconds(ticker_interval: str) -> int: """ - This function uses ccxt to parse the timeframe interval value written in the human readable - form ('1m', '5m', '1h', '1d', '1w', etc.) and returns the number + Translates the timeframe interval value written in the human readable + form ('1m', '5m', '1h', '1d', '1w', etc.) to the number of seconds for one timeframe interval. """ return Exchange.parse_timeframe(ticker_interval) @@ -145,17 +145,13 @@ def timeframe_to_seconds(ticker_interval: str) -> int: def timeframe_to_minutes(ticker_interval: str) -> int: """ - This function uses ccxt to parse the timeframe interval value written in the human readable - form ('1m', '5m', '1h', '1d', '1w', etc.) and returns the number - of minutes for one timeframe interval. + Same as above, but returns minutes. """ return Exchange.parse_timeframe(ticker_interval) // 60 def timeframe_to_msecs(ticker_interval: str) -> int: """ - This function uses ccxt to parse the timeframe interval value written in the human readable - form ('1m', '5m', '1h', '1d', '1w', etc.) and returns the number - of milliseconds for one timeframe interval. + Same as above, but returns milliseconds. """ return Exchange.parse_timeframe(ticker_interval) * 1000 From e7c8e62d751faaf3abd22742eccb52c0d77abc84 Mon Sep 17 00:00:00 2001 From: TL Nguyen Date: Sun, 7 Apr 2019 10:31:03 +0300 Subject: [PATCH 418/457] Remove requirements-pi.txt, change Dockerfile.pi to utilize the requirements.txt instead --- Dockerfile.pi | 7 ++++--- requirements-pi.txt | 23 ----------------------- 2 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 requirements-pi.txt diff --git a/Dockerfile.pi b/Dockerfile.pi index 5184e2d37..1041b3c87 100644 --- a/Dockerfile.pi +++ b/Dockerfile.pi @@ -27,9 +27,10 @@ RUN wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryco && rm Berryconda3-2.0.0-Linux-armv7l.sh # Install dependencies -COPY requirements-pi.txt /freqtrade/ -RUN ~/berryconda3/bin/conda install -y numpy pandas scipy \ - && ~/berryconda3/bin/pip install -r requirements-pi.txt --no-cache-dir +COPY requirements.txt /freqtrade/ +RUN sed -i -e '/^numpy==/d' -e '/^pandas==/d' -e '/^scipy==/d' ./requirements.txt \ + && ~/berryconda3/bin/conda install -y numpy pandas scipy \ + && ~/berryconda3/bin/pip install -r requirements.txt --no-cache-dir # Install and execute COPY . /freqtrade/ diff --git a/requirements-pi.txt b/requirements-pi.txt deleted file mode 100644 index 575107e2d..000000000 --- a/requirements-pi.txt +++ /dev/null @@ -1,23 +0,0 @@ -ccxt==1.18.270 -SQLAlchemy==1.2.18 -python-telegram-bot==11.1.0 -arrow==0.13.1 -cachetools==3.1.0 -requests==2.21.0 -urllib3==1.24.1 -wrapt==1.11.1 -scikit-learn==0.20.2 -joblib==0.13.2 -jsonschema==2.6.0 -TA-Lib==0.4.17 -tabulate==0.8.3 -coinmarketcap==5.0.3 - -# Required for hyperopt -scikit-optimize==0.5.2 - -# find first, C search in arrays -py_find_1st==1.1.3 - -#Load ticker files 30% faster -python-rapidjson==0.7.0 From c35e5ca7dda865243006b1dd251b8a1eeb82af00 Mon Sep 17 00:00:00 2001 From: TL Nguyen Date: Sun, 7 Apr 2019 14:05:41 +0300 Subject: [PATCH 419/457] Add back requirements-pi.txt file and put it into .pyup.yml --- .pyup.yml | 1 + requirements-pi.txt | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 requirements-pi.txt diff --git a/.pyup.yml b/.pyup.yml index 01d4bba2a..462ae5783 100644 --- a/.pyup.yml +++ b/.pyup.yml @@ -22,6 +22,7 @@ requirements: - requirements.txt - requirements-dev.txt - requirements-plot.txt + - requirements-pi.txt # configure the branch prefix the bot is using diff --git a/requirements-pi.txt b/requirements-pi.txt new file mode 100644 index 000000000..4fca8b032 --- /dev/null +++ b/requirements-pi.txt @@ -0,0 +1,23 @@ +ccxt==1.18.353 +SQLAlchemy==1.3.1 +python-telegram-bot==11.1.0 +arrow==0.13.1 +cachetools==3.1.0 +requests==2.21.0 +urllib3==1.24.1 +wrapt==1.11.1 +scikit-learn==0.20.3 +joblib==0.13.2 +jsonschema==3.0.1 +TA-Lib==0.4.17 +tabulate==0.8.3 +coinmarketcap==5.0.3 + +# Required for hyperopt +scikit-optimize==0.5.2 + +# find first, C search in arrays +py_find_1st==1.1.3 + +#Load ticker files 30% faster +python-rapidjson==0.7.0 From 3ad4d937c5731bd9ef169684ca72d68a0082ffd0 Mon Sep 17 00:00:00 2001 From: TL Nguyen Date: Sun, 7 Apr 2019 14:07:26 +0300 Subject: [PATCH 420/457] Correct Dockerfile.pi file to use requirements-pi.txt --- Dockerfile.pi | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile.pi b/Dockerfile.pi index 1041b3c87..5184e2d37 100644 --- a/Dockerfile.pi +++ b/Dockerfile.pi @@ -27,10 +27,9 @@ RUN wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryco && rm Berryconda3-2.0.0-Linux-armv7l.sh # Install dependencies -COPY requirements.txt /freqtrade/ -RUN sed -i -e '/^numpy==/d' -e '/^pandas==/d' -e '/^scipy==/d' ./requirements.txt \ - && ~/berryconda3/bin/conda install -y numpy pandas scipy \ - && ~/berryconda3/bin/pip install -r requirements.txt --no-cache-dir +COPY requirements-pi.txt /freqtrade/ +RUN ~/berryconda3/bin/conda install -y numpy pandas scipy \ + && ~/berryconda3/bin/pip install -r requirements-pi.txt --no-cache-dir # Install and execute COPY . /freqtrade/ From 3a81eb7d483c46afdf30deed04f1e9a065437544 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 7 Apr 2019 12:38:05 +0000 Subject: [PATCH 421/457] Update ccxt from 1.18.435 to 1.18.437 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7006c52c7..99892a544 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.435 +ccxt==1.18.437 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From ebf11263516258f853573468e59c1c9bf0941f34 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 7 Apr 2019 16:14:40 +0300 Subject: [PATCH 422/457] cosmetic: rename interval, tick_interval, etc --> ticker_interval --- freqtrade/data/dataprovider.py | 12 +++---- freqtrade/data/history.py | 20 ++++++------ freqtrade/exchange/exchange.py | 32 +++++++++--------- freqtrade/tests/data/test_dataprovider.py | 40 +++++++++++------------ freqtrade/tests/data/test_history.py | 14 ++++---- freqtrade/tests/exchange/test_exchange.py | 6 ++-- scripts/download_backtest_data.py | 10 +++--- scripts/plot_dataframe.py | 16 ++++----- scripts/plot_profit.py | 16 ++++----- 9 files changed, 83 insertions(+), 83 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 375b8bf5b..df4accf93 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -37,23 +37,23 @@ class DataProvider(object): @property def available_pairs(self) -> List[Tuple[str, str]]: """ - Return a list of tuples containing pair, tick_interval for which data is currently cached. + Return a list of tuples containing pair, ticker_interval for which data is currently cached. Should be whitelist + open trades. """ return list(self._exchange._klines.keys()) - def ohlcv(self, pair: str, tick_interval: str = None, copy: bool = True) -> DataFrame: + def ohlcv(self, pair: str, ticker_interval: str = None, copy: bool = True) -> DataFrame: """ get ohlcv data for the given pair as DataFrame Please check `available_pairs` to verify which pairs are currently cached. :param pair: pair to get the data for - :param tick_interval: ticker_interval to get pair for + :param ticker_interval: ticker_interval to get pair for :param copy: copy dataframe before returning. Use false only for RO operations (where the dataframe is not modified) """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): - if tick_interval: - pairtick = (pair, tick_interval) + if ticker_interval: + pairtick = (pair, ticker_interval) else: pairtick = (pair, self._config['ticker_interval']) @@ -65,7 +65,7 @@ class DataProvider(object): """ get stored historic ohlcv data :param pair: pair to get the data for - :param tick_interval: ticker_interval to get pair for + :param ticker_interval: ticker_interval to get pair for """ return load_pair_history(pair=pair, ticker_interval=ticker_interval, diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 0ecc632e4..594c85b5f 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -101,7 +101,7 @@ def load_pair_history(pair: str, download_pair_history(datadir=datadir, exchange=exchange, pair=pair, - tick_interval=ticker_interval, + ticker_interval=ticker_interval, timerange=timerange) pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) @@ -151,7 +151,7 @@ def make_testdata_path(datadir: Optional[Path]) -> Path: return datadir or (Path(__file__).parent.parent / "tests" / "testdata").resolve() -def load_cached_data_for_updating(filename: Path, tick_interval: str, +def load_cached_data_for_updating(filename: Path, ticker_interval: str, timerange: Optional[TimeRange]) -> Tuple[List[Any], Optional[int]]: """ @@ -165,7 +165,7 @@ def load_cached_data_for_updating(filename: Path, tick_interval: str, if timerange.starttype == 'date': since_ms = timerange.startts * 1000 elif timerange.stoptype == 'line': - num_minutes = timerange.stopts * timeframe_to_minutes(tick_interval) + num_minutes = timerange.stopts * timeframe_to_minutes(ticker_interval) since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000 # read the cached file @@ -192,7 +192,7 @@ def load_cached_data_for_updating(filename: Path, tick_interval: str, def download_pair_history(datadir: Optional[Path], exchange: Exchange, pair: str, - tick_interval: str = '5m', + ticker_interval: str = '5m', timerange: Optional[TimeRange] = None) -> bool: """ Download the latest ticker intervals from the exchange for the pair passed in parameters @@ -202,7 +202,7 @@ def download_pair_history(datadir: Optional[Path], Based on @Rybolov work: https://github.com/rybolov/freqtrade-data :param pair: pair to download - :param tick_interval: ticker interval + :param ticker_interval: ticker interval :param timerange: range of time to download :return: bool with success state @@ -210,17 +210,17 @@ def download_pair_history(datadir: Optional[Path], try: path = make_testdata_path(datadir) filepair = pair.replace("/", "_") - filename = path.joinpath(f'{filepair}-{tick_interval}.json') + filename = path.joinpath(f'{filepair}-{ticker_interval}.json') - logger.info('Download the pair: "%s", Interval: %s', pair, tick_interval) + logger.info('Download the pair: "%s", Interval: %s', pair, ticker_interval) - data, since_ms = load_cached_data_for_updating(filename, tick_interval, timerange) + data, since_ms = load_cached_data_for_updating(filename, ticker_interval, timerange) logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None') logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None') # Default since_ms to 30 days if nothing is given - new_data = exchange.get_history(pair=pair, tick_interval=tick_interval, + new_data = exchange.get_history(pair=pair, ticker_interval=ticker_interval, since_ms=since_ms if since_ms else int(arrow.utcnow().shift(days=-30).float_timestamp) * 1000) @@ -233,5 +233,5 @@ def download_pair_history(datadir: Optional[Path], return True except BaseException: logger.info('Failed to download the pair: "%s", Interval: %s', - pair, tick_interval) + pair, ticker_interval) return False diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f18a71a50..2e8d6b99a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -489,26 +489,26 @@ class Exchange(object): logger.info("returning cached ticker-data for %s", pair) return self._cached_ticker[pair] - def get_history(self, pair: str, tick_interval: str, + def get_history(self, pair: str, ticker_interval: str, since_ms: int) -> List: """ Gets candle history using asyncio and returns the list of candles. Handles all async doing. """ return asyncio.get_event_loop().run_until_complete( - self._async_get_history(pair=pair, tick_interval=tick_interval, + self._async_get_history(pair=pair, ticker_interval=ticker_interval, since_ms=since_ms)) async def _async_get_history(self, pair: str, - tick_interval: str, + ticker_interval: str, since_ms: int) -> List: # Assume exchange returns 500 candles _LIMIT = 500 - one_call = timeframe_to_msecs(tick_interval) * _LIMIT + one_call = timeframe_to_msecs(ticker_interval) * _LIMIT logger.debug("one_call: %s msecs", one_call) input_coroutines = [self._async_get_candle_history( - pair, tick_interval, since) for since in + pair, ticker_interval, since) for since in range(since_ms, arrow.utcnow().timestamp * 1000, one_call)] tickers = await asyncio.gather(*input_coroutines, return_exceptions=True) @@ -548,14 +548,14 @@ class Exchange(object): logger.warning("Async code raised an exception: %s", res.__class__.__name__) continue pair = res[0] - tick_interval = res[1] + ticker_interval = res[1] ticks = res[2] # keeping last candle time as last refreshed time of the pair if ticks: - self._pairs_last_refresh_time[(pair, tick_interval)] = ticks[-1][0] // 1000 + self._pairs_last_refresh_time[(pair, ticker_interval)] = ticks[-1][0] // 1000 # keeping parsed dataframe in cache - self._klines[(pair, tick_interval)] = parse_ticker_dataframe( - ticks, tick_interval, fill_missing=True) + self._klines[(pair, ticker_interval)] = parse_ticker_dataframe( + ticks, ticker_interval, fill_missing=True) return tickers def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool: @@ -566,17 +566,17 @@ class Exchange(object): + interval_in_sec) >= arrow.utcnow().timestamp) @retrier_async - async def _async_get_candle_history(self, pair: str, tick_interval: str, + async def _async_get_candle_history(self, pair: str, ticker_interval: str, since_ms: Optional[int] = None) -> Tuple[str, str, List]: """ Asyncronously gets candle histories using fetch_ohlcv - returns tuple: (pair, tick_interval, ohlcv_list) + returns tuple: (pair, ticker_interval, ohlcv_list) """ try: # fetch ohlcv asynchronously - logger.debug("fetching %s, %s since %s ...", pair, tick_interval, since_ms) + logger.debug("fetching %s, %s since %s ...", pair, ticker_interval, since_ms) - data = await self._api_async.fetch_ohlcv(pair, timeframe=tick_interval, + data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval, since=since_ms) # Because some exchange sort Tickers ASC and other DESC. @@ -588,9 +588,9 @@ class Exchange(object): data = sorted(data, key=lambda x: x[0]) except IndexError: logger.exception("Error loading %s. Result was %s.", pair, data) - return pair, tick_interval, [] - logger.debug("done fetching %s, %s ...", pair, tick_interval) - return pair, tick_interval, data + return pair, ticker_interval, [] + logger.debug("done fetching %s, %s ...", pair, ticker_interval) + return pair, ticker_interval, data except ccxt.NotSupported as e: raise OperationalException( diff --git a/freqtrade/tests/data/test_dataprovider.py b/freqtrade/tests/data/test_dataprovider.py index b17bba273..993f0b59b 100644 --- a/freqtrade/tests/data/test_dataprovider.py +++ b/freqtrade/tests/data/test_dataprovider.py @@ -9,31 +9,31 @@ from freqtrade.tests.conftest import get_patched_exchange def test_ohlcv(mocker, default_conf, ticker_history): default_conf["runmode"] = RunMode.DRY_RUN - tick_interval = default_conf["ticker_interval"] + ticker_interval = default_conf["ticker_interval"] exchange = get_patched_exchange(mocker, default_conf) - exchange._klines[("XRP/BTC", tick_interval)] = ticker_history - exchange._klines[("UNITTEST/BTC", tick_interval)] = ticker_history + exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history + exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.DRY_RUN - assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", tick_interval)) - assert isinstance(dp.ohlcv("UNITTEST/BTC", tick_interval), DataFrame) - assert dp.ohlcv("UNITTEST/BTC", tick_interval) is not ticker_history - assert dp.ohlcv("UNITTEST/BTC", tick_interval, copy=False) is ticker_history - assert not dp.ohlcv("UNITTEST/BTC", tick_interval).empty - assert dp.ohlcv("NONESENSE/AAA", tick_interval).empty + assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval)) + assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) + assert dp.ohlcv("UNITTEST/BTC", ticker_interval) is not ticker_history + assert dp.ohlcv("UNITTEST/BTC", ticker_interval, copy=False) is ticker_history + assert not dp.ohlcv("UNITTEST/BTC", ticker_interval).empty + assert dp.ohlcv("NONESENSE/AAA", ticker_interval).empty # Test with and without parameter - assert dp.ohlcv("UNITTEST/BTC", tick_interval).equals(dp.ohlcv("UNITTEST/BTC")) + assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC")) default_conf["runmode"] = RunMode.LIVE dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.LIVE - assert isinstance(dp.ohlcv("UNITTEST/BTC", tick_interval), DataFrame) + assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) default_conf["runmode"] = RunMode.BACKTEST dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.BACKTEST - assert dp.ohlcv("UNITTEST/BTC", tick_interval).empty + assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty def test_historic_ohlcv(mocker, default_conf, ticker_history): @@ -54,15 +54,15 @@ def test_historic_ohlcv(mocker, default_conf, ticker_history): def test_available_pairs(mocker, default_conf, ticker_history): exchange = get_patched_exchange(mocker, default_conf) - tick_interval = default_conf["ticker_interval"] - exchange._klines[("XRP/BTC", tick_interval)] = ticker_history - exchange._klines[("UNITTEST/BTC", tick_interval)] = ticker_history + ticker_interval = default_conf["ticker_interval"] + exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history + exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history dp = DataProvider(default_conf, exchange) assert len(dp.available_pairs) == 2 assert dp.available_pairs == [ - ("XRP/BTC", tick_interval), - ("UNITTEST/BTC", tick_interval), + ("XRP/BTC", ticker_interval), + ("UNITTEST/BTC", ticker_interval), ] @@ -71,10 +71,10 @@ def test_refresh(mocker, default_conf, ticker_history): mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock) exchange = get_patched_exchange(mocker, default_conf, id="binance") - tick_interval = default_conf["ticker_interval"] - pairs = [("XRP/BTC", tick_interval), ("UNITTEST/BTC", tick_interval)] + ticker_interval = default_conf["ticker_interval"] + pairs = [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval)] - pairs_non_trad = [("ETH/USDT", tick_interval), ("BTC/TUSD", "1h")] + pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")] dp = DataProvider(default_conf, exchange) dp.refresh(pairs) diff --git a/freqtrade/tests/data/test_history.py b/freqtrade/tests/data/test_history.py index bc859b325..c0b1cade3 100644 --- a/freqtrade/tests/data/test_history.py +++ b/freqtrade/tests/data/test_history.py @@ -242,10 +242,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf) -> Non assert download_pair_history(datadir=None, exchange=exchange, pair='MEME/BTC', - tick_interval='1m') + ticker_interval='1m') assert download_pair_history(datadir=None, exchange=exchange, pair='CFI/BTC', - tick_interval='1m') + ticker_interval='1m') assert not exchange._pairs_last_refresh_time assert os.path.isfile(file1_1) is True assert os.path.isfile(file2_1) is True @@ -259,10 +259,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf) -> Non assert download_pair_history(datadir=None, exchange=exchange, pair='MEME/BTC', - tick_interval='5m') + ticker_interval='5m') assert download_pair_history(datadir=None, exchange=exchange, pair='CFI/BTC', - tick_interval='5m') + ticker_interval='5m') assert not exchange._pairs_last_refresh_time assert os.path.isfile(file1_5) is True assert os.path.isfile(file2_5) is True @@ -280,8 +280,8 @@ def test_download_pair_history2(mocker, default_conf) -> None: json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None) mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=tick) exchange = get_patched_exchange(mocker, default_conf) - download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='1m') - download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='3m') + download_pair_history(None, exchange, pair="UNITTEST/BTC", ticker_interval='1m') + download_pair_history(None, exchange, pair="UNITTEST/BTC", ticker_interval='3m') assert json_dump_mock.call_count == 2 @@ -298,7 +298,7 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog, def assert not download_pair_history(datadir=None, exchange=exchange, pair='MEME/BTC', - tick_interval='1m') + ticker_interval='1m') # clean files freshly downloaded _clean_test_file(file1_1) _clean_test_file(file1_5) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index eed16d39b..6997c6ba3 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -940,8 +940,8 @@ def test_get_history(default_conf, mocker, caplog, exchange_name): ] pair = 'ETH/BTC' - async def mock_candle_hist(pair, tick_interval, since_ms): - return pair, tick_interval, tick + async def mock_candle_hist(pair, ticker_interval, since_ms): + return pair, ticker_interval, tick exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls @@ -1037,7 +1037,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ # exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), "_async_get_candle_history", "fetch_ohlcv", - pair='ABCD/BTC', tick_interval=default_conf['ticker_interval']) + pair='ABCD/BTC', ticker_interval=default_conf['ticker_interval']) api_mock = MagicMock() with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index 5dee41bdd..50005b97b 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -92,18 +92,18 @@ for pair in PAIRS: pairs_not_available.append(pair) print(f"skipping pair {pair}") continue - for tick_interval in timeframes: + for ticker_interval in timeframes: pair_print = pair.replace('/', '_') - filename = f'{pair_print}-{tick_interval}.json' + filename = f'{pair_print}-{ticker_interval}.json' dl_file = dl_path.joinpath(filename) if args.erase and dl_file.exists(): - print(f'Deleting existing data for pair {pair}, interval {tick_interval}') + print(f'Deleting existing data for pair {pair}, interval {ticker_interval}') dl_file.unlink() - print(f'downloading pair {pair}, interval {tick_interval}') + print(f'downloading pair {pair}, interval {ticker_interval}') download_pair_history(datadir=dl_path, exchange=exchange, pair=pair, - tick_interval=tick_interval, + ticker_interval=ticker_interval, timerange=timerange) diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 14d57265e..7fdc607e0 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -82,7 +82,7 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram return trades -def generate_plot_file(fig, pair, tick_interval, is_last) -> None: +def generate_plot_file(fig, pair, ticker_interval, is_last) -> None: """ Generate a plot html file from pre populated fig plotly object :return: None @@ -90,7 +90,7 @@ def generate_plot_file(fig, pair, tick_interval, is_last) -> None: logger.info('Generate plot file for %s', pair) pair_name = pair.replace("/", "_") - file_name = 'freqtrade-plot-' + pair_name + '-' + tick_interval + '.html' + file_name = 'freqtrade-plot-' + pair_name + '-' + ticker_interval + '.html' Path("user_data/plots").mkdir(parents=True, exist_ok=True) @@ -135,20 +135,20 @@ def get_tickers_data(strategy, exchange, pairs: List[str], args): :return: dictinnary of tickers. output format: {'pair': tickersdata} """ - tick_interval = strategy.ticker_interval + ticker_interval = strategy.ticker_interval timerange = Arguments.parse_timerange(args.timerange) tickers = {} if args.live: logger.info('Downloading pairs.') - exchange.refresh_latest_ohlcv([(pair, tick_interval) for pair in pairs]) + exchange.refresh_latest_ohlcv([(pair, ticker_interval) for pair in pairs]) for pair in pairs: - tickers[pair] = exchange.klines((pair, tick_interval)) + tickers[pair] = exchange.klines((pair, ticker_interval)) else: tickers = history.load_data( datadir=Path(str(_CONF.get("datadir"))), pairs=pairs, - ticker_interval=tick_interval, + ticker_interval=ticker_interval, refresh_pairs=_CONF.get('refresh_pairs', False), timerange=timerange, exchange=Exchange(_CONF) @@ -399,7 +399,7 @@ def analyse_and_plot_pairs(args: Namespace): strategy, exchange, pairs = get_trading_env(args) # Set timerange to use timerange = Arguments.parse_timerange(args.timerange) - tick_interval = strategy.ticker_interval + ticker_interval = strategy.ticker_interval tickers = get_tickers_data(strategy, exchange, pairs, args) pair_counter = 0 @@ -422,7 +422,7 @@ def analyse_and_plot_pairs(args: Namespace): ) is_last = (False, True)[pair_counter == len(tickers)] - generate_plot_file(fig, pair, tick_interval, is_last) + generate_plot_file(fig, pair, ticker_interval, is_last) logger.info('End of ploting process %s plots generated', pair_counter) diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 2b6360bec..500d9fcde 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -76,7 +76,7 @@ def plot_profit(args: Namespace) -> None: in helping out to find a good algorithm. """ - # We need to use the same pairs, same tick_interval + # We need to use the same pairs, same ticker_interval # and same timeperiod as used in backtesting # to match the tickerdata against the profits-results timerange = Arguments.parse_timerange(args.timerange) @@ -112,7 +112,7 @@ def plot_profit(args: Namespace) -> None: else: filter_pairs = config['exchange']['pair_whitelist'] - tick_interval = strategy.ticker_interval + ticker_interval = strategy.ticker_interval pairs = config['exchange']['pair_whitelist'] if filter_pairs: @@ -122,7 +122,7 @@ def plot_profit(args: Namespace) -> None: tickers = history.load_data( datadir=Path(str(config.get('datadir'))), pairs=pairs, - ticker_interval=tick_interval, + ticker_interval=ticker_interval, refresh_pairs=False, timerange=timerange ) @@ -134,7 +134,7 @@ def plot_profit(args: Namespace) -> None: dates = common_datearray(dataframes) min_date = int(min(dates).timestamp()) max_date = int(max(dates).timestamp()) - num_iterations = define_index(min_date, max_date, tick_interval) + 1 + num_iterations = define_index(min_date, max_date, ticker_interval) + 1 # Make an average close price of all the pairs that was involved. # this could be useful to gauge the overall market trend @@ -154,7 +154,7 @@ def plot_profit(args: Namespace) -> None: avgclose /= num # make an profits-growth array - pg = make_profit_array(data, num_iterations, min_date, tick_interval, filter_pairs) + pg = make_profit_array(data, num_iterations, min_date, ticker_interval, filter_pairs) # # Plot the pairs average close prices, and total profit growth @@ -178,7 +178,7 @@ def plot_profit(args: Namespace) -> None: fig.append_trace(profit, 2, 1) for pair in pairs: - pg = make_profit_array(data, num_iterations, min_date, tick_interval, [pair]) + pg = make_profit_array(data, num_iterations, min_date, ticker_interval, [pair]) pair_profit = go.Scattergl( x=dates, y=pg, @@ -189,11 +189,11 @@ def plot_profit(args: Namespace) -> None: plot(fig, filename=str(Path('user_data').joinpath('freqtrade-profit-plot.html'))) -def define_index(min_date: int, max_date: int, interval: str) -> int: +def define_index(min_date: int, max_date: int, ticker_interval: str) -> int: """ Return the index of a specific date """ - interval_seconds = timeframe_to_seconds(interval) + interval_seconds = timeframe_to_seconds(ticker_interval) return int((max_date - min_date) / interval_seconds) From ffdc33d964cc08de47a7f3eb35bbea812c3603a3 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 8 Apr 2019 12:39:06 +0000 Subject: [PATCH 423/457] Update ccxt from 1.18.437 to 1.18.442 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 99892a544..de437aa67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.437 +ccxt==1.18.442 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From 5c4170951a6f240f1a71f63d4edd43f23b6df58f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 8 Apr 2019 19:59:30 +0200 Subject: [PATCH 424/457] Don't send too large messages --- freqtrade/rpc/telegram.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ca108b17e..d1ef75cf5 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -20,6 +20,9 @@ logger = logging.getLogger(__name__) logger.debug('Included module rpc.telegram ...') +MAX_TELEGRAM_MESSAGE = 4096 + + def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: """ Decorator to check if the message comes from the correct chat_id @@ -327,13 +330,20 @@ class Telegram(RPC): output = '' for currency in result['currencies']: if currency['est_btc'] > 0.0001: - output += "*{currency}:*\n" \ + curr_output = "*{currency}:*\n" \ "\t`Available: {available: .8f}`\n" \ "\t`Balance: {balance: .8f}`\n" \ "\t`Pending: {pending: .8f}`\n" \ "\t`Est. BTC: {est_btc: .8f}`\n".format(**currency) else: - output += "*{currency}:* not showing <1$ amount \n".format(**currency) + curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency) + + # Handle overflowing messsage length + if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE: + self._send_msg(output, bot=bot) + output = curr_output + else: + output += curr_output output += "\n*Estimated Value*:\n" \ "\t`BTC: {total: .8f}`\n" \ From ff6967de9e5ce68a9c80c1072555e94335286bac Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 8 Apr 2019 19:59:54 +0200 Subject: [PATCH 425/457] Add test for too large balance --- freqtrade/tests/rpc/test_rpc_telegram.py | 40 ++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 8f43d7ed0..137d7b3b6 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -4,7 +4,8 @@ import re from datetime import datetime -from random import randint +from random import choice, randint +from string import ascii_uppercase from unittest.mock import MagicMock, PropertyMock import arrow @@ -20,7 +21,8 @@ from freqtrade.rpc import RPCMessageType from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.state import State from freqtrade.strategy.interface import SellType -from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange) +from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, + patch_exchange) from freqtrade.tests.test_freqtradebot import patch_get_signal @@ -587,6 +589,40 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: assert 'all balances are zero' in result +def test_balance_handle_too_large_response(default_conf, update, mocker) -> None: + balances = [] + for i in range(100): + curr = choice(ascii_uppercase) + choice(ascii_uppercase) + choice(ascii_uppercase) + balances.append({ + 'currency': curr, + 'available': 1.0, + 'pending': 0.5, + 'balance': i, + 'est_btc': 1 + }) + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={ + 'currencies': balances, + 'total': 100.0, + 'symbol': 100.0, + 'value': 1000.0, + }) + + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot, (True, False)) + + telegram = Telegram(freqtradebot) + + telegram._balance(bot=MagicMock(), update=update) + assert msg_mock.call_count > 1 + + def test_start_handle(default_conf, update, mocker) -> None: msg_mock = MagicMock() mocker.patch.multiple( From 71e671f053be5b2854b6dc738cad8afb43d65b6a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 9 Apr 2019 12:38:06 +0000 Subject: [PATCH 426/457] Update ccxt from 1.18.442 to 1.18.445 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index de437aa67..4bd9433fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.442 +ccxt==1.18.445 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From e75cdd4c27dcc6bde25f39c4986edb282fc677a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Apr 2019 06:59:10 +0200 Subject: [PATCH 427/457] Rename variable, add more tests --- freqtrade/rpc/telegram.py | 4 ++-- freqtrade/tests/rpc/test_rpc_telegram.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d1ef75cf5..4e4c080d0 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) logger.debug('Included module rpc.telegram ...') -MAX_TELEGRAM_MESSAGE = 4096 +MAX_TELEGRAM_MESSAGE_LENGTH = 4096 def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: @@ -339,7 +339,7 @@ class Telegram(RPC): curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency) # Handle overflowing messsage length - if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE: + if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE_LENGTH: self._send_msg(output, bot=bot) output = curr_output else: diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 137d7b3b6..f2f3f3945 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -621,6 +621,11 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None telegram._balance(bot=MagicMock(), update=update) assert msg_mock.call_count > 1 + # Test if wrap happens around 4000 - + # and each single currency-output is around 120 characters long so we need + # an offset to avoid random test failures + assert len(msg_mock.call_args_list[0][0][0]) < 4096 + assert len(msg_mock.call_args_list[0][0][0]) > (4096 - 120) def test_start_handle(default_conf, update, mocker) -> None: From f736646ac6975d2eed5a44f9db99e2ca89b7b511 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 10 Apr 2019 12:38:05 +0000 Subject: [PATCH 428/457] Update ccxt from 1.18.445 to 1.18.456 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4bd9433fe..99c5104c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.445 +ccxt==1.18.456 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From 902ffa68539bdda660cb0258b23dfef15d8b56c9 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 11 Apr 2019 00:15:17 +0300 Subject: [PATCH 429/457] impoved argument and exception handling in scripts/get_market_pairs.py --- scripts/get_market_pairs.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/get_market_pairs.py b/scripts/get_market_pairs.py index 1a4c593f9..1d1f2114b 100644 --- a/scripts/get_market_pairs.py +++ b/scripts/get_market_pairs.py @@ -1,5 +1,6 @@ import os import sys +import traceback root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(root + '/python') @@ -49,6 +50,11 @@ def print_supported_exchanges(): try: + if len(sys.argv) < 2: + dump("Usage: python " + sys.argv[0], green('id')) + print_supported_exchanges() + sys.exit(1) + id = sys.argv[1] # get exchange id from command line arguments # check if the exchange is supported by ccxt @@ -87,5 +93,7 @@ try: except Exception as e: dump('[' + type(e).__name__ + ']', str(e)) + dump(traceback.format_exc()) dump("Usage: python " + sys.argv[0], green('id')) print_supported_exchanges() + sys.exit(1) From c2ca899c7eb66dc83d1d2bda5adbe44f8381bb97 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 11 Apr 2019 00:59:53 +0300 Subject: [PATCH 430/457] fixed printed message; cosmetic changes in the code in scripts/download_backtest_data.py --- scripts/download_backtest_data.py | 32 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index 50005b97b..2acadc8b6 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 - -"""This script generate json data""" +""" +This script generates json data +""" import json import sys from pathlib import Path @@ -35,7 +36,7 @@ if args.config: config: Dict[str, Any] = {} # Now expecting a list of config filenames here, not a string for path in args.config: - print('Using config: %s ...', path) + print(f"Using config: {path}...") # Merge config options, overwriting old values config = deep_merge_dicts(configuration._load_config_file(path), config) @@ -44,18 +45,19 @@ if args.config: config['exchange']['key'] = '' config['exchange']['secret'] = '' else: - config = {'stake_currency': '', - 'dry_run': True, - 'exchange': { - 'name': args.exchange, - 'key': '', - 'secret': '', - 'pair_whitelist': [], - 'ccxt_async_config': { - "enableRateLimit": False - } - } - } + config = { + 'stake_currency': '', + 'dry_run': True, + 'exchange': { + 'name': args.exchange, + 'key': '', + 'secret': '', + 'pair_whitelist': [], + 'ccxt_async_config': { + 'enableRateLimit': False + } + } + } dl_path = Path(DEFAULT_DL_PATH).joinpath(config['exchange']['name']) From 12ca103f9f4d98af2b68a5e532d9b2fbac80513d Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 11 Apr 2019 12:38:06 +0000 Subject: [PATCH 431/457] Update ccxt from 1.18.456 to 1.18.458 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 99c5104c9..ed6e5e236 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.456 +ccxt==1.18.458 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From c3b9d69919e67a0f36cdd74a54a2b438ff96cfd7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 12 Apr 2019 07:05:00 +0200 Subject: [PATCH 432/457] Add docstring explaining the source of the script --- scripts/get_market_pairs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/get_market_pairs.py b/scripts/get_market_pairs.py index 1a4c593f9..c4b9777ea 100644 --- a/scripts/get_market_pairs.py +++ b/scripts/get_market_pairs.py @@ -1,3 +1,7 @@ +""" +This script was adapted from ccxt here: +https://github.com/ccxt/ccxt/blob/master/examples/py/arbitrage-pairs.py +""" import os import sys From d87db70ed0ddb41af199e1a1c69444ca763caf26 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 12 Apr 2019 07:05:15 +0200 Subject: [PATCH 433/457] Fix missing column header --- freqtrade/rpc/telegram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ca108b17e..0bc877272 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -266,7 +266,8 @@ class Telegram(RPC): headers=[ 'Day', f'Profit {stake_cur}', - f'Profit {fiat_disp_cur}' + f'Profit {fiat_disp_cur}', + f'Trades' ], tablefmt='simple') message = f'Daily Profit over the last {timescale} days:\n
{stats_tab}
' From 016e8fde89a2d1d7e5a85dde7c570e2147a07e6a Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 12 Apr 2019 10:32:43 +0300 Subject: [PATCH 434/457] wrong rendering at freqtrade.io fixed; other cosmetics in docs/ * Titles render wrong both in the docs dir and at freqtrade.io * Last list of links renders wring at freqtrade.io --- docs/configuration.md | 2 +- docs/faq.md | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f7e2a07f3..06937945d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -70,8 +70,8 @@ Mandatory Parameters are marked as **Required**. | `strategy` | DefaultStrategy | Defines Strategy class to use. | `strategy_path` | null | Adds an additional strategy lookup path (must be a folder). | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. -| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. | `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. +| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. ### Parameters in the strategy diff --git a/docs/faq.md b/docs/faq.md index 60c1509e0..c551e3638 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -19,8 +19,7 @@ of course constantly aim to improve the bot but it will _always_ be a gamble, which should leave you with modest wins on monthly basis but you can't say much from few trades. -#### I’d like to change the stake amount. Can I just stop the bot with -/stop and then change the config.json and run it again? +#### I’d like to change the stake amount. Can I just stop the bot with /stop and then change the config.json and run it again? Not quite. Trades are persisted to a database but the configuration is currently only read when the bot is killed and restarted. `/stop` more @@ -31,16 +30,16 @@ like pauses. You can stop your bot, adjust settings and start it again. That's great. We have a nice backtesting and hyperoptimizing setup. See the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands). -#### Is there a setting to only SELL the coins being held and not -perform anymore BUYS? +#### Is there a setting to only SELL the coins being held and not perform anymore BUYS? You can use the `/forcesell all` command from Telegram. ### 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 +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 have to run it for 10.000 or more. But it will take an eternity to compute. @@ -64,10 +63,10 @@ Finding a great Hyperopt results takes time. If you wonder why it takes a while to find great hyperopt results This answer was written during the under the release 0.15.1, when we had: + - 8 triggers - 9 guards: let's say we evaluate even 10 values from each -- 1 stoploss calculation: let's say we want 10 values from that too to -be evaluated +- 1 stoploss calculation: let's say we want 10 values from that too to be evaluated The following calculation is still very rough and not very precise but it will give the idea. With only these triggers and guards there is @@ -82,10 +81,9 @@ of the search space. 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 - +- 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 From 9f828224bc2bf72ed2f45e7857fe1df9d50f0d52 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 12 Apr 2019 12:39:05 +0000 Subject: [PATCH 435/457] Update ccxt from 1.18.458 to 1.18.460 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ed6e5e236..7ab39e78a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.458 +ccxt==1.18.460 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From 2f79cf13044b1eff958db96432f1f3b4f7e1fd7e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 13 Apr 2019 12:39:05 +0000 Subject: [PATCH 436/457] Update ccxt from 1.18.460 to 1.18.466 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7ab39e78a..952ee265e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.460 +ccxt==1.18.466 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From 37b1389f123730c65a2e30aa1c835ce9968ab8ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Apr 2019 10:17:06 +0200 Subject: [PATCH 437/457] Fix flake8 --- freqtrade/configuration.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index e7441c18d..ae9978f81 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -194,7 +194,7 @@ class Configuration(object): logger.info(f'Created data directory: {datadir}') return datadir - def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]: + def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]: # noqa: C901 """ Extract information for sys.argv and load Backtesting configuration :return: configuration as dictionary @@ -224,14 +224,16 @@ class Configuration(object): logger.info('max_open_trades set to unlimited ...') elif 'max_open_trades' in self.args and self.args.max_open_trades: config.update({'max_open_trades': self.args.max_open_trades}) - logger.info('Parameter --max_open_trades detected, overriding max_open_trades to: %s ...', config.get('max_open_trades')) + logger.info('Parameter --max_open_trades detected, ' + 'overriding max_open_trades to: %s ...', config.get('max_open_trades')) else: logger.info('Using max_open_trades: %s ...', config.get('max_open_trades')) # If --stake_amount is used we update configuration if 'stake_amount' in self.args and self.args.stake_amount: config.update({'stake_amount': self.args.stake_amount}) - logger.info('Parameter --stake_amount detected, overriding stake_amount to: %s ...', config.get('stake_amount')) + logger.info('Parameter --stake_amount detected, overriding stake_amount to: %s ...', + config.get('stake_amount')) # If --timerange is used we add it to the configuration if 'timerange' in self.args and self.args.timerange: From 5e0e8de4f6673f0a83161f70a2abdbf86a668e32 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Apr 2019 13:13:28 +0200 Subject: [PATCH 438/457] Version bump to 3.7.3 in docker file --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index aeefc0722..e36766530 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7.2-slim-stretch +FROM python:3.7.3-slim-stretch RUN apt-get update \ && apt-get -y install curl build-essential libssl-dev \ From 4f6df731563b933876e377b7c6f4ac40795351ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Apr 2019 15:57:44 +0200 Subject: [PATCH 439/457] Update documentation for Raspberry install since we now have a rpi-requirements file --- docs/installation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 995bc561b..23a6cbd23 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -315,7 +315,6 @@ Before installing FreqTrade on a Raspberry Pi running the official Raspbian Imag The following assumes that miniconda3 is installed and available in your environment. Last miniconda3 installation file use python 3.4, we will update to python 3.6 on this installation. It's recommended to use (mini)conda for this as installation/compilation of `numpy`, `scipy` and `pandas` takes a long time. -If you have installed it from (mini)conda, you can remove `numpy`, `scipy`, and `pandas` from `requirements.txt` before you install it with `pip`. Additional package to install on your Raspbian, `libffi-dev` required by cryptography (from python-telegram-bot). @@ -327,7 +326,7 @@ conda activate freqtrade conda install scipy pandas numpy sudo apt install libffi-dev -python3 -m pip install -r requirements.txt +python3 -m pip install -r requirements-pi.txt python3 -m pip install -e . ``` From 6be4c6af0e6ca45fdd2a9df4ce94db6b46a7777f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 15 Apr 2019 12:39:05 +0000 Subject: [PATCH 440/457] Update ccxt from 1.18.466 to 1.18.468 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 952ee265e..d8d4d84d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.466 +ccxt==1.18.468 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From 0ece1688330067732424bf51ebd01191f5086a7f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 15 Apr 2019 12:39:06 +0000 Subject: [PATCH 441/457] Update ccxt from 1.18.353 to 1.18.468 --- requirements-pi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-pi.txt b/requirements-pi.txt index 4fca8b032..c2e3e0414 100644 --- a/requirements-pi.txt +++ b/requirements-pi.txt @@ -1,4 +1,4 @@ -ccxt==1.18.353 +ccxt==1.18.468 SQLAlchemy==1.3.1 python-telegram-bot==11.1.0 arrow==0.13.1 From 7efab85b109251f15138f3d994ca585b7f3d3b6a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 15 Apr 2019 12:39:08 +0000 Subject: [PATCH 442/457] Update sqlalchemy from 1.3.1 to 1.3.2 --- requirements-pi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-pi.txt b/requirements-pi.txt index c2e3e0414..7d7b779e9 100644 --- a/requirements-pi.txt +++ b/requirements-pi.txt @@ -1,5 +1,5 @@ ccxt==1.18.468 -SQLAlchemy==1.3.1 +SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 cachetools==3.1.0 From 4f557af6cb80327dbc3b9115af8f52b1afe55fad Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 16 Apr 2019 12:39:04 +0000 Subject: [PATCH 443/457] Update ccxt from 1.18.468 to 1.18.470 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d8d4d84d5..8b6e3014d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.468 +ccxt==1.18.470 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From 5cb90bdf775116bbd866045ef7bf11cf8ccbb60a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 16 Apr 2019 12:39:05 +0000 Subject: [PATCH 444/457] Update ccxt from 1.18.468 to 1.18.470 --- requirements-pi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-pi.txt b/requirements-pi.txt index 7d7b779e9..aa1133a0f 100644 --- a/requirements-pi.txt +++ b/requirements-pi.txt @@ -1,4 +1,4 @@ -ccxt==1.18.468 +ccxt==1.18.470 SQLAlchemy==1.3.2 python-telegram-bot==11.1.0 arrow==0.13.1 From aa63f2be1fe3ddcdcc35fd24a851201d457130eb Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 16 Apr 2019 12:39:06 +0000 Subject: [PATCH 445/457] Update sqlalchemy from 1.3.2 to 1.3.3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8b6e3014d..40903e9d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ccxt==1.18.470 -SQLAlchemy==1.3.2 +SQLAlchemy==1.3.3 python-telegram-bot==11.1.0 arrow==0.13.1 cachetools==3.1.0 From 87ff5ad1e09534f12a438aec05b04c973cb57da5 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 16 Apr 2019 12:39:07 +0000 Subject: [PATCH 446/457] Update sqlalchemy from 1.3.2 to 1.3.3 --- requirements-pi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-pi.txt b/requirements-pi.txt index aa1133a0f..0fd26a3d9 100644 --- a/requirements-pi.txt +++ b/requirements-pi.txt @@ -1,5 +1,5 @@ ccxt==1.18.470 -SQLAlchemy==1.3.2 +SQLAlchemy==1.3.3 python-telegram-bot==11.1.0 arrow==0.13.1 cachetools==3.1.0 From c40406d26eb924356773148a5b8abc525accb10d Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 16 Apr 2019 12:39:09 +0000 Subject: [PATCH 447/457] Update pytest from 4.4.0 to 4.4.1 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c59923ed2..77f122a59 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ flake8==3.7.7 flake8-type-annotations==0.1.0 flake8-tidy-imports==2.0.0 -pytest==4.4.0 +pytest==4.4.1 pytest-mock==1.10.3 pytest-asyncio==0.10.0 pytest-cov==2.6.1 From b2a623ee169485bb8bf82367c38d4679a1777fb5 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 16 Apr 2019 12:39:12 +0000 Subject: [PATCH 448/457] Update plotly from 3.7.1 to 3.8.0 --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index e582fddf6..0b924b608 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==3.7.1 +plotly==3.8.0 From 43119efaf0b2262984611a789eb0ba7bb32d7904 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Apr 2019 19:41:02 +0200 Subject: [PATCH 449/457] Remove ccxt_rate_limit completely (was deprecated) --- freqtrade/configuration.py | 5 ----- freqtrade/exchange/exchange.py | 1 - freqtrade/tests/test_configuration.py | 9 --------- 3 files changed, 15 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index ae9978f81..7f0f3c34a 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -393,11 +393,6 @@ class Configuration(object): raise OperationalException( exception_msg ) - # Depreciation warning - if 'ccxt_rate_limit' in config.get('exchange', {}): - logger.warning("`ccxt_rate_limit` has been deprecated in favor of " - "`ccxt_config` and `ccxt_async_config` and will be removed " - "in a future version.") logger.debug('Exchange "%s" supported', exchange) return True diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ffd574f90..57a002384 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -146,7 +146,6 @@ class Exchange(object): 'secret': exchange_config.get('secret'), 'password': exchange_config.get('password'), 'uid': exchange_config.get('uid', ''), - 'enableRateLimit': exchange_config.get('ccxt_rate_limit', True) } if ccxt_kwargs: logger.info('Applying additional ccxt config: %s', ccxt_kwargs) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 45e539c2f..bcd0bd92c 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -485,15 +485,6 @@ def test_check_exchange(default_conf, caplog) -> None: ): configuration.check_exchange(default_conf) - # Test ccxt_rate_limit depreciation - default_conf.get('exchange').update({'name': 'binance'}) - default_conf['exchange']['ccxt_rate_limit'] = True - configuration.check_exchange(default_conf) - assert log_has("`ccxt_rate_limit` has been deprecated in favor of " - "`ccxt_config` and `ccxt_async_config` and will be removed " - "in a future version.", - caplog.record_tuples) - def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: mocker.patch('freqtrade.configuration.open', mocker.mock_open( From 5db10bdcc7929b8b05cf69ec249cc6b537122480 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Apr 2019 19:51:42 +0200 Subject: [PATCH 450/457] Add rateLimit parameters for different exchanges --- config.json.example | 3 ++- config_binance.json.example | 3 ++- config_full.json.example | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config.json.example b/config.json.example index 323ff711e..94084434a 100644 --- a/config.json.example +++ b/config.json.example @@ -30,7 +30,8 @@ "secret": "your_exchange_secret", "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { - "enableRateLimit": false + "enableRateLimit": true, + "rateLimit": 500 }, "pair_whitelist": [ "ETH/BTC", diff --git a/config_binance.json.example b/config_binance.json.example index 3d11f317a..ab57db88f 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -30,7 +30,8 @@ "secret": "your_exchange_secret", "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { - "enableRateLimit": false + "enableRateLimit": true, + "rateLimit": 200 }, "pair_whitelist": [ "AST/BTC", diff --git a/config_full.json.example b/config_full.json.example index 58d3d3ac6..20ba10c89 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -61,6 +61,7 @@ "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { "enableRateLimit": false, + "rateLimit": 500, "aiohttp_trust_env": false }, "pair_whitelist": [ From 52cc2d224e47ff86a527423f444cd8131d5626d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Apr 2019 19:51:56 +0200 Subject: [PATCH 451/457] improve documentation for exchange configuration --- docs/configuration.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 06937945d..0da14d9f6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -46,7 +46,6 @@ Mandatory Parameters are marked as **Required**. | `exchange.secret` | secret | API secret to use for the exchange. Only required when you are in production mode. | `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param. | `exchange.pair_blacklist` | [] | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param. -| `exchange.ccxt_rate_limit` | True | DEPRECATED!! Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange. | `exchange.ccxt_config` | None | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) | `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) | `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded. @@ -217,6 +216,7 @@ The below is the default which is used if this is not configured in either strat the bot would recreate one. ### Understand order_time_in_force + The `order_time_in_force` configuration parameter defines the policy by which the order is executed on the exchange. Three commonly used time in force are: @@ -252,9 +252,9 @@ The possible values are: `gtc` (default), `fok` or `ioc`. This is an ongoing work. For now it is supported only for binance and only for buy orders. Please don't change the default value unless you know what you are doing. -### What values for exchange.name? +### Exchange configuration -Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency +Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency exchange markets and trading APIs. The complete up-to-date list can be found in the [CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested with only Bittrex and Binance. @@ -266,6 +266,30 @@ The bot was tested with the following exchanges: Feel free to test other exchanges and submit your PR to improve the bot. +#### Sample exchange configuration + +A exchange configuration for "binance" would look as follows: + +```json +"exchange": { + "name": "binance", + "key": "your_exchange_key", + "secret": "your_exchange_secret", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 200 + }, +``` + +This configuration enables binance, as well as rate limiting to avoid bans from the exchange. +`"rateLimit": 200` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false. + +!!! Note + Optimal settings for rate limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings. + We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step. + + ### What values can be used for fiat_display_currency? The `fiat_display_currency` configuration parameter sets the base currency to use for the From a7383ad35d64a08850350eb5ece372e41efef896 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Apr 2019 19:54:04 +0200 Subject: [PATCH 452/457] enable ratelimit in download-backtest-data too --- scripts/download_backtest_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/download_backtest_data.py b/scripts/download_backtest_data.py index 2acadc8b6..42b305778 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -54,7 +54,8 @@ else: 'secret': '', 'pair_whitelist': [], 'ccxt_async_config': { - 'enableRateLimit': False + 'enableRateLimit': True, + 'rateLimit': 200 } } } From 2cee716181cd414b29d45ab46eb3d1a0d653e7fd Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Apr 2019 20:25:48 +0200 Subject: [PATCH 453/457] Gracefully handle pickle-errors when @staticmethod is used pOinted out in https://github.com/freqtrade/freqtrade-strategies/issues/28 --- freqtrade/resolvers/strategy_resolver.py | 11 +++++++---- freqtrade/tests/strategy/test_strategy.py | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 44cc3fe76..b2743a417 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -157,12 +157,15 @@ class StrategyResolver(IResolver): getfullargspec(strategy.populate_indicators).args) strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) - - return import_strategy(strategy, config=config) + try: + return import_strategy(strategy, config=config) + except TypeError as e: + logger.warning( + f"Impossible to load strategy '{strategy}' from {_path}. Error: {e}") except FileNotFoundError: logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) raise ImportError( - "Impossible to load Strategy '{}'. This class does not exist" - " or contains Python code errors".format(strategy_name) + f"Impossible to load Strategy '{strategy_name}'. This class does not exist" + " or contains Python code errors" ) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index b63180d1e..2ed2567f9 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -1,17 +1,19 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 import logging +import warnings from base64 import urlsafe_b64encode from os import path from pathlib import Path -import warnings +from unittest.mock import Mock import pytest from pandas import DataFrame +from freqtrade.resolvers import StrategyResolver from freqtrade.strategy import import_strategy from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import IStrategy -from freqtrade.resolvers import StrategyResolver +from freqtrade.tests.conftest import log_has_re def test_import_strategy(caplog): @@ -94,6 +96,16 @@ def test_load_not_found_strategy(): strategy._load_strategy(strategy_name='NotFoundStrategy', config={}) +def test_load_staticmethod_importerror(mocker, caplog): + mocker.patch("freqtrade.resolvers.strategy_resolver.import_strategy", Mock( + side_effect=TypeError("can't pickle staticmethod objects"))) + with pytest.raises(ImportError, + match=r"Impossible to load Strategy 'DefaultStrategy'." + r" This class does not exist or contains Python code errors"): + StrategyResolver() + assert log_has_re(r".*Error: can't pickle staticmethod objects", caplog.record_tuples) + + def test_strategy(result): config = {'strategy': 'DefaultStrategy'} From d4947ba0ee8c54d19249cbb0e1d10b1aa57d7499 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 17 Apr 2019 12:40:07 +0000 Subject: [PATCH 454/457] Update ccxt from 1.18.470 to 1.18.472 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 40903e9d6..771102e90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.18.470 +ccxt==1.18.472 SQLAlchemy==1.3.3 python-telegram-bot==11.1.0 arrow==0.13.1 From 7f229bbf3950ecd9790880a1c1063b237f09419a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 17 Apr 2019 12:40:09 +0000 Subject: [PATCH 455/457] Update ccxt from 1.18.470 to 1.18.472 --- requirements-pi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-pi.txt b/requirements-pi.txt index 0fd26a3d9..30e4a4ce4 100644 --- a/requirements-pi.txt +++ b/requirements-pi.txt @@ -1,4 +1,4 @@ -ccxt==1.18.470 +ccxt==1.18.472 SQLAlchemy==1.3.3 python-telegram-bot==11.1.0 arrow==0.13.1 From 8abdbc41e16de38eec1e81edea8d230ca5e8b333 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 17 Apr 2019 12:40:10 +0000 Subject: [PATCH 456/457] Update mypy from 0.700 to 0.701 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 77f122a59..9d0e99843 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,4 +9,4 @@ pytest-mock==1.10.3 pytest-asyncio==0.10.0 pytest-cov==2.6.1 coveralls==1.7.0 -mypy==0.700 +mypy==0.701 From 795c2e4aa2f238a17d38dc087ee65644c59b8d21 Mon Sep 17 00:00:00 2001 From: Misagh Date: Thu, 18 Apr 2019 08:07:43 +0200 Subject: [PATCH 457/457] version to 0.18.5 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 292613297..f28809f33 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ FreqTrade bot """ -__version__ = '0.18.2-dev' +__version__ = '0.18.5' class DependencyException(BaseException):