From 1b4b10f8cdfb2e04f0acba0d6f9f674e8773ecbf Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Tue, 23 Jul 2019 23:45:27 -0500 Subject: [PATCH 001/119] Update docs/installation.md Address that numpy is required before `python3 -m pip install -r requirements.txt` can run. --- docs/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installation.md b/docs/installation.md index 657273e2f..35cdcda62 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -175,6 +175,7 @@ cp config.json.example config.json ``` bash python3 -m pip install --upgrade pip +pip install numpy python3 -m pip install -r requirements.txt python3 -m pip install -e . ``` From 8e03fee8685ccbb8098cabb81bc622498e8dbf53 Mon Sep 17 00:00:00 2001 From: radwayne <73605415+radwayne@users.noreply.github.com> Date: Fri, 6 Nov 2020 13:56:46 +0100 Subject: [PATCH 002/119] Update interface.py Changed The should_sell() method, to handle the case where both ROI and trailing stoploss are reached in backtest. --- freqtrade/strategy/interface.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1c6aa535d..44a281ebe 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -475,16 +475,27 @@ class IStrategy(ABC): stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, force_stoploss=force_stoploss, high=high) - - if stoplossflag.sell_flag: - logger.debug(f"{trade.pair} - Stoploss hit. sell_flag=True, " - f"sell_type={stoplossflag.sell_type}") - return stoplossflag - + # Set current rate to high for backtesting sell current_rate = high or rate current_profit = trade.calc_profit_ratio(current_rate) config_ask_strategy = self.config.get('ask_strategy', {}) + + roi_reached = self.min_roi_reached(trade=trade, current_profit=current_profit, + current_time=date) + + if stoplossflag.sell_flag: + + # When backtesting, in the case of trailing_stop_loss, + # make sure we don't make a profit higher than ROI. + if stoplossflag.sell_type == SellType.TRAILING_STOP_LOSS and roi_reached: + logger.debug(f"{trade.pair} - Required profit reached. sell_flag=True, " + f"sell_type=SellType.ROI") + return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI) + + logger.debug(f"{trade.pair} - Stoploss hit. sell_flag=True, " + f"sell_type={stoplossflag.sell_type}") + return stoplossflag if buy and config_ask_strategy.get('ignore_roi_if_buy_signal', False): # This one is noisy, commented out @@ -492,7 +503,7 @@ class IStrategy(ABC): return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) - if self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date): + if roi_reached: logger.debug(f"{trade.pair} - Required profit reached. sell_flag=True, " f"sell_type=SellType.ROI") return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI) From 13da8f936812bbfd255c4dfce4dd451dc9134c46 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Mon, 9 Nov 2020 08:34:40 +0000 Subject: [PATCH 003/119] Added ConstPairList handler to skip validation of pairs if you want to backtest a pair that's not live any more, e.g. expiring contracts. --- freqtrade/constants.py | 2 +- freqtrade/pairlist/ConstPairList.py | 60 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 freqtrade/pairlist/ConstPairList.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index dc5384f6f..21308b2dc 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -23,7 +23,7 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] -AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', +AVAILABLE_PAIRLISTS = ['ConstPairList', 'StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] diff --git a/freqtrade/pairlist/ConstPairList.py b/freqtrade/pairlist/ConstPairList.py new file mode 100644 index 000000000..e5b009c55 --- /dev/null +++ b/freqtrade/pairlist/ConstPairList.py @@ -0,0 +1,60 @@ +""" +Const Pair List provider + +Provides pair white list as it configured in config without checking for active markets +""" +import logging +from typing import Any, Dict, List + +from freqtrade.exceptions import OperationalException +from freqtrade.pairlist.IPairList import IPairList + + +logger = logging.getLogger(__name__) + + +class ConstPairList(IPairList): + + def __init__(self, exchange, pairlistmanager, + config: Dict[str, Any], pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + if self._pairlist_pos != 0: + raise OperationalException(f"{self.name} can only be used in the first position " + "in the list of Pairlist Handlers.") + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return False + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + -> Please overwrite in subclasses + """ + return f"{self.name}" + + def gen_pairlist(self, cached_pairlist: List[str], tickers: Dict) -> List[str]: + """ + Generate the pairlist + :param cached_pairlist: Previously generated pairlist (cached) + :param tickers: Tickers (from exchange.get_tickers()). + :return: List of pairs + """ + return self._config['exchange']['pair_whitelist'] + + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + """ + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new whitelist + """ + return pairlist From 916776bb53642b25c4be09bca82ef0c1467798d8 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Mon, 9 Nov 2020 08:37:38 +0000 Subject: [PATCH 004/119] Option to skip exchange validation, required to backtest pairs that are not live on the exchange any more. --- 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 659ff59bc..baa379db1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -115,7 +115,7 @@ class Exchange: logger.info('Using Exchange "%s"', self.name) - if validate: + if validate and not exchange_config.get('skip_validation'): # Check if timeframe is available self.validate_timeframes(config.get('timeframe')) From 2640dfee9387613480e66da00a4fac5b260c1e41 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Thu, 12 Nov 2020 11:27:30 +0000 Subject: [PATCH 005/119] Revert "Added ConstPairList handler to skip validation of pairs if you want to backtest a pair that's not live any more, e.g. expiring contracts." This reverts commit 13da8f936812bbfd255c4dfce4dd451dc9134c46. --- freqtrade/constants.py | 2 +- freqtrade/pairlist/ConstPairList.py | 60 ----------------------------- 2 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 freqtrade/pairlist/ConstPairList.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 21308b2dc..dc5384f6f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -23,7 +23,7 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] -AVAILABLE_PAIRLISTS = ['ConstPairList', 'StaticPairList', 'VolumePairList', +AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] diff --git a/freqtrade/pairlist/ConstPairList.py b/freqtrade/pairlist/ConstPairList.py deleted file mode 100644 index e5b009c55..000000000 --- a/freqtrade/pairlist/ConstPairList.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Const Pair List provider - -Provides pair white list as it configured in config without checking for active markets -""" -import logging -from typing import Any, Dict, List - -from freqtrade.exceptions import OperationalException -from freqtrade.pairlist.IPairList import IPairList - - -logger = logging.getLogger(__name__) - - -class ConstPairList(IPairList): - - def __init__(self, exchange, pairlistmanager, - config: Dict[str, Any], pairlistconfig: Dict[str, Any], - pairlist_pos: int) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - - if self._pairlist_pos != 0: - raise OperationalException(f"{self.name} can only be used in the first position " - "in the list of Pairlist Handlers.") - - @property - def needstickers(self) -> bool: - """ - Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed - as tickers argument to filter_pairlist - """ - return False - - def short_desc(self) -> str: - """ - Short whitelist method description - used for startup-messages - -> Please overwrite in subclasses - """ - return f"{self.name}" - - def gen_pairlist(self, cached_pairlist: List[str], tickers: Dict) -> List[str]: - """ - Generate the pairlist - :param cached_pairlist: Previously generated pairlist (cached) - :param tickers: Tickers (from exchange.get_tickers()). - :return: List of pairs - """ - return self._config['exchange']['pair_whitelist'] - - def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: - """ - Filters and sorts pairlist and returns the whitelist again. - Called on each bot iteration - please use internal caching if necessary - :param pairlist: pairlist to filter or sort - :param tickers: Tickers (from exchange.get_tickers()). May be cached. - :return: new whitelist - """ - return pairlist From 2424ac94c27c146a553aed362c04ac1850733381 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Thu, 12 Nov 2020 11:29:46 +0000 Subject: [PATCH 006/119] skip the check for active markets with flag for existing StaticPairList --- freqtrade/pairlist/StaticPairList.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index aa6268ba3..3b6440763 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -24,6 +24,8 @@ class StaticPairList(IPairList): raise OperationalException(f"{self.name} can only be used in the first position " "in the list of Pairlist Handlers.") + self._allow_inactive = self._pairlistconfig.get('allow_inactive', False) + @property def needstickers(self) -> bool: """ @@ -47,7 +49,10 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). :return: List of pairs """ - return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist']) + if self._allow_inactive: + return self._config['exchange']['pair_whitelist'] + else: + return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist']) def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ From 2d6bfe1592ab61258d523065cdf4b38cb3857521 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Thu, 12 Nov 2020 11:32:45 +0000 Subject: [PATCH 007/119] only skip pair validation rather than all of it --- freqtrade/exchange/exchange.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index baa379db1..0e982a06f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -115,7 +115,7 @@ class Exchange: logger.info('Using Exchange "%s"', self.name) - if validate and not exchange_config.get('skip_validation'): + if validate: # Check if timeframe is available self.validate_timeframes(config.get('timeframe')) @@ -124,7 +124,8 @@ class Exchange: # Check if all pairs are available self.validate_stakecurrency(config['stake_currency']) - self.validate_pairs(config['exchange']['pair_whitelist']) + if not exchange_config.get('skip_pair_validation'): + 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_required_startup_candles(config.get('startup_candle_count', 0)) From 26176d4c91c20cb24a762d4eb8ab204a9951d1ac Mon Sep 17 00:00:00 2001 From: Samaoo Date: Sun, 15 Nov 2020 19:55:09 +0100 Subject: [PATCH 008/119] Update exchanges.md According to https://blog.kraken.com/post/5282/stop-loss-limit-take-profit-limit-two-new-advanced-orders-go-live-on-kraken/ Stop Loss Limit orders are enabled again --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index fcf7c1cad..ac386c937 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -23,7 +23,7 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f ## Kraken !!! Tip "Stoploss on Exchange" - Kraken supports `stoploss_on_exchange` and uses stop-loss-market orders. It provides great advantages, so we recommend to benefit from it, however since the resulting order is a stoploss-market order, sell-rates are not guaranteed, which makes this feature less secure than on other exchanges. This limitation is based on kraken's policy [source](https://blog.kraken.com/post/1234/announcement-delisting-pairs-and-temporary-suspension-of-advanced-order-types/) and [source2](https://blog.kraken.com/post/1494/kraken-enables-advanced-orders-and-adds-10-currency-pairs/) - which has stoploss-limit orders disabled. + Kraken supports `stoploss_on_exchange` and uses stop-loss-market orders. It provides great advantages, so we recommend to benefit from it. ### Historic Kraken data From ef4ab601a9fc2a059d0eaf3fd77c4bb7f63f8ac3 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Sun, 15 Nov 2020 20:02:19 +0100 Subject: [PATCH 009/119] Update exchanges.md --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index ac386c937..5d7505795 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -23,7 +23,7 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f ## Kraken !!! Tip "Stoploss on Exchange" - Kraken supports `stoploss_on_exchange` and uses stop-loss-market orders. It provides great advantages, so we recommend to benefit from it. + Kraken supports `stoploss_on_exchange`. It provides great advantages, so we recommend to benefit from it. ### Historic Kraken data From f88fe5d950daf3ab68f9153648bf8d94f20593b0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Nov 2020 19:14:43 +0100 Subject: [PATCH 010/119] Document new "allow_inactive" option --- docs/includes/pairlists.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index ae4ec818d..aebd084ab 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -35,6 +35,10 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis ], ``` +By default, only currently enabled pairs are allowed. +To skip pair validation against active markets, set `"allow_inactive": true` within the `StaticPairList` configuration. +This can be useful for backtesting expired pairs (like quarterly spot-markets). + #### Volume Pair List `VolumePairList` employs sorting/filtering of pairs by their trading volume. It selects `number_assets` top pairs with sorting based on the `sort_key` (which can only be `quoteVolume`). From 97e58a42f4caf1803af411937123461bda7ca244 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Nov 2020 19:17:31 +0100 Subject: [PATCH 011/119] Update documentation with new options --- docs/configuration.md | 1 + docs/includes/pairlists.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 47362e525..080ddd046 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -87,6 +87,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) 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)
**Datatype:** Dict | `exchange.ccxt_async_config` | 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)
**Datatype:** Dict | `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
**Datatype:** Positive Integer +| `exchange.skip_pair_validation` | Skip pairlist validation on startup.
*Defaults to `false`
**Datatype:** Boolean | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
**Datatype:** Boolean | `pairlists` | Define one or more pairlists to be used. [More information below](#pairlists-and-pairlist-handlers).
*Defaults to `StaticPairList`.*
**Datatype:** List of Dicts diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index aebd084ab..e6a9fc1a8 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -38,6 +38,7 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis By default, only currently enabled pairs are allowed. To skip pair validation against active markets, set `"allow_inactive": true` within the `StaticPairList` configuration. This can be useful for backtesting expired pairs (like quarterly spot-markets). +This option must be configured along with `exchange.skip_pair_validation` in the exchange configuration. #### Volume Pair List From aa0c3dced8bac13b35e241f287d1d9a4cbcecd31 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Nov 2020 13:14:02 +0100 Subject: [PATCH 012/119] Improve order types documentation --- docs/configuration.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 080ddd046..56ba13414 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -314,22 +314,21 @@ Configuration: } ``` -!!! Note +!!! Note "Market order support" 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."` + `"Exchange does not support market orders."` and the bot will refuse to start. -!!! Note - Stoploss on exchange interval is not mandatory. Do not change its value if you are +!!! Warning "Using market orders" + Please carefully read the section [Market order pricing](#market-order-pricing) section when using market orders. + +!!! Note "Stoploss on exchange" + `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 refer to [the stoploss documentation](stoploss.md). -!!! Note If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order. -!!! Warning "Using market orders" - Please read the section [Market order pricing](#market-order-pricing) section when using market orders. - !!! Warning "Warning: stoploss_on_exchange failures" If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however this is not advised. From 5ed85963a950eada9571b2f6f19130ff15878157 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 10:39:49 +0100 Subject: [PATCH 013/119] Allow forcebuy price to be a string by converting it to float fix #3970 --- freqtrade/rpc/api_server.py | 2 ++ freqtrade/rpc/rpc.py | 2 +- tests/rpc/test_rpc.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 7f4773d57..384d7c6c2 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -508,6 +508,8 @@ class ApiServer(RPC): """ asset = request.json.get("pair") price = request.json.get("price", None) + price = float(price) if price is not None else price + trade = self._rpc_forcebuy(asset, price) if trade: return jsonify(trade.to_json()) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 90564a19d..e608a2274 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -524,7 +524,7 @@ class RPC: stake_currency = self._freqtrade.config.get('stake_currency') if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: raise RPCException( - f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only') + f'Wrong pair selected. Only pairs with stake-currency {stake_currency} allowed.') # check if valid pair # check if pair already has an open pair diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 23ca53e53..47e0f763d 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -868,7 +868,8 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> assert trade.open_rate == 0.0001 # Test buy pair not with stakes - with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'): + with pytest.raises(RPCException, + match=r'Wrong pair selected. Only pairs with stake-currency.*'): rpc._rpc_forcebuy('LTC/ETH', 0.0001) pair = 'XRP/BTC' From 83861fabdea798100c5322a9efe5db4f7a23c84a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 10:52:15 +0100 Subject: [PATCH 014/119] Fix #3967, move TradeList type to constants --- freqtrade/constants.py | 3 +++ freqtrade/data/converter.py | 10 +++++---- freqtrade/data/history/hdf5datahandler.py | 4 ++-- freqtrade/data/history/idatahandler.py | 5 +---- freqtrade/data/history/jsondatahandler.py | 4 ++-- tests/data/test_converter.py | 27 ++++++++++++++++++++++- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index dc5384f6f..3271dda39 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -365,3 +365,6 @@ CANCEL_REASON = { # List of pairs with their timeframes PairWithTimeframe = Tuple[str, str] ListPairsWithTimeframes = List[PairWithTimeframe] + +# Type for trades list +TradeList = List[List] diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 38fa670e9..09930950a 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -10,7 +10,7 @@ from typing import Any, Dict, List import pandas as pd from pandas import DataFrame, to_datetime -from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS +from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList logger = logging.getLogger(__name__) @@ -168,7 +168,7 @@ def trades_remove_duplicates(trades: List[List]) -> List[List]: return [i for i, _ in itertools.groupby(sorted(trades, key=itemgetter(0)))] -def trades_dict_to_list(trades: List[Dict]) -> List[List]: +def trades_dict_to_list(trades: List[Dict]) -> TradeList: """ Convert fetch_trades result into a List (to be more memory efficient). :param trades: List of trades, as returned by ccxt.fetch_trades. @@ -177,16 +177,18 @@ def trades_dict_to_list(trades: List[Dict]) -> List[List]: return [[t[col] for col in DEFAULT_TRADES_COLUMNS] for t in trades] -def trades_to_ohlcv(trades: List, timeframe: str) -> DataFrame: +def trades_to_ohlcv(trades: TradeList, timeframe: str) -> DataFrame: """ Converts trades list to OHLCV list - TODO: This should get a dedicated test :param trades: List of trades, as returned by ccxt.fetch_trades. :param timeframe: Timeframe to resample data to :return: OHLCV Dataframe. + :raises: Valueerror if no trades are provided """ from freqtrade.exchange import timeframe_to_minutes timeframe_minutes = timeframe_to_minutes(timeframe) + if not trades: + raise ValueError('Trade-list empty.') df = pd.DataFrame(trades, columns=DEFAULT_TRADES_COLUMNS) df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True,) diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py index 00e41673d..d116637e7 100644 --- a/freqtrade/data/history/hdf5datahandler.py +++ b/freqtrade/data/history/hdf5datahandler.py @@ -9,9 +9,9 @@ import pandas as pd from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, - ListPairsWithTimeframes) + ListPairsWithTimeframes, TradeList) -from .idatahandler import IDataHandler, TradeList +from .idatahandler import IDataHandler logger = logging.getLogger(__name__) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index a170a9dc5..070d9039d 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -13,16 +13,13 @@ from typing import List, Optional, Type from pandas import DataFrame from freqtrade.configuration import TimeRange -from freqtrade.constants import ListPairsWithTimeframes +from freqtrade.constants import ListPairsWithTimeframes, TradeList from freqtrade.data.converter import clean_ohlcv_dataframe, trades_remove_duplicates, trim_dataframe from freqtrade.exchange import timeframe_to_seconds logger = logging.getLogger(__name__) -# Type for trades list -TradeList = List[List] - class IDataHandler(ABC): diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py index 6436aa13d..9122170d5 100644 --- a/freqtrade/data/history/jsondatahandler.py +++ b/freqtrade/data/history/jsondatahandler.py @@ -8,10 +8,10 @@ from pandas import DataFrame, read_json, to_datetime from freqtrade import misc from freqtrade.configuration import TimeRange -from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes +from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes, TradeList from freqtrade.data.converter import trades_dict_to_list -from .idatahandler import IDataHandler, TradeList +from .idatahandler import IDataHandler logger = logging.getLogger(__name__) diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index fdba7900f..4fdcce4d2 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -1,10 +1,13 @@ # pragma pylint: disable=missing-docstring, C0103 import logging +import pytest + from freqtrade.configuration.timerange import TimeRange from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format, ohlcv_fill_up_missing_data, ohlcv_to_dataframe, - trades_dict_to_list, trades_remove_duplicates, trim_dataframe) + trades_dict_to_list, trades_remove_duplicates, + trades_to_ohlcv, trim_dataframe) from freqtrade.data.history import (get_timerange, load_data, load_pair_history, validate_backtest_data) from tests.conftest import log_has @@ -26,6 +29,28 @@ def test_ohlcv_to_dataframe(ohlcv_history_list, caplog): assert log_has('Converting candle (OHLCV) data to dataframe for pair UNITTEST/BTC.', caplog) +def test_trades_to_ohlcv(ohlcv_history_list, caplog): + + caplog.set_level(logging.DEBUG) + with pytest.raises(ValueError, match="Trade-list empty."): + trades_to_ohlcv([], '1m') + + trades = [ + [1570752011620, "13519807", None, "sell", 0.00141342, 23.0, 0.03250866], + [1570752011620, "13519808", None, "sell", 0.00141266, 54.0, 0.07628364], + [1570752017964, "13519809", None, "sell", 0.00141266, 8.0, 0.01130128]] + + df = trades_to_ohlcv(trades, '1m') + assert not df.empty + assert len(df) == 1 + assert 'open' in df.columns + assert 'high' in df.columns + assert 'low' in df.columns + assert 'close' in df.columns + assert df.loc[:, 'high'][0] == 0.00141342 + assert df.loc[:, 'low'][0] == 0.00141266 + + def test_ohlcv_fill_up_missing_data(testdatadir, caplog): data = load_pair_history(datadir=testdatadir, timeframe='1m', From e8e3ca0c3c114637a460fdddeea5fca155bdf534 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 10:57:19 +0100 Subject: [PATCH 015/119] Catch ValueError from trade_conversion closes #3967 --- freqtrade/data/converter.py | 2 +- freqtrade/data/history/history_utils.py | 9 ++++++--- tests/data/test_history.py | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 09930950a..d4053abaa 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -183,7 +183,7 @@ def trades_to_ohlcv(trades: TradeList, timeframe: str) -> DataFrame: :param trades: List of trades, as returned by ccxt.fetch_trades. :param timeframe: Timeframe to resample data to :return: OHLCV Dataframe. - :raises: Valueerror if no trades are provided + :raises: ValueError if no trades are provided """ from freqtrade.exchange import timeframe_to_minutes timeframe_minutes = timeframe_to_minutes(timeframe) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index a420b9dcc..17b510b92 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -356,9 +356,12 @@ def convert_trades_to_ohlcv(pairs: List[str], timeframes: List[str], if erase: if data_handler_ohlcv.ohlcv_purge(pair, timeframe): logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.') - ohlcv = trades_to_ohlcv(trades, timeframe) - # Store ohlcv - data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv) + try: + ohlcv = trades_to_ohlcv(trades, timeframe) + # Store ohlcv + data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv) + except ValueError: + logger.exception(f'Could not convert {pair} to OHLCV.') def get_timerange(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 538a0840f..905798041 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -620,6 +620,12 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog): _clean_test_file(file1) _clean_test_file(file5) + assert not log_has('Could not convert NoDatapair to OHLCV.', caplog) + + convert_trades_to_ohlcv(['NoDatapair'], timeframes=['1m', '5m'], + datadir=testdatadir, timerange=tr, erase=True) + assert log_has('Could not convert NoDatapair to OHLCV.', caplog) + def test_datahandler_ohlcv_get_pairs(testdatadir): pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m') From 89ea8dbef2a73c050649d45db6185de8fb8cf789 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 11:13:44 +0100 Subject: [PATCH 016/119] Update slack invite --- CONTRIBUTING.md | 3 +-- README.md | 2 +- docs/developer.md | 2 +- docs/faq.md | 2 +- docs/index.md | 2 +- docs/strategy-customization.md | 2 -- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 399588f88..6b4e8adaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,8 +12,7 @@ Few pointers for contributions: - New features need to contain unit tests, must conform to PEP8 (max-line-length = 100) and should be documented with the introduction PR. - PR's can be declared as `[WIP]` - which signify Work in Progress Pull Requests (which are not finished). -If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) -or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR. +If you are unsure, discuss the feature on our [discord server](https://discord.gg/MA9v74M), on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR. ## Getting started diff --git a/README.md b/README.md index c9f4d0a52..4daa1854a 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ to understand the requirements before sending your pull-requests. Coding is not a necessity to contribute - maybe start with improving our documentation? Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase. -**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it. +**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/MA9v74M) or [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it. **Important:** Always create your PR against the `develop` branch, not `stable`. diff --git a/docs/developer.md b/docs/developer.md index 8ef816d5d..c253f4460 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -2,7 +2,7 @@ This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running. -All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) where you can ask questions. +All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel on [discord](https://discord.gg/MA9v74M) or [slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) where you can ask questions. ## Documentation diff --git a/docs/faq.md b/docs/faq.md index a775060de..aa33218fb 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -145,7 +145,7 @@ freqtrade hyperopt --hyperop SampleHyperopt --hyperopt-loss SharpeHyperOptLossDa ### Why does it take a long time to run hyperopt? -* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you. +* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you. * If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers: diff --git a/docs/index.md b/docs/index.md index 5608587db..c6697d165 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,7 +63,7 @@ Alternatively For any questions not covered by the documentation or for further information about the bot, we encourage you to join our passionate Slack community. -Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) to join the Freqtrade Slack channel. +Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) to join the Freqtrade Slack channel. Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M). diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 6c7d78864..db007985f 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -770,8 +770,6 @@ To get additional Ideas for strategies, head over to our [strategy repository](h Feel free to use any of them as inspiration for your own strategies. We're happy to accept Pull Requests containing new Strategies to that repo. -We also got a *strategy-sharing* channel in our [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) which is a great place to get and/or share ideas. - ## Next step Now you have a perfect strategy you probably want to backtest it. From 4d60a4cf4e9a850a1c03ac2923e163b6969564fd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 11:32:46 +0100 Subject: [PATCH 017/119] Add warning to StochRSI in sample strategy closes #2961 --- freqtrade/templates/sample_strategy.py | 2 ++ freqtrade/templates/subtemplates/indicators_full.j2 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 44590dbbe..b3f9fef07 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -184,6 +184,8 @@ class SampleStrategy(IStrategy): dataframe['fastk'] = stoch_fast['fastk'] # # Stochastic RSI + # Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this. + # STOCHRSI is NOT aligned with tradingview, which may result in non-expected results. # stoch_rsi = ta.STOCHRSI(dataframe) # dataframe['fastd_rsi'] = stoch_rsi['fastd'] # dataframe['fastk_rsi'] = stoch_rsi['fastk'] diff --git a/freqtrade/templates/subtemplates/indicators_full.j2 b/freqtrade/templates/subtemplates/indicators_full.j2 index 60a358bec..57d2ca665 100644 --- a/freqtrade/templates/subtemplates/indicators_full.j2 +++ b/freqtrade/templates/subtemplates/indicators_full.j2 @@ -62,6 +62,8 @@ dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] # # Stochastic RSI +# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this. +# STOCHRSI is NOT aligned with tradingview, which may result in non-expected results. # stoch_rsi = ta.STOCHRSI(dataframe) # dataframe['fastd_rsi'] = stoch_rsi['fastd'] # dataframe['fastk_rsi'] = stoch_rsi['fastk'] From 73f0e6e704fc85c57f0b7d982a1ff0df0fae7fa5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 11:40:28 +0100 Subject: [PATCH 018/119] Improve wording for discord server fix link to correct docker install guide --- README.md | 10 ++++------ docs/index.md | 13 +++++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4daa1854a..8526b5c91 100644 --- a/README.md +++ b/README.md @@ -132,15 +132,13 @@ The project is currently setup in two main branches: ## Support -### Help / Slack / Discord +### Help / Discord / 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, or to simply engage with like-minded individuals, we encourage you to join our slack channel. -- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE). +Please check out our [discord server](https://discord.gg/MA9v74M). -Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M). - -*Note*: Since the discord server is relatively new, answers to questions might be slightly delayed as currently the user base quite small. +You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). ### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue) diff --git a/docs/index.md b/docs/index.md index c6697d165..f63aeb6b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -59,17 +59,14 @@ Alternatively ## Support -### Help / Slack / Discord +### Help / Discord / Slack -For any questions not covered by the documentation or for further information about the bot, we encourage you to join our passionate Slack community. +For any questions not covered by the documentation or for further information about the bot, or to simply engage with like-minded individuals, we encourage you to join our slack channel. -Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) to join the Freqtrade Slack channel. +Please check out our [discord server](https://discord.gg/MA9v74M). -Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M). - -!!! Note - Since the discord server is relatively new, answers to questions might be slightly delayed as currently the user base quite small. +You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). ## Ready to try? -Begin by reading our installation guide [for docker](docker.md) (recommended), or for [installation without docker](installation.md). +Begin by reading our installation guide [for docker](docker_quickstart.md) (recommended), or for [installation without docker](installation.md). From fb86d8f8ff43646ded784c1ebace16cc1e8fd616 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:28:50 +0100 Subject: [PATCH 019/119] Add get_historic_ohlcv_as_df to support VolatilityFilter --- freqtrade/exchange/exchange.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2bbdb0d59..2f52c512f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -679,12 +679,25 @@ class Exchange: :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from - :returns List with candle (OHLCV) data + :return: List with candle (OHLCV) data """ return asyncio.get_event_loop().run_until_complete( self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, since_ms=since_ms)) + def get_historic_ohlcv_as_df(self, pair: str, timeframe: str, + since_ms: int) -> DataFrame: + """ + Minimal wrapper around get_historic_ohlcv - converting the result into a dataframe + :param pair: Pair to download + :param timeframe: Timeframe to get data for + :param since_ms: Timestamp in milliseconds to get history from + :return: OHLCV DataFrame + """ + ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms) + return ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True, + drop_incomplete=self._ohlcv_partial_candle) + async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int) -> List: From 109824c9a80cb78c7c4ec9d6f90cb1c8c3afa640 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:29:11 +0100 Subject: [PATCH 020/119] Add VolatilityFilter --- freqtrade/constants.py | 2 +- freqtrade/pairlist/AgeFilter.py | 2 +- freqtrade/pairlist/volatilityfilter.py | 89 ++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 freqtrade/pairlist/volatilityfilter.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 3271dda39..55d802587 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', - 'ShuffleFilter', 'SpreadFilter'] + 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py index 19cf1c090..352fff082 100644 --- a/freqtrade/pairlist/AgeFilter.py +++ b/freqtrade/pairlist/AgeFilter.py @@ -49,7 +49,7 @@ class AgeFilter(IPairList): return (f"{self.name} - Filtering pairs with age less than " f"{self._min_days_listed} {plural(self._min_days_listed, 'day')}.") - def _validate_pair(self, ticker: dict) -> bool: + def _validate_pair(self, ticker: Dict) -> bool: """ Validate age for the ticker :param ticker: ticker dict as returned from ccxt.load_markets() diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py new file mode 100644 index 000000000..6039f6f69 --- /dev/null +++ b/freqtrade/pairlist/volatilityfilter.py @@ -0,0 +1,89 @@ +""" +Minimum age (days listed) pair list filter +""" +import logging +from typing import Any, Dict + +import arrow +from cachetools.ttl import TTLCache + +from freqtrade.exceptions import OperationalException +from freqtrade.misc import plural +from freqtrade.pairlist.IPairList import IPairList + + +logger = logging.getLogger(__name__) + + +class VolatilityFilter(IPairList): + + def __init__(self, exchange, pairlistmanager, + config: Dict[str, Any], pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + self._days = pairlistconfig.get('volatility_over_days', 10) + self._min_volatility = pairlistconfig.get('min_volatility', 0.01) + self._refresh_period = pairlistconfig.get('refresh_period', 1440) + + self._pair_cache: TTLCache = TTLCache(maxsize=100, ttl=self._refresh_period) + + if self._days < 1: + raise OperationalException("VolatilityFilter requires min_days_listed to be >= 1") + if self._days > exchange.ohlcv_candle_limit: + raise OperationalException("VolatilityFilter requires min_days_listed to not exceed " + "exchange max request size " + f"({exchange.ohlcv_candle_limit})") + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return True + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return (f"{self.name} - Filtering pairs with volatility below {self._min_volatility} " + f"over the last {plural(self._days, 'day')}.") + + def _validate_pair(self, ticker: Dict) -> bool: + """ + Validate volatility + :param ticker: ticker dict as returned from ccxt.load_markets() + :return: True if the pair can stay, False if it should be removed + """ + pair = ticker['symbol'] + # Check symbol in cache + if pair in self._pair_cache: + return self._pair_cache[pair] + + since_ms = int(arrow.utcnow() + .floor('day') + .shift(days=-self._days) + .float_timestamp) * 1000 + + daily_candles = self._exchange.get_historic_ohlcv_as_df(pair=pair, + timeframe='1d', + since_ms=since_ms) + result = False + if daily_candles is not None: + highest_high = daily_candles['high'].max() + lowest_low = daily_candles['low'].min() + pct_change = (highest_high - lowest_low) / lowest_low + if pct_change >= self._min_volatility: + result = True + else: + self.log_on_refresh(logger.info, + f"Removed {pair} from whitelist, " + f"because volatility over {plural(self._days, 'day')} is " + f"{pct_change:.3f}, which is below the " + f"threshold of {self._min_volatility}.") + result = False + self._pair_cache[pair] = result + + return result From 191616e4e5cbb558f48ec39e67bf5399fbf87da5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:39:04 +0100 Subject: [PATCH 021/119] Add first tests for volatilityFilter --- tests/pairlist/test_pairlist.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 1f05bef1e..3e1cca30c 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -340,6 +340,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): ([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.02}], "USDT", ['ETH/USDT', 'NANO/USDT']), + ([{"method": "StaticPairList"}, + {"method": "VolatilityFilter", "volatility_over_days": 10, + "min_volatility": 0.01, "refresh_period": 1440}], + "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history_list, pairlists, base_currency, @@ -617,6 +621,11 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his None, "PriceFilter requires max_price to be >= 0" ), # OperationalException expected + ({"method": "VolatilityFilter", "volatility_over_days": 10, "min_volatility": 0.01}, + "[{'VolatilityFilter': 'VolatilityFilter - Filtering pairs with volatility below 0.01 " + "over the last days.'}]", + None + ), ]) def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, desc_expected, exception_expected): From 6b672cd0b95f8a35fe83dab95e6b931e6b85c51d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:43:29 +0100 Subject: [PATCH 022/119] Document volatilityFilter --- docs/includes/pairlists.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index e6a9fc1a8..301a5453d 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -19,6 +19,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac * [`PriceFilter`](#pricefilter) * [`ShuffleFilter`](#shufflefilter) * [`SpreadFilter`](#spreadfilter) +* [`VolatilityFilter`](#volatilityfilter) !!! Tip "Testing pairlists" Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility sub-command to test your configuration quickly. @@ -118,6 +119,27 @@ Example: If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005` and this pair will be filtered out. +#### VolatilityFilter + +Removes pairs where the difference between lowest low and highest high over `volatility_over_days` days is below `min_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. + +In the below example: +If volatility over the last 10 days is <1%, remove the pair from the whitelist. + +```json +"pairlists": [ + { + "method": "VolatilityFilter", + "volatility_over_days": 10, + "min_volatility": 0.01, + "refresh_period": 1440 + } +] +``` + +!!! Tip + This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely hard to trade with profit. + ### Full example of Pairlist Handlers The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies both [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#price-filter), filtering all assets where 1 price unit is > 1%. Then the `SpreadFilter` is applied and pairs are finally shuffled with the random seed set to some predefined value. @@ -137,6 +159,12 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, + { + "method": "VolatilityFilter", + "volatility_over_days": 10, + "min_volatility": 0.01, + "refresh_period": 1440 + }, {"method": "ShuffleFilter", "seed": 42} ], ``` From f8fab5c4f8d120b7838cac24c6a0c7d30df85fc2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:51:39 +0100 Subject: [PATCH 023/119] Add tests for failure cases --- freqtrade/pairlist/volatilityfilter.py | 4 ++-- tests/pairlist/test_pairlist.py | 33 ++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py index 6039f6f69..e9ca61794 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/volatilityfilter.py @@ -29,9 +29,9 @@ class VolatilityFilter(IPairList): self._pair_cache: TTLCache = TTLCache(maxsize=100, ttl=self._refresh_period) if self._days < 1: - raise OperationalException("VolatilityFilter requires min_days_listed to be >= 1") + raise OperationalException("VolatilityFilter requires volatility_over_days to be >= 1") if self._days > exchange.ohlcv_candle_limit: - raise OperationalException("VolatilityFilter requires min_days_listed to not exceed " + raise OperationalException("VolatilityFilter requires volatility_over_days to not exceed " "exchange max request size " f"({exchange.ohlcv_candle_limit})") diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 3e1cca30c..5bbb233b4 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -58,7 +58,7 @@ def whitelist_conf_2(default_conf): @pytest.fixture(scope="function") -def whitelist_conf_3(default_conf): +def whitelist_conf_agefilter(default_conf): default_conf['stake_currency'] = 'BTC' default_conf['exchange']['pair_whitelist'] = [ 'ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC', @@ -532,7 +532,7 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf -def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers, caplog): +def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': -1}] @@ -547,7 +547,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick get_patched_freqtradebot(mocker, default_conf) -def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers, caplog): +def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': 99999}] @@ -563,7 +563,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick get_patched_freqtradebot(mocker, default_conf) -def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_history_list): +def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history_list): mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -575,7 +575,7 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), ) - freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_3) + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter) assert freqtrade.exchange.get_historic_ohlcv.call_count == 0 freqtrade.pairlists.refresh_pairlist() assert freqtrade.exchange.get_historic_ohlcv.call_count > 0 @@ -586,6 +586,29 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count +def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): + default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, + {'method': 'VolatilityFilter', 'volatility_over_days': 99999}] + + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) + + with pytest.raises(OperationalException, + match=r'VolatilityFilter requires volatility_over_days to not exceed ' + r'exchange max request size \([0-9]+\)'): + get_patched_freqtradebot(mocker, default_conf) + + default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, + {'method': 'VolatilityFilter', 'volatility_over_days': 0}] + + with pytest.raises(OperationalException, + match='VolatilityFilter requires volatility_over_days to be >= 1'): + get_patched_freqtradebot(mocker, default_conf) + + @pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [ ({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010, "max_price": 1.0}, From 2e1551a2ebce9cd9d288ba03a019778ff758b7ad Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 16:01:52 +0100 Subject: [PATCH 024/119] Improve tests of volatilityfilter --- docs/includes/pairlists.md | 2 +- freqtrade/pairlist/volatilityfilter.py | 4 ++-- tests/pairlist/test_pairlist.py | 33 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 301a5453d..7cd2369b1 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -138,7 +138,7 @@ If volatility over the last 10 days is <1%, remove the pair from the whitelist. ``` !!! Tip - This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely hard to trade with profit. + This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely difficult to trade with profit. ### Full example of Pairlist Handlers diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py index e9ca61794..415b6e89e 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/volatilityfilter.py @@ -31,8 +31,8 @@ class VolatilityFilter(IPairList): if self._days < 1: raise OperationalException("VolatilityFilter requires volatility_over_days to be >= 1") if self._days > exchange.ohlcv_candle_limit: - raise OperationalException("VolatilityFilter requires volatility_over_days to not exceed " - "exchange max request size " + raise OperationalException("VolatilityFilter requires volatility_over_days to not " + "exceed exchange max request size " f"({exchange.ohlcv_candle_limit})") @property diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 5bbb233b4..e9df5d3f4 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -609,6 +609,39 @@ def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): get_patched_freqtradebot(mocker, default_conf) +@pytest.mark.parametrize('min_volatility,expected_length', [ + (0.01, 5), + (0.05, 0), # Setting volatility to 5% removes all pairs from the whitelist. +]) +def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, + min_volatility, expected_length): + default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, + {'method': 'VolatilityFilter', 'volatility_over_days': 2, + 'min_volatility': min_volatility}] + + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), + ) + + freqtrade = get_patched_freqtradebot(mocker, default_conf) + assert freqtrade.exchange.get_historic_ohlcv.call_count == 0 + freqtrade.pairlists.refresh_pairlist() + assert len(freqtrade.pairlists.whitelist) == expected_length + assert freqtrade.exchange.get_historic_ohlcv.call_count > 0 + + previous_call_count = freqtrade.exchange.get_historic_ohlcv.call_count + freqtrade.pairlists.refresh_pairlist() + assert len(freqtrade.pairlists.whitelist) == expected_length + # Should not have increased since first call. + assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count + + @pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [ ({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010, "max_price": 1.0}, From f12a8afd4151d6a2f069f5375291dc57e6b862b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 10:56:19 +0100 Subject: [PATCH 025/119] Add test for ohlcv_as_df --- tests/exchange/test_exchange.py | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index e4452a83c..42681b367 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1307,6 +1307,57 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): assert log_has_re(r"Async code raised an exception: .*", caplog) +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + ohlcv = [ + [ + arrow.utcnow().int_timestamp * 1000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ], + [ + arrow.utcnow().shift(minutes=5).int_timestamp * 1000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ], + [ + arrow.utcnow().shift(minutes=10).int_timestamp * 1000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ] + ] + pair = 'ETH/BTC' + + async def mock_candle_hist(pair, timeframe, since_ms): + return pair, timeframe, ohlcv + + exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) + # one_call calculation * 1.8 should do 2 calls + + since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8 + ret = exchange.get_historic_ohlcv_as_df(pair, "5m", int(( + arrow.utcnow().int_timestamp - since) * 1000)) + + assert exchange._async_get_candle_history.call_count == 2 + # Returns twice the above OHLCV data + assert len(ret) == 2 + assert isinstance(ret, DataFrame) + assert 'date' in ret.columns + assert 'open' in ret.columns + assert 'close' in ret.columns + assert 'high' in ret.columns + + def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: ohlcv = [ [ From 7e4fe23bf94128fa1df7477011d65d6d3ff2afd8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 11:08:01 +0100 Subject: [PATCH 026/119] Add VolatilityFilter to full config --- config_full.json.example | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config_full.json.example b/config_full.json.example index 45c5c695c..0d82b9a2b 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -67,7 +67,13 @@ {"method": "AgeFilter", "min_days_listed": 10}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.01, "min_price": 0.00000010}, - {"method": "SpreadFilter", "max_spread_ratio": 0.005} + {"method": "SpreadFilter", "max_spread_ratio": 0.005}, + { + "method": "VolatilityFilter", + "volatility_over_days": 10, + "min_volatility": 0.01, + "refresh_period": 1440 + } ], "exchange": { "name": "bittrex", From 29c6a9263de13b3a480662d1c59b203512df8bd3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 15:50:44 +0100 Subject: [PATCH 027/119] Protect against 0 values --- freqtrade/pairlist/volatilityfilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py index 415b6e89e..14ac0c617 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/volatilityfilter.py @@ -74,7 +74,7 @@ class VolatilityFilter(IPairList): if daily_candles is not None: highest_high = daily_candles['high'].max() lowest_low = daily_candles['low'].min() - pct_change = (highest_high - lowest_low) / lowest_low + pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 if pct_change >= self._min_volatility: result = True else: From ec330112552afc3902bef12b6ea3884a39a16193 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:43 +0000 Subject: [PATCH 028/119] Bump ccxt from 1.37.69 to 1.38.13 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.37.69 to 1.38.13. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.37.69...1.38.13) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67b69a5dd..7f2ac98b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.4 pandas==1.1.4 -ccxt==1.37.69 +ccxt==1.38.13 aiohttp==3.7.2 SQLAlchemy==1.3.20 python-telegram-bot==13.0 From 83b4cd7b39f1ea2eba4d820043f47b7a42c96fd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:51 +0000 Subject: [PATCH 029/119] Bump mkdocs-material from 6.1.5 to 6.1.6 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.5 to 6.1.6. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.5...6.1.6) Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index cd294bef6..87bc6dfdd 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,3 +1,3 @@ -mkdocs-material==6.1.5 +mkdocs-material==6.1.6 mdx_truly_sane_lists==1.2 pymdown-extensions==8.0.1 From be4807d85c24c719f87a3ee43d13012d5988de41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:52 +0000 Subject: [PATCH 030/119] Bump questionary from 1.8.0 to 1.8.1 Bumps [questionary](https://github.com/tmbo/questionary) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/tmbo/questionary/releases) - [Commits](https://github.com/tmbo/questionary/compare/1.8.0...1.8.1) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67b69a5dd..66fa04824 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,5 +35,5 @@ flask-cors==3.0.9 # Support for colorized terminal output colorama==0.4.4 # Building config files interactively -questionary==1.8.0 +questionary==1.8.1 prompt-toolkit==3.0.8 From 7c7a8190abf5548ba54cc54141929668492ac56b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:54 +0000 Subject: [PATCH 031/119] Bump python-rapidjson from 0.9.3 to 0.9.4 Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 0.9.3 to 0.9.4. - [Release notes](https://github.com/python-rapidjson/python-rapidjson/releases) - [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst) - [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v0.9.3...v0.9.4) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67b69a5dd..487b19159 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ blosc==1.9.2 py_find_1st==1.1.4 # Load ticker files 30% faster -python-rapidjson==0.9.3 +python-rapidjson==0.9.4 # Notify systemd sdnotify==0.3.2 From 56629d882e1d5fc39c00de14f5995af9ce16ce6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:51:03 +0000 Subject: [PATCH 032/119] Bump coveralls from 2.1.2 to 2.2.0 Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 2.1.2 to 2.2.0. - [Release notes](https://github.com/coveralls-clients/coveralls-python/releases) - [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md) - [Commits](https://github.com/coveralls-clients/coveralls-python/compare/2.1.2...2.2.0) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1c96a880a..e681274c8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ -r requirements-plot.txt -r requirements-hyperopt.txt -coveralls==2.1.2 +coveralls==2.2.0 flake8==3.8.4 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 From 1ec99e6b76010c0f0471d009b64e7f3bef0c433d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 08:05:45 +0000 Subject: [PATCH 033/119] Bump aiohttp from 3.7.2 to 3.7.3 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.7.2 to 3.7.3. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.7.2...v3.7.3) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fa3024e30..7490688d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.19.4 pandas==1.1.4 ccxt==1.38.13 -aiohttp==3.7.2 +aiohttp==3.7.3 SQLAlchemy==1.3.20 python-telegram-bot==13.0 arrow==0.17.0 From 312533fded2c794798a860b3ad523aec6f10ecc1 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Mon, 23 Nov 2020 22:08:53 -0600 Subject: [PATCH 034/119] Match current dev file --- docs/installation.md | 249 +++++++++++++++++++------------------------ 1 file changed, 111 insertions(+), 138 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 35cdcda62..ec2d27174 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,6 +2,8 @@ This page explains how to prepare your environment for running the bot. +Please consider using the prebuilt [docker images](docker.md) to get started quickly while trying out freqtrade evaluating how it operates. + ## Prerequisite ### Requirements @@ -11,70 +13,78 @@ Click each one for install guide: * [Python >= 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/) * [pip](https://pip.pypa.io/en/stable/installing/) * [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -* [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended) +* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended) * [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) (install instructions below) -### API keys + We also recommend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot), which is optional but recommended. -Before running your bot in production you will need to setup few -external API. In production mode, the bot will require valid Exchange API -credentials. We also recommend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot) (optional but recommended). - -### Setup your exchange account - -You will need to create API Keys (Usually you get `key` and `secret`) from the Exchange website and insert this into the appropriate fields in the configuration or when asked by the installation script. +!!! Warning "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. ## Quick start -Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot. - -!!! Note - Python3.6 or higher and the corresponding pip are assumed to be available. The install-script will warn and stop if that's not the case. - -```bash -git clone git@github.com:freqtrade/freqtrade.git -cd freqtrade -git checkout develop -./setup.sh --install -``` +Freqtrade provides the Linux/MacOS Easy Installation script to install all dependencies and help you configure the bot. !!! Note Windows installation is explained [here](#windows). -## Easy Installation - Linux Script +The easiest way to install and run Freqtrade is to clone the bot Github repository and then run the Easy Installation script, if it's available for your platform. -If you are on Debian, Ubuntu or MacOS freqtrade provides a script to Install, Update, Configure, and Reset your bot. +!!! Note "Version considerations" + When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). + +!!! Note + Python3.6 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository. + +This can be achieved with the following commands: + +```bash +git clone https://github.com/freqtrade/freqtrade.git +cd freqtrade +# git checkout stable # Optional, see (1) +./setup.sh --install +``` + +(1) This command switches the cloned repository to the use of the `stable` branch. It's not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout stable`/`git checkout develop` commands. + +## Easy Installation Script (Linux/MacOS) + +If you are on Debian, Ubuntu or MacOS Freqtrade provides the script to install, update, configure and reset the codebase of your bot. ```bash $ ./setup.sh usage: -i,--install Install freqtrade from scratch -u,--update Command git pull to update. - -r,--reset Hard reset your develop/master branch. + -r,--reset Hard reset your develop/stable branch. -c,--config Easy config generator (Will override your existing file). ``` ** --install ** -This script will install everything you need to run the bot: +With this option, the script will install the bot and most dependencies: +You will need to have git and python3.6+ installed beforehand for this to work. * Mandatory software as: `ta-lib` -* Setup your virtualenv -* Configure your `config.json` file +* Setup your virtualenv under `.env/` -This script is a combination of `install script` `--reset`, `--config` +This option is a combination of installation tasks, `--reset` and `--config`. ** --update ** -Update parameter will pull the last version of your current branch and update your virtualenv. +This option will pull the last version of your current branch and update your virtualenv. Run the script with this option periodically to update your bot. ** --reset ** -Reset parameter will hard reset your branch (only if you are on `master` or `develop`) and recreate your virtualenv. +This option will hard reset your branch (only if you are on either `stable` or `develop`) and recreate your virtualenv. ** --config ** -Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`. +DEPRECATED - use `freqtrade new-config -c config.json` instead. + +### Activate your virtual environment + +Each time you open a new terminal, you must run `source .env/bin/activate`. ------ @@ -86,40 +96,50 @@ OS Specific steps are listed first, the [Common](#common) section below is neces !!! Note Python3.6 or higher and the corresponding pip are assumed to be available. -### Linux - Ubuntu 16.04 +=== "Ubuntu 16.04" + #### Install necessary dependencies -#### Install necessary dependencies + ```bash + sudo apt-get update + sudo apt-get install build-essential git + ``` -```bash -sudo apt-get update -sudo apt-get install build-essential git -``` +=== "RaspberryPi/Raspbian" + The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/) from at least September 2019. + This image comes with python3.7 preinstalled, making it easy to get freqtrade up and running. -#### Raspberry Pi / Raspbian + Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied. -Before installing FreqTrade on a Raspberry Pi running the official Raspbian Image, make sure you have at least Python 3.6 installed. The default image only provides Python 3.5. Probably the easiest way to get a recent version of python is [miniconda](https://repo.continuum.io/miniconda/). + ``` bash + sudo apt-get install python3-venv libatlas-base-dev + git clone https://github.com/freqtrade/freqtrade.git + cd freqtrade -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. + bash setup.sh -i + ``` -Additional package to install on your Raspbian, `libffi-dev` required by cryptography (from python-telegram-bot). + !!! Note "Installation duration" + Depending on your internet speed and the Raspberry Pi version, installation can take multiple hours to complete. -``` bash -conda config --add channels rpi -conda install python=3.6 -conda create -n freqtrade python=3.6 -conda activate freqtrade -conda install scipy pandas numpy - -sudo apt install libffi-dev -python3 -m pip install -r requirements-common.txt -python3 -m pip install -e . -``` + !!! Note + The above does not install hyperopt dependencies. To install these, please use `python3 -m pip install -e .[hyperopt]`. + We do not advise to run hyperopt on a Raspberry Pi, since this is a very resource-heavy operation, which should be done on powerful machine. ### Common #### 1. Install TA-Lib +Use the provided ta-lib installation script + +```bash +sudo ./build_helpers/install_ta-lib.sh +``` + +!!! Note + This will use the ta-lib tar.gz included in this repository. + +##### TA-Lib manual installation + Official webpage: https://mrjbq7.github.io/ta-lib/install.html ```bash @@ -147,126 +167,79 @@ python3 -m venv .env source .env/bin/activate ``` -#### 3. Install FreqTrade +#### 3. Install Freqtrade Clone the git repository: ```bash git clone https://github.com/freqtrade/freqtrade.git - -``` - -Optionally checkout the master branch to get the latest stable release: - -```bash -git checkout master -``` - -#### 4. Initialize the configuration - -```bash cd freqtrade -cp config.json.example config.json +git checkout stable ``` -> *To edit the config please refer to [Bot Configuration](configuration.md).* - -#### 5. Install python dependencies +#### 4. Install python dependencies ``` bash python3 -m pip install --upgrade pip -pip install numpy -python3 -m pip install -r requirements.txt python3 -m pip install -e . ``` +#### 5. Initialize the configuration + +```bash +# Initialize the user_directory +freqtrade create-userdir --userdir user_data/ + +# Create a new configuration file +freqtrade new-config --config config.json +``` + +> *To edit the config please refer to [Bot Configuration](configuration.md).* + #### 6. Run the Bot 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 -freqtrade -c config.json +freqtrade trade -c config.json ``` *Note*: If you run the bot on a server, you should consider using [Docker](docker.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout. -#### 7. [Optional] Configure `freqtrade` as a `systemd` service +#### 7. (Optional) Post-installation Tasks -From the freqtrade repo... copy `freqtrade.service` to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup. - -After that you can start the daemon with: - -```bash -systemctl --user start freqtrade -``` - -For this to be persistent (run when user is logged out) you'll need to enable `linger` for your freqtrade user. - -```bash -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. +On Linux, as an optional post-installation task, you may wish to setup the bot to run as a `systemd` service or configure it to send the log messages to the `syslog`/`rsyslog` or `journald` daemons. See [Advanced Logging](advanced-setup.md#advanced-logging) for details. ------ -## Windows +### Anaconda -We recommend that Windows users use [Docker](docker.md) as this will work much easier and smoother (also more secure). +Freqtrade can also be installed using Anaconda (or Miniconda). -If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. -If that is not available on your system, feel free to try the instructions below, which led to success for some. - -### Install freqtrade manually - -#### Clone the git repository - -```bash -git clone https://github.com/freqtrade/freqtrade.git -``` - -#### Install ta-lib - -Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows). - -As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib‑0.4.17‑cp36‑cp36m‑win32.whl` (make sure to use the version matching your python version) - -```cmd ->cd \path\freqtrade-develop ->python -m venv .env ->cd .env\Scripts ->activate.bat ->cd \path\freqtrade-develop -REM optionally install ta-lib from wheel -REM >pip install TA_Lib‑0.4.17‑cp36‑cp36m‑win32.whl ->pip install -r requirements.txt ->pip install -e . ->python freqtrade\main.py -``` - -> Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/freqtrade/freqtrade/issues/222) - -#### Error during installation under Windows +!!! Note + This requires the [ta-lib](#1-install-ta-lib) C-library to be installed first. See below. ``` bash -error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools +conda env create -f environment.yml ``` -Unfortunately, many packages requiring compilation don't provide a pre-build wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. +----- +## Troubleshooting -The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first. +### MacOS installation error ---- +Newer versions of MacOS may have installation failed with errors like `error: command 'g++' failed with exit status 1`. + +This error will require explicit installation of the SDK Headers, which are not installed by default in this version of MacOS. +For MacOS 10.14, this can be accomplished with the below command. + +``` bash +open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg +``` + +If this file is inexistent, then you're probably on a different version of MacOS, so you may need to consult the internet for specific resolution details. + +----- Now you have an environment ready, the next step is -[Bot Configuration](configuration.md). +[Bot Configuration](configuration.md). \ No newline at end of file From 730c9ce4719ee257b62a149d2c807c5da43e07d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 06:57:11 +0100 Subject: [PATCH 035/119] Add Max_open_trades to summary metrics --- docs/backtesting.md | 9 +++++++-- freqtrade/optimize/optimize_reports.py | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 84911568b..277b11083 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -162,6 +162,8 @@ A backtesting result will look like that: |-----------------------+---------------------| | Backtesting from | 2019-01-01 00:00:00 | | Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | | Total trades | 429 | | First trade | 2019-01-01 18:30:00 | | First trade Pair | EOS/USDT | @@ -233,6 +235,8 @@ It contains some useful key metrics about performance of your strategy on backte |-----------------------+---------------------| | Backtesting from | 2019-01-01 00:00:00 | | Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | | Total trades | 429 | | First trade | 2019-01-01 18:30:00 | | First trade Pair | EOS/USDT | @@ -251,16 +255,17 @@ It contains some useful key metrics about performance of your strategy on backte ``` +- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). +- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - to clearly see settings for this. - `Total trades`: Identical to the total trades of the backtest output table. - `First trade`: First trade entered. - `First trade pair`: Which pair was part of the first trade. -- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). - `Total Profit %`: Total profit per stake amount. Aligned to the TOTAL column of the first table. - `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy). - `Best day` / `Worst day`: Best and worst day based on daily profit. - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). -- `Drawdown Start` / `Drawdown End`: Start and end datetimes for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). +- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. ### Assumptions made by backtesting diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index c977a991b..fc04cbd93 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -396,6 +396,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: metrics = [ ('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)), ('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)), + ('Max open trades', strat_results['max_open_trades']), + ('', ''), # Empty line to improve readability ('Total trades', strat_results['total_trades']), ('First trade', min_trade['open_date'].strftime(DATETIME_PRINT_FORMAT)), ('First trade Pair', min_trade['pair']), From 006436a18d2d2c821ca4a51ff1e604074ddacf9e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 07:47:35 +0100 Subject: [PATCH 036/119] Require use_sell_signal to be true for edge Otherwise edge will have strange results, as edge runs with sell signal, while the bot runs without sell signal, causing results to be invalid closes #3900 --- freqtrade/configuration/config_validation.py | 4 ++++ tests/test_configuration.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index d4612d8e0..ab21bc686 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -137,6 +137,10 @@ def _validate_edge(conf: Dict[str, Any]) -> None: "Edge and VolumePairList are incompatible, " "Edge will override whatever pairs VolumePairlist selects." ) + if not conf.get('ask_strategy', {}).get('use_sell_signal', True): + raise OperationalException( + "Edge requires `use_sell_signal` to be True, otherwise no sells will happen." + ) def _validate_whitelist(conf: Dict[str, Any]) -> None: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7d6c81f74..9594b6413 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -812,6 +812,21 @@ def test_validate_edge(edge_conf): validate_config_consistency(edge_conf) +def test_validate_edge2(edge_conf): + edge_conf.update({"ask_strategy": { + "use_sell_signal": True, + }}) + # Passes test + validate_config_consistency(edge_conf) + + edge_conf.update({"ask_strategy": { + "use_sell_signal": False, + }}) + with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, " + "otherwise no sells will happen."): + validate_config_consistency(edge_conf) + + def test_validate_whitelist(default_conf): default_conf['runmode'] = RunMode.DRY_RUN # Test regular case - has whitelist and uses StaticPairlist From bd98ff6332f9bd3d4ea73d1ee18446b48f0e187e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 20:24:51 +0100 Subject: [PATCH 037/119] Update docstring in all pairlists --- freqtrade/pairlist/AgeFilter.py | 2 +- freqtrade/pairlist/IPairList.py | 2 +- freqtrade/pairlist/PrecisionFilter.py | 2 +- freqtrade/pairlist/PriceFilter.py | 2 +- freqtrade/pairlist/ShuffleFilter.py | 2 +- freqtrade/pairlist/SpreadFilter.py | 2 +- freqtrade/pairlist/StaticPairList.py | 2 +- freqtrade/pairlist/VolumePairList.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py index 19cf1c090..20635a9ed 100644 --- a/freqtrade/pairlist/AgeFilter.py +++ b/freqtrade/pairlist/AgeFilter.py @@ -37,7 +37,7 @@ class AgeFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 6b5bd11e7..c869e499b 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -68,7 +68,7 @@ class IPairList(ABC): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index cf853397b..29e32fd44 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -32,7 +32,7 @@ class PrecisionFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py index 8cd57ee1d..bef1c0a15 100644 --- a/freqtrade/pairlist/PriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -35,7 +35,7 @@ class PriceFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/ShuffleFilter.py b/freqtrade/pairlist/ShuffleFilter.py index eb4f6dcc3..28778db7b 100644 --- a/freqtrade/pairlist/ShuffleFilter.py +++ b/freqtrade/pairlist/ShuffleFilter.py @@ -25,7 +25,7 @@ class ShuffleFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return False diff --git a/freqtrade/pairlist/SpreadFilter.py b/freqtrade/pairlist/SpreadFilter.py index 2527a3131..a636b90bd 100644 --- a/freqtrade/pairlist/SpreadFilter.py +++ b/freqtrade/pairlist/SpreadFilter.py @@ -24,7 +24,7 @@ class SpreadFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 3b6440763..2879cb364 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -30,7 +30,7 @@ class StaticPairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return False diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 44e5c52d7..7d3c2c653 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -49,7 +49,7 @@ class VolumePairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True From ceb50a78071c7295b486de887161875126bb3f72 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 07:57:23 +0100 Subject: [PATCH 038/119] use exception handler when downloading data closes #3992 --- freqtrade/data/history/history_utils.py | 12 +++++------- tests/data/test_history.py | 5 +---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 17b510b92..3b8b5a2f0 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -214,10 +214,9 @@ def _download_pair_history(datadir: Path, data_handler.ohlcv_store(pair, timeframe, data=data) return True - except Exception as e: - logger.error( - f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}. ' - f'Error: {e}' + except Exception: + logger.exception( + f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}.' ) return False @@ -304,10 +303,9 @@ def _download_trades_history(exchange: Exchange, logger.info(f"New Amount of trades: {len(trades)}") return True - except Exception as e: - logger.error( + except Exception: + logger.exception( f'Failed to download historic trades for pair: "{pair}". ' - f'Error: {e}' ) return False diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 905798041..99b22adda 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -312,10 +312,7 @@ def test_download_backtesting_data_exception(ohlcv_history, mocker, caplog, # clean files freshly downloaded _clean_test_file(file1_1) _clean_test_file(file1_5) - assert log_has( - 'Failed to download history data for pair: "MEME/BTC", timeframe: 1m. ' - 'Error: File Error', caplog - ) + assert log_has('Failed to download history data for pair: "MEME/BTC", timeframe: 1m.', caplog) def test_load_partial_missing(testdatadir, caplog) -> None: From 99b67348b206a75556d35f3994ec293b3d543dac Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 14:30:58 +0100 Subject: [PATCH 039/119] Add test for double-logging --- tests/test_configuration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 9594b6413..47d393860 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -678,6 +678,9 @@ def test_set_loggers_syslog(mocker): assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler] assert [x for x in logger.handlers if type(x) == logging.StreamHandler] assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler] + # setting up logging again should NOT cause the loggers to be added a second time. + setup_logging(config) + assert len(logger.handlers) == 3 # reset handlers to not break pytest logger.handlers = orig_handlers From 0104c9fde68601e694969006029cd1b17e8c015c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 14:31:34 +0100 Subject: [PATCH 040/119] Fix double logging --- freqtrade/loggers.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index 169cd2610..fbb05d879 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -37,6 +37,13 @@ def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None: ) +def get_existing_handlers(handlertype): + """ + Returns Existing handler or None (if the handler has not yet been added to the root handlers). + """ + return next((h for h in logging.root.handlers if isinstance(h, handlertype)), None) + + def setup_logging_pre() -> None: """ Early setup for logging. @@ -71,18 +78,24 @@ def setup_logging(config: Dict[str, Any]) -> None: # config['logfilename']), which defaults to '/dev/log', applicable for most # of the systems. address = (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else '/dev/log' - handler = SysLogHandler(address=address) + handler_sl = get_existing_handlers(SysLogHandler) + if handler_sl: + logging.root.removeHandler(handler_sl) + handler_sl = SysLogHandler(address=address) # No datetime field for logging into syslog, to allow syslog # to perform reduction of repeating messages if this is set in the # syslog config. The messages should be equal for this. - handler.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) - logging.root.addHandler(handler) + handler_sl.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) + logging.root.addHandler(handler_sl) elif s[0] == 'journald': try: from systemd.journal import JournaldLogHandler except ImportError: raise OperationalException("You need the systemd python package be installed in " "order to use logging to journald.") + handler_jd = get_existing_handlers(JournaldLogHandler) + if handler_jd: + logging.root.removeHandler(handler_jd) handler_jd = JournaldLogHandler() # No datetime field for logging into journald, to allow syslog # to perform reduction of repeating messages if this is set in the @@ -90,6 +103,9 @@ def setup_logging(config: Dict[str, Any]) -> None: handler_jd.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) logging.root.addHandler(handler_jd) else: + handler_rf = get_existing_handlers(RotatingFileHandler) + if handler_rf: + logging.root.removeHandler(handler_rf) handler_rf = RotatingFileHandler(logfile, maxBytes=1024 * 1024 * 10, # 10Mb backupCount=10) From b9980330a5469aa99160c8e40815a0c8707e0482 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 14:53:25 +0100 Subject: [PATCH 041/119] Add explicit test for FileHandler --- tests/test_configuration.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 47d393860..3501f1f3d 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -663,7 +663,7 @@ def test_set_loggers() -> None: @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") -def test_set_loggers_syslog(mocker): +def test_set_loggers_syslog(): logger = logging.getLogger() orig_handlers = logger.handlers logger.handlers = [] @@ -685,6 +685,30 @@ def test_set_loggers_syslog(mocker): logger.handlers = orig_handlers +def test_set_loggers_Filehandler(tmpdir): + logger = logging.getLogger() + orig_handlers = logger.handlers + logger.handlers = [] + logfile = Path(tmpdir) / 'ft_logfile.log' + config = {'verbosity': 2, + 'logfile': str(logfile), + } + + setup_logging_pre() + setup_logging(config) + assert len(logger.handlers) == 3 + assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler] + assert [x for x in logger.handlers if type(x) == logging.StreamHandler] + assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler] + # setting up logging again should NOT cause the loggers to be added a second time. + setup_logging(config) + assert len(logger.handlers) == 3 + # reset handlers to not break pytest + if logfile.exists: + logfile.unlink() + logger.handlers = orig_handlers + + @pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.") def test_set_loggers_journald(mocker): logger = logging.getLogger() From 46389e343bc0314427823023604c55bf212686e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 15:10:17 +0100 Subject: [PATCH 042/119] Skip filehandler test on windows - as that causes a permission-error --- tests/test_configuration.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 3501f1f3d..e6c91a96e 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -685,6 +685,7 @@ def test_set_loggers_syslog(): logger.handlers = orig_handlers +@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") def test_set_loggers_Filehandler(tmpdir): logger = logging.getLogger() orig_handlers = logger.handlers From 8f1d2ff0701bcf34609cec0c52e25697e3a8c65f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 19:47:27 +0100 Subject: [PATCH 043/119] Renamd volatilityFilter to RangeStabilityFilter --- config_full.json.example | 4 +-- docs/includes/pairlists.md | 22 ++++++------- freqtrade/constants.py | 2 +- ...ilityfilter.py => rangestabilityfilter.py} | 22 ++++++------- tests/pairlist/test_pairlist.py | 32 +++++++++---------- 5 files changed, 41 insertions(+), 41 deletions(-) rename freqtrade/pairlist/{volatilityfilter.py => rangestabilityfilter.py} (77%) diff --git a/config_full.json.example b/config_full.json.example index 0d82b9a2b..365b6180b 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -69,8 +69,8 @@ {"method": "PriceFilter", "low_price_ratio": 0.01, "min_price": 0.00000010}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, { - "method": "VolatilityFilter", - "volatility_over_days": 10, + "method": "RangeStabilityFilter", + "lookback_days": 10, "min_volatility": 0.01, "refresh_period": 1440 } diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 7cd2369b1..149e784bd 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -19,7 +19,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac * [`PriceFilter`](#pricefilter) * [`ShuffleFilter`](#shufflefilter) * [`SpreadFilter`](#spreadfilter) -* [`VolatilityFilter`](#volatilityfilter) +* [`RangeStabilityFilter`](#rangestabilityfilter) !!! Tip "Testing pairlists" Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility sub-command to test your configuration quickly. @@ -119,26 +119,26 @@ Example: If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005` and this pair will be filtered out. -#### VolatilityFilter +#### RangeStabilityFilter -Removes pairs where the difference between lowest low and highest high over `volatility_over_days` days is below `min_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. +Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. In the below example: -If volatility over the last 10 days is <1%, remove the pair from the whitelist. +If the trading range over the last 10 days is <1%, remove the pair from the whitelist. ```json "pairlists": [ { - "method": "VolatilityFilter", - "volatility_over_days": 10, - "min_volatility": 0.01, + "method": "RangeStabilityFilter", + "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440 } ] ``` !!! Tip - This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely difficult to trade with profit. + This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit. ### Full example of Pairlist Handlers @@ -160,9 +160,9 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, {"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, { - "method": "VolatilityFilter", - "volatility_over_days": 10, - "min_volatility": 0.01, + "method": "RangeStabilityFilter", + "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440 }, {"method": "ShuffleFilter", "seed": 42} diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 55d802587..2022556d2 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', - 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] + 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/rangestabilityfilter.py similarity index 77% rename from freqtrade/pairlist/volatilityfilter.py rename to freqtrade/pairlist/rangestabilityfilter.py index 14ac0c617..f428bb113 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/rangestabilityfilter.py @@ -15,23 +15,23 @@ from freqtrade.pairlist.IPairList import IPairList logger = logging.getLogger(__name__) -class VolatilityFilter(IPairList): +class RangeStabilityFilter(IPairList): def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: Dict[str, Any], pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - self._days = pairlistconfig.get('volatility_over_days', 10) - self._min_volatility = pairlistconfig.get('min_volatility', 0.01) + self._days = pairlistconfig.get('lookback_days', 10) + self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01) self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._pair_cache: TTLCache = TTLCache(maxsize=100, ttl=self._refresh_period) if self._days < 1: - raise OperationalException("VolatilityFilter requires volatility_over_days to be >= 1") + raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1") if self._days > exchange.ohlcv_candle_limit: - raise OperationalException("VolatilityFilter requires volatility_over_days to not " + raise OperationalException("RangeStabilityFilter requires lookback_days to not " "exceed exchange max request size " f"({exchange.ohlcv_candle_limit})") @@ -48,12 +48,12 @@ class VolatilityFilter(IPairList): """ Short whitelist method description - used for startup-messages """ - return (f"{self.name} - Filtering pairs with volatility below {self._min_volatility} " - f"over the last {plural(self._days, 'day')}.") + return (f"{self.name} - Filtering pairs with rate of change below " + f"{self._min_rate_of_change} over the last {plural(self._days, 'day')}.") def _validate_pair(self, ticker: Dict) -> bool: """ - Validate volatility + Validate trading range :param ticker: ticker dict as returned from ccxt.load_markets() :return: True if the pair can stay, False if it should be removed """ @@ -75,14 +75,14 @@ class VolatilityFilter(IPairList): highest_high = daily_candles['high'].max() lowest_low = daily_candles['low'].min() pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 - if pct_change >= self._min_volatility: + if pct_change >= self._min_rate_of_change: result = True else: self.log_on_refresh(logger.info, f"Removed {pair} from whitelist, " - f"because volatility over {plural(self._days, 'day')} is " + f"because rate of change over {plural(self._days, 'day')} is " f"{pct_change:.3f}, which is below the " - f"threshold of {self._min_volatility}.") + f"threshold of {self._min_rate_of_change}.") result = False self._pair_cache[pair] = result diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index e9df5d3f4..d696e6d02 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -341,8 +341,8 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): {"method": "PriceFilter", "low_price_ratio": 0.02}], "USDT", ['ETH/USDT', 'NANO/USDT']), ([{"method": "StaticPairList"}, - {"method": "VolatilityFilter", "volatility_over_days": 10, - "min_volatility": 0.01, "refresh_period": 1440}], + {"method": "RangeStabilityFilter", "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, @@ -586,9 +586,9 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count -def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): +def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 99999}] + {'method': 'RangeStabilityFilter', 'lookback_days': 99999}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -597,27 +597,27 @@ def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): ) with pytest.raises(OperationalException, - match=r'VolatilityFilter requires volatility_over_days to not exceed ' + match=r'RangeStabilityFilter requires lookback_days to not exceed ' r'exchange max request size \([0-9]+\)'): get_patched_freqtradebot(mocker, default_conf) default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 0}] + {'method': 'RangeStabilityFilter', 'lookback_days': 0}] with pytest.raises(OperationalException, - match='VolatilityFilter requires volatility_over_days to be >= 1'): + match='RangeStabilityFilter requires lookback_days to be >= 1'): get_patched_freqtradebot(mocker, default_conf) -@pytest.mark.parametrize('min_volatility,expected_length', [ +@pytest.mark.parametrize('min_rate_of_change,expected_length', [ (0.01, 5), - (0.05, 0), # Setting volatility to 5% removes all pairs from the whitelist. + (0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist. ]) -def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, - min_volatility, expected_length): +def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, + min_rate_of_change, expected_length): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 2, - 'min_volatility': min_volatility}] + {'method': 'RangeStabilityFilter', 'lookback_days': 2, + 'min_rate_of_change': min_rate_of_change}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -677,9 +677,9 @@ def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_ None, "PriceFilter requires max_price to be >= 0" ), # OperationalException expected - ({"method": "VolatilityFilter", "volatility_over_days": 10, "min_volatility": 0.01}, - "[{'VolatilityFilter': 'VolatilityFilter - Filtering pairs with volatility below 0.01 " - "over the last days.'}]", + ({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01}, + "[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below " + "0.01 over the last days.'}]", None ), ]) From 0d349cb3550bfbbfa6af7915a53789074a76d6a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 19:59:18 +0100 Subject: [PATCH 044/119] Small finetuning --- config_full.json.example | 2 +- freqtrade/exchange/exchange.py | 2 +- freqtrade/pairlist/rangestabilityfilter.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 365b6180b..5ee2a1faf 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -71,7 +71,7 @@ { "method": "RangeStabilityFilter", "lookback_days": 10, - "min_volatility": 0.01, + "min_rate_of_change": 0.01, "refresh_period": 1440 } ], diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2f52c512f..18f4fbff5 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -689,7 +689,7 @@ class Exchange: since_ms: int) -> DataFrame: """ Minimal wrapper around get_historic_ohlcv - converting the result into a dataframe - :param pair: Pair to download + :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from :return: OHLCV DataFrame diff --git a/freqtrade/pairlist/rangestabilityfilter.py b/freqtrade/pairlist/rangestabilityfilter.py index f428bb113..798d192bd 100644 --- a/freqtrade/pairlist/rangestabilityfilter.py +++ b/freqtrade/pairlist/rangestabilityfilter.py @@ -1,5 +1,5 @@ """ -Minimum age (days listed) pair list filter +Rate of change pairlist filter """ import logging from typing import Any, Dict From 8ae604d473df16f769c9cf43d4b37f3eec9f26cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 20:05:06 +0100 Subject: [PATCH 045/119] Ensure we're not running off of empty dataframes --- freqtrade/pairlist/rangestabilityfilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/rangestabilityfilter.py b/freqtrade/pairlist/rangestabilityfilter.py index 798d192bd..b460ff477 100644 --- a/freqtrade/pairlist/rangestabilityfilter.py +++ b/freqtrade/pairlist/rangestabilityfilter.py @@ -71,7 +71,7 @@ class RangeStabilityFilter(IPairList): timeframe='1d', since_ms=since_ms) result = False - if daily_candles is not None: + if daily_candles is not None and not daily_candles.empty: highest_high = daily_candles['high'].max() lowest_low = daily_candles['low'].min() pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 From 6810192992df1e9f7943728c65c6c02e675c2d92 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 20:25:18 +0100 Subject: [PATCH 046/119] Update docstring for new filter --- freqtrade/pairlist/VolumePairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 44e5c52d7..7d3c2c653 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -49,7 +49,7 @@ class VolumePairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True From c14c0f60a1b8a2fd52c501b5355713b92e3ba100 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 16:27:27 +0100 Subject: [PATCH 047/119] Add Support for kraken stoploss-limit --- docs/exchanges.md | 8 ++++---- docs/stoploss.md | 12 +++++++++--- freqtrade/exchange/kraken.py | 11 ++++++++--- tests/exchange/test_kraken.py | 26 +++++++++++++++----------- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 5d7505795..d877e6da2 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -23,7 +23,8 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f ## Kraken !!! Tip "Stoploss on Exchange" - Kraken supports `stoploss_on_exchange`. It provides great advantages, so we recommend to benefit from it. + Kraken supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. + You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use. ### Historic Kraken data @@ -75,8 +76,7 @@ print(res) !!! Tip "Stoploss on Exchange" FTX supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. - You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide. - + You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used. ### Using subaccounts @@ -99,10 +99,10 @@ To use subaccounts with FTX, you need to edit the configuration and add the foll Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys. - ## Random notes for other exchanges * The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed: + ```shell $ pip3 install web3 ``` diff --git a/docs/stoploss.md b/docs/stoploss.md index fa888cd47..7993da401 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -23,11 +23,12 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market) and FTX (stop limit and stop-market) as of now. - Do not set too low stoploss value if using stop loss on exchange! - If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work + Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market, stop-loss-limit) and FTX (stop limit and stop-market) as of now. + Do not set too low/tight stoploss value if using stop loss on exchange! + If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. ### stoploss_on_exchange and stoploss_on_exchange_limit_ratio + Enable or Disable stop loss on exchange. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfully. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. @@ -40,13 +41,18 @@ Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the limit order For example, assuming the stoploss is on exchange, and trailing stoploss is enabled, and the market is going up, then the bot automatically cancels the previous stoploss order and puts a new one with a stop value higher than the previous stoploss order. +!!! Note + If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order. + ### stoploss_on_exchange_interval + In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. The bot cannot do these every 5 seconds (at each iteration), otherwise it would get banned by the exchange. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). This same logic will reapply a stoploss order on the exchange should you cancel it accidentally. ### emergencysell + `emergencysell` is an optional value, which defaults to `market` and is used when creating stop loss on exchange orders fails. The below is the default which is used if not changed in strategy or configuration file. diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 5b7aa5c5b..d66793845 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -77,8 +77,15 @@ class Kraken(Exchange): Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. """ + params = self._params.copy() - ordertype = "stop-loss" + if order_types.get('stoploss', 'market') == 'limit': + ordertype = "stop-loss-limit" + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + limit_rate = stop_price * limit_price_pct + params['price2'] = self.price_to_precision(pair, limit_rate) + else: + ordertype = "stop-loss" stop_price = self.price_to_precision(pair, stop_price) @@ -88,8 +95,6 @@ class Kraken(Exchange): return dry_order try: - params = self._params.copy() - amount = self.amount_to_precision(pair, amount) order = self._api.create_order(symbol=pair, type=ordertype, side='sell', diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 31b79a202..3803658eb 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -10,6 +10,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers STOPLOSS_ORDERTYPE = 'stop-loss' +STOPLOSS_LIMIT_ORDERTYPE = 'stop-loss-limit' def test_buy_kraken_trading_agreement(default_conf, mocker): @@ -156,7 +157,8 @@ def test_get_balances_prod(default_conf, mocker): "get_balances", "fetch_balance") -def test_stoploss_order_kraken(default_conf, mocker): +@pytest.mark.parametrize('ordertype', ['market', 'limit']) +def test_stoploss_order_kraken(default_conf, mocker, ordertype): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -173,24 +175,26 @@ def test_stoploss_order_kraken(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - # stoploss_on_exchange_limit_ratio is irrelevant for kraken market orders - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) - assert api_mock.create_order.call_count == 1 - - api_mock.create_order.reset_mock() - - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={'stoploss': ordertype, + 'stoploss_on_exchange_limit_ratio': 0.99 + }) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' - assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE + if ordertype == 'limit': + assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE + assert api_mock.create_order.call_args_list[0][1]['params'] == { + 'trading_agreement': 'agree', 'price2': 217.8} + else: + assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE + assert api_mock.create_order.call_args_list[0][1]['params'] == { + 'trading_agreement': 'agree'} assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert api_mock.create_order.call_args_list[0][1]['price'] == 220 - assert api_mock.create_order.call_args_list[0][1]['params'] == {'trading_agreement': 'agree'} # test exception handling with pytest.raises(DependencyException): From d0d9921b42d5e4e10e57d3307d976c1f987c7e6e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 16:27:41 +0100 Subject: [PATCH 048/119] Reorder mkdocs sequence --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 8d1ce1cfe..2cc0c9fcb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,13 +20,13 @@ nav: - Hyperopt: hyperopt.md - Edge Positioning: edge.md - Utility Subcommands: utils.md - - Exchange-specific Notes: exchanges.md - FAQ: faq.md - Data Analysis: - Jupyter Notebooks: data-analysis.md - Strategy analysis: strategy_analysis_example.md - Plotting: plotting.md - SQL Cheatsheet: sql_cheatsheet.md + - Exchange-specific Notes: exchanges.md - Advanced Post-installation Tasks: advanced-setup.md - Advanced Strategy: strategy-advanced.md - Advanced Hyperopt: advanced-hyperopt.md From 1d56c87a34850453e88500ee6028ec5e222b3d3f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 21:39:12 +0100 Subject: [PATCH 049/119] Fully support kraken limit stoploss --- freqtrade/exchange/exchange.py | 2 +- freqtrade/persistence/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 18f4fbff5..611ce4abd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -524,7 +524,7 @@ class Exchange: 'rate': self.get_fee(pair) } }) - if closed_order["type"] in ["stop_loss_limit"]: + if closed_order["type"] in ["stop_loss_limit", "stop-loss-limit"]: closed_order["info"].update({"stopPrice": closed_order["price"]}) self._dry_run_open_orders[closed_order["id"]] = closed_order diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 8160ffbbf..6027908da 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -397,7 +397,7 @@ class Trade(_DECL_BASE): if self.is_open: logger.info(f'{order_type.upper()}_SELL has been fulfilled for {self}.') self.close(safe_value_fallback(order, 'average', 'price')) - elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'): + elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): self.stoploss_order_id = None self.close_rate_requested = self.stop_loss if self.is_open: From 0b68402c1094c89ead68bd4908eec47383005835 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Thu, 26 Nov 2020 10:24:48 +0100 Subject: [PATCH 050/119] Fixed a small typo in the pairlist documentation Signed-off-by: hoeckxer --- docs/includes/pairlists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 149e784bd..5bb02470d 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -60,7 +60,7 @@ The `refresh_period` setting allows to define the period (in seconds), at which "method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume", - "refresh_period": 1800, + "refresh_period": 1800 }], ``` From dddbc799f9b1c8d686a33de95a6f620322a56ec7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Nov 2020 19:40:08 +0100 Subject: [PATCH 051/119] have kraken stoploss-limit support trailing stop --- freqtrade/exchange/kraken.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index d66793845..4e4713052 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -69,7 +69,8 @@ class Kraken(Exchange): Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - return order['type'] == 'stop-loss' and stop_loss > float(order['price']) + return (order['type'] in ('stop-loss', 'stop-loss-limit') + and stop_loss > float(order['price'])) @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: From 98118f5e956d02c3f646ccb8feff1992e118cc22 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Thu, 26 Nov 2020 18:46:36 -0600 Subject: [PATCH 052/119] Fix parameter name Correct which parameter name was referred to within the 2nd Note under "Amend last stake amount" --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 56ba13414..2e8f6555f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -177,7 +177,7 @@ In the example above this would mean: This option only applies with [Static stake amount](#static-stake-amount) - since [Dynamic stake amount](#dynamic-stake-amount) divides the balances evenly. !!! Note - The minimum last stake amount can be configured using `amend_last_stake_amount` - which defaults to 0.5 (50%). This means that the minimum stake amount that's ever used is `stake_amount * 0.5`. This avoids very low stake amounts, that are close to the minimum tradable amount for the pair and can be refused by the exchange. + The minimum last stake amount can be configured using `last_stake_amount_min_ratio` - which defaults to 0.5 (50%). This means that the minimum stake amount that's ever used is `stake_amount * 0.5`. This avoids very low stake amounts, that are close to the minimum tradable amount for the pair and can be refused by the exchange. #### Static stake amount From fce31447edf2c0466cef8de13e0f8d0a26d71e61 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Thu, 26 Nov 2020 19:38:20 -0600 Subject: [PATCH 053/119] Prevent unintended LaTeX rendering --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 7993da401..14b04c7e0 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -140,7 +140,7 @@ For example, simplified math: * the stop loss would get triggered once the asset drops below 90$ * assuming the asset now increases to 102$ * the stop loss will now be -2% of 102$ = 99.96$ (99.96$ stop loss will be locked in and will follow asset price increasements with -2%) -* now the asset drops in value to 101$, the stop loss will still be 99.96$ and would trigger at 99.96$ +* now the asset drops in value to 101$, the stop loss will still be 99.96$ and would trigger at 99.96$ The 0.02 would translate to a -2% stop loss. Before this, `stoploss` is used for the trailing stoploss. From 31449987c08780afc384d524fd2bf9098995ae5b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 07:35:12 +0100 Subject: [PATCH 054/119] Fix mkdocs rendering --- docs/stoploss.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 14b04c7e0..1e21fc50d 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -36,8 +36,8 @@ If `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the st `stoploss` defines the stop-price where the limit order is placed - and limit should be slightly below this. If an exchange supports both limit and market stoploss orders, then the value of `stoploss` will be used to determine the stoploss type. -Calculation example: we bought the asset at 100$. -Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the limit order fill can happen between 95$ and 94.05$. +Calculation example: we bought the asset at 100\$. +Stop-price is 95\$, then limit would be `95 * 0.99 = 94.05$` - so the limit order fill can happen between 95$ and 94.05$. For example, assuming the stoploss is on exchange, and trailing stoploss is enabled, and the market is going up, then the bot automatically cancels the previous stoploss order and puts a new one with a stop value higher than the previous stoploss order. @@ -90,6 +90,7 @@ Example of stop loss: ``` For example, simplified math: + * the bot buys an asset at a price of 100$ * the stop loss is defined at -10% * the stop loss would get triggered once the asset drops below 90$ @@ -113,7 +114,7 @@ For example, simplified math: * the stop loss would get triggered once the asset drops below 90$ * assuming the asset now increases to 102$ * the stop loss will now be -10% of 102$ = 91.8$ -* now the asset drops in value to 101$, the stop loss will still be 91.8$ and would trigger at 91.8$. +* now the asset drops in value to 101\$, the stop loss will still be 91.8$ and would trigger at 91.8$. In summary: The stoploss will be adjusted to be always be -10% of the highest observed price. @@ -139,8 +140,8 @@ For example, simplified math: * the stop loss is defined at -10% * the stop loss would get triggered once the asset drops below 90$ * assuming the asset now increases to 102$ -* the stop loss will now be -2% of 102$ = 99.96$ (99.96$ stop loss will be locked in and will follow asset price increasements with -2%) -* now the asset drops in value to 101$, the stop loss will still be 99.96$ and would trigger at 99.96$ +* the stop loss will now be -2% of 102$ = 99.96$ (99.96$ stop loss will be locked in and will follow asset price increments with -2%) +* now the asset drops in value to 101\$, the stop loss will still be 99.96$ and would trigger at 99.96$ The 0.02 would translate to a -2% stop loss. Before this, `stoploss` is used for the trailing stoploss. @@ -157,7 +158,7 @@ This option can be used with or without `trailing_stop_positive`, but uses `trai trailing_only_offset_is_reached = True ``` -Configuration (offset is buyprice + 3%): +Configuration (offset is buy-price + 3%): ``` python stoploss = -0.10 @@ -175,7 +176,7 @@ For example, simplified math: * stoploss will remain at 90$ unless asset increases to or above our configured offset * assuming the asset now increases to 103$ (where we have the offset configured) * the stop loss will now be -2% of 103$ = 100.94$ -* now the asset drops in value to 101$, the stop loss will still be 100.94$ and would trigger at 100.94$ +* now the asset drops in value to 101\$, the stop loss will still be 100.94$ and would trigger at 100.94$ !!! Tip Make sure to have this value (`trailing_stop_positive_offset`) lower than minimal ROI, otherwise minimal ROI will apply first and sell the trade. From 81d08c4deff25a9bd8b24e6d6847bdc04ccdace0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 08:16:11 +0100 Subject: [PATCH 055/119] Add detailed backtest test verifying the ROI / trailing stop collision --- freqtrade/strategy/interface.py | 12 +++--- tests/optimize/test_backtest_detail.py | 55 ++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 44a281ebe..172264b10 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -475,24 +475,24 @@ class IStrategy(ABC): stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=date, current_profit=current_profit, force_stoploss=force_stoploss, high=high) - + # Set current rate to high for backtesting sell current_rate = high or rate current_profit = trade.calc_profit_ratio(current_rate) config_ask_strategy = self.config.get('ask_strategy', {}) - + roi_reached = self.min_roi_reached(trade=trade, current_profit=current_profit, - current_time=date) - + current_time=date) + if stoplossflag.sell_flag: - + # When backtesting, in the case of trailing_stop_loss, # make sure we don't make a profit higher than ROI. if stoplossflag.sell_type == SellType.TRAILING_STOP_LOSS and roi_reached: logger.debug(f"{trade.pair} - Required profit reached. sell_flag=True, " f"sell_type=SellType.ROI") return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI) - + logger.debug(f"{trade.pair} - Stoploss hit. sell_flag=True, " f"sell_type={stoplossflag.sell_type}") return stoplossflag diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index a5de64fe4..f3a2d8b96 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -328,6 +328,58 @@ tc20 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) +# Test 21: trailing_stop ROI collision. +# Roi should trigger before Trailing stop - otherwise Trailing stop profits can be > ROI +# which cannot happen in reality +# stop-loss: 10%, ROI: 4%, Trailing stop adjusted at the sell candle +tc21 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 4650, 5100, 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": 0.04}, profit_perc=0.04, trailing_stop=True, + trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, + trailing_stop_positive=0.03, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] +) + +# Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time. +# applying a positive trailing stop of 3% - ROI should apply before trailing stop. +# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2 +tc22 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 5100, 5100, 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": 0.04}, profit_perc=0.04, trailing_stop=True, + trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, + trailing_stop_positive=0.03, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] +) + +# Test 23: trailing_stop Raises in candle 2 (does not trigger) +# applying a positive trailing stop of 3% since stop_positive_offset is reached. +# ROI is changed after this to 4%, dropping ROI below trailing_stop_positive, causing a sell +# in the candle after the raised stoploss candle with ROI reason. +# Stoploss would trigger in this candle too, but it's no longer relevant. +# stop-loss: 10%, ROI: 4%, stoploss adjusted candle 2, ROI adjusted in candle 3 (causing the sell) +tc23 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [2, 5100, 5251, 5100, 5100, 6172, 0, 0], + [3, 4850, 5251, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True, + trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, + trailing_stop_positive=0.03, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] +) + TESTS = [ tc0, @@ -351,6 +403,9 @@ TESTS = [ tc18, tc19, tc20, + tc21, + tc22, + tc23, ] From 57461a59f3b416add4e2a5169d89b81cd4c12ea8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 08:30:17 +0100 Subject: [PATCH 056/119] Update backtesting documentation with new logic --- docs/backtesting.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/backtesting.md b/docs/backtesting.md index 84911568b..2121e3126 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -278,6 +278,7 @@ Since backtesting lacks some detailed information about what happens within a ca - Trailing stoploss - High happens first - adjusting stoploss - Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly) + - ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies - Sell-reason does not explain if a trade was positive or negative, just what triggered the sell (this can look odd if negative ROI values are used) - Stoploss (and trailing stoploss) is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` and/or `trailing_stop` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes. From 4aa6ebee049a924b5fd5c931f14360af917eb3e4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 09:17:25 +0100 Subject: [PATCH 057/119] Add more tests for #2422 --- tests/optimize/test_backtest_detail.py | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index f3a2d8b96..720ed8c13 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -380,6 +380,66 @@ tc23 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) +# Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle) +# Stoploss at 1%. +# Stoploss wins over Sell-signal (because sell-signal is acted on in the next candle) +tc24 = 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) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5000, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal + [4, 5010, 4987, 4977, 4995, 6172, 0, 0], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True, + trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] +) + +# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle) +# Stoploss at 1%. +# Sell-signal wins over stoploss +tc25 = 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) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5000, 4986, 5010, 6172, 0, 1], + [4, 5010, 4987, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, + trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] +) + +# Test 26: Sell with signal sell in candle 3 (ROI at signal candle) +# Stoploss at 10% (irrelevant), ROI at 5% (will trigger) +# Sell-signal wins over stoploss +tc26 = 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) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5251, 4986, 5010, 6172, 0, 1], # Triggers ROI, sell-signal + [4, 5010, 4987, 4855, 4995, 6172, 0, 0], + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] +) + +# Test 27: Sell with signal sell in candle 3 (ROI at signal candle) +# Stoploss at 10% (irrelevant), ROI at 5% (will trigger) - Wins over Sell-signal +# TODO: figure out if sell-signal should win over ROI +# Sell-signal wins over stoploss +tc27 = 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) + [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [3, 5010, 5012, 4986, 5010, 6172, 0, 1], # sell-signal + [4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on + [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)] +) TESTS = [ tc0, @@ -406,6 +466,10 @@ TESTS = [ tc21, tc22, tc23, + tc24, + tc25, + tc26, + tc27, ] From fefb4b23d0603683800790de198f1894143449a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 09:18:03 +0100 Subject: [PATCH 058/119] revise logic in should_sell --- freqtrade/strategy/interface.py | 53 ++++++++++++++------------------- tests/test_freqtradebot.py | 2 +- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 172264b10..81f4e7651 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -481,46 +481,39 @@ class IStrategy(ABC): current_profit = trade.calc_profit_ratio(current_rate) config_ask_strategy = self.config.get('ask_strategy', {}) - roi_reached = self.min_roi_reached(trade=trade, current_profit=current_profit, - current_time=date) + # if buy signal and ignore_roi is set, we don't need to evaluate min_roi. + roi_reached = (not (buy and config_ask_strategy.get('ignore_roi_if_buy_signal', False)) + and self.min_roi_reached(trade=trade, current_profit=current_profit, + current_time=date)) - if stoplossflag.sell_flag: + if config_ask_strategy.get('sell_profit_only', False) and trade.calc_profit(rate=rate) <= 0: + # Negative profits and sell_profit_only - ignore sell signal + sell_signal = False + else: + sell_signal = sell and not buy and config_ask_strategy.get('use_sell_signal', True) + # TODO: return here if sell-signal should be favored over ROI - # When backtesting, in the case of trailing_stop_loss, - # make sure we don't make a profit higher than ROI. - if stoplossflag.sell_type == SellType.TRAILING_STOP_LOSS and roi_reached: - logger.debug(f"{trade.pair} - Required profit reached. sell_flag=True, " - f"sell_type=SellType.ROI") - return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI) - - logger.debug(f"{trade.pair} - Stoploss hit. sell_flag=True, " - f"sell_type={stoplossflag.sell_type}") - return stoplossflag - - if buy and config_ask_strategy.get('ignore_roi_if_buy_signal', False): - # This one is noisy, commented out - # logger.debug(f"{trade.pair} - Buy signal still active. sell_flag=False") - return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) - - # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) - if roi_reached: + # Start evaluations + # Sequence: + # ROI (if not stoploss) + # Sell-signal + # Stoploss + if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS: logger.debug(f"{trade.pair} - Required profit reached. sell_flag=True, " f"sell_type=SellType.ROI") return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI) - if config_ask_strategy.get('sell_profit_only', False): - # This one is noisy, commented out - # logger.debug(f"{trade.pair} - Checking if trade is profitable...") - if trade.calc_profit(rate=rate) <= 0: - # This one is noisy, commented out - # logger.debug(f"{trade.pair} - Trade is not profitable. sell_flag=False") - return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) - - if sell and not buy and config_ask_strategy.get('use_sell_signal', True): + if sell_signal: logger.debug(f"{trade.pair} - Sell signal received. sell_flag=True, " f"sell_type=SellType.SELL_SIGNAL") return SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL) + if stoplossflag.sell_flag: + + logger.debug(f"{trade.pair} - Stoploss hit. sell_flag=True, " + f"sell_type={stoplossflag.sell_type}") + return stoplossflag + # This one is noisy, commented out... # logger.debug(f"{trade.pair} - No sell signal. sell_flag=False") return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 1f5b3ecaa..64dfb016e 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3556,7 +3556,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b # Test if buy-signal is absent patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is True - assert trade.sell_reason == SellType.STOP_LOSS.value + assert trade.sell_reason == SellType.SELL_SIGNAL.value def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, fee, caplog, mocker): From c69ce28b76449a1785cc41bc1491d66c766e8f9d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 09:26:58 +0100 Subject: [PATCH 059/119] Update backtest assumption documentation --- docs/backtesting.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 2121e3126..953198ddd 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -268,19 +268,24 @@ It contains some useful key metrics about performance of your strategy on backte Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions: - Buys happen at open-price -- Sell signal sells happen at open-price of the following candle -- Low happens before high for stoploss, protecting capital first +- Sell-signal sells happen at open-price of the consecutive candle +- Sell-signal is favored over Stoploss, because sell-signals are assumed to trigger on candle's open - ROI - sells are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the sell will be at 2%) - sells are never "below the candle", so a ROI of 2% may result in a sell at 2.4% if low was at 2.4% profit - Forcesells caused by `=-1` ROI entries use low as sell value, unless N falls on the candle open (e.g. `120: -1` for 1h candles) - Stoploss sells happen exactly at stoploss price, even if low was lower +- Stoploss is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes +- Low happens before high for stoploss, protecting capital first - Trailing stoploss - High happens first - adjusting stoploss - Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly) - ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies - Sell-reason does not explain if a trade was positive or negative, just what triggered the sell (this can look odd if negative ROI values are used) -- Stoploss (and trailing stoploss) is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` and/or `trailing_stop` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes. +- Evaluation sequence (if multiple signals happen on the same candle) + - ROI (if not stoploss) + - Sell-signal + - Stoploss Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode. Also, keep in mind that past results don't guarantee future success. From 46ec6f498c7cf95c677955051ce17e3770cfb6ed Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 12:51:44 -0600 Subject: [PATCH 060/119] Correct link Fix prior redirection to a non-working link: https://www.freqtrade.io/en/latest/telegram-usage/configuration/#understand-forcebuy_enable --- docs/telegram-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 09cf21223..f4bd0a12a 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -207,7 +207,7 @@ 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) +[More details](configuration.md#understand-forcebuy_enable) ### /performance From 95c3c45ec95c3c4daa4c190f62bff3f867d0375b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 20:24:04 +0100 Subject: [PATCH 061/119] Remove long deprecated settings that moved from experimental to ask_strategy --- .../configuration/deprecated_settings.py | 30 ++++++++--- freqtrade/constants.py | 3 -- tests/test_configuration.py | 50 +++++++++++++++++-- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 03ed41ab8..6873ab405 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -26,6 +26,24 @@ def check_conflicting_settings(config: Dict[str, Any], ) +def process_removed_setting(config: Dict[str, Any], + section1: str, name1: str, + section2: str, name2: str) -> None: + """ + :param section1: Removed section + :param name1: Removed setting name + :param section2: new section for this key + :param name2: new setting name + """ + section1_config = config.get(section1, {}) + if name1 in section1_config: + raise OperationalException( + f"Setting `{section1}.{name1}` has been moved to `{section2}.{name2}. " + f"Please delete it from your configuration and use the `{section2}.{name2}` " + "setting instead." + ) + + def process_deprecated_setting(config: Dict[str, Any], section1: str, name1: str, section2: str, name2: str) -> None: @@ -51,12 +69,12 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: check_conflicting_settings(config, 'ask_strategy', 'ignore_roi_if_buy_signal', 'experimental', 'ignore_roi_if_buy_signal') - process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal', - 'experimental', 'use_sell_signal') - process_deprecated_setting(config, 'ask_strategy', 'sell_profit_only', - 'experimental', 'sell_profit_only') - process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal', - 'experimental', 'ignore_roi_if_buy_signal') + process_removed_setting(config, 'experimental', 'use_sell_signal', + 'ask_strategy', 'use_sell_signal') + process_removed_setting(config, 'experimental', 'sell_profit_only', + 'ask_strategy', 'sell_profit_only') + process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal', + 'ask_strategy', 'ignore_roi_if_buy_signal') if (config.get('edge', {}).get('enabled', False) and 'capital_available_percentage' in config.get('edge', {})): diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 2022556d2..3e523a49e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -182,9 +182,6 @@ CONF_SCHEMA = { 'experimental': { 'type': 'object', 'properties': { - 'use_sell_signal': {'type': 'boolean'}, - 'sell_profit_only': {'type': 'boolean'}, - 'ignore_roi_if_buy_signal': {'type': 'boolean'}, 'block_bad_exchanges': {'type': 'boolean'} } }, diff --git a/tests/test_configuration.py b/tests/test_configuration.py index e6c91a96e..6c895a00b 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -16,6 +16,7 @@ from freqtrade.configuration import (Configuration, check_exchange, remove_crede from freqtrade.configuration.config_validation import validate_config_schema from freqtrade.configuration.deprecated_settings import (check_conflicting_settings, process_deprecated_setting, + process_removed_setting, process_temporary_deprecated_settings) from freqtrade.configuration.load_config import load_config_file, log_config_error_range from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL @@ -1061,13 +1062,11 @@ def test_pairlist_resolving_fallback(mocker): assert config['datadir'] == Path.cwd() / "user_data/data/binance" +@pytest.mark.skip(reason='Currently no deprecated / moved sections') +# The below is kept as a sample for the future. @pytest.mark.parametrize("setting", [ ("ask_strategy", "use_sell_signal", True, "experimental", "use_sell_signal", False), - ("ask_strategy", "sell_profit_only", False, - "experimental", "sell_profit_only", True), - ("ask_strategy", "ignore_roi_if_buy_signal", False, - "experimental", "ignore_roi_if_buy_signal", True), ]) def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog): patched_configuration_load_config_file(mocker, default_conf) @@ -1097,6 +1096,25 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca assert default_conf[setting[0]][setting[1]] == setting[5] +@pytest.mark.parametrize("setting", [ + ("experimental", "use_sell_signal", False), + ("experimental", "sell_profit_only", True), + ("experimental", "ignore_roi_if_buy_signal", True), + ]) +def test_process_removed_settings(mocker, default_conf, setting, caplog): + patched_configuration_load_config_file(mocker, default_conf) + + # Create sections for new and deprecated settings + # (they may not exist in the config) + default_conf[setting[0]] = {} + # Assign removed setting + default_conf[setting[0]][setting[1]] = setting[2] + + # New and deprecated settings are conflicting ones + with pytest.raises(OperationalException, + match=r'Setting .* has been moved'): + process_temporary_deprecated_settings(default_conf) + def test_process_deprecated_setting_edge(mocker, edge_conf, caplog): patched_configuration_load_config_file(mocker, edge_conf) edge_conf.update({'edge': { @@ -1196,6 +1214,30 @@ def test_process_deprecated_setting(mocker, default_conf, caplog): assert default_conf['sectionA']['new_setting'] == 'valA' +def test_process_removed_setting(mocker, default_conf, caplog): + patched_configuration_load_config_file(mocker, default_conf) + + # Create sections for new and deprecated settings + # (they may not exist in the config) + default_conf['sectionA'] = {} + default_conf['sectionB'] = {} + # Assign new setting + default_conf['sectionB']['somesetting'] = 'valA' + + # Only new setting exists (nothing should happen) + process_removed_setting(default_conf, + 'sectionA', 'somesetting', + 'sectionB', 'somesetting') + # Assign removed setting + default_conf['sectionA']['somesetting'] = 'valB' + + with pytest.raises(OperationalException, + match=r"Setting .* has been moved"): + process_removed_setting(default_conf, + 'sectionA', 'somesetting', + 'sectionB', 'somesetting') + + def test_process_deprecated_ticker_interval(mocker, default_conf, caplog): message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." config = deepcopy(default_conf) From af1b3721fb736409cb8107912b664b07d4d7be30 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 20:28:17 +0100 Subject: [PATCH 062/119] remove duplicate settings check --- freqtrade/configuration/deprecated_settings.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 6873ab405..6b2a20c8c 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -62,12 +62,11 @@ def process_deprecated_setting(config: Dict[str, Any], def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: - check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal', - 'experimental', 'use_sell_signal') - check_conflicting_settings(config, 'ask_strategy', 'sell_profit_only', - 'experimental', 'sell_profit_only') - check_conflicting_settings(config, 'ask_strategy', 'ignore_roi_if_buy_signal', - 'experimental', 'ignore_roi_if_buy_signal') + # Kept for future deprecated / moved settings + # check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal', + # 'experimental', 'use_sell_signal') + # process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal', + # 'experimental', 'use_sell_signal') process_removed_setting(config, 'experimental', 'use_sell_signal', 'ask_strategy', 'use_sell_signal') From 89573348b6ed83ac2e8f16f0888d41c767f1f337 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 20:37:52 -0600 Subject: [PATCH 063/119] Fix link --- docs/telegram-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 09cf21223..f4bd0a12a 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -207,7 +207,7 @@ 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) +[More details](configuration.md#understand-forcebuy_enable) ### /performance From 7cbd89657f1f477451d91c962f1fad260858385c Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 21:24:40 -0600 Subject: [PATCH 064/119] Initial step towards implementing proposed code --- freqtrade/constants.py | 2 +- freqtrade/pairlist/PerformanceFilter.py | 61 +++++++++++++++++++++++++ tests/pairlist/test_pairlist.py | 22 ++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 freqtrade/pairlist/PerformanceFilter.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 3271dda39..f47301fa6 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', - 'ShuffleFilter', 'SpreadFilter'] + 'ShuffleFilter', 'SpreadFilter', 'PerformanceFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py new file mode 100644 index 000000000..e689ba0bc --- /dev/null +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -0,0 +1,61 @@ +""" +Performance pair list filter +""" +import logging +import random +from typing import Any, Dict, List + +import pandas as pd +from pandas import DataFrame, Series + +from freqtrade.pairlist.IPairList import IPairList + +from freqtrade.persistence import Trade +from datetime import timedelta, datetime, timezone + +logger = logging.getLogger(__name__) + +class PerformanceFilter(IPairList): + + def __init__(self, exchange, pairlistmanager, + config: Dict[str, Any], pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requries tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return False + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return f"{self.name} - Sorting pairs by performance." + + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + """ + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new whitelist + """ + + # Get the trading performance for pairs from database + perf = pd.DataFrame(Trade.get_overall_performance()) + # update pairlist with values from performance dataframe + # set initial value for pairs with no trades to 0 + # and sort the list using performance and count + list_df = pd.DataFrame({'pair':pairlist}) + sorted_df = list_df.join(perf.set_index('pair'), on='pair')\ + .fillna(0).sort_values(by=['profit', 'count'], ascending=False) + pairlist = sorted_df['pair'].tolist() + + + return pairlist \ No newline at end of file diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 1f05bef1e..2643a0bd8 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -246,7 +246,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.03}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, - {"method": "ShuffleFilter"}], + {"method": "ShuffleFilter"}, {"method": "PerformanceFilter"}], "ETH", []), # AgeFilter and VolumePairList (require 2 days only, all should pass age test) ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, @@ -302,6 +302,18 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter"}], "USDT", 3), # whitelist_result is integer -- check only length of randomized pairlist + # PerformanceFilter + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + {"method": "PerformanceFilter", "seed": 77}], + "USDT", ['ADADOUBLE/USDT', 'ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT']), + # PerformanceFilter, other seed + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + {"method": "PerformanceFilter", "seed": 42}], + "USDT", ['ADAHALF/USDT', 'NANO/USDT', 'ADADOUBLE/USDT', 'ETH/USDT']), + # PerformanceFilter, no seed + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + {"method": "PerformanceFilter"}], + "USDT", 3), # whitelist_result is integer -- check only length of randomized pairlist # AgeFilter only ([{"method": "AgeFilter", "min_days_listed": 2}], "BTC", 'filter_at_the_beginning'), # OperationalException expected @@ -326,6 +338,13 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # ShuffleFilter only ([{"method": "ShuffleFilter", "seed": 42}], "BTC", 'filter_at_the_beginning'), # OperationalException expected + # PrecisionFilter after StaticPairList + ([{"method": "StaticPairList"}, + {"method": "PrecisionFilter", "seed": 42}], + "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC']), + # PrecisionFilter only + ([{"method": "PrecisionFilter", "seed": 42}], + "BTC", 'filter_at_the_beginning'), # OperationalException expected # SpreadFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}], @@ -379,6 +398,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t assert isinstance(whitelist, list) # Verify length of pairlist matches (used for ShuffleFilter without seed) + # TBD if this applies to PerformanceFilter if type(whitelist_result) is list: assert whitelist == whitelist_result else: From 05686998bbc497f56d7407cc96d713686c4f6d85 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 21:26:42 -0600 Subject: [PATCH 065/119] Add starter entry in documentation --- docs/includes/pairlists.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index e6a9fc1a8..f8b33b27d 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -15,6 +15,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac * [`StaticPairList`](#static-pair-list) (default, if not configured differently) * [`VolumePairList`](#volume-pair-list) * [`AgeFilter`](#agefilter) +* [`PerformanceFilter`](#performancefilter) * [`PrecisionFilter`](#precisionfilter) * [`PriceFilter`](#pricefilter) * [`ShuffleFilter`](#shufflefilter) @@ -73,6 +74,10 @@ be caught out buying before the pair has finished dropping in price. This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days. +#### PerformanceFilter + +Lorem ipsum. + #### PrecisionFilter Filters low-value coins which would not allow setting stoplosses. From c34150552f348245cac68611a27d4b26eabc5f8a Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 21:36:55 -0600 Subject: [PATCH 066/119] Revert unrelated change --- docs/telegram-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index f4bd0a12a..09cf21223 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -207,7 +207,7 @@ 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) +[More details](configuration.md/#understand-forcebuy_enable) ### /performance From 335735062835636d1bce627b9117030a5595f69c Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 22:00:36 -0600 Subject: [PATCH 067/119] Revert unintended change --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index ec2d27174..9b15c9685 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -242,4 +242,4 @@ If this file is inexistent, then you're probably on a different version of MacOS ----- Now you have an environment ready, the next step is -[Bot Configuration](configuration.md). \ No newline at end of file +[Bot Configuration](configuration.md). From 380cca225239397f04d503fee36c189ee06014aa Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 22:00:48 -0600 Subject: [PATCH 068/119] Remove unused imports --- freqtrade/pairlist/PerformanceFilter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index e689ba0bc..a2f2eb489 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -2,16 +2,13 @@ Performance pair list filter """ import logging -import random from typing import Any, Dict, List import pandas as pd -from pandas import DataFrame, Series from freqtrade.pairlist.IPairList import IPairList from freqtrade.persistence import Trade -from datetime import timedelta, datetime, timezone logger = logging.getLogger(__name__) From afb795b6f53de9ec00ccdbe2b4b128659831ad81 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 22:08:23 -0600 Subject: [PATCH 069/119] Remove unnecessary test PerforamnceFilter doesn't use seeds, so no need to provide different ones. --- tests/pairlist/test_pairlist.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 2643a0bd8..64468fc05 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -302,14 +302,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter"}], "USDT", 3), # whitelist_result is integer -- check only length of randomized pairlist - # PerformanceFilter + # PerformanceFilter, unneeded seed provided ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PerformanceFilter", "seed": 77}], "USDT", ['ADADOUBLE/USDT', 'ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT']), - # PerformanceFilter, other seed - ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, - {"method": "PerformanceFilter", "seed": 42}], - "USDT", ['ADAHALF/USDT', 'NANO/USDT', 'ADADOUBLE/USDT', 'ETH/USDT']), # PerformanceFilter, no seed ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PerformanceFilter"}], From 91b4c80d35611bbcf812a3a46dd5860e0ec949d2 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Fri, 27 Nov 2020 22:18:49 -0600 Subject: [PATCH 070/119] Remove unused parameters --- freqtrade/pairlist/PerformanceFilter.py | 2 -- tests/pairlist/test_pairlist.py | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index a2f2eb489..d4bd5936d 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -43,7 +43,6 @@ class PerformanceFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - # Get the trading performance for pairs from database perf = pd.DataFrame(Trade.get_overall_performance()) # update pairlist with values from performance dataframe @@ -53,6 +52,5 @@ class PerformanceFilter(IPairList): sorted_df = list_df.join(perf.set_index('pair'), on='pair')\ .fillna(0).sort_values(by=['profit', 'count'], ascending=False) pairlist = sorted_df['pair'].tolist() - return pairlist \ No newline at end of file diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 64468fc05..9814aea3e 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -425,7 +425,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t assert not log_has(logmsg, caplog) -def test_PrecisionFilter_error(mocker, whitelist_conf, tickers) -> None: +def test_PrecisionFilter_error(mocker, whitelist_conf) -> None: whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}] del whitelist_conf['stoploss'] @@ -498,7 +498,7 @@ def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) -def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, markets, pairlist, tickers): +def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, pairlist, tickers): whitelist_conf['pairlists'][0]['method'] = pairlist mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) @@ -514,7 +514,7 @@ def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, markets, pa pairlist_handler._whitelist_for_active_markets(['ETH/BTC']) -def test_volumepairlist_invalid_sortvalue(mocker, markets, whitelist_conf): +def test_volumepairlist_invalid_sortvalue(mocker, whitelist_conf): whitelist_conf['pairlists'][0].update({"sort_key": "asdf"}) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) @@ -652,7 +652,7 @@ def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) -def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog): +def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) whitelist_conf['pairlists'] = [] From 9538fa1d723cca40a862a265e24fc89e3f559c06 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 00:24:48 -0600 Subject: [PATCH 071/119] Tweak main parameterized block for PerformanceFilter Remove randomized exception that was geared toward ShuffleFilter. Remove case involvoing seed, also geared toward ShuffleFilter. Mock get_overall_performance(). --- tests/pairlist/test_pairlist.py | 46 ++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 9814aea3e..71d65d236 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -302,14 +302,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter"}], "USDT", 3), # whitelist_result is integer -- check only length of randomized pairlist - # PerformanceFilter, unneeded seed provided - ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, - {"method": "PerformanceFilter", "seed": 77}], - "USDT", ['ADADOUBLE/USDT', 'ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT']), - # PerformanceFilter, no seed + # PerformanceFilter ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PerformanceFilter"}], - "USDT", 3), # whitelist_result is integer -- check only length of randomized pairlist + "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT', 'ADADOUBLE/USDT']), # AgeFilter only ([{"method": "AgeFilter", "min_days_listed": 2}], "BTC", 'filter_at_the_beginning'), # OperationalException expected @@ -381,6 +377,11 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), ) + # Provide for PerformanceFilter's dependency + mocker.patch.multiple('freqtrade.persistence.Trade', + get_overall_performance=MagicMock(return_value=[{'pair':'ETH/BTC','profit':5,'count':3}]), + ) + # Set whitelist_result to None if pairlist is invalid and should produce exception if whitelist_result == 'filter_at_the_beginning': with pytest.raises(OperationalException, @@ -394,7 +395,6 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t assert isinstance(whitelist, list) # Verify length of pairlist matches (used for ShuffleFilter without seed) - # TBD if this applies to PerformanceFilter if type(whitelist_result) is list: assert whitelist == whitelist_result else: @@ -544,7 +544,7 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf -def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers, caplog): +def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': -1}] @@ -559,7 +559,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick get_patched_freqtradebot(mocker, default_conf) -def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers, caplog): +def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': 99999}] @@ -660,3 +660,31 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): with pytest.raises(OperationalException, match=r"No Pairlist Handlers defined"): get_patched_freqtradebot(mocker, whitelist_conf) + + +@pytest.mark.parametrize("pairlists,base_currency,overall_performance,expected", [ + # Happy path, descening order, all values filled + ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}],'BTC',[{'pair':'ETH/BTC','profit':5,'count':3}, {'pair':'ETC/BTC','profit':4,'count':2}],['ETC/BTC']), +]) +def test_performance_filter(mocker, whitelist_conf, base_currency, pairlists, overall_performance, expected, tickers, markets, ohlcv_history_list): + whitelist_conf['pairlists'] = pairlists + whitelist_conf['stake_currency'] = base_currency + + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) + mocker.patch.multiple('freqtrade.exchange.Exchange', + get_tickers=tickers, + markets=PropertyMock(return_value=markets) + ) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), + ) + + mocker.patch.multiple('freqtrade.persistence.Trade', + get_overall_performance=MagicMock(return_value=overall_performance), + ) + freqtrade.pairlists.refresh_pairlist() + whitelist = freqtrade.pairlists.whitelist + assert whitelist == expected From 4600bb807c41782420806c62f819fa56b393c6b9 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 00:38:06 -0600 Subject: [PATCH 072/119] Existing tests pass. --- tests/pairlist/test_pairlist.py | 56 ++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 71d65d236..86e4616e0 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -330,12 +330,12 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # ShuffleFilter only ([{"method": "ShuffleFilter", "seed": 42}], "BTC", 'filter_at_the_beginning'), # OperationalException expected - # PrecisionFilter after StaticPairList + # PerformanceFilter after StaticPairList ([{"method": "StaticPairList"}, - {"method": "PrecisionFilter", "seed": 42}], - "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC']), - # PrecisionFilter only - ([{"method": "PrecisionFilter", "seed": 42}], + {"method": "PerformanceFilter", "seed": 42}], + "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # Order matches order of appearance in whitelist_conf > exchange > pair_whitelist + # PerformanceFilter only + ([{"method": "PerformanceFilter", "seed": 42}], "BTC", 'filter_at_the_beginning'), # OperationalException expected # SpreadFilter after StaticPairList ([{"method": "StaticPairList"}, @@ -662,29 +662,29 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): get_patched_freqtradebot(mocker, whitelist_conf) -@pytest.mark.parametrize("pairlists,base_currency,overall_performance,expected", [ - # Happy path, descening order, all values filled - ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}],'BTC',[{'pair':'ETH/BTC','profit':5,'count':3}, {'pair':'ETC/BTC','profit':4,'count':2}],['ETC/BTC']), -]) -def test_performance_filter(mocker, whitelist_conf, base_currency, pairlists, overall_performance, expected, tickers, markets, ohlcv_history_list): - whitelist_conf['pairlists'] = pairlists - whitelist_conf['stake_currency'] = base_currency +# @pytest.mark.parametrize("pairlists,base_currency,overall_performance,expected", [ +# # Happy path, descening order, all values filled +# ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}],'BTC',[{'pair':'ETH/BTC','profit':5,'count':3}, {'pair':'ETC/BTC','profit':4,'count':2}],['ETC/BTC']), +# ]) +# def test_performance_filter(mocker, whitelist_conf, base_currency, pairlists, overall_performance, expected, tickers, markets, ohlcv_history_list): +# whitelist_conf['pairlists'] = pairlists +# whitelist_conf['stake_currency'] = base_currency - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) +# mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch.multiple('freqtrade.exchange.Exchange', - get_tickers=tickers, - markets=PropertyMock(return_value=markets) - ) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), - ) +# freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) +# mocker.patch.multiple('freqtrade.exchange.Exchange', +# get_tickers=tickers, +# markets=PropertyMock(return_value=markets) +# ) +# mocker.patch.multiple( +# 'freqtrade.exchange.Exchange', +# get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), +# ) - mocker.patch.multiple('freqtrade.persistence.Trade', - get_overall_performance=MagicMock(return_value=overall_performance), - ) - freqtrade.pairlists.refresh_pairlist() - whitelist = freqtrade.pairlists.whitelist - assert whitelist == expected +# mocker.patch.multiple('freqtrade.persistence.Trade', +# get_overall_performance=MagicMock(return_value=overall_performance), +# ) +# freqtrade.pairlists.refresh_pairlist() +# whitelist = freqtrade.pairlists.whitelist +# assert whitelist == expected From 26855800a3b0229c1ea3a3c0f9d97268e9de57f7 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 00:39:18 -0600 Subject: [PATCH 073/119] Remove unused seed --- tests/pairlist/test_pairlist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 86e4616e0..ae80a3975 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -332,10 +332,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "BTC", 'filter_at_the_beginning'), # OperationalException expected # PerformanceFilter after StaticPairList ([{"method": "StaticPairList"}, - {"method": "PerformanceFilter", "seed": 42}], + {"method": "PerformanceFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # Order matches order of appearance in whitelist_conf > exchange > pair_whitelist # PerformanceFilter only - ([{"method": "PerformanceFilter", "seed": 42}], + ([{"method": "PerformanceFilter"}], "BTC", 'filter_at_the_beginning'), # OperationalException expected # SpreadFilter after StaticPairList ([{"method": "StaticPairList"}, From 662ec3207310dd0c269e1a5bc4554eefec1891a6 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 01:15:36 -0600 Subject: [PATCH 074/119] Add test cases --- freqtrade/pairlist/PerformanceFilter.py | 4 +- tests/pairlist/test_pairlist.py | 65 ++++++++++++++++--------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index d4bd5936d..b2889dc6b 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -45,10 +45,10 @@ class PerformanceFilter(IPairList): """ # Get the trading performance for pairs from database perf = pd.DataFrame(Trade.get_overall_performance()) - # update pairlist with values from performance dataframe + # get pairlist from performance dataframe values + list_df = pd.DataFrame({'pair':pairlist}) # set initial value for pairs with no trades to 0 # and sort the list using performance and count - list_df = pd.DataFrame({'pair':pairlist}) sorted_df = list_df.join(perf.set_index('pair'), on='pair')\ .fillna(0).sort_values(by=['profit', 'count'], ascending=False) pairlist = sorted_df['pair'].tolist() diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index ae80a3975..a99651727 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -662,29 +662,48 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): get_patched_freqtradebot(mocker, whitelist_conf) -# @pytest.mark.parametrize("pairlists,base_currency,overall_performance,expected", [ -# # Happy path, descening order, all values filled -# ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}],'BTC',[{'pair':'ETH/BTC','profit':5,'count':3}, {'pair':'ETC/BTC','profit':4,'count':2}],['ETC/BTC']), -# ]) -# def test_performance_filter(mocker, whitelist_conf, base_currency, pairlists, overall_performance, expected, tickers, markets, ohlcv_history_list): -# whitelist_conf['pairlists'] = pairlists -# whitelist_conf['stake_currency'] = base_currency +@pytest.mark.parametrize("pairlists,pair_allowlist,overall_performance,allowlist_result", [ + # Happy path, descending order, all values filled + ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], + ['ETH/BTC','TKN/BTC'], + [{'pair':'TKN/BTC','profit':5,'count':3}, {'pair':'ETH/BTC','profit':4,'count':2}], + ['TKN/BTC','ETH/BTC']), + # Performance data outside allow list ignored + ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], + ['ETH/BTC','TKN/BTC'], + [{'pair':'OTHER/BTC','profit':5,'count':3}, {'pair':'ETH/BTC','profit':4,'count':2}], + ['ETH/BTC','TKN/BTC']), + # Partial performance data missing and sorted between positive and negative profit + ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], + ['ETH/BTC','TKN/BTC','LTC/BTC'], + [{'pair':'ETH/BTC','profit':-5,'count':100}, {'pair':'TKN/BTC','profit':4,'count':2}], + ['TKN/BTC','LTC/BTC','ETH/BTC']), + # Tie in performance data broken by count + ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], + ['ETH/BTC','TKN/BTC','LTC/BTC'], + [{'pair':'LTC/BTC','profit':-5,'count':101}, {'pair':'TKN/BTC','profit':-5,'count':2}, {'pair':'ETH/BTC','profit':-5,'count':100}, ], + ['LTC/BTC','ETH/BTC','TKN/BTC']), +]) +def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance, allowlist_result, tickers, markets, ohlcv_history_list): + allowlist_conf = whitelist_conf + allowlist_conf['pairlists'] = pairlists + allowlist_conf['exchange']['pair_whitelist'] = pair_allowlist -# mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) -# freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) -# mocker.patch.multiple('freqtrade.exchange.Exchange', -# get_tickers=tickers, -# markets=PropertyMock(return_value=markets) -# ) -# mocker.patch.multiple( -# 'freqtrade.exchange.Exchange', -# get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), -# ) + freqtrade = get_patched_freqtradebot(mocker, allowlist_conf) + mocker.patch.multiple('freqtrade.exchange.Exchange', + get_tickers=tickers, + markets=PropertyMock(return_value=markets) + ) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), + ) -# mocker.patch.multiple('freqtrade.persistence.Trade', -# get_overall_performance=MagicMock(return_value=overall_performance), -# ) -# freqtrade.pairlists.refresh_pairlist() -# whitelist = freqtrade.pairlists.whitelist -# assert whitelist == expected + mocker.patch.multiple('freqtrade.persistence.Trade', + get_overall_performance=MagicMock(return_value=overall_performance), + ) + freqtrade.pairlists.refresh_pairlist() + allowlist = freqtrade.pairlists.whitelist + assert allowlist == allowlist_result From dbd50fdff64e5e72a052db687082b26794790f7b Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 01:22:03 -0600 Subject: [PATCH 075/119] Document filter. --- docs/includes/pairlists.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index f8b33b27d..50ef52653 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -76,7 +76,12 @@ This filter allows freqtrade to ignore pairs until they have been listed for at #### PerformanceFilter -Lorem ipsum. +Sorts pairs by performance, as follows: +1. Positive performance. +2. No closed trades yet. +3. Negative performance. + +Trade count is used as a tie breaker. #### PrecisionFilter From 966c6b308f182392c85a74568350de4ac5cd9ced Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 01:34:18 -0600 Subject: [PATCH 076/119] Satisfy linter. --- freqtrade/pairlist/PerformanceFilter.py | 6 +-- tests/pairlist/test_pairlist.py | 51 +++++++++++++------------ 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index b2889dc6b..099b8d271 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -12,6 +12,7 @@ from freqtrade.persistence import Trade logger = logging.getLogger(__name__) + class PerformanceFilter(IPairList): def __init__(self, exchange, pairlistmanager, @@ -19,7 +20,6 @@ class PerformanceFilter(IPairList): pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - @property def needstickers(self) -> bool: """ @@ -46,11 +46,11 @@ class PerformanceFilter(IPairList): # Get the trading performance for pairs from database perf = pd.DataFrame(Trade.get_overall_performance()) # get pairlist from performance dataframe values - list_df = pd.DataFrame({'pair':pairlist}) + list_df = pd.DataFrame({'pair': pairlist}) # set initial value for pairs with no trades to 0 # and sort the list using performance and count sorted_df = list_df.join(perf.set_index('pair'), on='pair')\ .fillna(0).sort_values(by=['profit', 'count'], ascending=False) pairlist = sorted_df['pair'].tolist() - return pairlist \ No newline at end of file + return pairlist diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 65a0fa835..9e2bab12c 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -729,27 +729,32 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): @pytest.mark.parametrize("pairlists,pair_allowlist,overall_performance,allowlist_result", [ # Happy path, descending order, all values filled - ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], - ['ETH/BTC','TKN/BTC'], - [{'pair':'TKN/BTC','profit':5,'count':3}, {'pair':'ETH/BTC','profit':4,'count':2}], - ['TKN/BTC','ETH/BTC']), + ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], + ['ETH/BTC', 'TKN/BTC'], + [{'pair': 'TKN/BTC', 'profit': 5, 'count': 3}, {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}], + ['TKN/BTC', 'ETH/BTC']), # Performance data outside allow list ignored - ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], - ['ETH/BTC','TKN/BTC'], - [{'pair':'OTHER/BTC','profit':5,'count':3}, {'pair':'ETH/BTC','profit':4,'count':2}], - ['ETH/BTC','TKN/BTC']), + ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], + ['ETH/BTC', 'TKN/BTC'], + [{'pair': 'OTHER/BTC', 'profit': 5, 'count': 3}, + {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}], + ['ETH/BTC', 'TKN/BTC']), # Partial performance data missing and sorted between positive and negative profit - ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], - ['ETH/BTC','TKN/BTC','LTC/BTC'], - [{'pair':'ETH/BTC','profit':-5,'count':100}, {'pair':'TKN/BTC','profit':4,'count':2}], - ['TKN/BTC','LTC/BTC','ETH/BTC']), + ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], + ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], + [{'pair': 'ETH/BTC', 'profit': -5, 'count': 100}, + {'pair': 'TKN/BTC', 'profit': 4, 'count': 2}], + ['TKN/BTC', 'LTC/BTC', 'ETH/BTC']), # Tie in performance data broken by count - ([{"method": "StaticPairList"},{"method": "PerformanceFilter"}], - ['ETH/BTC','TKN/BTC','LTC/BTC'], - [{'pair':'LTC/BTC','profit':-5,'count':101}, {'pair':'TKN/BTC','profit':-5,'count':2}, {'pair':'ETH/BTC','profit':-5,'count':100}, ], - ['LTC/BTC','ETH/BTC','TKN/BTC']), + ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], + ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], + [{'pair': 'LTC/BTC', 'profit': -5, 'count': 101}, + {'pair': 'TKN/BTC', 'profit': -5, 'count': 2}, + {'pair': 'ETH/BTC', 'profit': -5, 'count': 100}], + ['LTC/BTC', 'ETH/BTC', 'TKN/BTC']), ]) -def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance, allowlist_result, tickers, markets, ohlcv_history_list): +def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance, + allowlist_result, tickers, markets, ohlcv_history_list): allowlist_conf = whitelist_conf allowlist_conf['pairlists'] = pairlists allowlist_conf['exchange']['pair_whitelist'] = pair_allowlist @@ -761,14 +766,12 @@ def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, o get_tickers=tickers, markets=PropertyMock(return_value=markets) ) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), - ) - + mocker.patch.multiple('freqtrade.exchange.Exchange', + get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), + ) mocker.patch.multiple('freqtrade.persistence.Trade', - get_overall_performance=MagicMock(return_value=overall_performance), - ) + get_overall_performance=MagicMock(return_value=overall_performance), + ) freqtrade.pairlists.refresh_pairlist() allowlist = freqtrade.pairlists.whitelist assert allowlist == allowlist_result From fefa500963d7d4a8b88d740783190755605942ac Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 01:34:40 -0600 Subject: [PATCH 077/119] More lint --- tests/pairlist/test_pairlist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 9e2bab12c..5e9847e3d 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -333,7 +333,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # PerformanceFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # Order matches order of appearance in whitelist_conf > exchange > pair_whitelist + "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # PerformanceFilter only ([{"method": "PerformanceFilter"}], "BTC", 'filter_at_the_beginning'), # OperationalException expected @@ -383,7 +383,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t # Provide for PerformanceFilter's dependency mocker.patch.multiple('freqtrade.persistence.Trade', - get_overall_performance=MagicMock(return_value=[{'pair':'ETH/BTC','profit':5,'count':3}]), + get_overall_performance=MagicMock( + return_value=[{'pair': 'ETH/BTC', 'profit': 5, 'count' :3}]), ) # Set whitelist_result to None if pairlist is invalid and should produce exception From ecce5265f5e5fa153261095c563e505c243fc0a7 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 01:43:19 -0600 Subject: [PATCH 078/119] Linting --- tests/pairlist/test_pairlist.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 5e9847e3d..a4df031c9 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -383,8 +383,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t # Provide for PerformanceFilter's dependency mocker.patch.multiple('freqtrade.persistence.Trade', - get_overall_performance=MagicMock( - return_value=[{'pair': 'ETH/BTC', 'profit': 5, 'count' :3}]), + get_overall_performance=MagicMock(return_value=\ + [{'pair': 'ETH/BTC', 'profit': 5, 'count': 3}]), ) # Set whitelist_result to None if pairlist is invalid and should produce exception @@ -737,7 +737,7 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): # Performance data outside allow list ignored ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC'], - [{'pair': 'OTHER/BTC', 'profit': 5, 'count': 3}, + [{'pair': 'OTHER/BTC', 'profit': 5, 'count': 3}, {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}], ['ETH/BTC', 'TKN/BTC']), # Partial performance data missing and sorted between positive and negative profit @@ -769,7 +769,7 @@ def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, o ) mocker.patch.multiple('freqtrade.exchange.Exchange', get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), - ) + ) mocker.patch.multiple('freqtrade.persistence.Trade', get_overall_performance=MagicMock(return_value=overall_performance), ) From f448564073b2d7c487ccb2d75e749bfe949bd547 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 01:49:46 -0600 Subject: [PATCH 079/119] Lint --- tests/pairlist/test_pairlist.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index a4df031c9..d40cece41 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -383,9 +383,9 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t # Provide for PerformanceFilter's dependency mocker.patch.multiple('freqtrade.persistence.Trade', - get_overall_performance=MagicMock(return_value=\ - [{'pair': 'ETH/BTC', 'profit': 5, 'count': 3}]), - ) + get_overall_performance=MagicMock( + return_value=[{'pair': 'ETH/BTC', 'profit': 5, 'count': 3}]), + ) # Set whitelist_result to None if pairlist is invalid and should produce exception if whitelist_result == 'filter_at_the_beginning': From 37d2e476df19bfafa86c6405404d0bc578f270d9 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 01:59:30 -0600 Subject: [PATCH 080/119] isort imports --- freqtrade/pairlist/PerformanceFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index 099b8d271..bd56a4607 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -7,9 +7,9 @@ from typing import Any, Dict, List import pandas as pd from freqtrade.pairlist.IPairList import IPairList - from freqtrade.persistence import Trade + logger = logging.getLogger(__name__) From 4cb331b5ad644f9d1aeee2909e158de25078a913 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Nov 2020 10:24:44 +0100 Subject: [PATCH 081/119] Remove non-needed parameters from tests --- tests/test_configuration.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 6c895a00b..167215f29 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1101,7 +1101,7 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca ("experimental", "sell_profit_only", True), ("experimental", "ignore_roi_if_buy_signal", True), ]) -def test_process_removed_settings(mocker, default_conf, setting, caplog): +def test_process_removed_settings(mocker, default_conf, setting): patched_configuration_load_config_file(mocker, default_conf) # Create sections for new and deprecated settings @@ -1115,7 +1115,8 @@ def test_process_removed_settings(mocker, default_conf, setting, caplog): match=r'Setting .* has been moved'): process_temporary_deprecated_settings(default_conf) -def test_process_deprecated_setting_edge(mocker, edge_conf, caplog): + +def test_process_deprecated_setting_edge(mocker, edge_conf): patched_configuration_load_config_file(mocker, edge_conf) edge_conf.update({'edge': { 'enabled': True, From a47d8dbe56c1b3813a15b125726f7dc66a35666b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Nov 2020 11:31:28 +0100 Subject: [PATCH 082/119] Small refactor, avoiding duplicate calculation of profits --- freqtrade/optimize/optimize_reports.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index fc04cbd93..6aef031d3 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -58,16 +58,19 @@ def _generate_result_line(result: DataFrame, max_open_trades: int, first_column: """ Generate one result dict, with "first_column" as key. """ + profit_sum = result['profit_percent'].sum() + profit_total = profit_sum / max_open_trades + return { 'key': first_column, 'trades': len(result), 'profit_mean': result['profit_percent'].mean() if len(result) > 0 else 0.0, 'profit_mean_pct': result['profit_percent'].mean() * 100.0 if len(result) > 0 else 0.0, - 'profit_sum': result['profit_percent'].sum(), - 'profit_sum_pct': result['profit_percent'].sum() * 100.0, + 'profit_sum': profit_sum, + 'profit_sum_pct': round(profit_sum * 100.0, 2), 'profit_total_abs': result['profit_abs'].sum(), - 'profit_total': result['profit_percent'].sum() / max_open_trades, - 'profit_total_pct': result['profit_percent'].sum() * 100.0 / max_open_trades, + 'profit_total': profit_total, + 'profit_total_pct': round(profit_total * 100.0, 2), 'duration_avg': str(timedelta( minutes=round(result['trade_duration'].mean())) ) if not result.empty else '0:00', @@ -122,8 +125,8 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List result = results.loc[results['sell_reason'] == reason] profit_mean = result['profit_percent'].mean() - profit_sum = result["profit_percent"].sum() - profit_percent_tot = result['profit_percent'].sum() / max_open_trades + profit_sum = result['profit_percent'].sum() + profit_total = profit_sum / max_open_trades tabular_data.append( { @@ -137,8 +140,8 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List 'profit_sum': profit_sum, 'profit_sum_pct': round(profit_sum * 100, 2), 'profit_total_abs': result['profit_abs'].sum(), - 'profit_total': profit_percent_tot, - 'profit_total_pct': round(profit_percent_tot * 100, 2), + 'profit_total': profit_total, + 'profit_total_pct': round(profit_total * 100, 2), } ) return tabular_data From ff286bd80cd82fa079ec51cc0592ba2f986c2ceb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Nov 2020 16:32:44 +0100 Subject: [PATCH 083/119] Slightly clarify hyperopt docs --- docs/hyperopt.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index fc7a0dd93..c42889831 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -173,6 +173,11 @@ one we call `trigger` and use it to decide which buy trigger we want to use. So let's write the buy strategy using these values: ```python + @staticmethod + def buy_strategy_generator(params: Dict[str, Any]) -> Callable: + """ + Define the buy strategy parameters to be used by Hyperopt. + """ def populate_buy_trend(dataframe: DataFrame) -> DataFrame: conditions = [] # GUARDS AND TRENDS From 56529180eb6e73dfcac8ebcab5f35e4c731e2a20 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Nov 2020 16:42:08 +0100 Subject: [PATCH 084/119] Further improve hyperopt docs --- docs/hyperopt.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index c42889831..f88d9cd4f 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -64,9 +64,9 @@ Depending on the space you want to optimize, only some of the below are required Optional in hyperopt - can also be loaded from a strategy (recommended): -* copy `populate_indicators` from your strategy - otherwise default-strategy will be used -* copy `populate_buy_trend` from your strategy - otherwise default-strategy will be used -* copy `populate_sell_trend` from your strategy - otherwise default-strategy will be used +* `populate_indicators` - fallback to create indicators +* `populate_buy_trend` - fallback if not optimizing for buy space. should come from strategy +* `populate_sell_trend` - fallback if not optimizing for sell space. should come from strategy !!! Note You always have to provide a strategy to Hyperopt, even if your custom Hyperopt class contains all methods. @@ -104,7 +104,7 @@ This command will create a new hyperopt file from a template, allowing you to ge There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing: * Inside `indicator_space()` - the parameters hyperopt shall be optimizing. -* Inside `populate_buy_trend()` - applying the parameters. +* Within `buy_strategy_generator()` - populate the nested `populate_buy_trend()` to apply the parameters. There you have two different types of indicators: 1. `guards` and 2. `triggers`. @@ -128,7 +128,7 @@ 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. -* Inside `populate_sell_trend()` - applying the parameters. +* Within `sell_strategy_generator()` - populate the nested method `populate_sell_trend()` to apply the parameters. The configuration and rules are the same than for buy signals. To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`. From e1d42ba78ce670a662420b7bb19337c57cc335ca Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 09:44:01 -0600 Subject: [PATCH 085/119] Alphabetize --- freqtrade/constants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 20cc70d2e..9d0078d21 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -24,9 +24,9 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', - 'AgeFilter', 'PrecisionFilter', 'PriceFilter', - 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter', - 'PerformanceFilter'] + 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter', + 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', + 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' From 03c5714399d57af12cb18ffa7f4b6937ed3cd6b4 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 09:45:17 -0600 Subject: [PATCH 086/119] Use explicit merge without depending on library detail. Add no trades case. --- freqtrade/pairlist/PerformanceFilter.py | 9 +++++++-- tests/pairlist/test_pairlist.py | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index bd56a4607..2d360a346 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -44,12 +44,17 @@ class PerformanceFilter(IPairList): :return: new whitelist """ # Get the trading performance for pairs from database - perf = pd.DataFrame(Trade.get_overall_performance()) + performance = pd.DataFrame(Trade.get_overall_performance()) + + # Skip performance-based sorting if no performance data is available + if len(performance) == 0: + return pairlist + # get pairlist from performance dataframe values list_df = pd.DataFrame({'pair': pairlist}) # set initial value for pairs with no trades to 0 # and sort the list using performance and count - sorted_df = list_df.join(perf.set_index('pair'), on='pair')\ + sorted_df = list_df.merge(performance, on='pair', how='left')\ .fillna(0).sort_values(by=['profit', 'count'], ascending=False) pairlist = sorted_df['pair'].tolist() diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index d40cece41..c62ec81f3 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -383,8 +383,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t # Provide for PerformanceFilter's dependency mocker.patch.multiple('freqtrade.persistence.Trade', - get_overall_performance=MagicMock( - return_value=[{'pair': 'ETH/BTC', 'profit': 5, 'count': 3}]), + get_overall_performance=MagicMock(return_value=[]) ) # Set whitelist_result to None if pairlist is invalid and should produce exception @@ -729,7 +728,10 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): @pytest.mark.parametrize("pairlists,pair_allowlist,overall_performance,allowlist_result", [ - # Happy path, descending order, all values filled + # No trades yet + ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], + ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], [], ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), + # Happy path: Descending order, all values filled ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC'], [{'pair': 'TKN/BTC', 'profit': 5, 'count': 3}, {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}], From a00f852cf99106b314a05d425b7ab6fcad9d158d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Nov 2020 16:56:08 +0100 Subject: [PATCH 087/119] Add best / worst pair to summary statistics --- docs/backtesting.md | 12 ++++++------ freqtrade/optimize/optimize_reports.py | 15 +++++++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 277b11083..01624e5c2 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -165,8 +165,8 @@ A backtesting result will look like that: | Max open trades | 3 | | | | | Total trades | 429 | -| First trade | 2019-01-01 18:30:00 | -| First trade Pair | EOS/USDT | +| Best Pair | LSK/BTC - 26.26% | +| Worst Pair | ZEC/BTC - -10.18% | | Total Profit % | 152.41% | | Trades per day | 3.575 | | Best day | 25.27% | @@ -238,8 +238,8 @@ It contains some useful key metrics about performance of your strategy on backte | Max open trades | 3 | | | | | Total trades | 429 | -| First trade | 2019-01-01 18:30:00 | -| First trade Pair | EOS/USDT | +| Best Pair | LSK/BTC - 26.26% | +| Worst Pair | ZEC/BTC - -10.18% | | Total Profit % | 152.41% | | Trades per day | 3.575 | | Best day | 25.27% | @@ -258,8 +258,8 @@ It contains some useful key metrics about performance of your strategy on backte - `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). - `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - to clearly see settings for this. - `Total trades`: Identical to the total trades of the backtest output table. -- `First trade`: First trade entered. -- `First trade pair`: Which pair was part of the first trade. +- `Best Pair`: Which pair performed best, and it's corresponding `Cum Profit %`. +- `Worst pair`: Which pair performed worst and it's corresponding `Cum Profit %`. - `Total Profit %`: Total profit per stake amount. Aligned to the TOTAL column of the first table. - `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy). - `Best day` / `Worst day`: Best and worst day based on daily profit. diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 6aef031d3..589e0ba1c 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -256,13 +256,18 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], results=results.loc[results['open_at_end']], skip_nan=True) daily_stats = generate_daily_stats(results) - + best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'], + key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None + worst_pair = min([pair for pair in pair_results if pair['key'] != 'TOTAL'], + key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None results['open_timestamp'] = results['open_date'].astype(int64) // 1e6 results['close_timestamp'] = results['close_date'].astype(int64) // 1e6 backtest_days = (max_date - min_date).days strat_stats = { 'trades': results.to_dict(orient='records'), + 'best_pair': best_pair, + 'worst_pair': worst_pair, 'results_per_pair': pair_results, 'sell_reason_summary': sell_reason_stats, 'left_open_trades': left_open_results, @@ -395,17 +400,19 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: def text_table_add_metrics(strat_results: Dict) -> str: if len(strat_results['trades']) > 0: - min_trade = min(strat_results['trades'], key=lambda x: x['open_date']) metrics = [ ('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)), ('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)), ('Max open trades', strat_results['max_open_trades']), ('', ''), # Empty line to improve readability ('Total trades', strat_results['total_trades']), - ('First trade', min_trade['open_date'].strftime(DATETIME_PRINT_FORMAT)), - ('First trade Pair', min_trade['pair']), ('Total Profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"), ('Trades per day', strat_results['trades_per_day']), + ('', ''), # Empty line to improve readability + ('Best Pair', f"{strat_results['best_pair']['key']} - " + f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"), + ('Worst Pair', f"{strat_results['worst_pair']['key']} - " + f"{round(strat_results['worst_pair']['profit_sum_pct'], 2)}%"), ('Best day', f"{round(strat_results['backtest_best_day'] * 100, 2)}%"), ('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"), ('Days win/draw/lose', f"{strat_results['winning_days']} / " From 5d3f59df90d97f1d96e7b4e9734dcc722b6bc7a4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Nov 2020 17:45:56 +0100 Subject: [PATCH 088/119] Add best / worst trade --- docs/backtesting.md | 18 ++++++++++++------ freqtrade/optimize/optimize_reports.py | 5 +++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 01624e5c2..42de9bdc3 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -165,10 +165,13 @@ A backtesting result will look like that: | Max open trades | 3 | | | | | Total trades | 429 | -| Best Pair | LSK/BTC - 26.26% | -| Worst Pair | ZEC/BTC - -10.18% | | Total Profit % | 152.41% | | Trades per day | 3.575 | +| | | +| Best Pair | LSK/BTC - 26.26% | +| Worst Pair | ZEC/BTC - -10.18% | +| Best Trade | LSK/BTC - 4.25% | +| Worst Trade | ZEC/BTC - -10.25% | | Best day | 25.27% | | Worst day | -30.67% | | Avg. Duration Winners | 4:23:00 | @@ -238,10 +241,13 @@ It contains some useful key metrics about performance of your strategy on backte | Max open trades | 3 | | | | | Total trades | 429 | -| Best Pair | LSK/BTC - 26.26% | -| Worst Pair | ZEC/BTC - -10.18% | | Total Profit % | 152.41% | | Trades per day | 3.575 | +| | | +| Best Pair | LSK/BTC - 26.26% | +| Worst Pair | ZEC/BTC - -10.18% | +| Best Trade | LSK/BTC - 4.25% | +| Worst Trade | ZEC/BTC - -10.25% | | Best day | 25.27% | | Worst day | -30.67% | | Avg. Duration Winners | 4:23:00 | @@ -258,10 +264,10 @@ It contains some useful key metrics about performance of your strategy on backte - `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). - `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - to clearly see settings for this. - `Total trades`: Identical to the total trades of the backtest output table. -- `Best Pair`: Which pair performed best, and it's corresponding `Cum Profit %`. -- `Worst pair`: Which pair performed worst and it's corresponding `Cum Profit %`. - `Total Profit %`: Total profit per stake amount. Aligned to the TOTAL column of the first table. - `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy). +- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`. +- `Best Trade` / `Worst Trade`: Biggest winning trade and biggest losing trade - `Best day` / `Worst day`: Best and worst day based on daily profit. - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 589e0ba1c..3e44a6067 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -400,6 +400,8 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: def text_table_add_metrics(strat_results: Dict) -> str: if len(strat_results['trades']) > 0: + best_trade = max(strat_results['trades'], key=lambda x: x['profit_percent']) + worst_trade = min(strat_results['trades'], key=lambda x: x['profit_percent']) metrics = [ ('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)), ('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)), @@ -413,6 +415,9 @@ def text_table_add_metrics(strat_results: Dict) -> str: f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"), ('Worst Pair', f"{strat_results['worst_pair']['key']} - " f"{round(strat_results['worst_pair']['profit_sum_pct'], 2)}%"), + ('Best trade', f"{best_trade['pair']} {round(best_trade['profit_percent'] * 100, 2)}%"), + ('Worst trade', f"{worst_trade['pair']} {round(worst_trade['profit_percent'] * 100, 2)}%"), + ('Best day', f"{round(strat_results['backtest_best_day'] * 100, 2)}%"), ('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"), ('Days win/draw/lose', f"{strat_results['winning_days']} / " From e40d97e05e765c5946208b47b5519c47b79aa135 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Nov 2020 17:52:29 +0100 Subject: [PATCH 089/119] Small formatting improvements --- docs/backtesting.md | 16 ++++++++-------- freqtrade/optimize/optimize_reports.py | 7 ++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 42de9bdc3..c841899a7 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -168,10 +168,10 @@ A backtesting result will look like that: | Total Profit % | 152.41% | | Trades per day | 3.575 | | | | -| Best Pair | LSK/BTC - 26.26% | -| Worst Pair | ZEC/BTC - -10.18% | -| Best Trade | LSK/BTC - 4.25% | -| Worst Trade | ZEC/BTC - -10.25% | +| Best Pair | LSK/BTC 26.26% | +| Worst Pair | ZEC/BTC -10.18% | +| Best Trade | LSK/BTC 4.25% | +| Worst Trade | ZEC/BTC -10.25% | | Best day | 25.27% | | Worst day | -30.67% | | Avg. Duration Winners | 4:23:00 | @@ -244,10 +244,10 @@ It contains some useful key metrics about performance of your strategy on backte | Total Profit % | 152.41% | | Trades per day | 3.575 | | | | -| Best Pair | LSK/BTC - 26.26% | -| Worst Pair | ZEC/BTC - -10.18% | -| Best Trade | LSK/BTC - 4.25% | -| Worst Trade | ZEC/BTC - -10.25% | +| Best Pair | LSK/BTC 26.26% | +| Worst Pair | ZEC/BTC -10.18% | +| Best Trade | LSK/BTC 4.25% | +| Worst Trade | ZEC/BTC -10.25% | | Best day | 25.27% | | Worst day | -30.67% | | Avg. Duration Winners | 4:23:00 | diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 3e44a6067..b3799856e 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -411,12 +411,13 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Total Profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"), ('Trades per day', strat_results['trades_per_day']), ('', ''), # Empty line to improve readability - ('Best Pair', f"{strat_results['best_pair']['key']} - " + ('Best Pair', f"{strat_results['best_pair']['key']} " f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"), - ('Worst Pair', f"{strat_results['worst_pair']['key']} - " + ('Worst Pair', f"{strat_results['worst_pair']['key']} " f"{round(strat_results['worst_pair']['profit_sum_pct'], 2)}%"), ('Best trade', f"{best_trade['pair']} {round(best_trade['profit_percent'] * 100, 2)}%"), - ('Worst trade', f"{worst_trade['pair']} {round(worst_trade['profit_percent'] * 100, 2)}%"), + ('Worst trade', f"{worst_trade['pair']} " + f"{round(worst_trade['profit_percent'] * 100, 2)}%"), ('Best day', f"{round(strat_results['backtest_best_day'] * 100, 2)}%"), ('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"), From 6a74c57c3d5c4ab422ced69643459c70269981ab Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 11:33:25 -0600 Subject: [PATCH 090/119] Pair name-based sorting. Attempt at more rational string sorting. Change test to show not working as expected. --- freqtrade/pairlist/PerformanceFilter.py | 2 +- tests/pairlist/test_pairlist.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index 2d360a346..5e1ec3c66 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -55,7 +55,7 @@ class PerformanceFilter(IPairList): # set initial value for pairs with no trades to 0 # and sort the list using performance and count sorted_df = list_df.merge(performance, on='pair', how='left')\ - .fillna(0).sort_values(by=['profit', 'count'], ascending=False) + .fillna(0).sort_values(by=['profit', 'count', 'pair'], ascending=False) pairlist = sorted_df['pair'].tolist() return pairlist diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index c62ec81f3..475691327 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -305,7 +305,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # PerformanceFilter ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PerformanceFilter"}], - "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT', 'ADADOUBLE/USDT']), + "USDT", ['ETH/USDT', 'NANO/USDT', 'ADADOUBLE/USDT', 'ADAHALF/USDT']), # AgeFilter only ([{"method": "AgeFilter", "min_days_listed": 2}], "BTC", 'filter_at_the_beginning'), # OperationalException expected From 323c0657f8a80b0af14d0ff18920f431f66af7a0 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 12:17:03 -0600 Subject: [PATCH 091/119] Sort by profit after sort by count/pair --- freqtrade/pairlist/PerformanceFilter.py | 21 +++++++++++++-------- tests/pairlist/test_pairlist.py | 17 ++++++++++++----- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index 5e1ec3c66..cdc3c78ad 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -31,17 +31,17 @@ class PerformanceFilter(IPairList): def short_desc(self) -> str: """ - Short whitelist method description - used for startup-messages + Short allowlist method description - used for startup-messages """ return f"{self.name} - Sorting pairs by performance." def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ - Filters and sorts pairlist and returns the whitelist again. + Filters and sorts pairlist and returns the allowlist again. Called on each bot iteration - please use internal caching if necessary :param pairlist: pairlist to filter or sort :param tickers: Tickers (from exchange.get_tickers()). May be cached. - :return: new whitelist + :return: new allowlist """ # Get the trading performance for pairs from database performance = pd.DataFrame(Trade.get_overall_performance()) @@ -49,13 +49,18 @@ class PerformanceFilter(IPairList): # Skip performance-based sorting if no performance data is available if len(performance) == 0: return pairlist - - # get pairlist from performance dataframe values + + # Get pairlist from performance dataframe values list_df = pd.DataFrame({'pair': pairlist}) - # set initial value for pairs with no trades to 0 - # and sort the list using performance and count + + # Set initial value for pairs with no trades to 0 + # Sort the list using: + # - primarily performance (high to low) + # - then count (low to high, so as to favor same performance with fewer trades) + # - then pair name alphametically sorted_df = list_df.merge(performance, on='pair', how='left')\ - .fillna(0).sort_values(by=['profit', 'count', 'pair'], ascending=False) + .fillna(0).sort_values(by=['count', 'pair'], ascending=True)\ + .sort_values(by=['profit'], ascending=False) pairlist = sorted_df['pair'].tolist() return pairlist diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 475691327..244f92d8b 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -748,13 +748,20 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): [{'pair': 'ETH/BTC', 'profit': -5, 'count': 100}, {'pair': 'TKN/BTC', 'profit': 4, 'count': 2}], ['TKN/BTC', 'LTC/BTC', 'ETH/BTC']), - # Tie in performance data broken by count + # Tie in performance data broken by count (ascending) ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], - [{'pair': 'LTC/BTC', 'profit': -5, 'count': 101}, - {'pair': 'TKN/BTC', 'profit': -5, 'count': 2}, - {'pair': 'ETH/BTC', 'profit': -5, 'count': 100}], - ['LTC/BTC', 'ETH/BTC', 'TKN/BTC']), + [{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 101}, + {'pair': 'TKN/BTC', 'profit': -5.01, 'count': 2}, + {'pair': 'ETH/BTC', 'profit': -5.01, 'count': 100}], + ['TKN/BTC', 'ETH/BTC', 'LTC/BTC']), + # Tie in performance and count, broken by alphabetical sort + ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], + ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], + [{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 1}, + {'pair': 'TKN/BTC', 'profit': -5.01, 'count': 1}, + {'pair': 'ETH/BTC', 'profit': -5.01, 'count': 1}], + ['ETH/BTC', 'LTC/BTC', 'TKN/BTC']), ]) def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance, allowlist_result, tickers, markets, ohlcv_history_list): From d6c93919246a7e1e250f2934b3da7417a292118b Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 12:18:23 -0600 Subject: [PATCH 092/119] Restoring expectation --- tests/pairlist/test_pairlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 244f92d8b..4b4f51b37 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -305,7 +305,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # PerformanceFilter ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PerformanceFilter"}], - "USDT", ['ETH/USDT', 'NANO/USDT', 'ADADOUBLE/USDT', 'ADAHALF/USDT']), + "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT', 'ADADOUBLE/USDT']), # AgeFilter only ([{"method": "AgeFilter", "min_days_listed": 2}], "BTC", 'filter_at_the_beginning'), # OperationalException expected From e7a035eefe3bdcaeca5688051b778f7b48788505 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 12:29:31 -0600 Subject: [PATCH 093/119] Lint --- freqtrade/pairlist/PerformanceFilter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/PerformanceFilter.py b/freqtrade/pairlist/PerformanceFilter.py index cdc3c78ad..92a97099e 100644 --- a/freqtrade/pairlist/PerformanceFilter.py +++ b/freqtrade/pairlist/PerformanceFilter.py @@ -49,7 +49,7 @@ class PerformanceFilter(IPairList): # Skip performance-based sorting if no performance data is available if len(performance) == 0: return pairlist - + # Get pairlist from performance dataframe values list_df = pd.DataFrame({'pair': pairlist}) @@ -60,7 +60,7 @@ class PerformanceFilter(IPairList): # - then pair name alphametically sorted_df = list_df.merge(performance, on='pair', how='left')\ .fillna(0).sort_values(by=['count', 'pair'], ascending=True)\ - .sort_values(by=['profit'], ascending=False) + .sort_values(by=['profit'], ascending=False) pairlist = sorted_df['pair'].tolist() return pairlist From 4b6f5b92b59e21896035c2f47475a3fcdf07c377 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 12:47:36 -0600 Subject: [PATCH 094/119] Remove non-pertinent test case --- tests/pairlist/test_pairlist.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 4b4f51b37..1d2f16b45 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -302,10 +302,6 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter"}], "USDT", 3), # whitelist_result is integer -- check only length of randomized pairlist - # PerformanceFilter - ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, - {"method": "PerformanceFilter"}], - "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT', 'ADADOUBLE/USDT']), # AgeFilter only ([{"method": "AgeFilter", "min_days_listed": 2}], "BTC", 'filter_at_the_beginning'), # OperationalException expected From 1791495475e1e0bbab0b820a065b627b92f28f7d Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 16:50:44 -0600 Subject: [PATCH 095/119] Trigger another run of tests --- tests/pairlist/test_pairlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 1d2f16b45..884be3c24 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -332,7 +332,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # PerformanceFilter only ([{"method": "PerformanceFilter"}], - "BTC", 'filter_at_the_beginning'), # OperationalException expected + "BTC", 'filter_at_the_beginning'), # OperationalException expected # SpreadFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}], From 90070f0dc54cd2ea92268ce75240ff3972523666 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sat, 28 Nov 2020 17:17:40 -0600 Subject: [PATCH 096/119] Force test rerun --- tests/pairlist/test_pairlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 884be3c24..1d2f16b45 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -332,7 +332,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # PerformanceFilter only ([{"method": "PerformanceFilter"}], - "BTC", 'filter_at_the_beginning'), # OperationalException expected + "BTC", 'filter_at_the_beginning'), # OperationalException expected # SpreadFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}], From 5f8e67d2b25c40454343414653e02b48fd4518d8 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sun, 29 Nov 2020 05:05:54 -0600 Subject: [PATCH 097/119] Update docs/includes/pairlists.md Co-authored-by: Matthias --- docs/includes/pairlists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index a1bbebbf7..844f1d70a 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -77,7 +77,7 @@ This filter allows freqtrade to ignore pairs until they have been listed for at #### PerformanceFilter -Sorts pairs by performance, as follows: +Sorts pairs by past trade performance, as follows: 1. Positive performance. 2. No closed trades yet. 3. Negative performance. From 99abe52043ab7b7f54e79fd75c1c80831eafebf5 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sun, 29 Nov 2020 10:30:02 -0600 Subject: [PATCH 098/119] Trigger CI --- tests/pairlist/test_pairlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 1d2f16b45..1f434ae34 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -326,7 +326,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # ShuffleFilter only ([{"method": "ShuffleFilter", "seed": 42}], "BTC", 'filter_at_the_beginning'), # OperationalException expected - # PerformanceFilter after StaticPairList + # PerformanceFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), From b7de18608d8f977578e56d3c83b88fbbbb459f3e Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Sun, 29 Nov 2020 10:30:43 -0600 Subject: [PATCH 099/119] Trigger CI --- tests/pairlist/test_pairlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 1f434ae34..1d2f16b45 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -326,7 +326,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # ShuffleFilter only ([{"method": "ShuffleFilter", "seed": 42}], "BTC", 'filter_at_the_beginning'), # OperationalException expected - # PerformanceFilter after StaticPairList + # PerformanceFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), From f17c7f0609f66f33b4e14fc79c9f56fd6665e394 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 05:42:00 +0000 Subject: [PATCH 100/119] Bump plotly from 4.12.0 to 4.13.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 4.12.0 to 4.13.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v4.12.0...v4.13.0) Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index bd40bc0b5..1c3b03133 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==4.12.0 +plotly==4.13.0 From 275cfb3a9c8bfa69be15a14b3b38252df86f061a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 05:42:12 +0000 Subject: [PATCH 101/119] Bump ccxt from 1.38.13 to 1.38.55 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.38.13 to 1.38.55. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.38.13...1.38.55) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7490688d4..f72e30480 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.4 pandas==1.1.4 -ccxt==1.38.13 +ccxt==1.38.55 aiohttp==3.7.3 SQLAlchemy==1.3.20 python-telegram-bot==13.0 From 14d44b2cd6b8c9dc5884ee61b3432a0df43d131b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 08:02:09 +0000 Subject: [PATCH 102/119] Bump python-telegram-bot from 13.0 to 13.1 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 13.0 to 13.1. - [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases) - [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst) - [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v13.0...v13.1) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f72e30480..f59754f93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas==1.1.4 ccxt==1.38.55 aiohttp==3.7.3 SQLAlchemy==1.3.20 -python-telegram-bot==13.0 +python-telegram-bot==13.1 arrow==0.17.0 cachetools==4.1.1 requests==2.25.0 From 202ca88e2311bda492757351cef667630fa498de Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Nov 2020 17:37:19 +0100 Subject: [PATCH 103/119] Changes to pi steup --- docs/installation.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 9b15c9685..b6197c905 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -105,13 +105,17 @@ OS Specific steps are listed first, the [Common](#common) section below is neces ``` === "RaspberryPi/Raspbian" - The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/) from at least September 2019. + The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/). This image comes with python3.7 preinstalled, making it easy to get freqtrade up and running. Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied. + ``` bash sudo apt-get install python3-venv libatlas-base-dev + # Use pywheels.org to speed up installation + sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > sudo tee /etc/pip.conf + git clone https://github.com/freqtrade/freqtrade.git cd freqtrade @@ -120,6 +124,7 @@ OS Specific steps are listed first, the [Common](#common) section below is neces !!! Note "Installation duration" Depending on your internet speed and the Raspberry Pi version, installation can take multiple hours to complete. + Due to this, we recommend to use the prebuild docker-image for Raspberry, by following the [Docker quickstart documentation](docker_quickstart.md) !!! Note The above does not install hyperopt dependencies. To install these, please use `python3 -m pip install -e .[hyperopt]`. From 95b24ba8a931ab8a7d440548f820a40342fb694e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Nov 2020 20:56:14 +0100 Subject: [PATCH 104/119] Update setup.sh with some specifics --- docs/installation.md | 4 ++-- setup.sh | 28 ++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index b6197c905..4a2450ea2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -90,13 +90,13 @@ Each time you open a new terminal, you must run `source .env/bin/activate`. ## Custom Installation -We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros. +We've included/collected install instructions for Ubuntu, MacOS, and Windows. These are guidelines and your success may vary with other distros. OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems. !!! Note Python3.6 or higher and the corresponding pip are assumed to be available. -=== "Ubuntu 16.04" +=== "Ubuntu/Debian" #### Install necessary dependencies ```bash diff --git a/setup.sh b/setup.sh index 049a6a77e..83ba42d9b 100755 --- a/setup.sh +++ b/setup.sh @@ -61,13 +61,25 @@ function updateenv() { read -p "Do you want to install dependencies for dev [y/N]? " if [[ $REPLY =~ ^[Yy]$ ]] then - ${PYTHON} -m pip install --upgrade -r requirements-dev.txt + REQUIREMENTS=requirements-dev.txt else - ${PYTHON} -m pip install --upgrade -r requirements.txt - echo "Dev dependencies ignored." + REQUIREMENTS=requirements.txt + fi + SYS_ARCH=$(uname -m) + if [ "${SYS_ARCH}" == "armv7l" ]; then + echo "Detected Raspberry, installing cython." + ${PYTHON} -m pip install --upgrade cython + fi + ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} + if [ $? -ne 0 ]; then + echo "Failed installing dependencies" + exit 1 fi - ${PYTHON} -m pip install -e . + if [ $? -ne 0 ]; then + echo "Failed installing Freqtrade" + exit 1 + fi echo "pip install completed" echo } @@ -134,11 +146,11 @@ function reset() { git fetch -a - if [ "1" == $(git branch -vv |grep -c "* develop") ] + if [ "1" == $(git branch -vv | grep -c "* develop") ] then echo "- Hard resetting of 'develop' branch." git reset --hard origin/develop - elif [ "1" == $(git branch -vv |grep -c "* stable") ] + elif [ "1" == $(git branch -vv | grep -c "* stable") ] then echo "- Hard resetting of 'stable' branch." git reset --hard origin/stable @@ -149,7 +161,7 @@ function reset() { fi if [ -d ".env" ]; then - echo "- Delete your previous virtual env" + echo "- Deleting your previous virtual env" rm -rf .env fi echo @@ -253,7 +265,7 @@ function install() { echo "Run the bot !" echo "-------------------------" echo "You can now use the bot by executing 'source .env/bin/activate; freqtrade '." - echo "You can see the list of available bot subcommands by executing 'source .env/bin/activate; freqtrade --help'." + echo "You can see the list of available bot sub-commands by executing 'source .env/bin/activate; freqtrade --help'." echo "You verify that freqtrade is installed successfully by running 'source .env/bin/activate; freqtrade --version'." } From 5f70d1f9a7bc58969cee2c3fa40b981047d22a9a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Nov 2020 21:10:11 +0100 Subject: [PATCH 105/119] Ask for hyperopt installation during setup closes #2871 --- docs/installation.md | 2 +- setup.sh | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 4a2450ea2..5cc0e03f4 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -114,7 +114,7 @@ OS Specific steps are listed first, the [Common](#common) section below is neces ``` bash sudo apt-get install python3-venv libatlas-base-dev # Use pywheels.org to speed up installation - sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > sudo tee /etc/pip.conf + sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > tee /etc/pip.conf git clone https://github.com/freqtrade/freqtrade.git cd freqtrade diff --git a/setup.sh b/setup.sh index 83ba42d9b..8896331e3 100755 --- a/setup.sh +++ b/setup.sh @@ -56,6 +56,7 @@ function updateenv() { exit 1 fi source .env/bin/activate + SYS_ARCH=$(uname -m) echo "pip install in-progress. Please wait..." ${PYTHON} -m pip install --upgrade pip read -p "Do you want to install dependencies for dev [y/N]? " @@ -65,12 +66,21 @@ function updateenv() { else REQUIREMENTS=requirements.txt fi - SYS_ARCH=$(uname -m) + REQUIREMENTS_HYPEROPT="" + if [ "${SYS_ARCH}" != "armv7l" ]; then + # Is not Raspberry + read -p "Do you want to install hyperopt dependencies for dev [y/N]? " + if [[ $REPLY =~ ^[Yy]$ ]] + then + REQUIREMENTS_HYPEROPT="-r requirements-hyperopt.txt" + fi + fi + if [ "${SYS_ARCH}" == "armv7l" ]; then echo "Detected Raspberry, installing cython." ${PYTHON} -m pip install --upgrade cython fi - ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} + ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} ${REQUIREMENTS_HYPEROPT} if [ $? -ne 0 ]; then echo "Failed installing dependencies" exit 1 From cec771b59396658d7d89241e869da9459411a4b0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Nov 2020 21:17:50 +0100 Subject: [PATCH 106/119] Ask for plotting dependency installation --- setup.sh | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/setup.sh b/setup.sh index 8896331e3..af5e70691 100755 --- a/setup.sh +++ b/setup.sh @@ -67,20 +67,25 @@ function updateenv() { REQUIREMENTS=requirements.txt fi REQUIREMENTS_HYPEROPT="" - if [ "${SYS_ARCH}" != "armv7l" ]; then + REQUIREMENTS_PLOT="" + read -p "Do you want to install plotting dependencies (plotly) [y/N]? " + if [[ $REPLY =~ ^[Yy]$ ]] + then + REQUIREMENTS_PLOT="-r requirements-plot.txt" + fi + if [ "${SYS_ARCH}" == "armv7l" ]; then + echo "Detected Raspberry, installing cython, skipping hyperopt installation." + ${PYTHON} -m pip install --upgrade cython + else # Is not Raspberry - read -p "Do you want to install hyperopt dependencies for dev [y/N]? " + read -p "Do you want to install hyperopt dependencies [y/N]? " if [[ $REPLY =~ ^[Yy]$ ]] then REQUIREMENTS_HYPEROPT="-r requirements-hyperopt.txt" fi fi - if [ "${SYS_ARCH}" == "armv7l" ]; then - echo "Detected Raspberry, installing cython." - ${PYTHON} -m pip install --upgrade cython - fi - ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} ${REQUIREMENTS_HYPEROPT} + ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} ${REQUIREMENTS_HYPEROPT} ${REQUIREMENTS_PLOT} if [ $? -ne 0 ]; then echo "Failed installing dependencies" exit 1 From 36b7edc342cac2680c7408eb17cf412b3416b863 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Dec 2020 19:55:20 +0100 Subject: [PATCH 107/119] Update typing errors --- freqtrade/rpc/api_server.py | 2 +- freqtrade/rpc/rpc.py | 2 +- freqtrade/rpc/telegram.py | 48 +++++++++++++++++++++---------------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 384d7c6c2..8c2c203e6 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -470,7 +470,7 @@ class ApiServer(RPC): @require_login @rpc_catch_errors - def _trades_delete(self, tradeid): + def _trades_delete(self, tradeid: int): """ Handler for DELETE /trades/ endpoint. Removes the trade from the database (tries to cancel open orders first!) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index e608a2274..9ac271ba0 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -542,7 +542,7 @@ class RPC: else: return None - def _rpc_delete(self, trade_id: str) -> Dict[str, Union[str, int]]: + def _rpc_delete(self, trade_id: int) -> Dict[str, Union[str, int]]: """ Handler for delete . Delete the given trade and close eventually existing open orders. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 31d5bbfbd..7239eab02 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -5,11 +5,11 @@ This module manage Telegram communication """ import json import logging -from typing import Any, Callable, Dict +from typing import Any, Callable, Dict, List, Union import arrow from tabulate import tabulate -from telegram import ParseMode, ReplyKeyboardMarkup, Update +from telegram import KeyboardButton, ParseMode, ReplyKeyboardMarkup, Update from telegram.error import NetworkError, TelegramError from telegram.ext import CallbackContext, CommandHandler, Updater from telegram.utils.helpers import escape_markdown @@ -71,7 +71,7 @@ class Telegram(RPC): """ super().__init__(freqtrade) - self._updater: Updater = None + self._updater: Updater self._config = freqtrade.config self._init() if self._config.get('fiat_display_currency', None): @@ -231,7 +231,7 @@ class Telegram(RPC): :return: None """ - if 'table' in context.args: + if context.args and 'table' in context.args: self._status_table(update, context) return @@ -305,7 +305,7 @@ class Telegram(RPC): stake_cur = self._config['stake_currency'] fiat_disp_cur = self._config.get('fiat_display_currency', '') try: - timescale = int(context.args[0]) + timescale = int(context.args[0]) if context.args else 0 except (TypeError, ValueError, IndexError): timescale = 7 try: @@ -485,7 +485,10 @@ class Telegram(RPC): :return: None """ - trade_id = context.args[0] if len(context.args) > 0 else None + trade_id = context.args[0] if context.args and len(context.args) > 0 else None + if not trade_id: + self._send_msg("You must specify a trade-id or 'all'.") + return try: msg = self._rpc_forcesell(trade_id) self._send_msg('Forcesell Result: `{result}`'.format(**msg)) @@ -502,13 +505,13 @@ class Telegram(RPC): :param update: message update :return: None """ - - pair = context.args[0] - price = float(context.args[1]) if len(context.args) > 1 else None - try: - self._rpc_forcebuy(pair, price) - except RPCException as e: - self._send_msg(str(e)) + if context.args: + pair = context.args[0] + price = float(context.args[1]) if len(context.args) > 1 else None + try: + self._rpc_forcebuy(pair, price) + except RPCException as e: + self._send_msg(str(e)) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: @@ -521,7 +524,7 @@ class Telegram(RPC): """ stake_cur = self._config['stake_currency'] try: - nrecent = int(context.args[0]) + nrecent = int(context.args[0]) if context.args else 10 except (TypeError, ValueError, IndexError): nrecent = 10 try: @@ -554,9 +557,10 @@ class Telegram(RPC): :param update: message update :return: None """ - - trade_id = context.args[0] if len(context.args) > 0 else None try: + if not context.args or len(context.args) == 0: + raise RPCException("Trade-id not set.") + trade_id = int(context.args[0]) msg = self._rpc_delete(trade_id) self._send_msg(( '`{result_msg}`\n' @@ -676,7 +680,7 @@ class Telegram(RPC): """ try: try: - limit = int(context.args[0]) + limit = int(context.args[0]) if context.args else 10 except (TypeError, ValueError, IndexError): limit = 10 logs = self._rpc_get_logs(limit)['logs'] @@ -802,7 +806,7 @@ class Telegram(RPC): f"*Current state:* `{val['state']}`" ) - def _send_msg(self, msg: str, parse_mode: ParseMode = ParseMode.MARKDOWN, + def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN, disable_notification: bool = False) -> None: """ Send given markdown message @@ -812,9 +816,11 @@ class Telegram(RPC): :return: None """ - keyboard = [['/daily', '/profit', '/balance'], - ['/status', '/status table', '/performance'], - ['/count', '/start', '/stop', '/help']] + keyboard: List[List[Union[str, KeyboardButton]]] = [ + ['/daily', '/profit', '/balance'], + ['/status', '/status table', '/performance'], + ['/count', '/start', '/stop', '/help'] + ] reply_markup = ReplyKeyboardMarkup(keyboard) From 5dfa1807a3c99499f3304d6046eeed96f8cc6825 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Dec 2020 19:57:43 +0100 Subject: [PATCH 108/119] Fix tests after small updates --- tests/rpc/test_rpc_telegram.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ace44a34a..8264ab3df 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -58,7 +58,6 @@ def test__init__(default_conf, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) telegram = Telegram(get_patched_freqtradebot(mocker, default_conf)) - assert telegram._updater is None assert telegram._config == default_conf @@ -881,7 +880,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: context.args = [] telegram._forcesell(update=update, context=context) assert msg_mock.call_count == 1 - assert 'invalid argument' in msg_mock.call_args_list[0][0][0] + assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0] # Invalid argument msg_mock.reset_mock() @@ -1251,7 +1250,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee): context.args = [] telegram._delete_trade(update=update, context=context) - assert "invalid argument" in msg_mock.call_args_list[0][0][0] + assert "Trade-id not set." in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() create_mock_trades(fee) From d6cc3d737453fb6e6f0aad81d07bd63ddca2cbb3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Dec 2020 19:58:06 +0100 Subject: [PATCH 109/119] Improve FAQ related to question in #4023 --- docs/faq.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index aa33218fb..3cf5a74ca 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## Beginner Tips & Tricks -* When you work with your strategy & hyperopt file you should use a proper code editor like vscode or Pycharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely, pointed out by Freqtrade during startup). +* When you work with your strategy & hyperopt file you should use a proper code editor like VScode or Pycharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely, pointed out by Freqtrade during startup). ## Freqtrade common issues @@ -17,7 +17,7 @@ This could have the following reasons: * The installation did not work correctly. * Please check the [Installation documentation](installation.md). -### I have waited 5 minutes, why hasn't the bot made any trades yet?! +### I have waited 5 minutes, why hasn't the bot made any trades yet? * Depending on the buy strategy, the amount of whitelisted coins, the situation of the market etc, it can take up to hours to find good entry @@ -47,9 +47,9 @@ like pauses. You can stop your bot, adjust settings and start it again. That's great. We have a nice backtesting and hyperoptimization 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 Buys the coins being held and not perform anymore Buys? -You can use the `/forcesell all` command from Telegram. +You can use the `/stopbuy` to prevent future buys, followed `/forcesell all` (sell all open trades) command from Telegram. ### I want to run multiple bots on the same machine @@ -59,7 +59,7 @@ Please look at the [advanced setup documentation Page](advanced-setup.md#running This message is just a warning that the latest candles had missing candles in them. Depending on the exchange, this can indicate that the pair didn't have a trade for the timeframe you are using - and the exchange does only return candles with volume. -On low volume pairs, this is a rather common occurance. +On low volume pairs, this is a rather common occurrence. If this happens for all pairs in the pairlist, this might indicate a recent exchange downtime. Please check your exchange's public channels for details. @@ -130,7 +130,7 @@ On Windows, the `--logfile` option is also supported by Freqtrade and you can us ### How many epoch do I need to get a good Hyperopt result? Per default Hyperopt called without the `-e`/`--epochs` command line option will only -run 100 epochs, means 100 evals of your triggers, guards, ... Too few +run 100 epochs, means 100 evaluations 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. @@ -140,7 +140,7 @@ Since hyperopt uses Bayesian search, running for too many epochs may not produce It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going. ```bash -freqtrade hyperopt --hyperop SampleHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy SampleStrategy -e 1000 +freqtrade hyperopt --hyperopt SampleHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy SampleStrategy -e 1000 ``` ### Why does it take a long time to run hyperopt? @@ -151,21 +151,21 @@ freqtrade hyperopt --hyperop SampleHyperopt --hyperopt-loss SharpeHyperOptLossDa This answer was written during 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 +* 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 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. -Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th +already 8\*10^9\*10 evaluations. A roughly total of 80 billion evaluations. +Did you run 100 000 evaluations? Congrats, you've done roughly 1 / 100 000 th of the search space, assuming that the bot never tests the same parameters more than once. * The time it takes to run 1000 hyperopt epochs depends on things like: The available cpu, hard-disk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 10.0000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades. Example: 4% profit 650 times vs 0,3% profit a trade 10.000 times in a year. If we assume you set the --timerange to 365 days. -Example: +Example: `freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601` ## Edge module From c1fffb9925ea7d5f5fb661ac4a63f6eaff4b7754 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Tue, 1 Dec 2020 21:38:54 +0100 Subject: [PATCH 110/119] Update faq.md --- docs/faq.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 3cf5a74ca..e3a7895e3 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,15 +2,15 @@ ## Beginner Tips & Tricks -* When you work with your strategy & hyperopt file you should use a proper code editor like VScode or Pycharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely, pointed out by Freqtrade during startup). +* When you work with your strategy & hyperopt file you should use a proper code editor like VSCode or PyCharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely pointed out by Freqtrade during startup). ## Freqtrade common issues ### The bot does not start -Running the bot with `freqtrade trade --config config.json` does show the output `freqtrade: command not found`. +Running the bot with `freqtrade trade --config config.json` shows the output `freqtrade: command not found`. -This could have the following reasons: +This could be caused by the following reasons: * The virtual environment is not active * run `source .env/bin/activate` to activate the virtual environment @@ -20,12 +20,12 @@ This could have the following reasons: ### I have waited 5 minutes, why hasn't the bot made any trades yet? * Depending on the buy strategy, the amount of whitelisted coins, the -situation of the market etc, it can take up to hours to find good entry +situation of the market etc, it can take up to hours to find a good entry position for a trade. Be patient! -* Or it may because of a configuration error? Best check the logs, it's usually telling you if the bot is simply not getting buy signals (only heartbeat messages), or if there is something wrong (errors / exceptions in the log). +* It may be because of a configuration error. It's best check the logs, they usually tell you if the bot is simply not getting buy signals (only heartbeat messages), or if there is something wrong (errors / exceptions in the log). -### I have made 12 trades already, why is my total profit negative?! +### I have made 12 trades already, why is my total profit negative? I understand your disappointment but unfortunately 12 trades is just not enough to say anything. If you run backtesting, you can see that our @@ -36,20 +36,18 @@ 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 make changes to the config. Can I do that without having to kill the bot? -Not quite. Trades are persisted to a database but the configuration is -currently only read when the bot is killed and restarted. `/stop` more -like pauses. You can stop your bot, adjust settings and start it again. +Yes. You can edit your config, use the `/stop` command in Telegram, followed by `/reload_config` and the bot will run with the new config. ### I want to improve the bot with a new strategy That's great. We have a nice backtesting and hyperoptimization setup. See the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands). -### Is there a setting to only Buys 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 `/stopbuy` to prevent future buys, followed `/forcesell all` (sell all open trades) command from Telegram. +You can use the `/stopbuy` command in Telegram to prevent future buys, followed by `/forcesell all` (sell all open trades). ### I want to run multiple bots on the same machine @@ -73,7 +71,7 @@ Read [the Bittrex section about restricted markets](exchanges.md#restricted-mark ### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy -As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Probably your strategy was written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex). +As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Your strategy was probably written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex). To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market": @@ -85,7 +83,7 @@ To fix it for Bittrex, redefine order types in the strategy to use "limit" inste } ``` -Same fix should be done in the configuration file, if order types are defined in your custom config rather than in the strategy. +The same fix should be applied in the configuration file, if order types are defined in your custom config rather than in the strategy. ### How do I search the bot logs for something? @@ -127,7 +125,7 @@ On Windows, the `--logfile` option is also supported by Freqtrade and you can us ## Hyperopt module -### How many epoch do I need to get a good Hyperopt result? +### How many epochs do I need to get a good Hyperopt result? Per default Hyperopt called without the `-e`/`--epochs` command line option will only run 100 epochs, means 100 evaluations of your triggers, guards, ... Too few From 4bc24ece41c61100fbd352166f588f401345cf55 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Tue, 1 Dec 2020 21:49:50 +0100 Subject: [PATCH 111/119] Update faq.md --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index e3a7895e3..1940b4e6c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -12,7 +12,7 @@ Running the bot with `freqtrade trade --config config.json` shows the output `fr This could be caused by the following reasons: -* The virtual environment is not active +* The virtual environment is not active. * run `source .env/bin/activate` to activate the virtual environment * The installation did not work correctly. * Please check the [Installation documentation](installation.md). From 3c4fe66d86e2eeb88f0a06791890478c1e3e4405 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Tue, 1 Dec 2020 21:50:51 +0100 Subject: [PATCH 112/119] Update faq.md --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 1940b4e6c..337b87ec8 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -13,7 +13,7 @@ Running the bot with `freqtrade trade --config config.json` shows the output `fr This could be caused by the following reasons: * The virtual environment is not active. - * run `source .env/bin/activate` to activate the virtual environment + * Run `source .env/bin/activate` to activate the virtual environment. * The installation did not work correctly. * Please check the [Installation documentation](installation.md). From d039ce1fb3c84bc5535877f1afb04840a9d7c6cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Dec 2020 06:46:18 +0100 Subject: [PATCH 113/119] Update available columns for hyperopt closes #4025 --- docs/advanced-hyperopt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md index 59ebc16b5..1ace61769 100644 --- a/docs/advanced-hyperopt.md +++ b/docs/advanced-hyperopt.md @@ -77,7 +77,7 @@ Currently, the arguments are: * `results`: DataFrame containing the result The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`): - `pair, profit_percent, profit_abs, open_time, close_time, open_index, close_index, trade_duration, open_at_end, open_rate, close_rate, sell_reason` + `pair, profit_percent, profit_abs, open_date, open_rate, open_fee, close_date, close_rate, close_fee, amount, trade_duration, open_at_end, sell_reason` * `trade_count`: Amount of trades (identical to `len(results)`) * `min_date`: Start date of the hyperopting TimeFrame * `min_date`: End date of the hyperopting TimeFrame From c09c23eab15b920f757199fabebab9699d37ff4e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Dec 2020 07:51:59 +0100 Subject: [PATCH 114/119] Make sure non-int telegram values don't crash the bot --- tests/rpc/test_rpc_telegram.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 8264ab3df..33010484d 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1222,8 +1222,14 @@ def test_telegram_trades(mocker, update, default_conf, fee): telegram._trades(update=update, context=context) assert "0 recent trades:" in msg_mock.call_args_list[0][0][0] assert "
" not in msg_mock.call_args_list[0][0][0]
-
     msg_mock.reset_mock()
+
+    context.args = ['hello']
+    telegram._trades(update=update, context=context)
+    assert "0 recent trades:" in msg_mock.call_args_list[0][0][0]
+    assert "
" not in msg_mock.call_args_list[0][0][0]
+    msg_mock.reset_mock()
+
     create_mock_trades(fee)
 
     context = MagicMock()

From 9b4a81c0a4111e5779e07487ed570d717719d505 Mon Sep 17 00:00:00 2001
From: Samaoo 
Date: Wed, 2 Dec 2020 08:40:49 +0100
Subject: [PATCH 115/119] Update faq.md

---
 docs/faq.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/faq.md b/docs/faq.md
index 337b87ec8..b424cd31d 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -23,7 +23,7 @@ This could be caused by the following reasons:
 situation of the market etc, it can take up to hours to find a good entry
 position for a trade. Be patient!
 
-* It may be because of a configuration error. It's best check the logs, they usually tell you if the bot is simply not getting buy signals (only heartbeat messages), or if there is something wrong (errors / exceptions in the log).
+* It may be because of a configuration error. It's best to check the logs, they usually tell you if the bot is simply not getting buy signals (only heartbeat messages), or if there is something wrong (errors / exceptions in the log).
 
 ### I have made 12 trades already, why is my total profit negative?
 

From 2fbbeb970bb768de7c7efb3ed6b1ca2b0c922363 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Fri, 4 Dec 2020 07:42:16 +0100
Subject: [PATCH 116/119] Gracefully handle cases where no buy price was found

closes #4030
---
 freqtrade/freqtradebot.py  | 3 +++
 tests/test_freqtradebot.py | 6 ++++++
 2 files changed, 9 insertions(+)

diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 7416d8236..c8d281852 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -616,6 +616,9 @@ class FreqtradeBot:
             # Calculate price
             buy_limit_requested = self.get_buy_rate(pair, True)
 
+        if not buy_limit_requested:
+            raise PricingError('Could not determine buy price.')
+
         min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
         if min_stake_amount is not None and min_stake_amount > stake_amount:
             logger.warning(
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 64dfb016e..6adef510f 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1074,6 +1074,12 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
     mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order))
     assert not freqtrade.execute_buy(pair, stake_amount)
 
+    # Fail to get price...
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_buy_rate', MagicMock(return_value=0.0))
+
+    with pytest.raises(PricingError, match="Could not determine buy price."):
+        freqtrade.execute_buy(pair, stake_amount)
+
 
 def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None:
     freqtrade = get_patched_freqtradebot(mocker, default_conf)

From 7f453033a4928fd50eefc74bb737e03631f84bc8 Mon Sep 17 00:00:00 2001
From: Samaoo 
Date: Fri, 4 Dec 2020 16:53:41 +0100
Subject: [PATCH 117/119] Update edge.md

---
 docs/edge.md | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/docs/edge.md b/docs/edge.md
index 7442f1927..fd6d2cf7d 100644
--- a/docs/edge.md
+++ b/docs/edge.md
@@ -23,8 +23,8 @@ The Edge Positioning module seeks to improve a strategy's winning probability an
 We raise the following question[^1]:
 
 !!! Question "Which trade is a better option?"
-    a) A trade with 80% of chance of losing $100 and 20% chance of winning $200
- b) A trade with 100% of chance of losing $30 + a) A trade with 80% of chance of losing 100\$ and 20% chance of winning 200\$
+ b) A trade with 100% of chance of losing 30\$ ???+ Info "Answer" The expected value of *a)* is smaller than the expected value of *b)*.
@@ -34,8 +34,8 @@ We raise the following question[^1]: Another way to look at it is to ask a similar question: !!! Question "Which trade is a better option?" - a) A trade with 80% of chance of winning 100 and 20% chance of losing $200
- b) A trade with 100% of chance of winning $30 + a) A trade with 80% of chance of winning 100\$ and 20% chance of losing 200\$
+ b) A trade with 100% of chance of winning 30\$ Edge positioning tries to answer the hard questions about risk/reward and position size automatically, seeking to minimizes the chances of losing of a given strategy. @@ -82,7 +82,7 @@ Risk Reward Ratio ($R$) is a formula used to measure the expected gains of a giv $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ ???+ Example "Worked example of $R$ calculation" - Let's say that you think that the price of *stonecoin* today is $10.0. You believe that, because they will start mining stonecoin, it will go up to $15.0 tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to $0 tomorrow. You are planning to invest $100, which will give you 10 shares (100 / 10). + Let's say that you think that the price of *stonecoin* today is 10.0\$. You believe that, because they will start mining stonecoin, it will go up to 15.0\$ tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to 0\$ tomorrow. You are planning to invest 100\$, which will give you 10 shares (100 / 10). Your potential profit is calculated as: @@ -92,9 +92,9 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ &= 50 \end{aligned}$ - Since the price might go to $0, the $100 dollars invested could turn into 0. + Since the price might go to 0\$, the 100\$ dollars invested could turn into 0. - We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5$). + We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5$\). $\begin{aligned} \text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \frac{\text{investment}}{\text{entry_price}} \\ @@ -109,7 +109,7 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ &= \frac{50}{15}\\ &= 3.33 \end{aligned}$
- What it effectively means is that the strategy have the potential to make 3.33$ for each $1 invested. + What it effectively means is that the strategy have the potential to make 3.33\$ for each 1\$ invested. On a long horizon, that is, on many trades, we can calculate the risk reward by dividing the strategy' average profit on winning trades by the strategy' average loss on losing trades. We can calculate the average profit, $\mu_{win}$, as follows: @@ -141,7 +141,7 @@ $$E = R * W - L$$ $E = R * W - L = 5 * 0.28 - 0.72 = 0.68$
-The expectancy worked out in the example above means that, on average, this strategy' trades will return 1.68 times the size of its losses. Said another way, the strategy makes $1.68 for every $1 it loses, on average. +The expectancy worked out in the example above means that, on average, this strategy' trades will return 1.68 times the size of its losses. Said another way, the strategy makes 1.68\$ for every 1\$ it loses, on average. 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. @@ -222,7 +222,7 @@ Edge module has following configuration options: | `stoploss_range_max` | Maximum stoploss.
*Defaults to `-0.10`.*
**Datatype:** Float | `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.
*Defaults to `-0.001`.*
**Datatype:** Float | `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.
*Defaults to `0.60`.*
**Datatype:** Float -| `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.
*Defaults to `0.20`.*
**Datatype:** Float +| `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.
*Defaults to `0.20`.*
**Datatype:** Float | `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.
*Defaults to `10` (it is highly recommended not to decrease this number).*
**Datatype:** Integer | `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 timeframe. 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 `1440` (one day).*
**Datatype:** Integer | `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`.*
**Datatype:** Boolean From 71e46794b44ca1315c9f78ef98c33aa52cc76fed Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Dec 2020 19:59:26 +0100 Subject: [PATCH 118/119] Add updating documentation closes #4036 --- docs/updating.md | 31 +++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 32 insertions(+) create mode 100644 docs/updating.md diff --git a/docs/updating.md b/docs/updating.md new file mode 100644 index 000000000..b23ce32dc --- /dev/null +++ b/docs/updating.md @@ -0,0 +1,31 @@ +# How to update + +To update your freqtrade installation, please use one of the below methods, corresponding to your installation method. + +## docker-compose + +!!! Note "Legacy installations using the `master` image" + We're switching from master to stable for the release Images - please adjust your docker-file and replace `freqtradeorg/freqtrade:master` with `freqtradeorg/freqtrade:stable` + +``` bash +docker-compose pull +docker-compose up -d +``` + +## Installation via setup script + +``` bash +./setup.sh --update +``` + +!!! Note + Make sure to run this command with your virtual environment disabled! + +## Plain native installation + +Please ensure that you're also updating dependencies - otherwise things might break without you noticing. + +``` bash +git pull +pip install -U -r requirements.txt +``` diff --git a/mkdocs.yml b/mkdocs.yml index 2cc0c9fcb..c791386ae 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,6 +31,7 @@ nav: - Advanced Strategy: strategy-advanced.md - Advanced Hyperopt: advanced-hyperopt.md - Sandbox Testing: sandbox-testing.md + - Updating Freqtrade: updating.md - Deprecated Features: deprecated.md - Contributors Guide: developer.md theme: From 058d40a72c389ab90c643fe1cede812c4c5038b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Dec 2020 08:16:40 +0100 Subject: [PATCH 119/119] Fix telegram /daily command without arguments --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7239eab02..91306bf85 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -305,7 +305,7 @@ class Telegram(RPC): stake_cur = self._config['stake_currency'] fiat_disp_cur = self._config.get('fiat_display_currency', '') try: - timescale = int(context.args[0]) if context.args else 0 + timescale = int(context.args[0]) if context.args else 7 except (TypeError, ValueError, IndexError): timescale = 7 try: diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 33010484d..72d263bff 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -337,6 +337,18 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] + # Reset msg_mock + msg_mock.reset_mock() + context.args = [] + telegram._daily(update=update, context=context) + assert msg_mock.call_count == 1 + assert 'Daily' in msg_mock.call_args_list[0][0][0] + assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] + assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] + assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] + assert str(' 1 trade') in msg_mock.call_args_list[0][0][0] + assert str(' 0 trade') in msg_mock.call_args_list[0][0][0] + # Reset msg_mock msg_mock.reset_mock() freqtradebot.config['max_open_trades'] = 2