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 00000000000..76801ba56dc --- /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 47886989e0d..b6ef219c4fe 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -304,11 +304,14 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def sell(self, pair: str, ordertype: str, amount: float, 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa 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 @@ def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[s 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 7d89f7ad615..4e1b2729c77 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 ca148f51821..0d1ae9c26e6 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 ca148f51821..0d1ae9c26e6 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 8fbcdfed7f3..840d348f0f5 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -17,7 +17,7 @@ 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 7d89f7ad615..a7a3a61cf06 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 00000000000..63498866844 --- /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 b6ef219c4fe..9a34839e701 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -304,14 +304,11 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def sell(self, pair: str, ordertype: str, amount: float, 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa 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 00000000000..e8a0e4ee172 --- /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 00000000000..568fb0ac4c7 --- /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 568fb0ac4c7..b2c301982a7 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -42,12 +42,12 @@ def _load_exchange( 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 9c211608f19..2b0904abb01 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,7 +20,7 @@ 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 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 da2987b27ef..5cf6c616af5 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 2b0904abb01..fb132d60504 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -17,7 +17,6 @@ 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 fb132d60504..588b959d39a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -17,6 +17,7 @@ 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 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 b2c301982a7..5d46becf5f5 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -22,7 +22,7 @@ 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, + 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 9a34839e701..8e52ade529f 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -304,7 +304,7 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def sell(self, pair: str, ordertype: str, amount: float, 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 4e1b2729c77..7d89f7ad615 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 ff4f963f95e..3706834b4b4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -60,7 +60,8 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 e8a0e4ee172..8fcaf12635e 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -14,11 +14,11 @@ 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 a7a3a61cf06..7d89f7ad615 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 76801ba56dc..7a47b701fe3 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 8e52ade529f..2b6d13fcf59 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -67,6 +67,7 @@ def wrapper(*args, **kwargs): class Exchange(object): _conf: Dict = {} + _params: Dict = {} def __init__(self, config: dict) -> None: """ @@ -304,11 +305,12 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def sell(self, pair: str, ordertype: str, amount: float, 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa 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 8fcaf12635e..91b41a15943 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 @@ 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 3706834b4b4..f9c0b7b52fd 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -58,7 +58,7 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 5d46becf5f5..4d78684ec71 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 9b1b9a92543..53a46d14111 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -6,9 +6,7 @@ import os import re from typing import List, NamedTuple, Optional - import arrow - from freqtrade import __version__, constants @@ -55,6 +53,11 @@ def parse_args(self) -> argparse.Namespace: """ 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 @@ def common_args_parser(self) -> None: """ 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 @@ def common_args_parser(self) -> None: ) 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 @@ def common_args_parser(self) -> None: ) 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 @@ def common_args_parser(self) -> None: ) 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 @@ def common_args_parser(self) -> None: ) 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 @@ def common_args_parser(self) -> None: 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 @@ def backtesting_options(parser: argparse.ArgumentParser) -> None: """ 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 @@ def backtesting_options(parser: argparse.ArgumentParser) -> None: 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 @@ def backtesting_options(parser: argparse.ArgumentParser) -> None: ) 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 @@ def edge_options(parser: argparse.ArgumentParser) -> None: """ 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 @@ def optimizer_shared_options(parser: argparse.ArgumentParser) -> None: """ 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 @@ def hyperopt_options(parser: argparse.ArgumentParser) -> None: """ 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 @@ def hyperopt_options(parser: argparse.ArgumentParser) -> None: 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 @@ def hyperopt_options(parser: argparse.ArgumentParser) -> None: 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 @@ def _build_subcommands(self) -> None: 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 @@ def testdata_dl_options(self) -> None: """ 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 @@ def testdata_dl_options(self) -> None: 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 @@ def testdata_dl_options(self) -> None: 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 @@ def testdata_dl_options(self) -> None: 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 @@ def testdata_dl_options(self) -> None: 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 @@ def testdata_dl_options(self) -> None: 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 @@ def testdata_dl_options(self) -> None: 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 d972f50b84f..bddf600288f 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -13,6 +13,8 @@ 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 @@ def load_config(self) -> Dict[str, Any]: 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 @@ def _load_config_file(self, path: str) -> Dict[str, Any]: 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 8fbcdfed7f3..c214f0de9b6 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 d03187d7714..38f758669d6 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 | 28 ++++++++------------ freqtrade/tests/conftest.py | 7 ++++- freqtrade/tests/exchange/test_exchange.py | 32 +++++++++++++++++++++++ 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index b52a31d8e4b..9ed046c40b7 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 f9c0b7b52fd..f8d61d4aae5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -56,7 +56,7 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 4d78684ec71..b834ad24240 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -32,23 +32,17 @@ def _load_exchange( :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: - 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())) + 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) + 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 809dc12e033..d6628d92572 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -16,6 +16,7 @@ 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 b384035b038..746f101bab9 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 746f101bab9..6fb80194dd0 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 b834ad24240..62e89f4454f 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -39,7 +39,7 @@ def _load_exchange( 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 6bf7fa17d9f..e7683bc78c5 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -63,7 +63,7 @@ def _load_hyperopt( 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 aee292926f2..852d1dc0c2a 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -47,7 +47,7 @@ def _search_object(directory: Path, object_type, object_name: str, :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 286cea5bf01..4306a96695f 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -48,7 +48,7 @@ def _load_pairlist( 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 01467b0a1cc..c49da920505 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -149,7 +149,7 @@ def _load_strategy( 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 2b6d13fcf59..078c8e6be82 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -283,21 +283,32 @@ def symbol_price_prec(self, pair, price: float): 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 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa '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 f67be724c79..e12456cf8a2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -7,7 +7,7 @@ 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 @@ def _process(self) -> bool: 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 @@ def _process(self) -> bool: 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 e12456cf8a2..eda648a64b8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -157,7 +157,7 @@ def _process(self) -> bool: 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 eda648a64b8..6f884a98f64 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -163,11 +163,11 @@ def _process(self) -> bool: 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 @@ def _process(self) -> bool: 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 6f884a98f64..295c204e3b0 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -201,11 +201,15 @@ def _process(self) -> bool: 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 53a46d14111..f462926f9a6 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -78,7 +78,8 @@ def common_args_parser(self) -> None: ) 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 f462926f9a6..62f22befcc8 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -384,10 +384,10 @@ def testdata_dl_options(self) -> None: 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 c8fd08747e9..df3e4bb0c36 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -5,12 +5,14 @@ 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 @@ 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 @@ 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 042d43ed283..0952d1c5d1f 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 67445238b60..9f11e8ac250 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 7aae98ebe6a..51c95a4a977 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 df3e4bb0c36..5dee41bdd03 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -39,8 +39,6 @@ # 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 656744ab69d..92bdbc0427e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -17,7 +17,6 @@ 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 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 62e89f4454f..a68219527cb 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -22,7 +22,12 @@ 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={'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 6fb80194dd0..72919103c6b 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -13,7 +13,8 @@ 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 0aa01211d85..0d1ae9c26e6 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 6c71ddf7bc4..ff250c1f199 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -67,6 +67,7 @@ }, '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 47886989e0d..d05436a360d 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -540,9 +540,9 @@ def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[s 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 d05436a360d..c5385807623 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -159,7 +159,6 @@ def id(self) -> str: 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 c5385807623..145e802fa7f 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -541,12 +541,8 @@ def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[s # 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 @@ def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[s 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 | 123 ++++++++++++--------------------- 1 file changed, 43 insertions(+), 80 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 078c8e6be82..170f94182e5 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -284,9 +284,9 @@ def symbol_price_prec(self, pair, price: float): 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 @@ def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, 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, 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 - - params = self._params.copy() - if time_in_force != 'gtc': - params.update({'timeInForce': time_in_force}) - - return self._api.create_order(pair, ordertype, 'buy', + return self._api.create_order(pair, ordertype, side, 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'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 limit buy order on market {pair}.' - f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' + 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 buy order due to {e.__class__.__name__}. Message: {e}') + 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} + + # 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.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 - - params = self._params.copy() - if time_in_force != 'gtc': - params.update({'timeInForce': time_in_force}) + # 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, 'sell', - amount, rate, params) + params = self._params.copy() + if time_in_force != 'gtc': + params.update({'timeInForce': time_in_force}) - 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa 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 00000000000..204ed971eac --- /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 72919103c6b..ca76a0bd706 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -12,7 +12,8 @@ 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 @@ def sort_data(data, key): ] 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 a68219527cb..8d1845c71ab 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 @@ def __init__(self, exchange_name: str, config: dict) -> None: 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 ca76a0bd706..15da5e924cd 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -12,7 +12,7 @@ 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 b4dd302e34e..6ebbaa1caec 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 db77d0d5c69..2580bf6b19a 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -285,7 +285,7 @@ def symbol_price_prec(self, pair, price: float): 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 @@ def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, '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 2580bf6b19a..26fd56b5913 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -283,7 +283,7 @@ def symbol_price_prec(self, pair, price: float): 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 @@ def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, '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 @@ 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} - - # 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 @@ 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} - - # 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa '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 72919103c6b..c646c0db745 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 6ebbaa1caec..cd1d89c6de0 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 4bbf28fe60e..3eaa771ee10 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 3eaa771ee10..127f69e9f17 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 26fd56b5913..c3d562aa9bb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -331,7 +331,7 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def sell(self, pair: str, ordertype: str, amount: float, 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 @@ def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool: 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 63498866844..e7147c3b231 100644 --- a/freqtrade/pairlist/VolumePrecisionPairList.py +++ b/freqtrade/pairlist/VolumePrecisionPairList.py @@ -78,7 +78,7 @@ def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: 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 e7147c3b231..8caaa6f3989 100644 --- a/freqtrade/pairlist/VolumePrecisionPairList.py +++ b/freqtrade/pairlist/VolumePrecisionPairList.py @@ -3,13 +3,16 @@ 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 cd1d89c6de0..aa6051bdd10 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 c3d562aa9bb..94c0e6b3b15 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -290,15 +290,28 @@ def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, '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 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def sell(self, pair: str, ordertype: str, amount: float, 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa 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 @@ def cancel_order(self, order_id: str, pair: str) -> None: 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 94c0e6b3b15..23926d00fbf 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -299,10 +299,10 @@ def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, '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 b208cb31867..a4acffc4482 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 + +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 -`W = (Number of winning trades) / (Total number of trades)` + 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.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. -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**. +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 9f11e8ac250..51098baaaf8 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 aa6051bdd10..13584ebc0a3 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 13584ebc0a3..3257de6e973 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 204ed971eac..f6db04da6db 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 00000000000..c8bb92168b6 --- /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 23926d00fbf..b6ff261af13 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -24,7 +24,7 @@ # 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 @@ def validate_ordertypes(self, order_types: Dict) -> None: 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa """ 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 @@ def get_order_book(self, pair: str, limit: int = 100) -> dict: 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 c8bb92168b6..998f389f082 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,4 +23,4 @@ def get_order_book(self, pair: str, limit: int = 100) -> dict: # 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 153be89c142..837c767f683 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 837c767f683..e306d7d23fe 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -18,6 +18,10 @@ 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 @@ async def mock_coro(*args, **kwargs): 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 @@ async def mock_get_candle_hist(pair, *args, **kwargs): 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 fetch_ohlcv_mock(pair, timeframe, since): 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 @@ def sort_data(data, key): [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 @@ def sort_data(data, key): 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 998f389f082..60d35f34330 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -9,7 +9,7 @@ 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 e306d7d23fe..d904eb2c536 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -12,7 +12,7 @@ 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 b6ff261af13..de2670de745 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -374,7 +374,7 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa """ 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 de2670de745..42ef46eb812 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -244,9 +244,9 @@ def validate_ordertypes(self, order_types: Dict) -> None: 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 @@ def get_order_book(self, pair: str, limit: int = 100) -> dict: 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 3257de6e973..1a1d3436c12 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 295c204e3b0..392101e2676 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -163,7 +163,7 @@ def _process(self) -> bool: 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 @@ def _process(self) -> bool: 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 @@ def _get_trade_stake_amount(self, pair) -> Optional[float]: 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 @@ def create_trade(self) -> bool: 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 f9b34fc6471..f603b139f23 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -5,7 +5,7 @@ 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 @@ def total_open_trades_stakes() -> float: .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 e83d9d41baa..5aa9bae35c3 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -83,7 +83,7 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 @@ def _exec_forcesell(trade: Trade) -> None: 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 @@ def _rpc_count(self) -> List[Trade]: 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 be6efc2ff7f..a9519e69300 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 1a1d3436c12..ff62c1a2918 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 34d59d8027f..859d80482f4 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 42ef46eb812..874ed93aa70 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -654,16 +654,6 @@ def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> Lis 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 d615533053b..c51dd4673d5 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -376,7 +376,6 @@ def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = N :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 @@ def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = N '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 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> Non 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 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> Non '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 5aa9bae35c3..af64c3d6705 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -103,7 +103,6 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 3ce7c9167fa..9caa7288f96 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -125,7 +125,7 @@ def send_msg(self, msg: Dict[str, Any]) -> None: 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 @@ def send_msg(self, msg: Dict[str, Any]) -> None: 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 @@ def _status(self, bot: Bot, update: Update) -> None: 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 e5509d6c9c4..2b5365e32cf 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 d904eb2c536..e24d15aed87 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 bb685cad5b2..2de2668e889 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 686a9246904..9964973e1ff 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 0023088152f..da7aec0a66b 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 a0ac6ee99e6..2f66a5153bd 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 60d35f34330..127f4e91635 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,4 +23,4 @@ def get_order_book(self, pair: str, limit: int = 100) -> dict: # 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 | 154 ++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index baef811de81..e95b4a3c465 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -351,7 +351,7 @@ def _find_trades_for_stoploss_range(self, ticker_data, pair, stoploss_range): 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 @@ def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column, """ 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 - - stop_price_percentage = stoploss + 1 - open_price = ohlc_columns[open_trade_index, 0] - stop_price = (open_price * stop_price_percentage) - - # Searching for the index where stoploss is hit - stop_index = utf1st.find_1st( - ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller) - - # 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 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 sell_index will be far in future (infinite number) - if sell_index == -1: - sell_index = float('inf') - - # 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 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 have the next candle - if len(ohlc_columns) - 1 < exit_index: - return [] - - exit_type = SellType.SELL_SIGNAL - exit_price = ohlc_columns[exit_index, 0] - - 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 - } - - result.append(trade) - - # 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) - ) + 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 + 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 + + stop_price_percentage = stoploss + 1 + open_price = ohlc_columns[open_trade_index, 0] + stop_price = (open_price * stop_price_percentage) + + # Searching for the index where stoploss is hit + stop_index = utf1st.find_1st( + ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller) + + # 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 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 sell_index will be far in future (infinite number) + if sell_index == -1: + sell_index = float('inf') + + # 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 + + 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 have the next candle + if len(ohlc_columns) - 1 < exit_index: + break + + exit_type = SellType.SELL_SIGNAL + exit_price = ohlc_columns[exit_index, 0] + + 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 + } + + 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 e95b4a3c465..b4dfa562454 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -353,11 +353,12 @@ def _find_trades_for_stoploss_range(self, ticker_data, pair, stoploss_range): 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 @@ def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column, 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 @@ def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column, 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 @@ def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column, 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 ff62c1a2918..dbdb41e722d 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 dbdb41e722d..ac8707565d3 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 ded74bd18ad..aeefc072206 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 ac8707565d3..baff3ef9c72 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 4d0907d784a..6e504913e59 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -20,6 +20,7 @@ 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 @@ }, '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 c51dd4673d5..9671c4fa98a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -58,7 +58,7 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 8d9adc74c06..2c493cfc387 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 1f1d2c511f7..c8ab902763f 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 @@ def get_free(self, currency) -> float: 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 @@ def get_used(self, currency) -> float: 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 874ed93aa70..4745957874b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -66,7 +66,7 @@ def wrapper(*args, **kwargs): class Exchange(object): - _conf: Dict = {} + _config: Dict = {} _params: Dict = {} # Dict to specify which options each exchange implements @@ -82,7 +82,7 @@ def __init__(self, config: dict) -> None: 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 @@ def validate_pairs(self, pairs: List[str]) -> None: 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 @@ def create_order(self, pair: str, ordertype: str, side: str, amount: float, 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 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa 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 @@ def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: floa @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 @@ def get_balance(self, currency: str) -> float: @retrier def get_balances(self) -> dict: - if self._conf['dry_run']: + if self._config['dry_run']: return {} try: @@ -584,7 +584,7 @@ async def _async_get_candle_history(self, pair: str, tick_interval: str, @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 @@ def cancel_order(self, order_id: str, pair: str) -> None: @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 @@ def get_order_book(self, pair: str, limit: int = 100) -> dict: @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 baff3ef9c72..e6b2f1b2e02 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 e6b2f1b2e02..8a7c95ddbd5 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 8a7c95ddbd5..8cee146f5eb 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 8cee146f5eb..a72198ac357 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 23a36dd4c42..0f46a62e3f3 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 6b5b0db4bb7..4e9e28b3dc8 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -18,6 +18,7 @@ def __init__(self, freqtrade, config: dict) -> None: 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 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 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 262e4bf5940..7f1985d43d3 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 @@ def __init__(self, freqtrade, config: dict) -> None: '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 @@ def refresh_pairlist(self) -> None: -> 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 @@ def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: # 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 92bdbc0427e..86f739f2f26 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -206,7 +206,7 @@ def _process(self) -> bool: 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 @@ def get_target_bid(self, pair: str) -> float: 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 @@ def _get_trade_stake_amount(self, pair) -> Optional[float]: 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 25d3e62c06b..0ca939ec581 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -715,7 +715,9 @@ def get_pair_detail_url(self, pair: str) -> str: @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 86f739f2f26..28a7c91461e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -206,7 +206,7 @@ def _process(self) -> bool: 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 @@ def get_target_bid(self, pair: str, ticker: Dict = None) -> float: 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 @@ def _get_trade_stake_amount(self, pair) -> Optional[float]: 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 4e9e28b3dc8..7675c1eeef8 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -18,7 +18,7 @@ def __init__(self, freqtrade, config: dict) -> None: 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 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 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 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 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 d6628d92572..98171844a60 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 15da5e924cd..4fadfd4b767 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 9f90aac6ebf..e78404587f9 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 8caaa6f3989..00000000000 --- 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 a72198ac357..7f66d84eb48 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 108e264c614..c4b26eba9ab 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 d0fafbd26b1..4d0907d784a 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -19,7 +19,7 @@ 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 f5503002bee..874ed93aa70 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -657,9 +657,7 @@ def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> Lis @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 7675c1eeef8..948abe113fa 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -18,7 +18,6 @@ def __init__(self, freqtrade, config: dict) -> None: 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 7f1985d43d3..eb03236e5c1 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -77,9 +77,8 @@ def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: 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 98171844a60..6237a27c9ba 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 e78404587f9..dd6ebb62c1b 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 62f22befcc8..ee19f6fe117 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -108,14 +108,6 @@ def common_args_parser(self) -> None: 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 @@ def hyperopt_options(parser: argparse.ArgumentParser) -> None: """ 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 bddf600288f..e9630599388 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -67,9 +67,6 @@ def load_config(self) -> Dict[str, Any]: 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 @@ def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]: 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 49b10e417c7..ade62ce945d 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 96b16b6b6d2..fa048cb92c6 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 0c18110bdaa..e4f636aac6e 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 96b16b6b6d2..51a81066816 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 7f66d84eb48..31f8e5cc94b 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 92ea4fe4387..9abc7174787 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 e4f636aac6e..6e52be47fb0 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 36428d1c15a..0f3ab648d74 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 f16cc3fdd6b..8b10fb6e9e0 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) - -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 -``` + 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) -**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 00000000000..25043d981c6 --- /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 | 147 +++++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 52 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c4b26eba9ab..87b4a6844e1 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. - -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. +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 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 0f3ab648d74..9a6fec851b9 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 31f8e5cc94b..97df4e8721f 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 97df4e8721f..981bd62854a 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 87b4a6844e1..5d731bb578b 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 981bd62854a..92486ae042c 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 76f9b6016f9..9de9f13c737 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 a8f4e530a9e..031b490c8eb 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -17,11 +17,10 @@ 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 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 @@ def _set_strategy(self, strategy): 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 e69b1374eb5..1d5fb13845c 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 ff0b9234721..80a8e74cb43 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 92486ae042c..f7eb4c695d0 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 f7eb4c695d0..ff407848bfe 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 a4acffc4482..7372e3373cb 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 5d731bb578b..d7e774595fb 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 874ed93aa70..f6fb0a58a03 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -298,7 +298,7 @@ def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, '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 3b8d3ad6f39..fce9cba14be 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 84f3c78d94f..424ac579c55 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 424ac579c55..d24ffcf1bb6 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 80a8e74cb43..e85aceec8e4 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 ff407848bfe..57ec7ca05ba 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 c01ea6a6007..b49aad6262a 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 1d614735778..29976fb4a31 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -331,7 +331,11 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat 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 29976fb4a31..32efdeb1793 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -334,7 +334,7 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat # 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 57ec7ca05ba..2df498452d6 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 2df498452d6..f1ab77b0a31 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 0f46a62e3f3..be4e02039e6 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 2f66a5153bd..3be0e72c0bd 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 | 41 +++++++++++++++++++++++++++++++------- requirements.txt | 5 ++++- 9 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 freqtrade.service.watchdog diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 8b10fb6e9e0..739a89c0723 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 d7e774595fb..1874bef4b47 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 80223f9547d..bd6c50c5ac2 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 00000000000..ba491fa5366 --- /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 ee19f6fe117..60438642643 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -127,6 +127,12 @@ def common_args_parser(self) -> None: 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 @@ def backtesting_options(parser: argparse.ArgumentParser) -> None: 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 e9630599388..6ca0299f0de 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -122,6 +122,10 @@ def _load_common_config(self, config: Dict[str, Any]) -> Dict[str, Any]: 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 4d0907d784a..ff623df14ac 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -171,7 +171,8 @@ '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 dce3136df83..4f3edd600b9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -11,6 +11,7 @@ import arrow from requests.exceptions import RequestException +import sdnotify from freqtrade import (DependencyException, OperationalException, TemporaryError, __version__, constants, persistence) @@ -51,6 +52,10 @@ def __init__(self, config: Dict[str, Any]) -> None: # 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 @@ def __init__(self, config: Dict[str, Any]) -> 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 @@ -99,6 +109,12 @@ def cleanup(self) -> None: :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 @@ def worker(self, old_state: State = None) -> State: 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: - time.sleep(1) + # 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: - min_secs = self.config.get('internals', {}).get( - 'process_throttle_secs', - constants.PROCESS_THROTTLE_SECS - ) + # 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 f1ab77b0a31..4c223eed4af 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 f1ab77b0a31..434d597f955 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 | 21 ++++++++++++++------- freqtrade/main.py | 3 +++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4f3edd600b9..8018ed2528d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -84,7 +84,7 @@ def __init__(self, config: Dict[str, Any]) -> None: # 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,13 +110,20 @@ def cleanup(self) -> None: """ logger.info('Cleaning up modules ...') - # Tell systemd that we are stopping now + 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') + self._sd_notify.notify("STOPPING=1") - self.rpc.cleanup() - persistence.cleanup() + 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: """ @@ -144,7 +151,7 @@ def worker(self, old_state: State = None) -> State: # 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 @@ def worker(self, old_state: State = None) -> State: # 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 75b15915b7d..c41d54f0e94 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 852d1dc0c2a..6023bc2ba81 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -31,7 +31,11 @@ def _get_valid_object(object_type, module_path: Path, # 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 fce9cba14be..26b5297eaaa 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,9 @@ 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) @@ -617,14 +618,30 @@ def test_buy_considers_time_in_force(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'} + 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) + + 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] == {} + 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 26b5297eaaa..d24dd175784 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -4,7 +4,7 @@ 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 @@ 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 00000000000..8b81a08a94a --- /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 d24dd175784..ff36ab91cd0 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 f6fb0a58a03..32d95254251 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -352,7 +352,7 @@ def buy(self, pair: str, ordertype: str, amount: float, 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 @@ def sell(self, pair: str, ordertype: str, amount: float, 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 de861677d6a..bc69c97ad87 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -61,6 +61,8 @@ def startup_messages(self, config, pairlist) -> None: 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 @@ def startup_messages(self, config, pairlist) -> None: '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 32d95254251..96eebecc2f9 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -111,6 +111,8 @@ def __init__(self, config: dict) -> None: 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 @@ def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: 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 96eebecc2f9..8c4315906fb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -278,10 +278,10 @@ def validate_trailing_stoploss(self, config) -> None: 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 8c4315906fb..13ab51226c6 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -263,10 +263,8 @@ 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: + 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 434d597f955..6462cfd3035 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 13ab51226c6..88f255c85db 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -274,12 +274,12 @@ def validate_trailing_stoploss(self, config) -> None: 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 ff36ab91cd0..16e5e693b72 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 4d0907d784a..f0e9f7490c8 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -73,6 +73,7 @@ '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 c49da920505..60d1fe21c73 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -46,18 +46,19 @@ def __init__(self, config: Optional[Dict] = None) -> None: # 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 32efdeb1793..41dcb8c57ef 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 d7e774595fb..59816ec4da8 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 0726aebbcde..62276e7cc09 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 32d95254251..1fcf20ff813 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -657,7 +657,7 @@ def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> Lis @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 1fcf20ff813..b199f60516a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -106,7 +106,7 @@ def __init__(self, config: dict) -> None: 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 @@ def id(self) -> str: """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 b199f60516a..71c1c9a0817 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -661,13 +661,7 @@ def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> Lis @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 71c1c9a0817..a7e10d923b8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -276,8 +276,8 @@ def symbol_amount_prec(self, pair, amount: float): 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 @@ def symbol_price_prec(self, pair, price: float): 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 dce3136df83..939904c737d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -280,12 +280,10 @@ def _get_trade_stake_amount(self, pair) -> Optional[float]: 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 948abe113fa..5559c582f2e 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -66,12 +66,14 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 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 a7e10d923b8..e982446564d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -226,7 +226,7 @@ def validate_pairs(self, pairs: List[str]) -> None: 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 6237a27c9ba..4d363a17378 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 | 70 +++---- 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, 161 insertions(+), 191 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index ff36ab91cd0..0beea4ed32b 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) + 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) expected_return = {'ETH/BTC': 'available'} + 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 = Exchange(default_conf) + ex = get_patched_exchange(mocker, default_conf, api_mock, id="binance") 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)) - Exchange(default_conf) - assert log_has('Unable to initialize markets. Reason: ', caplog.record_tuples) - -def test_validate_pairs(default_conf, mocker): +def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() - api_mock.load_markets = MagicMock(return_value={ + 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 dd6ebb62c1b..c40e16f77d9 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 2de2668e889..e1261e02ee5 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 9964973e1ff..b5055d7f50c 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 2f66a5153bd..178081aff56 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -5,7 +5,7 @@ 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 _refresh_whitelist(list): 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 e982446564d..7e97923b087 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -200,7 +200,7 @@ def _load_markets(self) -> Dict[str, Any]: 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 7e97923b087..994e1c3a4a4 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -186,24 +186,23 @@ def set_sandbox(self, api, exchange_config: dict, name: str): "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 994e1c3a4a4..2d811e8e828 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -168,6 +168,9 @@ def id(self) -> str: @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 2d811e8e828..8eb74bdd50c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -199,7 +199,7 @@ def _load_async_markets(self, reload=False) -> None: 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 8eb74bdd50c..30bf9a30c3d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -88,6 +88,8 @@ def __init__(self, config: dict) -> None: # 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 @@ def __init__(self, config: dict) -> None: 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 @@ def _load_markets(self, reload=False) -> None: 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 939904c737d..0422b0f88d2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -155,6 +155,9 @@ def _process(self) -> bool: """ 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 0f46a62e3f3..a1aabb1b820 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 30bf9a30c3d..272d81e59f7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -680,10 +680,6 @@ def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> Lis 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 0beea4ed32b..e9debbfc2cf 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 c40e16f77d9..52f44c41bff 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 b5055d7f50c..02f4f4afbac 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 272d81e59f7..f177e1d54c6 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -222,9 +222,8 @@ def _reload_markets(self) -> None: 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 e9debbfc2cf..08e46033625 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 4d363a17378..26262cb4b23 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 08e46033625..41ca0a8846e 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 f177e1d54c6..6ee5b17a2b9 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -206,11 +206,11 @@ def _load_async_markets(self, reload=False) -> None: 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 @@ def _reload_markets(self) -> None: > 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 d7e774595fb..10fe50a2756 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 4d0907d784a..e3c059ae0a5 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -202,6 +202,7 @@ '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 41ca0a8846e..7c757df093b 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 d7e774595fb..2d5c958bbde 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 c49da920505..8e582d09ca0 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -46,7 +46,7 @@ def __init__(self, config: Optional[Dict] = None) -> None: # 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 f603b139f23..10aff72ec43 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -266,6 +266,7 @@ def update(self, order: Dict) -> None: 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 dce3136df83..58888a4f869 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -512,6 +512,7 @@ def process_maybe_execute_sell(self, trade: Trade) -> bool: 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 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> Non 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 bc69c97ad87..7a397135612 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 @@ def startup_messages(self, config, pairlist) -> None: '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 2f66a5153bd..bd40a063fb9 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 58888a4f869..f6662e0628a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -658,7 +658,7 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> Non 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 7a397135612..7f0d0a5d4f0 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -4,7 +4,6 @@ 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 @@ def startup_messages(self, config, pairlist) -> None: '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 6462cfd3035..618de66fa34 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 859d80482f4..68d63101a3b 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 f6662e0628a..2eecb580262 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -855,9 +855,10 @@ def notify_sell(self, trade: Trade): """ 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 10fe50a2756..9b08f77c669 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 6ee5b17a2b9..c6b3a3796ba 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -224,6 +224,7 @@ def _reload_markets(self) -> None: 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 8e582d09ca0..9d9d59a4adf 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -46,18 +46,18 @@ def __init__(self, config: Optional[Dict] = None) -> None: # 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 796b4411e4a..fc7c486636b 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 e9630599388..585704b2d45 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -58,6 +58,7 @@ def load_config(self) -> Dict[str, Any]: 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 @@ def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]: 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 @@ def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]: 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 c71c1580ff8..7c757df093b 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 51098baaaf8..3b7b1285c86 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,6 +13,7 @@ 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 b8295968014..33f62f2f70a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -118,7 +118,6 @@ def __init__(self, config: dict) -> None: 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 @@ def validate_order_time_in_force(self, order_time_in_force: Dict) -> None: 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 3b7b1285c86..dace9904b52 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,7 +13,6 @@ 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 618de66fa34..e5cd62d22c4 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 e5cd62d22c4..83f89882b90 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 eb03236e5c1..9a2e2eac49a 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -69,7 +69,8 @@ def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: 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 83f89882b90..ea74f3d395f 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 6023bc2ba81..86b9a799b11 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -33,9 +33,9 @@ def _get_valid_object(object_type, module_path: Path, 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 585704b2d45..d98b2ba2128 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -59,7 +59,7 @@ def load_config(self) -> Dict[str, Any]: 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 @@ def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]: 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 @@ def _validate_trailing_stoploss(self, conf: Dict[str, Any]) -> None: 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 dace9904b52..21547d20540 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 62276e7cc09..cbe4fd3c489 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 9caa7288f96..e599172e4ab 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -466,6 +466,8 @@ def _help(self, bot: Bot, update: Update) -> None: :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 @@ def _help(self, bot: Bot, update: Update) -> None: "*/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 e01c8f9bc8d..db98cbb1220 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 8b959404759..6ba59bea4ac 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -579,6 +579,25 @@ def get_real_amount(self, trade: Trade, order: Dict) -> float: 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 @@ def handle_trade(self, trade: Trade) -> bool: 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 @@ def notify_sell(self, trade: Trade): """ 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 af64c3d6705..ffbc8d3343c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -94,7 +94,7 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 @@ def _rpc_status_table(self) -> DataFrame: 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 @@ def _rpc_trade_statistics( 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 @@ def _rpc_balance(self, fiat_display_currency: str) -> Dict: 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 @@ def _exec_forcesell(trade: Trade) -> None: 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 fc7c486636b..950662bfb07 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 f6c9dd4d1bc..e155dce88e2 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 00000000000..17e3c8c09ba --- /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 | 38 ++++++++++-------------------------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 17e3c8c09ba..6fce4361b1b 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -8,6 +8,10 @@ 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 6b954ac4c9f..581518b12b7 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -41,6 +41,7 @@ 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) + 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 1d5fb13845c..ea3aa95b33e 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -15,6 +15,7 @@ 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 00000000000..dd7cbe0d9a1 --- /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 ea3aa95b33e..40754cfbc28 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -14,8 +14,8 @@ 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 00000000000..8701451dcc4 --- /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 44950e408a5..e7ad9c9bfcc 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 10aff72ec43..f6cdc815f19 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 @@ def __repr__(self): 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 @@ def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool 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 a9519e69300..7e809de0d86 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 41dcb8c57ef..d65c1289590 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -254,6 +254,8 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, 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 f6cdc815f19..ad85d5efd48 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 @@ def __repr__(self): 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 | 43 +++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 7e809de0d86..c4d07b3f9bd 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,7 @@ 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, @@ -594,44 +594,69 @@ def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee): 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.max_rate == 1 assert trade.initial_stop_loss == 0.95 - # Get percent of profit with a lowre rate + # Get percent of profit with a lower rate trade.adjust_stop_loss(0.96, 0.05) assert trade.stop_loss == 0.95 - assert trade.max_rate == 1 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.max_rate == 1.3 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.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 +def test_adjust_high_low(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, + ) + + trade.adjust_high_low(trade.open_rate) + assert trade.max_rate == 1 + assert trade.min_rate == 1 + + # check min adjusted, max remained + trade.adjust_high_low(0.96) + assert trade.max_rate == 1 + assert trade.min_rate == 0.96 + + # 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 "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): init(default_conf) 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 ad85d5efd48..ebafe735584 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -204,7 +204,7 @@ def __repr__(self): 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 d65c1289590..85d4d8c13be 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -254,7 +254,7 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, 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 c4d07b3f9bd..042237ce7cd 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 6ba59bea4ac..cfff46fea18 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -592,7 +592,7 @@ def get_sell_rate(self, pair: str, refresh: bool) -> float: 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 950662bfb07..e4f0415f7c1 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 ebafe735584..b807e10e12a 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -209,8 +209,8 @@ def adjust_min_max_rates(self, current_price: float): 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 85d4d8c13be..b844c1e580b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -301,8 +301,9 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat # 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 b807e10e12a..a1ac65c049b 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -213,7 +213,13 @@ def adjust_min_max_rates(self, current_price: float): 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 e8514e76f68..d6295b77822 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -14,10 +14,10 @@ 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 @@ ) -# 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 @@ # 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 @@ # 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 @@ 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 @@ # 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 @@ # 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 @@ ) 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 96360f39dc1..400940d1663 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 129a09f4079..075938a6175 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 d6295b77822..b4ca4ee260c 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 fc7c486636b..a5de283a510 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 b4ca4ee260c..f33f56efc71 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -130,6 +130,22 @@ 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 @@ 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 b844c1e580b..6e2f35cc898 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -247,6 +247,9 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, """ 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 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, 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 6e2f35cc898..a086139c782 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -257,11 +257,11 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, 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 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, 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 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat 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 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat # 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 5559c582f2e..f9a66fe8927 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -65,29 +65,26 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: :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 33f62f2f70a..e4838b74c8a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -239,13 +239,9 @@ def validate_pairs(self, pairs: List[str]) -> None: 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 f9a66fe8927..8e3b11ff473 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -68,10 +68,10 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 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 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 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 8e3b11ff473..8416992514d 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -60,9 +60,8 @@ def refresh_pairlist(self) -> None: 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 8416992514d..7960e82fcd7 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -66,7 +66,7 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: """ 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 7960e82fcd7..fcc12981493 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -67,8 +67,8 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 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 fcc12981493..2564c484cbe 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -66,14 +66,11 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: """ 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 7c757df093b..736f2298ad0 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 e155dce88e2..93278316090 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 f33f56efc71..b983695333c 100644 --- a/freqtrade/tests/optimize/test_backtest_detail.py +++ b/freqtrade/tests/optimize/test_backtest_detail.py @@ -49,7 +49,6 @@ # 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 @@ ) # 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 @@ ) # 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 @@ # 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 @@ 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 @@ 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 2564c484cbe..d08bd2587fd 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -71,13 +71,15 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: # 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 | 70 +++++++++++------------ 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/freqtrade/tests/pairlist/test_pairlist.py b/freqtrade/tests/pairlist/test_pairlist.py index 52f44c41bff..5fe2dfc08aa 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -5,7 +5,7 @@ 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'] - new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist) - assert set(whitelist) == set(new_whitelist) +@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() - 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 5fe2dfc08aa..38a8d78c7c4 100644 --- a/freqtrade/tests/pairlist/test_pairlist.py +++ b/freqtrade/tests/pairlist/test_pairlist.py @@ -5,7 +5,7 @@ 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 af64c3d6705..eb0269b5b79 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -328,6 +328,16 @@ def _rpc_reload_conf(self) -> Dict[str, str]: 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 e599172e4ab..6771ec80383 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -91,6 +91,7 @@ def _init(self) -> None: 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 @@ def _reload_conf(self, bot: Bot, update: Update) -> None: 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 @@ def _help(self, bot: Bot, update: Update) -> None: "*/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 e1261e02ee5..e5ac79febea 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 02f4f4afbac..dd82155515e 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 db98cbb1220..face22404ca 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 eb0269b5b79..7ad51fa8e35 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -336,7 +336,7 @@ def _rpc_stopbuy(self) -> Dict[str, str]: # 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 e5ac79febea..baddc0685e2 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 dd82155515e..8e8d1f1bbee 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 400940d1663..85b43b4f80f 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 85b43b4f80f..7f3fe503d56 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 b49aad6262a..e582fddf67b 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 7f3fe503d56..3a25bb8bc15 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 68d63101a3b..e0aaf94617c 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 031b490c8eb..8d0768d438c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -10,7 +10,7 @@ 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 8d0768d438c..f54560a0e7f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -10,7 +10,7 @@ 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 @@ def backtest(self, args: Dict) -> DataFrame: # 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 e7ad9c9bfcc..4b8d990feac 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 d08bd2587fd..a112c63b437 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -78,9 +78,7 @@ def _validate_whitelist(self, whitelist: List[str]) -> List[str]: # 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 3a25bb8bc15..53e81aad31d 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 | 194 +++++++++++++++++++++++++++++++------- 2 files changed, 171 insertions(+), 136 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6f1fb2c9903..ef00ba21d03 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -7,11 +7,10 @@ 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.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 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 @@ def cleanup(self) -> None: 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 c41d54f0e94..48ae17f9d20 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -5,10 +5,12 @@ """ 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() - - # Create new instance - freqtrade = FreqtradeBot(Configuration(args, None).get_config()) - freqtrade.rpc.send_msg({ - 'type': RPCMessageType.STATUS_NOTIFICATION, - 'status': 'config reloaded' - }) - return freqtrade + + 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() + + # 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 48ae17f9d20..e1b4d80fa9b 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -13,7 +13,6 @@ 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 @@ def _init(self): # 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 51c95a4a977..188b9b9bde1 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -8,7 +8,7 @@ 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 e1b4d80fa9b..923230b7f3f 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 | 85 +++++++++++++++++---------------------- freqtrade/main.py | 22 +++++++++- 2 files changed, 56 insertions(+), 51 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ef00ba21d03..784c0b938c8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -4,7 +4,6 @@ import copy import logging -import time import traceback from datetime import datetime from typing import Any, Dict, List, Optional, Tuple @@ -13,7 +12,7 @@ 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 @@ def process(self) -> bool: :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() + + # Refresh whitelist + self.pairlists.refresh_pairlist() + self.active_pair_whitelist = self.pairlists.whitelist + + # Calculating Edge positioning + if self.edge: + self.edge.calculate() + self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist) + + # Query trades from persistence layer + trades = Trade.get_open_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, trades) + + # Refreshing candles + self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), + self.strategy.informative_pairs()) + + # First process current opened trades + for trade in trades: + state_changed |= self.process_maybe_execute_sell(trade) + + # 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() - # Calculating Edge positioning - if self.edge: - self.edge.calculate() - self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist) - - # Query trades from persistence layer - trades = Trade.get_open_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, trades) - - # Refreshing candles - self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist), - self.strategy.informative_pairs()) - - # First process current opened trades - for trade in trades: - state_changed |= self.process_maybe_execute_sell(trade) - - # 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 923230b7f3f..9331206fc66 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -6,11 +6,13 @@ 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 @@ def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) 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 53e81aad31d..9c083a6750a 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 40754cfbc28..531f7391651 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 _trend_alternate_hold(dataframe=None, metadata=None): 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 f54560a0e7f..f3661ab325c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -202,6 +202,32 @@ def _store_backtest_result(self, recordfilename: str, results: DataFrame, 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 c1c1b49cd2f..af8674188a1 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 129a09f4079..b1bf55f50f5 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 | 43 ++++++++++++++-------------- freqtrade/tests/test_freqtradebot.py | 9 ++---- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a086139c782..542b6bab048 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -302,25 +302,6 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss else self.stoploss, initial=True) - # 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'))): - - 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: - selltype = SellType.TRAILING_STOP_LOSS - logger.debug( - f"HIT STOP: current price at {current_rate:.6f}, " - f"stop loss is {trade.stop_loss:.6f}, " - f"initial stop loss was at {trade.initial_stop_loss:.6f}, " - f"trade opened at {trade.open_rate:.6f}") - logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}") - - 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: @@ -335,9 +316,8 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat # 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}%") + 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. @@ -345,6 +325,25 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat 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 + (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: + selltype = SellType.TRAILING_STOP_LOSS + logger.debug( + f"HIT STOP: current price at {current_rate:.6f}, " + f"stop loss is {trade.stop_loss:.6f}, " + f"initial stop loss was at {trade.initial_stop_loss:.6f}, " + f"trade opened at {trade.open_rate:.6f}") + logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}") + + logger.debug('Stop loss hit.') + return SellCheckTuple(sell_flag=True, sell_type=selltype) + 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 a5de283a510..561df7c05cd 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 542b6bab048..a9925b65430 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -299,18 +299,16 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat """ 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 - # 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: + 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 - 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 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat # 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 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a9925b65430..eb9ed825e2b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -301,6 +301,7 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat 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 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat 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: + # Don't update stoploss if trailing_only_offset_is_reached is true. + if not (tsl_only_offset 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_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. - 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 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 eb9ed825e2b..fcb27d7bde1 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -290,8 +290,9 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, 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 @@ def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: dat 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 d24ffcf1bb6..014eb00adda 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 6ee6464d355..1a4c593f913 100644 --- a/scripts/get_market_pairs.py +++ b/scripts/get_market_pairs.py @@ -51,7 +51,6 @@ def print_supported_exchanges(): 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 @@ def print_supported_exchanges(): 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 581518b12b7..2eccaca1818 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -24,24 +24,22 @@ > 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 e2f85932f67..8361e8bf620 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -12,26 +12,24 @@ --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 014eb00adda..71432ea0ac4 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 a9b191e754d..60c642ab335 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 2eccaca1818..14d57265e67 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 8361e8bf620..394f02116a6 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -37,7 +37,7 @@ # 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 8592f6cca71..00ed470f567 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 00ed470f567..d1ab086359c 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 6e52be47fb0..14021a3410c 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 bd6c50c5ac2..02fdf5b8f04 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 e85aceec8e4..54f9b82135a 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 face22404ca..92d60c7ed8d 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 8592f6cca71..89d35848e68 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 cece5a5d1c2..44cc3fe763e 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -56,6 +56,8 @@ def __init__(self, config: Optional[Dict] = None) -> None: ("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 602ea5dbe5a..b63180d1e48 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 9c083a6750a..73c65aff628 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 4b8d990feac..11b941220d5 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 d24ffcf1bb6..a711290eaae 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 93278316090..a25d3c1d5ba 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 d1ab086359c..511be18fa5d 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 739a89c0723..35e4a776dc4 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 25043d981c6..c218bd360e4 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 7372e3373cb..b0e0b2d42e9 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 127f69e9f17..60c1509e04c 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 14021a3410c..e25f35c3503 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 02fdf5b8f04..995bc561b13 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 66d449037c4..11df6820e9c 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 89d35848e68..276c992f1de 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 a0ffff107d0..687ee93753a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -456,7 +456,15 @@ def _rpc_count(self) -> List[Trade]: 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 6771ec80383..903efe7d188 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -93,6 +93,7 @@ def _init(self) -> None: 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 @@ def _whitelist(self, bot: Bot, update: Update) -> None: 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 @@ def _help(self, bot: Bot, update: Update) -> None: "*/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 26262cb4b23..c0f8e49b79d 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 baddc0685e2..b1fbd27ff5b 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 8e8d1f1bbee..fec2e508c82 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 92d60c7ed8d..6712292427d 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 687ee93753a..cacca4e3c8d 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -461,8 +461,11 @@ def _rpc_whitelist(self) -> Dict: } 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 903efe7d188..92108ded9cb 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 @@ def _init(self) -> None: 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 @@ def _whitelist(self, bot: Bot, update: Update) -> None: 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 6712292427d..d0ee2849d15 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 fec2e508c82..6adcda77b45 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 d0ee2849d15..2f7e9ada8ed 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 92108ded9cb..553ac4ed976 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -516,7 +516,8 @@ def _help(self, bot: Bot, update: Update) -> None: "*/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 6adcda77b45..dd49b0000bd 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 553ac4ed976..2c419e417c4 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -20,7 +20,7 @@ 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 b1fbd27ff5b..e6f7ea41e12 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 a0ffff107d0..3c0fbeba91c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -460,3 +460,18 @@ def _rpc_whitelist(self) -> Dict: '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 6771ec80383..edc380f921c 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -470,6 +470,29 @@ def _whitelist(self, bot: Bot, update: Update) -> None: 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 3c0fbeba91c..fb06c7d86f6 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -466,12 +466,12 @@ def _rpc_edge(self) -> Dict: 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 edc380f921c..64c35078d5c 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -93,6 +93,7 @@ def _init(self) -> None: 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 @@ def _edge(self, bot: Bot, update: Update) -> None: """ 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 @@ def _help(self, bot: Bot, update: Update) -> None: "*/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 fb06c7d86f6..6416a97eb5a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -468,10 +468,10 @@ def _rpc_edge(self) -> Dict: 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 2f7e9ada8ed..6788bec9003 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 73c65aff628..feed42b5286 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 e0aaf94617c..56d7964c3aa 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 784c0b938c8..94c1bca8aa0 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -22,7 +22,7 @@ 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 9331206fc66..877e2921d6a 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -5,18 +5,14 @@ """ 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 00000000000..9a7e6742479 --- /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 e4f0415f7c1..fa50b36e69e 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -13,12 +13,14 @@ 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 40754cfbc28..d0b21b8f494 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -16,6 +16,7 @@ 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 031b490c8eb..7c98bd3a764 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -18,6 +18,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.persistence import Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver @@ -64,6 +65,13 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 c01a0a03d24..ae6377bf57e 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 cacca4e3c8d..a5601a50207 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -464,10 +464,14 @@ def _rpc_whitelist(self) -> Dict: 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 ae6377bf57e..f018d92a781 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 7c98bd3a764..293511fc04d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -69,8 +69,10 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 02062acc441..bd021c327e9 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -22,6 +22,12 @@ 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 ea8bcfac17d..7781c85e479 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -21,13 +21,6 @@ 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 127f4e91635..1d633be2b24 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -24,3 +24,9 @@ def get_order_book(self, pair: str, limit: int = 100) -> dict: 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 7781c85e479..d422e2bbfd8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -13,7 +13,7 @@ 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 @@ 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 """ 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 bd021c327e9..02062acc441 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -22,12 +22,6 @@ 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 1d633be2b24..18e754e3f78 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 @@ def get_order_book(self, pair: str, limit: int = 100) -> dict: 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 d422e2bbfd8..2ec5c0e2592 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 @@ 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 """ - 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 94c1bca8aa0..7c92ac29a7c 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 @@ def __init__(self, config: Dict[str, Any], worker: Worker) -> None: # Init objects self.config = config - self._worker: Worker = worker + self._worker = worker self.strategy: IStrategy = StrategyResolver(self.config).strategy @@ -75,10 +75,14 @@ def __init__(self, config: Dict[str, Any], worker: Worker) -> None: @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 9a7e6742479..a6fba14607a 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -5,7 +5,7 @@ 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 @@ def state(self, value: State): 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 @@ def _reconfigure(self): 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 26262cb4b23..772602f6daf 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -15,6 +15,7 @@ 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 baddc0685e2..69b428693dc 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -8,6 +8,7 @@ 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 8e8d1f1bbee..1452267ea33 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -13,13 +13,14 @@ 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 e4f0415f7c1..276cdc0b914 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -13,6 +13,7 @@ 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 @@ # 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 throttled_func(): 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 _refresh_whitelist(list): 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 _refresh_whitelist(list): 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 188b9b9bde1..cce02bf8ba4 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 cce02bf8ba4..96cf2834d25 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -7,8 +7,9 @@ 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 1452267ea33..578e45e24a6 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 feed42b5286..345d32c0d84 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 276cdc0b914..ece07f6fd98 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 531f7391651..64a33fae259 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 f018d92a781..9e754c213b5 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 6788bec9003..fa10969dbff 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 dd49b0000bd..24e99b1c55b 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 345d32c0d84..2bf329bd8d8 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 e6f7ea41e12..a5ee4a26cfc 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 a5ee4a26cfc..8e8e7fc5077 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 24e99b1c55b..8118266cc0f 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -13,16 +13,16 @@ 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 8118266cc0f..c6a5fff54f2 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 fa10969dbff..d681835370a 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 2ec5c0e2592..011be58e57e 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -272,7 +272,7 @@ def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
         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 736f2298ad0..eed16d39b8d 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 2c419e417c4..1e143663127 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -193,21 +193,25 @@ def _status(self, bot: Bot, update: Update) -> None:
             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 dd49b0000bd..39973d5dba5 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 553f1a1fbcd..919c45757e5 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -533,8 +533,8 @@ def _help(self, bot: Bot, update: Update) -> None:
                   "*/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 d681835370a..1ca61e54a1c 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 919c45757e5..06bf5efe91e 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -494,7 +494,7 @@ def _blacklist(self, bot: Bot, update: Update, args: List[str]) -> None:
     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 1e143663127..0870e660b0e 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -203,7 +203,7 @@ def _status(self, bot: Bot, update: Update) -> None:
                     "*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 39973d5dba5..d1a068100c6 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 0870e660b0e..2ba431b07b0 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -196,18 +196,17 @@ def _status(self, bot: Bot, update: Update) -> None:
             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 a5601a50207..3272059a96f 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -110,6 +110,8 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]:
                     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 e6f7ea41e12..f9862c9ca68 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 3272059a96f..553e66d85ee 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -97,9 +97,13 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]:
                     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 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]:
                     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 2ba431b07b0..8bec8e6cdb2 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -204,9 +204,10 @@ def _status(self, bot: Bot, update: Update) -> None:
                     "*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 d1a068100c6..edd099dc449 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 2bf329bd8d8..0fff9dacfa9 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 553e66d85ee..20bfe41babb 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -101,8 +101,6 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]:
                 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 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]:
                     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 8bec8e6cdb2..d3f3521aabe 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -204,7 +204,7 @@ def _status(self, bot: Bot, update: Update) -> None:
                     "*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 f9862c9ca68..529bf31f336 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 20bfe41babb..55f9a302c5c 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -97,11 +97,9 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]:
                     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 edd099dc449..b66e29143c7 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 ceeae6865b5..8bc07107152 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 a1ac65c049b..0b46768c146 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 @@ def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool
         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 0b46768c146..5be61393e7d 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 0fff9dacfa9..22d3ae4859b 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 60438642643..8d7dac4bc85 100644
--- a/freqtrade/arguments.py
+++ b/freqtrade/arguments.py
@@ -71,6 +71,13 @@ def common_args_parser(self) -> None:
             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 ba7a0e2003a..adaf7a59b8f 100644
--- a/freqtrade/configuration.py
+++ b/freqtrade/configuration.py
@@ -4,7 +4,9 @@
 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.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 @@ def _load_common_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
             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 adaf7a59b8f..fdd71f2f5fd 100644
--- a/freqtrade/configuration.py
+++ b/freqtrade/configuration.py
@@ -7,7 +7,7 @@
 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 @@ def _load_common_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
             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 21547d20540..45e539c2fc7 100644
--- a/freqtrade/tests/test_configuration.py
+++ b/freqtrade/tests/test_configuration.py
@@ -5,6 +5,7 @@
 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 ade62ce945d..8f757856140 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 35e4a776dc4..5ec390d5c7b 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 11b941220d5..75843ef4a28 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 b44c196882a..384cdcfa009 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -14,7 +14,6 @@
 
 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 96cf2834d25..fc5d2e378ac 100644
--- a/freqtrade/tests/test_main.py
+++ b/freqtrade/tests/test_main.py
@@ -8,7 +8,6 @@
 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 a6fba14607a..f32e6ff49d6 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 22d3ae4859b..b2dee35426c 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 c0f8e49b79d..6478706a440 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 fbc9424329c..66870efcc71 100644
--- a/freqtrade/tests/rpc/test_fiat_convert.py
+++ b/freqtrade/tests/rpc/test_fiat_convert.py
@@ -8,7 +8,7 @@
 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 df89c03d498..627768ec2f9 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -14,7 +14,7 @@
 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 03a6442e55a..fb2d71d4fda 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -20,8 +20,7 @@
 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 mock_ticker(symbol, refresh):
             '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 6478706a440..cba754199c9 100644
--- a/freqtrade/tests/conftest.py
+++ b/freqtrade/tests/conftest.py
@@ -4,7 +4,6 @@
 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 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 f32e6ff49d6..e5ca360354f 100755
--- a/freqtrade/worker.py
+++ b/freqtrade/worker.py
@@ -159,7 +159,7 @@ def _process(self) -> bool:
     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 5be61393e7d..5a18a922a45 100644
--- a/freqtrade/persistence.py
+++ b/freqtrade/persistence.py
@@ -240,16 +240,16 @@ def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool
         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 042237ce7cd..f57a466e3ee 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 357a8f52541..58d3d3ac659 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 b2dee35426c..6e21e879d3b 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 56d7964c3aa..0592b99e8fa 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 6f1fb2c9903..291b7491369 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -472,7 +472,6 @@ def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = N
             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 291b7491369..f78a9078e01 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -530,24 +530,7 @@ def process_maybe_execute_sell(self, trade: Trade) -> bool:
         :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 @@ def get_real_amount(self, trade: Trade, order: Dict) -> float:
                         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 f78a9078e01..74c74324f8a 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -500,6 +500,10 @@ def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = N
             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 | 75 ++++++++++++++++++++-------------------
 1 file changed, 38 insertions(+), 37 deletions(-)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 74c74324f8a..a06e146f9d2 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -691,43 +691,44 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
         """
 
         result = False
-
-        # 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)
-
-            # 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)
-
+        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
+
+                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
+
+                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 5bf0bfcbb06..e53d26793dc 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 | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
index e53d26793dc..38a6df69500 100644
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -1306,21 +1306,31 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
     trade.open_order_id = '123'
     trade.open_fee = 0.001
 
-    # Test raise of OperationalException exception
+    # Test raise of DependencyException exception
     mocker.patch(
-        'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
-        side_effect=OperationalException()
+        'freqtrade.freqtradebot.FreqtradeBot.update_open_order',
+        side_effect=DependencyException()
     )
     freqtrade.process_maybe_execute_sell(trade)
-    assert log_has('Could not update trade amount: ', caplog.record_tuples)
+    assert log_has('Unable to sell trade: ', caplog.record_tuples)
 
-    # Test raise of DependencyException exception
+
+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=DependencyException()
+        side_effect=OperationalException()
     )
-    freqtrade.process_maybe_execute_sell(trade)
-    assert log_has('Unable to sell trade: ', caplog.record_tuples)
+    freqtrade.update_open_order(trade)
+    assert log_has('Could not update trade amount: ', caplog.record_tuples)
 
 
 def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,

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 38a6df69500..a3b01c5429a 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 a06e146f9d2..5b1e5625daa 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -500,7 +500,7 @@ def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = N
             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 a3b01c5429a..af8fc3b0951 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 7c92ac29a7c..6345dae569f 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -22,7 +22,6 @@
 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 @@ def __init__(self, config: Dict[str, Any], worker: Optional[Worker] = None) -> N
 
         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 @@ def __init__(self, config: Dict[str, Any], worker: Optional[Worker] = None) -> N
 
         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 e5ca360354f..2440e732095 100755
--- a/freqtrade/worker.py
+++ b/freqtrade/worker.py
@@ -11,6 +11,7 @@
 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 @@ def _init(self, reconfig: bool):
             # 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 @@ def _init(self, reconfig: bool):
 
     @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 @@ def _worker(self, old_state: State, throttle_secs: Optional[float] = None) -> St
         :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 @@ 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)
@@ -153,7 +142,8 @@ def _process(self) -> bool:
                 '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 6f1fb2c9903..c14f0d31a19 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -363,7 +363,8 @@ def create_trade(self) -> bool:
                 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 e4f0415f7c1..4e145c354e8 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 6e21e879d3b..bdad28181d4 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 0592b99e8fa..69082587a7a 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 1ca61e54a1c..381a47ae919 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 79db08fd3b2..5308c9d5106 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -111,6 +111,9 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]:
                     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 7b36e8a1f30..ca9b376ecd4 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -197,7 +197,7 @@ def _status(self, bot: Bot, update: Update) -> None:
             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 @@ def _status(self, bot: Bot, update: Update) -> None:
                     "*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 627768ec2f9..b454f9cd80b 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 fb2d71d4fda..b6d12fe4164 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 c14f0d31a19..e9b8b195649 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -363,7 +363,7 @@ def create_trade(self) -> bool:
                 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 5ec390d5c7b..55988985a8f 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 381a47ae919..4cc8eaa5cac 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 e9b8b195649..42013c85fe8 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -79,6 +79,9 @@ def __init__(self, config: Dict[str, Any]) -> None:
             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 42013c85fe8..e5400c1d075 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -79,8 +79,6 @@ def __init__(self, config: Dict[str, Any]) -> None:
             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 @@ def _process(self) -> bool:
             # 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 5b1e5625daa..55ef6f611a6 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -502,7 +502,7 @@ def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = N
 
         # 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 @@ def process_maybe_execute_sell(self, trade: Trade) -> bool:
         :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 @@ def get_real_amount(self, trade: Trade, order: Dict) -> float:
                         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 af8fc3b0951..416a085ade7 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 bdad28181d4..83d77b69378 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 0d1ae9c26e6..30fed8c53b0 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 011be58e57e..2b7e41847a3 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -13,7 +13,8 @@
 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 30fed8c53b0..292613297d0 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 2b7e41847a3..7f4ab062c91 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -14,7 +14,7 @@
 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 @@ def cancel_order(self, order_id: str, pair: str) -> None:
         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 eed16d39b8d..4deb74c67c6 100644
--- a/freqtrade/tests/exchange/test_exchange.py
+++ b/freqtrade/tests/exchange/test_exchange.py
@@ -11,7 +11,8 @@
 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 55ef6f611a6..fca3e346d66 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -745,8 +745,8 @@ def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
             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 ca9b376ecd4..9d1f9f20689 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -205,9 +205,11 @@ def _status(self, bot: Bot, update: Update) -> None:
                     "*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 9d1f9f20689..2d822820f6a 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -205,12 +205,17 @@ def _status(self, bot: Bot, update: Update) -> None:
                     "*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 8bc07107152..250e43c3bfd 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 46033ec230d..b7b0416956b 100644
--- a/freqtrade/tests/rpc/test_rpc.py
+++ b/freqtrade/tests/rpc/test_rpc.py
@@ -16,7 +16,6 @@
 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 384cdcfa009..c0b77076fc1 100644
--- a/freqtrade/tests/rpc/test_rpc_telegram.py
+++ b/freqtrade/tests/rpc/test_rpc_telegram.py
@@ -14,15 +14,15 @@
 
 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 4a3cafbd2ae..b454f9cd80b 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 c3ab5064ce1..b6d12fe4164 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 6790f513c89..211683d640b 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 b4dfa562454..0f70df43b5a 100644
--- a/freqtrade/edge/__init__.py
+++ b/freqtrade/edge/__init__.py
@@ -203,6 +203,24 @@ def adjust(self, pairs) -> list:
 
         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 5308c9d5106..79bfffb1de4 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -484,13 +484,4 @@ def _rpc_edge(self) -> List[Dict[str, Any]]:
         """ 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 2d822820f6a..8ff59d75927 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -511,7 +511,6 @@ def _edge(self, bot: Bot, update: Update) -> None:
         """
         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 2d822820f6a..81f9dc4f0a5 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -208,12 +208,12 @@ def _status(self, bot: Bot, update: Update) -> None: # 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 0f70df43b5a..7bda942d26e 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -206,19 +206,17 @@ def adjust(self, pairs) -> list: 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 79bfffb1de4..ceab003734b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -484,4 +484,4 @@ def _rpc_edge(self) -> List[Dict[str, Any]]: """ 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 7bda942d26e..4801c6cb343 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -211,12 +211,12 @@ def accepted_pairs(self) -> list: 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 83d77b69378..914c8e64422 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 914c8e64422..afb422e4b81 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 5308c9d5106..ec6c1feed26 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -103,11 +103,13 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 2d822820f6a..9a64f51971f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -199,7 +199,7 @@ def _status(self, bot: Bot, update: Update) -> None: 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 b454f9cd80b..981d3edfb3f 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 b6d12fe4164..8f43d7ed0bc 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 5308c9d5106..0a6a2388abf 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -111,9 +111,9 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 81f9dc4f0a5..2d822820f6a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -208,12 +208,12 @@ def _status(self, bot: Bot, update: Update) -> None: # 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 e5400c1d075..bf445e56dc7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -79,7 +79,6 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 @@ def _process(self) -> bool: # 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 @@ def create_trade(self) -> bool: 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 @@ def create_trade(self) -> bool: 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 4e145c354e8..fc13bc0f148 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 f3661ab325c..0fcfd11f152 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -337,7 +337,6 @@ def backtest(self, args: Dict) -> DataFrame: # 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 0a6a2388abf..090eb295faa 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -111,11 +111,13 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 090eb295faa..e59fb73f380 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -111,11 +111,11 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 2440e732095..c7afe5c974d 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -143,7 +143,9 @@ def _process(self) -> bool: }) 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 e3276c53fe9..88ade0c2708 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -109,7 +109,7 @@ def _rpc_trade_status(self) -> List[Dict[str, Any]]: 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 981d3edfb3f..8b10a131438 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 afb422e4b81..eb5d564fa8d 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 69082587a7a..c59923ed2a1 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 fca3e346d66..eeefaa6d821 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -690,46 +690,78 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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) + + + # 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) + 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. + 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 416a085ade7..3e8bdb001f5 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 eeefaa6d821..acff0761bda 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -700,7 +700,7 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 acff0761bda..9bd3384fa0d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -699,7 +699,6 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 3e8bdb001f5..c854d99e838 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 0fcfd11f152..fc007ce2b15 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -329,6 +329,8 @@ def backtest(self, args: Dict) -> DataFrame: 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 @@ def backtest(self, args: Dict) -> DataFrame: 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 @@ def backtest(self, args: Dict) -> DataFrame: # 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 64a33fae259..edfe022252b 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -701,9 +701,13 @@ def _trend_alternate_hold(dataframe=None, metadata=None): 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 02062acc441..5243eeb4aa6 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -6,7 +6,7 @@ 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 @@ 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 @@ '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 c32338bbe7b..28749293b7f 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -4,8 +4,8 @@ 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 7d89f7ad615..0ecc632e474 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -12,10 +12,12 @@ 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 011be58e57e..f18a71a5025 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -15,9 +15,12 @@ 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 @@ async def _async_get_history(self, pair: str, # 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 @@ def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[s 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 acf47b06580..e9f636ec732 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -16,6 +16,7 @@ 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 @@ def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = N 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 38f758669d6..9aeaef0695f 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -8,6 +8,7 @@ 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 293511fc04d..e0360cdefb0 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -19,7 +19,7 @@ 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 @@ def __init__(self, config: Dict[str, Any]) -> None: 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 @@ def _set_strategy(self, strategy): 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 @@ def start(self) -> None: 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 fcb27d7bde1..646bd2a94a2 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -12,8 +12,8 @@ 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 @@ def get_signal(self, pair: str, interval: str, # 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 075938a6175..9203ec19ca4 100644 --- a/freqtrade/tests/optimize/__init__.py +++ b/freqtrade/tests/optimize/__init__.py @@ -3,11 +3,11 @@ 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 99cd24c2659..08874303863 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 394f02116a6..8330e2a8847 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -28,6 +28,7 @@ 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 fc007ce2b15..bbbda9b1ffe 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -334,10 +334,11 @@ def backtest(self, args: Dict) -> DataFrame: 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 8330e2a8847..2b6360becb3 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -24,11 +24,10 @@ 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 54f9b82135a..803a2b48a61 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 e25f35c3503..b4e42de16af 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 803a2b48a61..f41520bd927 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 2b5365e32cf..43b036c74da 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 4cc8eaa5cac..9d68773180a 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 43b036c74da..811b57f9b25 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 9a6fec851b9..ecac265c13c 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 8d7dac4bc85..b0acb4122ef 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -247,6 +247,22 @@ def optimizer_shared_options(parser: argparse.ArgumentParser) -> None: 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 fdd71f2f5fd..e7441c18df3 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -217,14 +217,22 @@ def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]: 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 7f4ab062c91..7880ce46432 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -624,8 +624,8 @@ def get_order(self, order_id: str, pair: str) -> Dict: 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 4deb74c67c6..66bc47405b4 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 9bd3384fa0d..a0725ad084a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -703,10 +703,7 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: # 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 a0725ad084a..f04a39610ae 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -699,8 +699,7 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 f04a39610ae..3bab1758d69 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -13,7 +13,7 @@ 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 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: 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 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool: # 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 c854d99e838..5896e7cf933 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -12,7 +12,7 @@ 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 5896e7cf933..9687fe90388 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 75843ef4a28..f7e2a07f351 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 346bcfce316..009e039b39a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -12,7 +12,7 @@ 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 eb5d564fa8d..7006c52c701 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 88ade0c2708..d3a6dc0db64 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -459,7 +459,12 @@ def _rpc_count(self) -> List[Trade]: 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 e17f73502e7..ca108b17e86 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -456,12 +456,10 @@ def _count(self, bot: Bot, update: Update) -> None: :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 00000000000..5184e2d3773 --- /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 00000000000..575107e2d20 --- /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 d3a6dc0db64..aac419fe1e4 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -454,7 +454,7 @@ def _rpc_performance(self) -> List[Dict]: 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 @@ def _rpc_count(self) -> List[Trade]: 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 8b10a131438..25d1109b2ec 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 7880ce46432..275b2123f63 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -625,7 +625,7 @@ def get_order(self, order_id: str, pair: str) -> Dict: 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 9aeaef0695f..57a5673fdff 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 57a5673fdff..d066878be27 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 5184e2d3773..1041b3c871e 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 575107e2d20..00000000000 --- 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 01d4bba2ac1..462ae5783c1 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 00000000000..4fca8b03233 --- /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 1041b3c871e..5184e2d3773 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 7006c52c701..99892a5448a 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 375b8bf5b25..df4accf932e 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -37,23 +37,23 @@ def refresh(self, @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 @@ def historic_ohlcv(self, pair: str, ticker_interval: str) -> DataFrame: """ 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 0ecc632e474..594c85b5ff0 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 f18a71a5025..2e8d6b99a9f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -489,26 +489,26 @@ def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: 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 @@ def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[s 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 @@ def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool: + 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 @@ async def _async_get_candle_history(self, pair: str, tick_interval: str, 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 b17bba27321..993f0b59b37 100644 --- a/freqtrade/tests/data/test_dataprovider.py +++ b/freqtrade/tests/data/test_dataprovider.py @@ -9,31 +9,31 @@ 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 bc859b32553..c0b1cade333 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 eed16d39b8d..6997c6ba3f5 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 5dee41bdd03..50005b97b85 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -92,18 +92,18 @@ 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 14d57265e67..7fdc607e06f 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 2b6360becb3..500d9fcdec3 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 99892a5448a..de437aa67de 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 ca108b17e86..d1ef75cf515 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -20,6 +20,9 @@ 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 @@ def _balance(self, bot: Bot, update: Update) -> None: 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 8f43d7ed0bc..137d7b3b63d 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.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 de437aa67de..4bd9433fecb 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 d1ef75cf515..4e4c080d006 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -20,7 +20,7 @@ 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 @@ def _balance(self, bot: Bot, update: Update) -> None: 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 137d7b3b63d..f2f3f39459c 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 4bd9433fecb..99c5104c915 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 1a4c593f913..1d1f2114b32 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 @@ def print_supported_exchanges(): 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 50005b97b85..2acadc8b6dc 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 @@ 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 @@ 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 99c5104c915..ed6e5e23603 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 1a4c593f913..c4b9777ead0 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 ca108b17e86..0bc877272b0 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -266,7 +266,8 @@ def _daily(self, bot: Bot, update: Update) -> None: 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 f7e2a07f351..06937945d56 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 60c1509e04c..c551e3638ea 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 ed6e5e23603..7ab39e78a65 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 7ab39e78a65..952ee265e8a 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 e7441c18df3..ae9978f8199 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -194,7 +194,7 @@ def _create_datadir(self, config: Dict[str, Any], datadir: Optional[str] = None) 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 @@ def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]: 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 aeefc072206..e3676653002 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 995bc561b13..23a6cbd230e 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 952ee265e8a..d8d4d84d5f9 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 4fca8b03233..c2e3e041405 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 c2e3e041405..7d7b779e9d7 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 d8d4d84d5f9..8b6e3014dd9 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 7d7b779e9d7..aa1133a0fdf 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 8b6e3014dd9..40903e9d6ae 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 aa1133a0fdf..0fd26a3d916 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 c59923ed2a1..77f122a5971 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 e582fddf67b..0b924b60866 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 ae9978f8199..7f0f3c34a5d 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -393,11 +393,6 @@ def check_exchange(self, config: Dict[str, Any]) -> bool: 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 ffd574f903d..57a00238457 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -146,7 +146,6 @@ def _init_ccxt(self, exchange_config: dict, ccxt_module=ccxt, '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 45e539c2fc7..bcd0bd92cfa 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 323ff711e49..94084434a98 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 3d11f317acd..ab57db88f74 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 58d3d3ac659..20ba10c89cb 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 06937945d56..0da14d9f6d9 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 2acadc8b6dc..42b305778fb 100755 --- a/scripts/download_backtest_data.py +++ b/scripts/download_backtest_data.py @@ -54,7 +54,8 @@ '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 44cc3fe763e..b2743a4173d 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -157,12 +157,15 @@ def _load_strategy( 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 b63180d1e48..2ed2567f945 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 40903e9d6ae..771102e9013 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 0fd26a3d916..30e4a4ce40b 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 77f122a5971..9d0e9984317 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 292613297d0..f28809f3364 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):