From 8e89802b2d099566ffb1de5effdeec10a52f43d2 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 22 May 2020 15:03:49 +0300 Subject: [PATCH 001/191] Split the generation logic and filtering --- freqtrade/pairlist/IPairList.py | 11 +++ freqtrade/pairlist/StaticPairList.py | 11 ++- freqtrade/pairlist/VolumePairList.py | 60 ++++++------ freqtrade/pairlist/pairlistmanager.py | 3 + tests/pairlist/test_pairlist.py | 127 +++++++++++++++++--------- 5 files changed, 135 insertions(+), 77 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index e49ad1561..0a291fdf7 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -8,6 +8,7 @@ from typing import Any, Dict, List from cachetools import TTLCache, cached +from freqtrade.exceptions import OperationalException from freqtrade.exchange import market_is_active @@ -90,6 +91,16 @@ class IPairList(ABC): """ raise NotImplementedError() + 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 + """ + raise OperationalException("This Pairlist Handler should not be used " + "at the first position in the list of Pairlist Handlers.") + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlist and returns the whitelist again. diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 07e559168..0218833e3 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -30,6 +30,15 @@ class StaticPairList(IPairList): """ 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._whitelist_for_active_markets(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. @@ -38,4 +47,4 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist']) + return pairlist diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 6f39ae6d6..d32be3dc9 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -68,6 +68,31 @@ class VolumePairList(IPairList): """ return f"{self.name} - top {self._pairlistconfig['number_assets']} volume pairs." + 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 + """ + # Generate dynamic whitelist + # Must always run if this pairlist is not the first in the list. + if self._last_refresh + self.refresh_period < datetime.now().timestamp(): + self._last_refresh = int(datetime.now().timestamp()) + + # Use fresh pairlist + # Check if pair quote currency equals to the stake currency. + filtered_tickers = [ + v for k, v in tickers.items() + if (self._exchange.get_pair_quote_currency(k) == self._stake_currency + and v[self._sort_key] is not None)] + pairlist = [s['symbol'] for s in filtered_tickers] + else: + # Use the cached pairlist if it's not time yet to refresh + pairlist = cached_pairlist + + return pairlist + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlist and returns the whitelist again. @@ -76,37 +101,8 @@ class VolumePairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - # Generate dynamic whitelist - # Must always run if this pairlist is not the first in the list. - if (self._pairlist_pos != 0 or - (self._last_refresh + self.refresh_period < datetime.now().timestamp())): - - self._last_refresh = int(datetime.now().timestamp()) - pairs = self._gen_pair_whitelist(pairlist, tickers) - else: - pairs = pairlist - - self.log_on_refresh(logger.info, f"Searching {self._number_pairs} pairs: {pairs}") - - return pairs - - def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict) -> List[str]: - """ - Updates the whitelist with with a dynamically generated list - :param pairlist: pairlist to filter or sort - :param tickers: Tickers (from exchange.get_tickers()). - :return: List of pairs - """ - if self._pairlist_pos == 0: - # If VolumePairList is the first in the list, use fresh pairlist - # Check if pair quote currency equals to the stake currency. - filtered_tickers = [ - v for k, v in tickers.items() - if (self._exchange.get_pair_quote_currency(k) == self._stake_currency - and v[self._sort_key] is not None)] - else: - # If other pairlist is in front, use the incoming pairlist. - filtered_tickers = [v for k, v in tickers.items() if k in pairlist] + # Use the incoming pairlist. + filtered_tickers = [v for k, v in tickers.items() if k in pairlist] if self._min_value > 0: filtered_tickers = [ @@ -120,4 +116,6 @@ class VolumePairList(IPairList): # Limit pairlist to the requested number of pairs pairs = pairs[:self._number_pairs] + self.log_on_refresh(logger.info, f"Searching {self._number_pairs} pairs: {pairs}") + return pairs diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 6ad6c610b..177f79083 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -87,6 +87,9 @@ class PairListManager(): # Adjust whitelist if filters are using tickers pairlist = self._prepare_whitelist(self._whitelist.copy(), tickers) + # Generate the pairlist with first Pairlist Handler in the chain + pairlist = self._pairlist_handlers[0].gen_pairlist(self._whitelist, tickers) + # Process all Pairlist Handlers in the chain for pairlist_handler in self._pairlist_handlers: pairlist = pairlist_handler.filter_pairlist(pairlist, tickers) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index e9e688b78..febb8b163 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -19,7 +19,8 @@ def whitelist_conf(default_conf): 'TKN/BTC', 'TRST/BTC', 'SWT/BTC', - 'BCC/BTC' + 'BCC/BTC', + 'HOT/BTC', ] default_conf['exchange']['pair_blacklist'] = [ 'BLK/BTC' @@ -201,21 +202,21 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): assert set(whitelist) == set(pairslist) -@pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [ +@pytest.mark.parametrize("pairlists,base_currency,whitelist_result,operational_exception", [ # VolumePairList only ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC'], False), # Different sorting depending on quote or bid volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], - "BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), + "BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC'], False), ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT']), + "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT'], False), # No pair for ETH, VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "ETH", []), + "ETH", [], False), # No pair for ETH, StaticPairList ([{"method": "StaticPairList"}], - "ETH", []), + "ETH", [], False), # No pair for ETH, all handlers ([{"method": "StaticPairList"}, {"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, @@ -223,57 +224,87 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): {"method": "PriceFilter", "low_price_ratio": 0.03}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, {"method": "ShuffleFilter"}], - "ETH", []), + "ETH", [], False), # Precisionfilter and quote volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, - {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), + {"method": "PrecisionFilter"}], + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC'], False), # Precisionfilter bid ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, - {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), + {"method": "PrecisionFilter"}], + "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC'], False), # PriceFilter and VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.03}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC'], False), # PriceFilter and VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.03}], - "USDT", ['ETH/USDT', 'NANO/USDT']), + "USDT", ['ETH/USDT', 'NANO/USDT'], False), # Hot is removed by precision_filter, Fuel by low_price_filter. ([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.02}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC'], False), # HOT and XRP are removed because below 1250 quoteVolume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "min_value": 1250}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], False), # StaticPairlist only ([{"method": "StaticPairList"}], - "BTC", ['ETH/BTC', 'TKN/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC'], False), # Static Pairlist before VolumePairList - sorting changes ([{"method": "StaticPairList"}, {"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], - "BTC", ['TKN/BTC', 'ETH/BTC']), + "BTC", ['HOT/BTC', 'TKN/BTC', 'ETH/BTC'], False), # SpreadFilter ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}], - "USDT", ['ETH/USDT']), + "USDT", ['ETH/USDT'], False), # ShuffleFilter ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter", "seed": 77}], - "USDT", ['ETH/USDT', 'ADAHALF/USDT', 'NANO/USDT']), + "USDT", ['ETH/USDT', 'ADAHALF/USDT', 'NANO/USDT'], False), # ShuffleFilter, other seed ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter", "seed": 42}], - "USDT", ['NANO/USDT', 'ETH/USDT', 'ADAHALF/USDT']), + "USDT", ['NANO/USDT', 'ETH/USDT', 'ADAHALF/USDT'], False), # ShuffleFilter, no seed ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter"}], - "USDT", 3), + "USDT", 3, False), + # PrecisionFilter after StaticPairList + ([{"method": "StaticPairList"}, + {"method": "PrecisionFilter"}], + "BTC", ['ETH/BTC', 'TKN/BTC'], False), + # PrecisionFilter only + ([{"method": "PrecisionFilter"}], + "BTC", ['ETH/BTC', 'TKN/BTC'], True), + # PriceFilter after StaticPairList + ([{"method": "StaticPairList"}, + {"method": "PriceFilter", "low_price_ratio": 0.02}], + "BTC", ['ETH/BTC', 'TKN/BTC'], False), + # PriceFilter only + ([{"method": "PriceFilter", "low_price_ratio": 0.02}], + "BTC", ['ETH/BTC', 'TKN/BTC'], True), + # ShuffleFilter after StaticPairList + ([{"method": "StaticPairList"}, + {"method": "ShuffleFilter", "seed": 42}], + "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC'], False), + # ShuffleFilter only + ([{"method": "ShuffleFilter", "seed": 42}], + "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC'], True), + # SpreadFilter after StaticPairList + ([{"method": "StaticPairList"}, + {"method": "SpreadFilter", "max_spread_ratio": 0.005}], + "BTC", ['ETH/BTC', 'TKN/BTC'], False), + # SpreadFilter only + ([{"method": "SpreadFilter", "max_spread_ratio": 0.005}], + "BTC", ['ETH/BTC', 'TKN/BTC'], True), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, pairlists, base_currency, whitelist_result, - caplog) -> None: + operational_exception, caplog) -> None: whitelist_conf['pairlists'] = pairlists whitelist_conf['stake_currency'] = base_currency @@ -285,32 +316,38 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t markets=PropertyMock(return_value=shitcoinmarkets), ) - freqtrade.pairlists.refresh_pairlist() - whitelist = freqtrade.pairlists.whitelist - - assert isinstance(whitelist, list) - - # Verify length of pairlist matches (used for ShuffleFilter without seed) - if type(whitelist_result) is list: - assert whitelist == whitelist_result + if operational_exception: + with pytest.raises(OperationalException, + match=r"This Pairlist Handler should not be used at the first position " + r"in the list of Pairlist Handlers."): + freqtrade.pairlists.refresh_pairlist() else: - len(whitelist) == whitelist_result + freqtrade.pairlists.refresh_pairlist() + whitelist = freqtrade.pairlists.whitelist - for pairlist in pairlists: - if pairlist['method'] == 'PrecisionFilter' and whitelist_result: - assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' - r'would be <= stop limit.*', caplog) - if pairlist['method'] == 'PriceFilter' and whitelist_result: - assert (log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) or - log_has_re(r"^Removed .* from whitelist, because ticker\['last'\] is empty.*", - caplog)) - if pairlist['method'] == 'VolumePairList': - logmsg = ("DEPRECATED: using any key other than quoteVolume for " - "VolumePairList is deprecated.") - if pairlist['sort_key'] != 'quoteVolume': - assert log_has(logmsg, caplog) - else: - assert not log_has(logmsg, caplog) + assert isinstance(whitelist, list) + + # Verify length of pairlist matches (used for ShuffleFilter without seed) + if type(whitelist_result) is list: + assert whitelist == whitelist_result + else: + len(whitelist) == whitelist_result + + for pairlist in pairlists: + if pairlist['method'] == 'PrecisionFilter' and whitelist_result: + assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' + r'would be <= stop limit.*', caplog) + if pairlist['method'] == 'PriceFilter' and whitelist_result: + assert (log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) or + log_has_re(r"^Removed .* from whitelist, because ticker\['last'\] " + r"is empty.*", caplog)) + if pairlist['method'] == 'VolumePairList': + logmsg = ("DEPRECATED: using any key other than quoteVolume for " + "VolumePairList is deprecated.") + if pairlist['sort_key'] != 'quoteVolume': + assert log_has(logmsg, caplog) + else: + assert not log_has(logmsg, caplog) def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: From 0e416dc4f58983af5ee5440a493d52ccadaf97cc Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 22 May 2020 16:42:02 +0300 Subject: [PATCH 002/191] Simplify tests --- tests/pairlist/test_pairlist.py | 59 +++++++++++++++++---------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index febb8b163..3e3241071 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -202,21 +202,21 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): assert set(whitelist) == set(pairslist) -@pytest.mark.parametrize("pairlists,base_currency,whitelist_result,operational_exception", [ +@pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [ # VolumePairList only ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']), # Different sorting depending on quote or bid volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], - "BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC'], False), + "BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT'], False), + "USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT']), # No pair for ETH, VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "ETH", [], False), + "ETH", []), # No pair for ETH, StaticPairList ([{"method": "StaticPairList"}], - "ETH", [], False), + "ETH", []), # No pair for ETH, all handlers ([{"method": "StaticPairList"}, {"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, @@ -224,87 +224,87 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): {"method": "PriceFilter", "low_price_ratio": 0.03}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, {"method": "ShuffleFilter"}], - "ETH", [], False), + "ETH", []), # Precisionfilter and quote volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # Precisionfilter bid ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, {"method": "PrecisionFilter"}], - "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC'], False), + "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), # PriceFilter and VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.03}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # PriceFilter and VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.03}], - "USDT", ['ETH/USDT', 'NANO/USDT'], False), + "USDT", ['ETH/USDT', 'NANO/USDT']), # Hot is removed by precision_filter, Fuel by low_price_filter. ([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.02}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # HOT and XRP are removed because below 1250 quoteVolume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "min_value": 1250}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), # StaticPairlist only ([{"method": "StaticPairList"}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # Static Pairlist before VolumePairList - sorting changes ([{"method": "StaticPairList"}, {"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], - "BTC", ['HOT/BTC', 'TKN/BTC', 'ETH/BTC'], False), + "BTC", ['HOT/BTC', 'TKN/BTC', 'ETH/BTC']), # SpreadFilter ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}], - "USDT", ['ETH/USDT'], False), + "USDT", ['ETH/USDT']), # ShuffleFilter ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter", "seed": 77}], - "USDT", ['ETH/USDT', 'ADAHALF/USDT', 'NANO/USDT'], False), + "USDT", ['ETH/USDT', 'ADAHALF/USDT', 'NANO/USDT']), # ShuffleFilter, other seed ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter", "seed": 42}], - "USDT", ['NANO/USDT', 'ETH/USDT', 'ADAHALF/USDT'], False), + "USDT", ['NANO/USDT', 'ETH/USDT', 'ADAHALF/USDT']), # ShuffleFilter, no seed ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter"}], - "USDT", 3, False), + "USDT", 3), # whitelist_result is integer -- check only lenght of randomized pairlist # PrecisionFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "PrecisionFilter"}], - "BTC", ['ETH/BTC', 'TKN/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC']), # PrecisionFilter only ([{"method": "PrecisionFilter"}], - "BTC", ['ETH/BTC', 'TKN/BTC'], True), + "BTC", None), # OperationalException expected # PriceFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "PriceFilter", "low_price_ratio": 0.02}], - "BTC", ['ETH/BTC', 'TKN/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC']), # PriceFilter only ([{"method": "PriceFilter", "low_price_ratio": 0.02}], - "BTC", ['ETH/BTC', 'TKN/BTC'], True), + "BTC", None), # OperationalException expected # ShuffleFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "ShuffleFilter", "seed": 42}], - "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC'], False), + "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC']), # ShuffleFilter only ([{"method": "ShuffleFilter", "seed": 42}], - "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC'], True), + "BTC", None), # OperationalException expected # SpreadFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}], - "BTC", ['ETH/BTC', 'TKN/BTC'], False), + "BTC", ['ETH/BTC', 'TKN/BTC']), # SpreadFilter only ([{"method": "SpreadFilter", "max_spread_ratio": 0.005}], - "BTC", ['ETH/BTC', 'TKN/BTC'], True), + "BTC", None), # OperationalException expected ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, pairlists, base_currency, whitelist_result, - operational_exception, caplog) -> None: + caplog) -> None: whitelist_conf['pairlists'] = pairlists whitelist_conf['stake_currency'] = base_currency @@ -316,7 +316,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t markets=PropertyMock(return_value=shitcoinmarkets), ) - if operational_exception: + # Set whitelist_result to None if pairlist is invalid and should produce exception + if whitelist_result is None: with pytest.raises(OperationalException, match=r"This Pairlist Handler should not be used at the first position " r"in the list of Pairlist Handlers."): From c3206d72cbed5c1b24ba91cd8c9bd31dfbf4a7be Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 25 May 2020 22:49:57 +0300 Subject: [PATCH 003/191] Adjust docstring for IPairList.gen_pairlist() --- freqtrade/pairlist/IPairList.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 0a291fdf7..f48a7dcfd 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -93,7 +93,15 @@ class IPairList(ABC): def gen_pairlist(self, cached_pairlist: List[str], tickers: Dict) -> List[str]: """ - Generate the pairlist + Generate the pairlist. + + This method is called once by the pairlistmanager in the refresh_pairlist() + method to supply the starting pairlist for the chain of the Pairlist Handlers. + Pairlist Filters (those Pairlist Handlers that cannot be used at the first + position in the chain) shall not override this base implementation -- + it will raise the exception if a Pairlist Handler is used at the first + position in the chain. + :param cached_pairlist: Previously generated pairlist (cached) :param tickers: Tickers (from exchange.get_tickers()). :return: List of pairs From a48412427218109f937b17bf2309d6cd70cd79bc Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Mon, 25 May 2020 23:14:51 +0300 Subject: [PATCH 004/191] Raise exception if StaticPairList on a non-first position --- freqtrade/pairlist/StaticPairList.py | 7 ++++++- tests/pairlist/test_pairlist.py | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 0218833e3..4c260d2fe 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -6,6 +6,7 @@ Provides pair white list as it configured in config import logging from typing import Dict, List +from freqtrade.exceptions import OperationalException from freqtrade.pairlist.IPairList import IPairList @@ -47,4 +48,8 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - return pairlist + if self._pairlist_pos != 0: + raise OperationalException(f"{self.name} can only be used in the first position " + "in the list of Pairlist Handlers.") + else: + return pairlist diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 3e3241071..9c35eae1b 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -279,28 +279,32 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "BTC", ['ETH/BTC', 'TKN/BTC']), # PrecisionFilter only ([{"method": "PrecisionFilter"}], - "BTC", None), # OperationalException expected + "BTC", 'filter_at_the_beginning'), # OperationalException expected # PriceFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "PriceFilter", "low_price_ratio": 0.02}], "BTC", ['ETH/BTC', 'TKN/BTC']), # PriceFilter only ([{"method": "PriceFilter", "low_price_ratio": 0.02}], - "BTC", None), # OperationalException expected + "BTC", 'filter_at_the_beginning'), # OperationalException expected # ShuffleFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "ShuffleFilter", "seed": 42}], "BTC", ['TKN/BTC', 'ETH/BTC', 'HOT/BTC']), # ShuffleFilter only ([{"method": "ShuffleFilter", "seed": 42}], - "BTC", None), # OperationalException expected + "BTC", 'filter_at_the_beginning'), # OperationalException expected # SpreadFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}], "BTC", ['ETH/BTC', 'TKN/BTC']), # SpreadFilter only ([{"method": "SpreadFilter", "max_spread_ratio": 0.005}], - "BTC", None), # OperationalException expected + "BTC", 'filter_at_the_beginning'), # OperationalException expected + # Static Pairlist after VolumePairList, on a non-first position + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, + {"method": "StaticPairList"}], + "BTC", 'static_in_the_middle'), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, pairlists, base_currency, whitelist_result, @@ -317,11 +321,16 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t ) # Set whitelist_result to None if pairlist is invalid and should produce exception - if whitelist_result is None: + if whitelist_result == 'filter_at_the_beginning': with pytest.raises(OperationalException, match=r"This Pairlist Handler should not be used at the first position " r"in the list of Pairlist Handlers."): freqtrade.pairlists.refresh_pairlist() + elif whitelist_result == 'static_in_the_middle': + with pytest.raises(OperationalException, + match=r"StaticPairList can only be used in the first position " + r"in the list of Pairlist Handlers."): + freqtrade.pairlists.refresh_pairlist() else: freqtrade.pairlists.refresh_pairlist() whitelist = freqtrade.pairlists.whitelist From 0e8f95effd2798655b11d31e6b1659e9acc445ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 May 2020 06:51:53 +0200 Subject: [PATCH 005/191] Improve blacklist adding with proper feedback --- freqtrade/rpc/rpc.py | 16 +++++++++++++--- tests/rpc/test_rpc.py | 14 ++++++++++++++ tests/rpc/test_rpc_apiserver.py | 8 ++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 248b4a421..2a49e109a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -533,16 +533,26 @@ class RPC: def _rpc_blacklist(self, add: List[str] = None) -> Dict: """ Returns the currently active blacklist""" + errors = {} if add: stake_currency = self._freqtrade.config.get('stake_currency') for pair in add: - if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency - and pair not in self._freqtrade.pairlists.blacklist): - self._freqtrade.pairlists.blacklist.append(pair) + if self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: + if pair not in self._freqtrade.pairlists.blacklist: + self._freqtrade.pairlists.blacklist.append(pair) + else: + errors[pair] = { + 'error_msg': f'Pair {pair} already in pairlist.'} + + else: + errors[pair] = { + 'error_msg': f"Pair {pair} does not match stake currency." + } res = {'method': self._freqtrade.pairlists.name_list, 'length': len(self._freqtrade.pairlists.blacklist), 'blacklist': self._freqtrade.pairlists.blacklist, + 'errors': errors, } return res diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e94097545..bdd3c25d5 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -833,6 +833,20 @@ def test_rpc_blacklist(mocker, default_conf) -> None: assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] + ret = rpc._rpc_blacklist(["ETH/BTC"]) + assert 'errors' in ret + assert isinstance(ret['errors'], dict) + assert ret['errors']['ETH/BTC']['error_msg'] == 'Pair ETH/BTC already in pairlist.' + + ret = rpc._rpc_blacklist(["ETH/ETH"]) + assert 'StaticPairList' in ret['method'] + assert len(ret['blacklist']) == 3 + assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] + assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] + assert 'errors' in ret + assert isinstance(ret['errors'], dict) + assert ret['errors']['ETH/ETH']['error_msg'] == 'Pair ETH/ETH does not match stake currency.' + def test_rpc_edge_disabled(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cc63bf6e8..85f0e5122 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -554,7 +554,9 @@ def test_api_blacklist(botclient, mocker): assert_response(rc) assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"], "length": 2, - "method": ["StaticPairList"]} + "method": ["StaticPairList"], + "errors": {}, + } # Add ETH/BTC to blacklist rc = client_post(client, f"{BASE_URI}/blacklist", @@ -562,7 +564,9 @@ def test_api_blacklist(botclient, mocker): assert_response(rc) assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"], "length": 3, - "method": ["StaticPairList"]} + "method": ["StaticPairList"], + "errors": {}, + } def test_api_whitelist(botclient): From 7399c7e70caa80d2a052329e28a9d460bfdadce0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 May 2020 07:04:06 +0200 Subject: [PATCH 006/191] Provide blacklist feedback to telegram --- freqtrade/rpc/telegram.py | 5 +++++ tests/rpc/test_rpc_telegram.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 488fa9f37..6f092fcab 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -534,6 +534,11 @@ class Telegram(RPC): try: blacklist = self._rpc_blacklist(context.args) + errmsgs = [] + for pair, error in blacklist['errors'].items(): + errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`") + if errmsgs: + self._send_msg('\n'.join(errmsgs)) message = f"Blacklist contains {blacklist['length']} pairs\n" message += f"`{', '.join(blacklist['blacklist'])}`" diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 730bb2677..7d02a4339 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1085,6 +1085,18 @@ def test_blacklist_static(default_conf, update, mocker) -> None: in msg_mock.call_args_list[0][0][0]) assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] + msg_mock.reset_mock() + context = MagicMock() + context.args = ["ETH/ETH"] + telegram._blacklist(update=update, context=context) + assert msg_mock.call_count == 2 + assert ("Error adding `ETH/ETH` to blacklist: `Pair ETH/ETH does not match stake currency.`" + in msg_mock.call_args_list[0][0][0]) + + assert ("Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`" + in msg_mock.call_args_list[1][0][0]) + assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] + def test_edge_disabled(default_conf, update, mocker) -> None: msg_mock = MagicMock() From 909b81255005839eb7f41a02f5138f844e0a67bf Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 29 May 2020 00:28:24 +0300 Subject: [PATCH 007/191] Update developer.md --- docs/developer.md | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/docs/developer.md b/docs/developer.md index 34b2f1ba5..a680bc8dc 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -92,13 +92,13 @@ docker-compose exec freqtrade_develop /bin/bash You have a great idea for a new pair selection algorithm you would like to try out? Great. Hopefully you also want to contribute this back upstream. -Whatever your motivations are - This should get you off the ground in trying to develop a new Pairlist provider. +Whatever your motivations are - This should get you off the ground in trying to develop a new Pairlist Handler. -First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/pairlist/VolumePairList.py) provider, and best copy this file with a name of your new Pairlist Provider. +First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/pairlist/VolumePairList.py) Handler, and best copy this file with a name of your new Pairlist Handler. -This is a simple provider, which however serves as a good example on how to start developing. +This is a simple Handler, which however serves as a good example on how to start developing. -Next, modify the classname of the provider (ideally align this with the Filename). +Next, modify the classname of the Handler (ideally align this with the module filename). The base-class provides an instance of the exchange (`self._exchange`) the pairlist manager (`self._pairlistmanager`), as well as the main configuration (`self._config`), the pairlist dedicated configuration (`self._pairlistconfig`) and the absolute position within the list of pairlists. @@ -114,28 +114,44 @@ Now, let's step through the methods which require actions: #### Pairlist configuration -Configuration for PairListProvider is done in the bot configuration file in the element `"pairlist"`. -This Pairlist-object may contain configurations with additional configurations for the configured pairlist. -By convention, `"number_assets"` is used to specify the maximum number of pairs to keep in the whitelist. Please follow this to ensure a consistent user experience. +Configuration for the chain of Pairlist Handlers is done in the bot configuration file in the element `"pairlists"`, an array of configuration parameters for each Pairlist Handlers in the chain. -Additional elements can be configured as needed. `VolumePairList` uses `"sort_key"` to specify the sorting value - however feel free to specify whatever is necessary for your great algorithm to be successfull and dynamic. +By convention, `"number_assets"` is used to specify the maximum number of pairs to keep in the pairlist. Please follow this to ensure a consistent user experience. + +Additional parameters can be configured as needed. For instance, `VolumePairList` uses `"sort_key"` to specify the sorting value - however feel free to specify whatever is necessary for your great algorithm to be successfull and dynamic. #### short_desc Returns a description used for Telegram messages. -This should contain the name of the Provider, as well as a short description containing the number of assets. Please follow the format `"PairlistName - top/bottom X pairs"`. + +This should contain the name of the Pairlist Handler, as well as a short description containing the number of assets. Please follow the format `"PairlistName - top/bottom X pairs"`. + +#### gen_pairlist + +Override this method if the Pairlist Handler can be used as the leading Pairlist Handler in the chain, defining the initial pairlist which is then handled by all Pairlist Handlers in the chain. Examples are `StaticPairList` and `VolumePairList`. + +This is called with each iteration of the bot - so consider implementing caching for compute/network heavy calculations. + +It must return the resulting pairlist (which may then be passed into the chain of Pairlist Handlers). + +Validations are optional, the parent class exposes a `_verify_blacklist(pairlist)` and `_whitelist_for_active_markets(pairlist)` to do default filtering. Use this if you limit your result to a certain number of pairs - so the endresult is not shorter than expected. #### filter_pairlist -Override this method and run all calculations needed in this method. +This method is called for each Pairlist Handler in the chain by the pairlist manager. + This is called with each iteration of the bot - so consider implementing caching for compute/network heavy calculations. It get's passed a pairlist (which can be the result of previous pairlists) as well as `tickers`, a pre-fetched version of `get_tickers()`. -It must return the resulting pairlist (which may then be passed into the next pairlist filter). +The default implementation in the base class simply calls the `_validate_pair()` method for each pair in the pairlist, but you may override it. So you should either implement the `_validate_pair()` in your Pairlist Handler or override `filter_pairlist()` to do something else. + +If overridden, it must return the resulting pairlist (which may then be passed into the next Pairlist Handler in the chain). Validations are optional, the parent class exposes a `_verify_blacklist(pairlist)` and `_whitelist_for_active_markets(pairlist)` to do default filters. Use this if you limit your result to a certain number of pairs - so the endresult is not shorter than expected. +In `VolumePairList`, this implements different methods of sorting, does early validation so only the expected number of pairs is returned. + ##### sample ``` python @@ -145,11 +161,6 @@ Validations are optional, the parent class exposes a `_verify_blacklist(pairlist return pairs ``` -#### _gen_pair_whitelist - -This is a simple method used by `VolumePairList` - however serves as a good example. -In VolumePairList, this implements different methods of sorting, does early validation so only the expected number of pairs is returned. - ## Implement a new Exchange (WIP) !!! Note From 6261aef314427bc64c9b3c3dbabedeccb74b734b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 09:03:48 +0200 Subject: [PATCH 008/191] Return /profit even if no trade is closed --- freqtrade/persistence.py | 1 + freqtrade/rpc/rpc.py | 19 ++++++++----------- tests/rpc/test_rpc.py | 8 ++++++-- tests/rpc/test_rpc_apiserver.py | 12 +++++++----- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index da7137cba..d9951f1fc 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -546,6 +546,7 @@ class Trade(_DECL_BASE): def get_best_pair(): """ Get best pair with closed trade. + :returns: Tuple containing (pair, profit_sum) """ best_pair = Trade.session.query( Trade.pair, func.sum(Trade.close_profit).label('profit_sum') diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 248b4a421..eb822e234 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -281,11 +281,6 @@ class RPC: best_pair = Trade.get_best_pair() - if not best_pair: - raise RPCException('no closed trade') - - bp_pair, bp_rate = best_pair - # Prepare data to display profit_closed_coin_sum = round(sum(profit_closed_coin), 8) profit_closed_percent = (round(mean(profit_closed_ratio) * 100, 2) if profit_closed_ratio @@ -304,6 +299,8 @@ class RPC: fiat_display_currency ) if self._fiat_converter else 0 + first_date = trades[0].open_date if trades else None + last_date = trades[-1].open_date if trades else None num = float(len(durations) or 1) return { 'profit_closed_coin': profit_closed_coin_sum, @@ -313,13 +310,13 @@ class RPC: 'profit_all_percent': profit_all_percent, 'profit_all_fiat': profit_all_fiat, 'trade_count': len(trades), - 'first_trade_date': arrow.get(trades[0].open_date).humanize(), - 'first_trade_timestamp': int(trades[0].open_date.timestamp() * 1000), - 'latest_trade_date': arrow.get(trades[-1].open_date).humanize(), - 'latest_trade_timestamp': int(trades[-1].open_date.timestamp() * 1000), + 'first_trade_date': arrow.get(first_date).humanize() if first_date else '', + 'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0, + 'latest_trade_date': arrow.get(last_date).humanize() if last_date else '', + 'latest_trade_timestamp': int(last_date.timestamp() * 1000) if last_date else 0, 'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0], - 'best_pair': bp_pair, - 'best_rate': round(bp_rate * 100, 2), + 'best_pair': best_pair[0] if best_pair else '', + 'best_rate': round(best_pair[1] * 100, 2) if best_pair else 0, } def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e94097545..634b2f739 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -279,8 +279,12 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() - with pytest.raises(RPCException, match=r'.*no closed trade*'): - rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) + res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) + assert res['trade_count'] == 0 + assert res['first_trade_date'] == '' + assert res['first_trade_timestamp'] == 0 + assert res['latest_trade_date'] == '' + assert res['latest_trade_timestamp'] == 0 # Create some test data freqtradebot.enter_positions() diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cc63bf6e8..c68aae56d 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -396,9 +396,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li ) rc = client_get(client, f"{BASE_URI}/profit") - assert_response(rc, 502) - assert len(rc.json) == 1 - assert rc.json == {"error": "Error querying _profit: no closed trade"} + assert_response(rc, 200) + assert rc.json['trade_count'] == 0 ftbot.enter_positions() trade = Trade.query.first() @@ -406,8 +405,11 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) rc = client_get(client, f"{BASE_URI}/profit") - assert_response(rc, 502) - assert rc.json == {"error": "Error querying _profit: no closed trade"} + assert_response(rc, 200) + # One open trade + assert rc.json['trade_count'] == 1 + assert rc.json['best_pair'] == '' + assert rc.json['best_rate'] == 0 trade.update(limit_sell_order) From 1d6e3fea8588e2c54a8593546a9c9628a751d489 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 09:38:12 +0200 Subject: [PATCH 009/191] Update /profit telegram message to support non-closed trades --- freqtrade/rpc/rpc.py | 1 + freqtrade/rpc/telegram.py | 29 +++++++++++++++++------------ tests/rpc/test_rpc_apiserver.py | 3 ++- tests/rpc/test_rpc_telegram.py | 6 ++++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index eb822e234..6addf18ba 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -310,6 +310,7 @@ class RPC: 'profit_all_percent': profit_all_percent, 'profit_all_fiat': profit_all_fiat, 'trade_count': len(trades), + 'closed_trade_count': len([t for t in trades if not t.is_open]), 'first_trade_date': arrow.get(first_date).humanize() if first_date else '', 'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0, 'latest_trade_date': arrow.get(last_date).humanize() if last_date else '', diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 488fa9f37..78cdaef62 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -328,18 +328,23 @@ class Telegram(RPC): best_pair = stats['best_pair'] best_rate = stats['best_rate'] # Message to display - markdown_msg = "*ROI:* Close trades\n" \ - f"∙ `{profit_closed_coin:.8f} {stake_cur} "\ - f"({profit_closed_percent:.2f}%)`\n" \ - f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n" \ - f"*ROI:* All trades\n" \ - f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" \ - f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" \ - f"*Total Trade Count:* `{trade_count}`\n" \ - f"*First Trade opened:* `{first_trade_date}`\n" \ - f"*Latest Trade opened:* `{latest_trade_date}`\n" \ - f"*Avg. Duration:* `{avg_duration}`\n" \ - f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`" + if stats['closed_trade_count'] > 0: + markdown_msg = ("*ROI:* Close trades\n" + f"∙ `{profit_closed_coin:.8f} {stake_cur} " + f"({profit_closed_percent:.2f}%)`\n" + f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") + else: + markdown_msg = "`No closed trade` \n" + + markdown_msg += (f"*ROI:* All trades\n" + f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" + f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" + f"*Total Trade Count:* `{trade_count}`\n" + f"*First Trade opened:* `{first_trade_date}`\n" + f"*Latest Trade opened:* `{latest_trade_date}`") + if stats['closed_trade_count'] > 0: + markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" + f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") self._send_msg(markdown_msg) except RPCException as e: self._send_msg(str(e)) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index c68aae56d..5cab10244 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -431,7 +431,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li 'profit_closed_coin': 6.217e-05, 'profit_closed_fiat': 0, 'profit_closed_percent': 6.2, - 'trade_count': 1 + 'trade_count': 1, + 'closed_trade_count': 1, } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 730bb2677..0b990281f 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -420,7 +420,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'no closed trade' in msg_mock.call_args_list[0][0][0] + assert 'No closed trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data @@ -432,7 +432,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'no closed trade' in msg_mock.call_args_list[-1][0][0] + assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] + assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] + assert '∙ `-0.00000500 BTC (-0.50%)`' in msg_mock.call_args_list[-1][0][0] msg_mock.reset_mock() # Update the ticker with a market going up From 46456516bb677077eeafb0fb853a6517a1b7b1cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 10:10:45 +0200 Subject: [PATCH 010/191] Remove exception handler --- freqtrade/rpc/telegram.py | 69 +++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 78cdaef62..4bf19192d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -311,43 +311,40 @@ class Telegram(RPC): stake_cur = self._config['stake_currency'] fiat_disp_cur = self._config.get('fiat_display_currency', '') - try: - stats = self._rpc_trade_statistics( - stake_cur, - fiat_disp_cur) - profit_closed_coin = stats['profit_closed_coin'] - profit_closed_percent = stats['profit_closed_percent'] - profit_closed_fiat = stats['profit_closed_fiat'] - profit_all_coin = stats['profit_all_coin'] - profit_all_percent = stats['profit_all_percent'] - profit_all_fiat = stats['profit_all_fiat'] - trade_count = stats['trade_count'] - first_trade_date = stats['first_trade_date'] - latest_trade_date = stats['latest_trade_date'] - avg_duration = stats['avg_duration'] - best_pair = stats['best_pair'] - best_rate = stats['best_rate'] - # Message to display - if stats['closed_trade_count'] > 0: - markdown_msg = ("*ROI:* Close trades\n" - f"∙ `{profit_closed_coin:.8f} {stake_cur} " - f"({profit_closed_percent:.2f}%)`\n" - f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") - else: - markdown_msg = "`No closed trade` \n" + stats = self._rpc_trade_statistics( + stake_cur, + fiat_disp_cur) + profit_closed_coin = stats['profit_closed_coin'] + profit_closed_percent = stats['profit_closed_percent'] + profit_closed_fiat = stats['profit_closed_fiat'] + profit_all_coin = stats['profit_all_coin'] + profit_all_percent = stats['profit_all_percent'] + profit_all_fiat = stats['profit_all_fiat'] + trade_count = stats['trade_count'] + first_trade_date = stats['first_trade_date'] + latest_trade_date = stats['latest_trade_date'] + avg_duration = stats['avg_duration'] + best_pair = stats['best_pair'] + best_rate = stats['best_rate'] + # Message to display + if stats['closed_trade_count'] > 0: + markdown_msg = ("*ROI:* Close trades\n" + f"∙ `{profit_closed_coin:.8f} {stake_cur} " + f"({profit_closed_percent:.2f}%)`\n" + f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") + else: + markdown_msg = "`No closed trade` \n" - markdown_msg += (f"*ROI:* All trades\n" - f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" - f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" - f"*Total Trade Count:* `{trade_count}`\n" - f"*First Trade opened:* `{first_trade_date}`\n" - f"*Latest Trade opened:* `{latest_trade_date}`") - if stats['closed_trade_count'] > 0: - markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" - f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") - self._send_msg(markdown_msg) - except RPCException as e: - self._send_msg(str(e)) + markdown_msg += (f"*ROI:* All trades\n" + f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" + f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" + f"*Total Trade Count:* `{trade_count}`\n" + f"*First Trade opened:* `{first_trade_date}`\n" + f"*Latest Trade opened:* `{latest_trade_date}`") + if stats['closed_trade_count'] > 0: + markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" + f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") + self._send_msg(markdown_msg) @authorized_only def _balance(self, update: Update, context: CallbackContext) -> None: From 43225cfdcf71df41314ababe06860ea29c2e0638 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 29 May 2020 11:28:09 +0300 Subject: [PATCH 011/191] Update docs/developer.md Co-authored-by: Matthias --- docs/developer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer.md b/docs/developer.md index a680bc8dc..036109d5b 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -130,7 +130,7 @@ This should contain the name of the Pairlist Handler, as well as a short descrip Override this method if the Pairlist Handler can be used as the leading Pairlist Handler in the chain, defining the initial pairlist which is then handled by all Pairlist Handlers in the chain. Examples are `StaticPairList` and `VolumePairList`. -This is called with each iteration of the bot - so consider implementing caching for compute/network heavy calculations. +This is called with each iteration of the bot (only if the Pairlist Handler is at the first location) - so consider implementing caching for compute/network heavy calculations. It must return the resulting pairlist (which may then be passed into the chain of Pairlist Handlers). From a4cf9ba85b2a3c823fef5a9622c6f809745b8a76 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 29 May 2020 12:40:05 +0300 Subject: [PATCH 012/191] Move check for position for StaticPairList to init --- freqtrade/pairlist/StaticPairList.py | 17 +++++++++++------ tests/pairlist/test_pairlist.py | 14 ++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 4c260d2fe..b5c1bc767 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -4,7 +4,7 @@ Static Pair List provider Provides pair white list as it configured in config """ import logging -from typing import Dict, List +from typing import Any, Dict, List from freqtrade.exceptions import OperationalException from freqtrade.pairlist.IPairList import IPairList @@ -15,6 +15,15 @@ logger = logging.getLogger(__name__) class StaticPairList(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: """ @@ -48,8 +57,4 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - if self._pairlist_pos != 0: - raise OperationalException(f"{self.name} can only be used in the first position " - "in the list of Pairlist Handlers.") - else: - return pairlist + return pairlist diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 9c35eae1b..421f06911 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -313,8 +313,15 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t whitelist_conf['stake_currency'] = base_currency mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) + if whitelist_result == 'static_in_the_middle': + with pytest.raises(OperationalException, + match=r"StaticPairList can only be used in the first position " + r"in the list of Pairlist Handlers."): + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) + return + + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers, markets=PropertyMock(return_value=shitcoinmarkets), @@ -326,11 +333,6 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t match=r"This Pairlist Handler should not be used at the first position " r"in the list of Pairlist Handlers."): freqtrade.pairlists.refresh_pairlist() - elif whitelist_result == 'static_in_the_middle': - with pytest.raises(OperationalException, - match=r"StaticPairList can only be used in the first position " - r"in the list of Pairlist Handlers."): - freqtrade.pairlists.refresh_pairlist() else: freqtrade.pairlists.refresh_pairlist() whitelist = freqtrade.pairlists.whitelist From ea5daee5054621daf1577b2e1cde7336dea0b1fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 19:37:18 +0200 Subject: [PATCH 013/191] Allow changing severity of strategy-validations to log only. --- config_full.json.example | 1 + docs/configuration.md | 1 + freqtrade/constants.py | 1 + freqtrade/resolvers/strategy_resolver.py | 49 ++++++++++++------------ freqtrade/strategy/interface.py | 11 ++++-- tests/strategy/test_interface.py | 10 ++++- 6 files changed, 45 insertions(+), 28 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 0cd265cbe..481742817 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -132,6 +132,7 @@ "process_throttle_secs": 5, "heartbeat_interval": 60 }, + "disable_dataframe_checks": false, "strategy": "DefaultStrategy", "strategy_path": "user_data/strategies/", "dataformat_ohlcv": "json", diff --git a/docs/configuration.md b/docs/configuration.md index 93e53de6f..97e6b7911 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -107,6 +107,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
**Datatype:** String, SQLAlchemy connect string | `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.*
**Datatype:** Enum, either `stopped` or `running` | `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below.
**Datatype:** Boolean +| `disable_dataframe_checks` | Disable checking the dataframe for correctness. Only use when intentionally changing the dataframe. [Strategy Override](#parameters-in-the-strategy).[Strategy Override](#parameters-in-the-strategy).
*Defaults to `False`*.
**Datatype:** Boolean | `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`.
**Datatype:** ClassName | `strategy_path` | Adds an additional strategy lookup path (must be a directory).
**Datatype:** String | `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
**Datatype:** Positive Integer diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c1bf30f17..1984d4866 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -227,6 +227,7 @@ CONF_SCHEMA = { 'db_url': {'type': 'string'}, 'initial_state': {'type': 'string', 'enum': ['running', 'stopped']}, 'forcebuy_enable': {'type': 'boolean'}, + 'disable_dataframe_checks': {'type': 'boolean'}, 'internals': { 'type': 'object', 'default': {}, diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index cddc7c9cd..51cb8fc05 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -52,37 +52,38 @@ class StrategyResolver(IResolver): # Set attributes # Check if we need to override configuration - # (Attribute name, default, ask_strategy) - attributes = [("minimal_roi", {"0": 10.0}, False), - ("ticker_interval", None, False), - ("stoploss", None, False), - ("trailing_stop", None, False), - ("trailing_stop_positive", None, False), - ("trailing_stop_positive_offset", 0.0, False), - ("trailing_only_offset_is_reached", None, False), - ("process_only_new_candles", None, False), - ("order_types", None, False), - ("order_time_in_force", None, False), - ("stake_currency", None, False), - ("stake_amount", None, False), - ("startup_candle_count", None, False), - ("unfilledtimeout", None, False), - ("use_sell_signal", True, True), - ("sell_profit_only", False, True), - ("ignore_roi_if_buy_signal", False, True), + # (Attribute name, default, subkey) + attributes = [("minimal_roi", {"0": 10.0}, None), + ("ticker_interval", None, None), + ("stoploss", None, None), + ("trailing_stop", None, None), + ("trailing_stop_positive", None, None), + ("trailing_stop_positive_offset", 0.0, None), + ("trailing_only_offset_is_reached", None, None), + ("process_only_new_candles", None, None), + ("order_types", None, None), + ("order_time_in_force", None, None), + ("stake_currency", None, None), + ("stake_amount", None, None), + ("startup_candle_count", None, None), + ("unfilledtimeout", None, None), + ("use_sell_signal", True, 'ask_strategy'), + ("sell_profit_only", False, 'ask_strategy'), + ("ignore_roi_if_buy_signal", False, 'ask_strategy'), + ("disable_dataframe_checks", False, 'internals') ] - for attribute, default, ask_strategy in attributes: - if ask_strategy: - StrategyResolver._override_attribute_helper(strategy, config['ask_strategy'], + for attribute, default, subkey in attributes: + if subkey: + StrategyResolver._override_attribute_helper(strategy, config.get(subkey, {}), attribute, default) else: StrategyResolver._override_attribute_helper(strategy, config, attribute, default) # Loop this list again to have output combined - for attribute, _, exp in attributes: - if exp and attribute in config['ask_strategy']: - logger.info("Strategy using %s: %s", attribute, config['ask_strategy'][attribute]) + for attribute, _, subkey in attributes: + if subkey and attribute in config[subkey]: + logger.info("Strategy using %s: %s", attribute, config[subkey][attribute]) elif attribute in config: logger.info("Strategy using %s: %s", attribute, config[attribute]) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 400997baf..ed2344a53 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -106,6 +106,9 @@ class IStrategy(ABC): # run "populate_indicators" only for new candle process_only_new_candles: bool = False + # Disable checking the dataframe (converts the error into a warning message) + disable_dataframe_checks: bool = False + # Count of candles the strategy requires before producing valid signals startup_candle_count: int = 0 @@ -285,8 +288,7 @@ class IStrategy(ABC): """ keep some data for dataframes """ return len(dataframe), dataframe["close"].iloc[-1], dataframe["date"].iloc[-1] - @staticmethod - def assert_df(dataframe: DataFrame, df_len: int, df_close: float, df_date: datetime): + def assert_df(self, dataframe: DataFrame, df_len: int, df_close: float, df_date: datetime): """ make sure data is unmodified """ message = "" if df_len != len(dataframe): @@ -296,7 +298,10 @@ class IStrategy(ABC): elif df_date != dataframe["date"].iloc[-1]: message = "last date" if message: - raise StrategyError(f"Dataframe returned from strategy has mismatching {message}.") + if self.disable_dataframe_checks: + logger.warning(f"Dataframe returned from strategy has mismatching {message}.") + else: + raise StrategyError(f"Dataframe returned from strategy has mismatching {message}.") def get_signal(self, pair: str, interval: str, dataframe: DataFrame) -> Tuple[bool, bool]: """ diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index dd6b11a06..55dd07e9e 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -130,7 +130,7 @@ def test_assert_df_raise(default_conf, mocker, caplog, ohlcv_history): caplog) -def test_assert_df(default_conf, mocker, ohlcv_history): +def test_assert_df(default_conf, mocker, ohlcv_history, caplog): # Ensure it's running when passed correctly _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), ohlcv_history.loc[1, 'close'], ohlcv_history.loc[1, 'date']) @@ -148,6 +148,14 @@ def test_assert_df(default_conf, mocker, ohlcv_history): _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), ohlcv_history.loc[1, 'close'], ohlcv_history.loc[0, 'date']) + _STRATEGY.disable_dataframe_checks = True + caplog.clear() + _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), + ohlcv_history.loc[1, 'close'], ohlcv_history.loc[0, 'date']) + assert log_has_re(r"Dataframe returned from strategy.*last date\.", caplog) + # reset to avoid problems in other tests + _STRATEGY.disable_dataframe_checks = False + def test_get_signal_handles_exceptions(mocker, default_conf): exchange = get_patched_exchange(mocker, default_conf) From 2ed10aeb9be90fa5989455ae000db507032e24f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 19:39:13 +0200 Subject: [PATCH 014/191] Add to missing point in documentation --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index 97e6b7911..0bd913e88 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -136,6 +136,7 @@ Values set in the configuration file always overwrite values set in the strategy * `stake_currency` * `stake_amount` * `unfilledtimeout` +* `disable_dataframe_checks` * `use_sell_signal` (ask_strategy) * `sell_profit_only` (ask_strategy) * `ignore_roi_if_buy_signal` (ask_strategy) From 7ea59b6d8e5214ad27d2beb8a0970507acc755a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 09:43:50 +0200 Subject: [PATCH 015/191] Update comment (to trigger CI) --- tests/strategy/test_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 55dd07e9e..e5539099b 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -153,7 +153,7 @@ def test_assert_df(default_conf, mocker, ohlcv_history, caplog): _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), ohlcv_history.loc[1, 'close'], ohlcv_history.loc[0, 'date']) assert log_has_re(r"Dataframe returned from strategy.*last date\.", caplog) - # reset to avoid problems in other tests + # reset to avoid problems in other tests due to test leakage _STRATEGY.disable_dataframe_checks = False From 6b5947392e56d597a7c97a5c0da9bc715c5c2a63 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 09:47:09 +0200 Subject: [PATCH 016/191] Version bump pandas to 1.0.4 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 18cab206b..f5d09db4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ -r requirements-common.txt numpy==1.18.4 -pandas==1.0.3 +pandas==1.0.4 From f187753f8f73cef2a5233897426958b49129cbbc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 10:45:50 +0200 Subject: [PATCH 017/191] Add ccxt_sync_config to simplify ccxt configuration --- docs/configuration.md | 3 ++- freqtrade/exchange/exchange.py | 10 ++++++---- tests/exchange/test_exchange.py | 12 ++++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 93e53de6f..5e3be6485 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -83,7 +83,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#pairlists-and-pairlist-handlers)).
**Datatype:** List | `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#pairlists-and-pairlist-handlers)).
**Datatype:** List -| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict +| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances. This is usually the correct place for ccxt configurations. 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_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 | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index af745e8d0..7ef0d7750 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -98,12 +98,14 @@ class Exchange: # Initialize ccxt objects ccxt_config = self._ccxt_config.copy() - ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), - ccxt_config) - self._api = self._init_ccxt( - exchange_config, ccxt_kwargs=ccxt_config) + ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config) + ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_sync_config', {}), ccxt_config) + + self._api = self._init_ccxt(exchange_config, ccxt_kwargs=ccxt_config) ccxt_async_config = self._ccxt_config.copy() + ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), + ccxt_async_config) ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}), ccxt_async_config) self._api_async = self._init_ccxt( diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index e40f691a8..25aecba5c 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -88,15 +88,19 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog): caplog.clear() conf = copy.deepcopy(default_conf) conf['exchange']['ccxt_config'] = {'TestKWARG': 11} + conf['exchange']['ccxt_sync_config'] = {'TestKWARG44': 11} conf['exchange']['ccxt_async_config'] = {'asyncio_loop': True} - + asynclogmsg = "Applying additional ccxt config: {'TestKWARG': 11, 'asyncio_loop': True}" ex = Exchange(conf) - assert not log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}", caplog) assert not ex._api_async.aiohttp_trust_env assert hasattr(ex._api, 'TestKWARG') assert ex._api.TestKWARG == 11 - assert not hasattr(ex._api_async, 'TestKWARG') - assert log_has("Applying additional ccxt config: {'TestKWARG': 11}", caplog) + # ccxt_config is assigned to both sync and async + assert not hasattr(ex._api_async, 'TestKWARG44') + + assert hasattr(ex._api_async, 'TestKWARG') + assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog) + assert log_has(asynclogmsg, caplog) def test_destroy(default_conf, mocker, caplog): From 76ce2c66530165881c8f7cd651c01c6b653d2bbe Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 10:46:04 +0200 Subject: [PATCH 018/191] Document FTX subaccount configuration --- docs/exchanges.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index 06db26f89..81f017023 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -62,6 +62,25 @@ res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarket print(res) ``` +## FTX + +### Using subaccounts + +To use subaccounts with FTX, you need to edit the configuration and add the following: + +``` json +"exchange": { + "ccxt_config": { + "headers": { + "FTX-SUBACCOUNT": "name" + } + }, +} +``` + +!!! Note + Older versions of freqtrade may require this key to be added to `"ccxt_async_config"` as well. + ## All exchanges 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. From 57e951dbce90ff486c3267e62865fd0fa0399d06 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 10:59:26 +0200 Subject: [PATCH 019/191] Add orderbook sell rate to sell_rate_cache --- freqtrade/freqtradebot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d4afa1d60..4f63cc01d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -719,6 +719,9 @@ class FreqtradeBot: raise PricingError from e logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: " f"{sell_rate:0.8f}") + # Assign sell-rate to cache - otherwise sell-rate is never updated in the cache, + # resulting in outdated RPC messages + self._sell_rate_cache[trade.pair] = sell_rate if self._check_and_execute_sell(trade, sell_rate, buy, sell): return True From 28b35178e99182d5381e1941ebc5520f524301a4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 11:24:29 +0200 Subject: [PATCH 020/191] Update doc wording Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 0bd913e88..8853b6e05 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -107,7 +107,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
**Datatype:** String, SQLAlchemy connect string | `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.*
**Datatype:** Enum, either `stopped` or `running` | `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below.
**Datatype:** Boolean -| `disable_dataframe_checks` | Disable checking the dataframe for correctness. Only use when intentionally changing the dataframe. [Strategy Override](#parameters-in-the-strategy).[Strategy Override](#parameters-in-the-strategy).
*Defaults to `False`*.
**Datatype:** Boolean +| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).[Strategy Override](#parameters-in-the-strategy).
*Defaults to `False`*.
**Datatype:** Boolean | `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`.
**Datatype:** ClassName | `strategy_path` | Adds an additional strategy lookup path (must be a directory).
**Datatype:** String | `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
**Datatype:** Positive Integer From 97905f86be0686240d56ae9d62c4d130ba32c9a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 11:34:39 +0200 Subject: [PATCH 021/191] Add missing fields to to_json output of trade --- freqtrade/persistence.py | 7 +++++++ tests/rpc/test_rpc.py | 19 +++++++++++++++---- tests/rpc/test_rpc_apiserver.py | 15 +++++++++++++-- tests/test_persistence.py | 16 ++++++++++++++-- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index da7137cba..a0b9133c9 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -271,10 +271,16 @@ class Trade(_DECL_BASE): 'amount': round(self.amount, 8), 'stake_amount': round(self.stake_amount, 8), 'close_profit': self.close_profit, + 'close_profit_abs': self.close_profit_abs, 'sell_reason': self.sell_reason, 'sell_order_status': self.sell_order_status, 'stop_loss': self.stop_loss, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, + 'stoploss_order_id': self.stoploss_order_id, + 'stoploss_last_update': (self.stoploss_last_update.strftime("%Y-%m-%d %H:%M:%S") + if self.stoploss_last_update else None), + 'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000) + if self.stoploss_last_update else None), 'initial_stop_loss': self.initial_stop_loss, 'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100 if self.initial_stop_loss_pct else None), @@ -283,6 +289,7 @@ class Trade(_DECL_BASE): 'strategy': self.strategy, 'ticker_interval': self.ticker_interval, 'open_order_id': self.open_order_id, + 'exchange': self.exchange, } def adjust_min_max_rates(self, current_price: float) -> None: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e94097545..a68ca191a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -77,13 +77,19 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, + 'close_profit_abs': None, 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'stop_loss': 0.0, + 'stop_loss_pct': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, - 'stop_loss_pct': None, - 'open_order': '(limit buy rem=0.00000000)' + 'open_order': '(limit buy rem=0.00000000)', + 'exchange': 'bittrex', + } == results[0] mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', @@ -125,13 +131,18 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, + 'close_profit_abs': None, 'current_profit': ANY, 'current_profit_pct': ANY, 'stop_loss': 0.0, + 'stop_loss_pct': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, - 'stop_loss_pct': None, - 'open_order': '(limit buy rem=0.00000000)' + 'open_order': '(limit buy rem=0.00000000)', + 'exchange': 'bittrex', } == results[0] diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cc63bf6e8..c284ccb7b 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -502,6 +502,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'close_timestamp': None, 'close_profit': None, 'close_profit_pct': None, + 'close_profit_abs': None, 'close_rate': None, 'current_profit': -0.00408133, 'current_profit_pct': -0.41, @@ -517,6 +518,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'stake_amount': 0.001, 'stop_loss': 0.0, 'stop_loss_pct': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, 'trade_id': 1, 'close_rate_requested': None, 'current_rate': 1.099e-05, @@ -536,7 +540,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'sell_reason': None, 'sell_order_status': None, 'strategy': 'DefaultStrategy', - 'ticker_interval': 5}] + 'ticker_interval': 5, + 'exchange': 'bittrex', + }] def test_api_version(botclient): @@ -627,8 +633,12 @@ def test_api_forcebuy(botclient, mocker, fee): 'stake_amount': 1, 'stop_loss': None, 'stop_loss_pct': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, 'trade_id': None, 'close_profit': None, + 'close_profit_abs': None, 'close_rate_requested': None, 'fee_close': 0.0025, 'fee_close_cost': None, @@ -645,7 +655,8 @@ def test_api_forcebuy(botclient, mocker, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, - 'ticker_interval': None + 'ticker_interval': None, + 'exchange': 'bittrex', } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 60bf073f8..3b1041fd8 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -758,16 +758,22 @@ def test_to_json(default_conf, fee): 'amount': 123.0, 'stake_amount': 0.001, 'close_profit': None, + 'close_profit_abs': None, 'sell_reason': None, 'sell_order_status': None, 'stop_loss': None, 'stop_loss_pct': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, 'initial_stop_loss_pct': None, 'min_rate': None, 'max_rate': None, 'strategy': None, - 'ticker_interval': None} + 'ticker_interval': None, + 'exchange': 'bittrex', + } # Simulate dry_run entries trade = Trade( @@ -799,9 +805,13 @@ def test_to_json(default_conf, fee): 'stake_amount': 0.001, 'stop_loss': None, 'stop_loss_pct': None, + 'stoploss_order_id': None, + 'stoploss_last_update': None, + 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, 'initial_stop_loss_pct': None, 'close_profit': None, + 'close_profit_abs': None, 'close_rate_requested': None, 'fee_close': 0.0025, 'fee_close_cost': None, @@ -818,7 +828,9 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, - 'ticker_interval': None} + 'ticker_interval': None, + 'exchange': 'bittrex', + } def test_stoploss_reinitialization(default_conf, fee): From a9c57e51473d2cd6e9a29a84bdcadf23beba90fb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 11:46:48 +0200 Subject: [PATCH 022/191] Add disable_dataframe_checks to strategy templates --- freqtrade/templates/base_strategy.py.j2 | 4 ++++ freqtrade/templates/sample_strategy.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index c37164568..3ff490d6c 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -57,6 +57,10 @@ class {{ strategy }}(IStrategy): # Run "populate_indicators()" only for new candle. process_only_new_candles = False + # Disable checking the dataframe (converts the error into a warning message) + # Only use if you understand the implications! + disable_dataframe_checks: bool = False + # These values can be overridden in the "ask_strategy" section in the config. use_sell_signal = True sell_profit_only = False diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index f78489173..a70643aee 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -58,6 +58,10 @@ class SampleStrategy(IStrategy): # Run "populate_indicators()" only for new candle. process_only_new_candles = False + # Disable checking the dataframe (converts the error into a warning message) + # Only use if you understand the implications! + disable_dataframe_checks: bool = False + # These values can be overridden in the "ask_strategy" section in the config. use_sell_signal = True sell_profit_only = False From 376c536dd1d1081d5d88092ad63bbbe99e73612f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 16:23:33 +0200 Subject: [PATCH 023/191] Revert "Add disable_dataframe_checks to strategy templates" This reverts commit a9c57e51473d2cd6e9a29a84bdcadf23beba90fb. --- freqtrade/templates/base_strategy.py.j2 | 4 ---- freqtrade/templates/sample_strategy.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 3ff490d6c..c37164568 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -57,10 +57,6 @@ class {{ strategy }}(IStrategy): # Run "populate_indicators()" only for new candle. process_only_new_candles = False - # Disable checking the dataframe (converts the error into a warning message) - # Only use if you understand the implications! - disable_dataframe_checks: bool = False - # These values can be overridden in the "ask_strategy" section in the config. use_sell_signal = True sell_profit_only = False diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index a70643aee..f78489173 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -58,10 +58,6 @@ class SampleStrategy(IStrategy): # Run "populate_indicators()" only for new candle. process_only_new_candles = False - # Disable checking the dataframe (converts the error into a warning message) - # Only use if you understand the implications! - disable_dataframe_checks: bool = False - # These values can be overridden in the "ask_strategy" section in the config. use_sell_signal = True sell_profit_only = False From fe40e8305d94afd6613b4ac4b8c9b8af5ce4203e Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sat, 30 May 2020 18:58:11 +0300 Subject: [PATCH 024/191] fix #3401 --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index f017bef96..c03be55d1 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -13,7 +13,7 @@ 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) We also recommend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot), which is optional but recommended. From 48915d7945a094f7edcb4015b8669a2ddad005ed Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sat, 30 May 2020 19:35:44 +0300 Subject: [PATCH 025/191] minor: Fix docs --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8853b6e05..7686f3248 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -107,7 +107,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
**Datatype:** String, SQLAlchemy connect string | `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.*
**Datatype:** Enum, either `stopped` or `running` | `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below.
**Datatype:** Boolean -| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).[Strategy Override](#parameters-in-the-strategy).
*Defaults to `False`*.
**Datatype:** Boolean +| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `False`*.
**Datatype:** Boolean | `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`.
**Datatype:** ClassName | `strategy_path` | Adds an additional strategy lookup path (must be a directory).
**Datatype:** String | `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
**Datatype:** Positive Integer From 908449640ad020dd1becae9feb39ac8f7cbea829 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 19:17:17 +0200 Subject: [PATCH 026/191] Disabledataframecheck is not in internals and does not belong there --- freqtrade/resolvers/strategy_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 51cb8fc05..abd6a4195 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -70,7 +70,7 @@ class StrategyResolver(IResolver): ("use_sell_signal", True, 'ask_strategy'), ("sell_profit_only", False, 'ask_strategy'), ("ignore_roi_if_buy_signal", False, 'ask_strategy'), - ("disable_dataframe_checks", False, 'internals') + ("disable_dataframe_checks", False, None), ] for attribute, default, subkey in attributes: if subkey: From a0d6a72bc8dfdbb2373b0694611dab77ce1a2489 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 19:26:47 +0200 Subject: [PATCH 027/191] Update docs/configuration.md Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 5e3be6485..1f2dcb8a9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -83,7 +83,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#pairlists-and-pairlist-handlers)).
**Datatype:** List | `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#pairlists-and-pairlist-handlers)).
**Datatype:** List -| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances. This is usually the correct place for ccxt configurations. 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_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for ccxt configurations. 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_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 From 91f84f1a4304266d7f697a0418683bf3a202d796 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 19:28:30 +0200 Subject: [PATCH 028/191] Fix typo in close trade message --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 4bf19192d..dcb667d2d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -328,7 +328,7 @@ class Telegram(RPC): best_rate = stats['best_rate'] # Message to display if stats['closed_trade_count'] > 0: - markdown_msg = ("*ROI:* Close trades\n" + markdown_msg = ("*ROI:* Closed trades\n" f"∙ `{profit_closed_coin:.8f} {stake_cur} " f"({profit_closed_percent:.2f}%)`\n" f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 0b990281f..29820fba3 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -446,7 +446,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert '*ROI:* Close trades' in msg_mock.call_args_list[-1][0][0] + assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0] assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] From cc90e7b413f2b300fe692b0b75d415c6e1c3a3d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 19:42:09 +0200 Subject: [PATCH 029/191] Show "No trades yet." when no trade happened yet --- freqtrade/rpc/telegram.py | 35 ++++++++++++++++++---------------- tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index dcb667d2d..7eef18dfc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -326,24 +326,27 @@ class Telegram(RPC): avg_duration = stats['avg_duration'] best_pair = stats['best_pair'] best_rate = stats['best_rate'] - # Message to display - if stats['closed_trade_count'] > 0: - markdown_msg = ("*ROI:* Closed trades\n" - f"∙ `{profit_closed_coin:.8f} {stake_cur} " - f"({profit_closed_percent:.2f}%)`\n" - f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") + if stats['trade_count'] == 0: + markdown_msg = 'No trades yet.' else: - markdown_msg = "`No closed trade` \n" + # Message to display + if stats['closed_trade_count'] > 0: + markdown_msg = ("*ROI:* Closed trades\n" + f"∙ `{profit_closed_coin:.8f} {stake_cur} " + f"({profit_closed_percent:.2f}%)`\n" + f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") + else: + markdown_msg = "`No closed trade` \n" - markdown_msg += (f"*ROI:* All trades\n" - f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" - f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" - f"*Total Trade Count:* `{trade_count}`\n" - f"*First Trade opened:* `{first_trade_date}`\n" - f"*Latest Trade opened:* `{latest_trade_date}`") - if stats['closed_trade_count'] > 0: - markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" - f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") + markdown_msg += (f"*ROI:* All trades\n" + f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" + f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" + f"*Total Trade Count:* `{trade_count}`\n" + f"*First Trade opened:* `{first_trade_date}`\n" + f"*Latest Trade opened:* `{latest_trade_date}`") + if stats['closed_trade_count'] > 0: + markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" + f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") self._send_msg(markdown_msg) @authorized_only diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 29820fba3..500acaa30 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -420,7 +420,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'No closed trade' in msg_mock.call_args_list[0][0][0] + assert 'No trades yet.' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data From 7ad1c7e817ca115bf4bd8a79c000685ebc948730 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 May 2020 09:51:45 +0200 Subject: [PATCH 030/191] Allow lower verbosity level for api server Not logging all calls makes sense when running the UI otherwise this is VERY verbose, clogging up the log. --- config_full.json.example | 1 + docs/configuration.md | 1 + docs/rest-api.md | 1 + freqtrade/constants.py | 1 + freqtrade/loggers.py | 8 ++++++-- freqtrade/rpc/api_server.py | 3 --- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 481742817..57ebb22e6 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -121,6 +121,7 @@ "enabled": false, "listen_ip_address": "127.0.0.1", "listen_port": 8080, + "verbosity": "info", "jwt_secret_key": "somethingrandom", "username": "freqtrader", "password": "SuperSecurePassword" diff --git a/docs/configuration.md b/docs/configuration.md index a1cb45e0f..3a8262fab 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -103,6 +103,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Boolean | `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** IPv4 | `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Integer between 1024 and 65535 +| `api_server.verbosity` | Loggging verbosity. `info` will print all RPC Calls, while "error" will only display errors.
**Datatype:** Enum, either `info` or `error`. Defaults to `info`. | `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
**Datatype:** String, SQLAlchemy connect string diff --git a/docs/rest-api.md b/docs/rest-api.md index 7f1a95b12..ed5f355b4 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -11,6 +11,7 @@ Sample configuration: "enabled": true, "listen_ip_address": "127.0.0.1", "listen_port": 8080, + "verbosity": "info", "jwt_secret_key": "somethingrandom", "username": "Freqtrader", "password": "SuperSecret1!" diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1984d4866..a91e3b19f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -221,6 +221,7 @@ CONF_SCHEMA = { }, 'username': {'type': 'string'}, 'password': {'type': 'string'}, + 'verbosity': {'type': 'string', 'enum': ['error', 'info']}, }, 'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password'] }, diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index 153ce8c80..aa08ee8a7 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -11,7 +11,7 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) -def _set_loggers(verbosity: int = 0) -> None: +def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None: """ Set the logging level for third party libraries :return: None @@ -28,6 +28,10 @@ def _set_loggers(verbosity: int = 0) -> None: ) logging.getLogger('telegram').setLevel(logging.INFO) + logging.getLogger('werkzeug').setLevel( + logging.ERROR if api_verbosity == 'error' else logging.INFO + ) + def setup_logging(config: Dict[str, Any]) -> None: """ @@ -77,5 +81,5 @@ def setup_logging(config: Dict[str, Any]) -> None: format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=log_handlers ) - _set_loggers(verbosity) + _set_loggers(verbosity, config.get('api_server', {}).get('verbosity', 'info')) logger.info('Verbosity set to %s', verbosity) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 23b6a85b0..9d0899ccd 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -360,7 +360,6 @@ class ApiServer(RPC): Returns a cumulative profit statistics :return: stats """ - logger.info("LocalRPC - Profit Command Called") stats = self._rpc_trade_statistics(self._config['stake_currency'], self._config.get('fiat_display_currency') @@ -377,8 +376,6 @@ class ApiServer(RPC): Returns a cumulative performance statistics :return: stats """ - logger.info("LocalRPC - performance Command Called") - stats = self._rpc_performance() return self.rest_dump(stats) From dc7f0f11877d69f8a4d8fc23c2a7cff3d739755d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 May 2020 09:57:31 +0200 Subject: [PATCH 031/191] Add api-server to default config samples --- config.json.example | 9 +++++++++ config_binance.json.example | 9 +++++++++ config_kraken.json.example | 9 +++++++++ freqtrade/templates/base_config.json.j2 | 9 +++++++++ tests/test_configuration.py | 5 ++++- 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/config.json.example b/config.json.example index d37a6b336..ae87164d6 100644 --- a/config.json.example +++ b/config.json.example @@ -76,6 +76,15 @@ "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/config_binance.json.example b/config_binance.json.example index 5d7b6b656..74207d565 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -81,6 +81,15 @@ "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/config_kraken.json.example b/config_kraken.json.example index 54fbf4a00..f1e522da5 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -87,6 +87,15 @@ "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 6d3174347..5339595e8 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -53,6 +53,15 @@ "token": "{{ telegram_token }}", "chat_id": "{{ telegram_chat_id }}" }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "verbosity": "info", + "jwt_secret_key": "somethingrandom", + "username": "", + "password": "" + }, "initial_state": "running", "forcebuy_enable": false, "internals": { diff --git a/tests/test_configuration.py b/tests/test_configuration.py index edcbe4516..18fa607f2 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -634,6 +634,7 @@ def test_set_loggers() -> None: previous_value1 = logging.getLogger('requests').level previous_value2 = logging.getLogger('ccxt.base.exchange').level previous_value3 = logging.getLogger('telegram').level + previous_value3 = logging.getLogger('werkzeug').level _set_loggers() @@ -654,12 +655,14 @@ def test_set_loggers() -> None: assert logging.getLogger('requests').level is logging.DEBUG assert logging.getLogger('ccxt.base.exchange').level is logging.INFO assert logging.getLogger('telegram').level is logging.INFO + assert logging.getLogger('werkzeug').level is logging.INFO - _set_loggers(verbosity=3) + _set_loggers(verbosity=3, api_verbosity='error') assert logging.getLogger('requests').level is logging.DEBUG assert logging.getLogger('ccxt.base.exchange').level is logging.DEBUG assert logging.getLogger('telegram').level is logging.INFO + assert logging.getLogger('werkzeug').level is logging.ERROR @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") From 4087161d2b69b5b2d73751402fac9d1838f86de3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 May 2020 10:16:56 +0200 Subject: [PATCH 032/191] fix broken test --- tests/test_configuration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 18fa607f2..bd43f84ca 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -634,7 +634,6 @@ def test_set_loggers() -> None: previous_value1 = logging.getLogger('requests').level previous_value2 = logging.getLogger('ccxt.base.exchange').level previous_value3 = logging.getLogger('telegram').level - previous_value3 = logging.getLogger('werkzeug').level _set_loggers() From 123a556ec89e5e0eace9ca0f279d612b382e24f0 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 31 May 2020 13:05:58 +0300 Subject: [PATCH 033/191] Better exchange logging --- freqtrade/exchange/exchange.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index af745e8d0..6275b0eda 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -887,14 +887,20 @@ class Exchange: Async wrapper handling downloading trades using either time or id based methods. """ + logger.debug(f"_async_get_trade_history(), pair: {pair}, " + f"since: {since}, until: {until}, from_id: {from_id}") + + if not until: + exchange_msec = ccxt.Exchange.milliseconds() + logger.debug(f"Exchange milliseconds: {exchange_msec}") + until = exchange_msec + if self._trades_pagination == 'time': return await self._async_get_trade_history_time( - pair=pair, since=since, - until=until or ccxt.Exchange.milliseconds()) + pair=pair, since=since, until=until) elif self._trades_pagination == 'id': return await self._async_get_trade_history_id( - pair=pair, since=since, - until=until or ccxt.Exchange.milliseconds(), from_id=from_id + pair=pair, since=since, until=until, from_id=from_id ) else: raise OperationalException(f"Exchange {self.name} does use neither time, " From f202e09b10e3568d89cbf9ac7e765acfcbe78efa Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 08:56:12 +0200 Subject: [PATCH 034/191] Extract conversion to trades list to it's own function --- freqtrade/optimize/optimize_reports.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 1fc4d721e..77101a1f6 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -18,10 +18,7 @@ def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame :param all_results: Dict of Dataframes, one results dataframe per strategy """ for strategy, results in all_results.items(): - records = [(t.pair, t.profit_percent, t.open_time.timestamp(), - t.close_time.timestamp(), t.open_index - 1, t.trade_duration, - t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value) - for index, t in results.iterrows()] + records = backtest_result_to_list(results) if records: filename = recordfilename @@ -34,6 +31,18 @@ def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame file_dump_json(filename, records) +def backtest_result_to_list(results: DataFrame) -> List[List]: + """ + Converts a list of Backtest-results to list + :param results: Dataframe containing results for one strategy + :return: List of Lists containing the trades + """ + return [[t.pair, t.profit_percent, t.open_time.timestamp(), + t.close_time.timestamp(), t.open_index - 1, t.trade_duration, + t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value] + for index, t in results.iterrows()] + + def _get_line_floatfmt() -> List[str]: """ Generate floatformat (goes in line with _generate_result_line()) From ceaf32d304294104a06a2db8e15b609ec772a71c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 09:23:24 +0200 Subject: [PATCH 035/191] Extract backtesting report generation from show_backtest_Results --- freqtrade/optimize/optimize_reports.py | 42 +++++++++++++++++++------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 77101a1f6..5afdc6f77 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -255,12 +255,13 @@ def generate_edge_table(results: dict) -> str: floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore -def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], - all_results: Dict[str, DataFrame]): +def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], + all_results: Dict[str, DataFrame]): stake_currency = config['stake_currency'] max_open_trades = config['max_open_trades'] - + result = {'strategy': {}} for strategy, results in all_results.items(): + pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, max_open_trades=max_open_trades, results=results, skip_nan=False) @@ -270,21 +271,43 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], max_open_trades=max_open_trades, results=results.loc[results['open_at_end']], skip_nan=True) + strat_stats = { + 'trades': backtest_result_to_list(results), + 'results_per_pair': pair_results, + 'sell_reason_summary': sell_reason_stats, + 'left_open_trades': left_open_results, + } + result['strategy'][strategy] = strat_stats + + strategy_results = generate_strategy_metrics(stake_currency=stake_currency, + max_open_trades=max_open_trades, + all_results=all_results) + + result['strategy_comparison'] = strategy_results + + return result + + +def show_backtest_results(config: Dict, backtest_stats: Dict): + stake_currency = config['stake_currency'] + + for strategy, results in backtest_stats['strategy'].items(): + # Print results print(f"Result for strategy {strategy}") - table = generate_text_table(pair_results, stake_currency=stake_currency) + table = generate_text_table(results['results_per_pair'], stake_currency=stake_currency) if isinstance(table, str): print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) print(table) - table = generate_text_table_sell_reason(sell_reason_stats=sell_reason_stats, + table = generate_text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], stake_currency=stake_currency, ) if isinstance(table, str): print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) print(table) - table = generate_text_table(left_open_results, stake_currency=stake_currency) + table = generate_text_table(results['left_open_trades'], stake_currency=stake_currency) if isinstance(table, str): print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(table) @@ -292,13 +315,10 @@ def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame], print('=' * len(table.splitlines()[0])) print() - if len(all_results) > 1: + if len(backtest_stats['strategy']) > 1: # Print Strategy summary table - strategy_results = generate_strategy_metrics(stake_currency=stake_currency, - max_open_trades=max_open_trades, - all_results=all_results) - table = generate_text_table_strategy(strategy_results, stake_currency) + table = generate_text_table_strategy(backtest_stats['strategy_comparison'], stake_currency) print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) print(table) print('=' * len(table.splitlines()[0])) From 091693308ae1d16cb003aba2ff1fdea1d88b3be0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 09:24:27 +0200 Subject: [PATCH 036/191] Correctly call show_backtest_results --- freqtrade/optimize/backtesting.py | 6 ++++-- freqtrade/optimize/optimize_reports.py | 2 +- tests/optimize/test_backtesting.py | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3bf211d99..b47b38ea4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -18,7 +18,8 @@ from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.optimize.optimize_reports import (show_backtest_results, +from freqtrade.optimize.optimize_reports import (generate_backtest_stats, + show_backtest_results, store_backtest_result) from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.persistence import Trade @@ -411,4 +412,5 @@ class Backtesting: if self.config.get('export', False): store_backtest_result(self.config['exportfilename'], all_results) # Show backtest results - show_backtest_results(self.config, data, all_results) + stats = generate_backtest_stats(self.config, data, all_results) + show_backtest_results(self.config, stats) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 5afdc6f77..c148f0f44 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -259,7 +259,7 @@ def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], all_results: Dict[str, DataFrame]): stake_currency = config['stake_currency'] max_open_trades = config['max_open_trades'] - result = {'strategy': {}} + result: Dict[str, Any] = {'strategy': {}} for strategy, results in all_results.items(): pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ace82d28b..40c106975 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -333,6 +333,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: mocker.patch('freqtrade.data.history.get_timerange', get_timerange) patch_exchange(mocker) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') + mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats') mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) @@ -612,8 +613,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): patch_exchange(mocker) - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) - mocker.patch('freqtrade.optimize.backtesting.show_backtest_results', MagicMock()) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') + mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats') + mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) patched_configuration_load_config_file(mocker, default_conf) From adde1cfee2591820fb09db55b0957f57880be05b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 10:53:02 +0200 Subject: [PATCH 037/191] Add stoplosss_ratio and initial_stoploss_ratio --- freqtrade/persistence.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 111ccfe2a..2fe93f440 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -249,32 +249,40 @@ class Trade(_DECL_BASE): 'trade_id': self.id, 'pair': self.pair, 'is_open': self.is_open, + 'exchange': self.exchange, + 'amount': round(self.amount, 8), + 'stake_amount': round(self.stake_amount, 8), + 'strategy': self.strategy, + 'ticker_interval': self.ticker_interval, + 'fee_open': self.fee_open, 'fee_open_cost': self.fee_open_cost, 'fee_open_currency': self.fee_open_currency, 'fee_close': self.fee_close, 'fee_close_cost': self.fee_close_cost, 'fee_close_currency': self.fee_close_currency, + 'open_date_hum': arrow.get(self.open_date).humanize(), 'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"), 'open_timestamp': int(self.open_date.timestamp() * 1000), + 'open_rate': self.open_rate, + 'open_rate_requested': self.open_rate_requested, + 'open_trade_price': self.open_trade_price, + 'close_date_hum': (arrow.get(self.close_date).humanize() if self.close_date else None), 'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S") if self.close_date else None), 'close_timestamp': int(self.close_date.timestamp() * 1000) if self.close_date else None, - 'open_rate': self.open_rate, - 'open_rate_requested': self.open_rate_requested, - 'open_trade_price': self.open_trade_price, 'close_rate': self.close_rate, 'close_rate_requested': self.close_rate_requested, - 'amount': round(self.amount, 8), - 'stake_amount': round(self.stake_amount, 8), 'close_profit': self.close_profit, 'close_profit_abs': self.close_profit_abs, + 'sell_reason': self.sell_reason, 'sell_order_status': self.sell_order_status, 'stop_loss': self.stop_loss, + 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, 'stoploss_order_id': self.stoploss_order_id, 'stoploss_last_update': (self.stoploss_last_update.strftime("%Y-%m-%d %H:%M:%S") @@ -282,14 +290,14 @@ class Trade(_DECL_BASE): 'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000) if self.stoploss_last_update else None), 'initial_stop_loss': self.initial_stop_loss, + 'initial_stop_loss_ratio': (self.initial_stop_loss_pct + if self.initial_stop_loss_pct else None), 'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100 if self.initial_stop_loss_pct else None), 'min_rate': self.min_rate, 'max_rate': self.max_rate, - 'strategy': self.strategy, - 'ticker_interval': self.ticker_interval, + 'open_order_id': self.open_order_id, - 'exchange': self.exchange, } def adjust_min_max_rates(self, current_price: float) -> None: From 6dec508c5eb3a0106525f16428f27d4cc7b99b6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 10:56:05 +0200 Subject: [PATCH 038/191] Add new fields to tests --- tests/rpc/test_rpc.py | 4 ++++ tests/rpc/test_rpc_apiserver.py | 14 +++++++++----- tests/test_persistence.py | 4 ++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 7b7824310..dcd525574 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -82,11 +82,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit_pct': -0.41, 'stop_loss': 0.0, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', 'exchange': 'bittrex', @@ -136,11 +138,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit_pct': ANY, 'stop_loss': 0.0, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', 'exchange': 'bittrex', } == results[0] diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index c7f937b0c..13914fab0 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -510,8 +510,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'current_rate': 1.099e-05, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_pct': None, 'open_date': ANY, 'open_date_hum': 'just now', 'open_timestamp': ANY, @@ -521,9 +519,13 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'stake_amount': 0.001, 'stop_loss': 0.0, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, + 'initial_stop_loss': 0.0, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'trade_id': 1, 'close_rate_requested': None, 'current_rate': 1.099e-05, @@ -622,12 +624,11 @@ def test_api_forcebuy(botclient, mocker, fee): data='{"pair": "ETH/BTC"}') assert_response(rc) assert rc.json == {'amount': 1, + 'trade_id': None, 'close_date': None, 'close_date_hum': None, 'close_timestamp': None, 'close_rate': 0.265441, - 'initial_stop_loss': None, - 'initial_stop_loss_pct': None, 'open_date': ANY, 'open_date_hum': 'just now', 'open_timestamp': ANY, @@ -636,10 +637,13 @@ def test_api_forcebuy(botclient, mocker, fee): 'stake_amount': 1, 'stop_loss': None, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, - 'trade_id': None, + 'initial_stop_loss': None, + 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'close_profit': None, 'close_profit_abs': None, 'close_rate_requested': None, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 3b1041fd8..942e22a19 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -762,12 +762,14 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'stop_loss': None, + 'stop_loss_ratio': None, 'stop_loss_pct': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'min_rate': None, 'max_rate': None, 'strategy': None, @@ -805,11 +807,13 @@ def test_to_json(default_conf, fee): 'stake_amount': 0.001, 'stop_loss': None, 'stop_loss_pct': None, + 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, 'initial_stop_loss_pct': None, + 'initial_stop_loss_ratio': None, 'close_profit': None, 'close_profit_abs': None, 'close_rate_requested': None, From d2b7016dffe40d0581427f7cbdb00915838759b0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 11:05:37 +0200 Subject: [PATCH 039/191] Add stop_loss_abs ... --- freqtrade/persistence.py | 6 ++++-- tests/rpc/test_rpc.py | 4 ++++ tests/rpc/test_rpc_apiserver.py | 4 ++++ tests/test_persistence.py | 4 ++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 2fe93f440..823bf6dc0 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -281,7 +281,8 @@ class Trade(_DECL_BASE): 'sell_reason': self.sell_reason, 'sell_order_status': self.sell_order_status, - 'stop_loss': self.stop_loss, + 'stop_loss': self.stop_loss, # Deprecated - should not be used + 'stop_loss_abs': self.stop_loss, 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, 'stoploss_order_id': self.stoploss_order_id, @@ -289,7 +290,8 @@ class Trade(_DECL_BASE): if self.stoploss_last_update else None), 'stoploss_last_update_timestamp': (int(self.stoploss_last_update.timestamp() * 1000) if self.stoploss_last_update else None), - 'initial_stop_loss': self.initial_stop_loss, + 'initial_stop_loss': self.initial_stop_loss, # Deprecated - should not be used + 'initial_stop_loss_abs': self.initial_stop_loss, 'initial_stop_loss_ratio': (self.initial_stop_loss_pct if self.initial_stop_loss_pct else None), 'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100 diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index dcd525574..eb20e3903 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -81,12 +81,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'stop_loss': 0.0, + 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, + 'initial_stop_loss_abs': 0.0, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', @@ -137,12 +139,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit': ANY, 'current_profit_pct': ANY, 'stop_loss': 0.0, + 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, + 'initial_stop_loss_abs': 0.0, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'open_order': '(limit buy rem=0.00000000)', diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 13914fab0..4e4dd7dfd 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -518,12 +518,14 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'pair': 'ETH/BTC', 'stake_amount': 0.001, 'stop_loss': 0.0, + 'stop_loss_abs': 0.0, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': 0.0, + 'initial_stop_loss_abs': 0.0, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'trade_id': 1, @@ -636,12 +638,14 @@ def test_api_forcebuy(botclient, mocker, fee): 'pair': 'ETH/ETH', 'stake_amount': 1, 'stop_loss': None, + 'stop_loss_abs': None, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, + 'initial_stop_loss_abs': None, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'close_profit': None, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 942e22a19..ae639511b 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -762,12 +762,14 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'stop_loss': None, + 'stop_loss_abs': None, 'stop_loss_ratio': None, 'stop_loss_pct': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, + 'initial_stop_loss_abs': None, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'min_rate': None, @@ -806,12 +808,14 @@ def test_to_json(default_conf, fee): 'amount': 100.0, 'stake_amount': 0.001, 'stop_loss': None, + 'stop_loss_abs': None, 'stop_loss_pct': None, 'stop_loss_ratio': None, 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss': None, + 'initial_stop_loss_abs': None, 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'close_profit': None, From b06c1daddba2e81cc7904da3e21d6695949dda63 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:19:38 +0000 Subject: [PATCH 040/191] Bump mkdocs-material from 5.2.1 to 5.2.2 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.2.1 to 5.2.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.2.1...5.2.2) Signed-off-by: dependabot-preview[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 b34f93c95..9997fa854 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==5.2.1 +mkdocs-material==5.2.2 mdx_truly_sane_lists==1.2 From 74088ba43880539af782a5e9b5f740de4dde0964 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:20:01 +0000 Subject: [PATCH 041/191] Bump plotly from 4.7.1 to 4.8.1 Bumps [plotly](https://github.com/plotly/plotly.py) from 4.7.1 to 4.8.1. - [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.7.1...v4.8.1) Signed-off-by: dependabot-preview[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index d81239053..cb13a59bf 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.7.1 +plotly==4.8.1 From 005addf0a5bafdbc015e01dcb1e112859d4f97e3 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:20:47 +0000 Subject: [PATCH 042/191] Bump ccxt from 1.28.49 to 1.29.5 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.28.49 to 1.29.5. - [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.28.49...1.29.5) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index a9019cba1..07bc5caa3 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.28.49 +ccxt==1.29.5 SQLAlchemy==1.3.17 python-telegram-bot==12.7 arrow==0.15.6 From 65c5bba189ce8fc09f175a82ad52e13a0a1425ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 12:45:59 +0200 Subject: [PATCH 043/191] Fix typo in docs Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 3a8262fab..c9953f0f1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -103,7 +103,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Boolean | `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** IPv4 | `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Integer between 1024 and 65535 -| `api_server.verbosity` | Loggging verbosity. `info` will print all RPC Calls, while "error" will only display errors.
**Datatype:** Enum, either `info` or `error`. Defaults to `info`. +| `api_server.verbosity` | Logging verbosity. `info` will print all RPC Calls, while "error" will only display errors.
**Datatype:** Enum, either `info` or `error`. Defaults to `info`. | `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
**Datatype:** String, SQLAlchemy connect string From c35f9f8d39967999807cab6a8a405976fa691b2d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 19:45:37 +0200 Subject: [PATCH 044/191] Verify sell-rate got a value - otherwise downstream code does not work. Using PricingException here will cease operation for this pair for this iteration - postponing handling to the next iteration - where hopefully a price is again present. --- freqtrade/freqtradebot.py | 2 ++ tests/test_freqtradebot.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d4afa1d60..df0aad801 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -676,6 +676,8 @@ class FreqtradeBot: raise PricingError from e else: rate = self.exchange.fetch_ticker(pair)[ask_strategy['price_side']] + if rate is None: + raise PricingError(f"Sell-Rate for {pair} was empty.") self._sell_rate_cache[pair] = rate return rate diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5e951b585..17b51dff5 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3931,6 +3931,26 @@ def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog): assert log_has("Sell Price at location from orderbook could not be determined.", caplog) +def test_get_sell_rate_exception(default_conf, mocker, caplog): + # Ticker on one side can be empty in certain circumstances. + default_conf['ask_strategy']['price_side'] = 'ask' + pair = "ETH/BTC" + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': None, 'bid': 0.12}) + ft = get_patched_freqtradebot(mocker, default_conf) + with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): + ft.get_sell_rate(pair, True) + + ft.config['ask_strategy']['price_side'] = 'bid' + assert ft.get_sell_rate(pair, True) == 0.12 + # Reverse sides + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': 0.13, 'bid': None}) + with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): + ft.get_sell_rate(pair, True) + + ft.config['ask_strategy']['price_side'] = 'ask' + assert ft.get_sell_rate(pair, True) == 0.13 + + def test_startup_state(default_conf, mocker): default_conf['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} From f6f75072ba049815815e4b17e38acdcb8bf8d9fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 19:54:05 +0200 Subject: [PATCH 045/191] Fix linelength --- tests/test_freqtradebot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 17b51dff5..487e3a60e 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3935,7 +3935,8 @@ def test_get_sell_rate_exception(default_conf, mocker, caplog): # Ticker on one side can be empty in certain circumstances. default_conf['ask_strategy']['price_side'] = 'ask' pair = "ETH/BTC" - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': None, 'bid': 0.12}) + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + return_value={'ask': None, 'bid': 0.12}) ft = get_patched_freqtradebot(mocker, default_conf) with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): ft.get_sell_rate(pair, True) @@ -3943,7 +3944,8 @@ def test_get_sell_rate_exception(default_conf, mocker, caplog): ft.config['ask_strategy']['price_side'] = 'bid' assert ft.get_sell_rate(pair, True) == 0.12 # Reverse sides - mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'ask': 0.13, 'bid': None}) + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', + return_value={'ask': 0.13, 'bid': None}) with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."): ft.get_sell_rate(pair, True) From 3139343946889361c48442f684a7b661e4c1536b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 19:58:28 +0200 Subject: [PATCH 046/191] Remove capital_available_percentage and raise instead --- docs/edge.md | 1 - freqtrade/configuration/deprecated_settings.py | 2 +- freqtrade/constants.py | 1 - freqtrade/edge/edge_positioning.py | 6 ++---- tests/config_test_comments.json | 1 - tests/test_configuration.py | 5 +++-- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/edge.md b/docs/edge.md index 029844c0b..bdcf6cde9 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -148,7 +148,6 @@ Edge module has following configuration options: | `enabled` | If true, then Edge will run periodically.
*Defaults to `false`.*
**Datatype:** Boolean | `process_throttle_secs` | How often should Edge run in seconds.
*Defaults to `3600` (once per hour).*
**Datatype:** Integer | `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy.
**Note** that it downloads historical data so increasing this number would lead to slowing down the bot.
*Defaults to `7`.*
**Datatype:** Integer -| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency.
As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital.
*Defaults to `0.5`.*
**Datatype:** Float | `allowed_risk` | Ratio of allowed risk per trade.
*Defaults to `0.01` (1%)).*
**Datatype:** Float | `stoploss_range_min` | Minimum stoploss.
*Defaults to `-0.01`.*
**Datatype:** Float | `stoploss_range_max` | Maximum stoploss.
*Defaults to `-0.10`.*
**Datatype:** Float diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 3999ea422..ff4401c27 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -60,7 +60,7 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: if (config.get('edge', {}).get('enabled', False) and 'capital_available_percentage' in config.get('edge', {})): - logger.warning( + raise OperationalException( "DEPRECATED: " "Using 'edge.capital_available_percentage' has been deprecated in favor of " "'tradable_balance_ratio'. Please migrate your configuration to " diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1984d4866..0e08db91f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -286,7 +286,6 @@ CONF_SCHEMA = { 'process_throttle_secs': {'type': 'integer', 'minimum': 600}, 'calculate_since_number_of_days': {'type': 'integer'}, 'allowed_risk': {'type': 'number'}, - 'capital_available_percentage': {'type': 'number'}, 'stoploss_range_min': {'type': 'number'}, 'stoploss_range_max': {'type': 'number'}, 'stoploss_range_step': {'type': 'number'}, diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index c19d4552a..2550cf604 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -57,9 +57,7 @@ class Edge: if self.config['stake_amount'] != UNLIMITED_STAKE_AMOUNT: raise OperationalException('Edge works only with unlimited stake amount') - # Deprecated capital_available_percentage. Will use tradable_balance_ratio in the future. - self._capital_percentage: float = self.edge_config.get( - 'capital_available_percentage', self.config['tradable_balance_ratio']) + self._capital_ratio: float = self.config['tradable_balance_ratio'] self._allowed_risk: float = self.edge_config.get('allowed_risk') self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14) self._last_updated: int = 0 # Timestamp of pairs last updated time @@ -157,7 +155,7 @@ class Edge: def stake_amount(self, pair: str, free_capital: float, total_capital: float, capital_in_trade: float) -> float: stoploss = self.stoploss(pair) - available_capital = (total_capital + capital_in_trade) * self._capital_percentage + available_capital = (total_capital + capital_in_trade) * self._capital_ratio allowed_capital_at_risk = available_capital * self._allowed_risk max_position_size = abs(allowed_capital_at_risk / stoploss) position_size = min(max_position_size, free_capital) diff --git a/tests/config_test_comments.json b/tests/config_test_comments.json index 8f41b08fa..d9d4a86bc 100644 --- a/tests/config_test_comments.json +++ b/tests/config_test_comments.json @@ -92,7 +92,6 @@ "enabled": false, "process_throttle_secs": 3600, "calculate_since_number_of_days": 7, - "capital_available_percentage": 0.5, "allowed_risk": 0.01, "stoploss_range_min": -0.01, "stoploss_range_max": -0.1, diff --git a/tests/test_configuration.py b/tests/test_configuration.py index edcbe4516..2af89bea7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1048,8 +1048,9 @@ def test_process_deprecated_setting_edge(mocker, edge_conf, caplog): 'capital_available_percentage': 0.5, }}) - process_temporary_deprecated_settings(edge_conf) - assert log_has_re(r"DEPRECATED.*Using 'edge.capital_available_percentage'*", caplog) + with pytest.raises(OperationalException, + match=r"DEPRECATED.*Using 'edge.capital_available_percentage'*"): + process_temporary_deprecated_settings(edge_conf) def test_check_conflicting_settings(mocker, default_conf, caplog): From 67a3c323730f4b4a882c1688c2cf53be3aa24f98 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:02:12 +0200 Subject: [PATCH 047/191] Remove some occurances of percentage --- docs/configuration.md | 6 +++--- docs/stoploss.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a1cb45e0f..254c581fd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,8 +53,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float | `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions.
*Defaults to `false`.*
**Datatype:** Boolean | `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict -| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Float (as ratio) +| `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict +| `stoploss` | **Required.** Value as ratio of the stoploss used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Float (as ratio) | `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Boolean | `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Float | `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).*
**Datatype:** Float @@ -217,7 +217,7 @@ To allow the bot to trade all the available `stake_currency` in your account (mi ### Understand minimal_roi The `minimal_roi` configuration parameter is a JSON object where the key is a duration -in minutes and the value is the minimum ROI in percent. +in minutes and the value is the minimum ROI as ratio. See the example below: ```json diff --git a/docs/stoploss.md b/docs/stoploss.md index f6d56fd41..0e43817ec 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -1,6 +1,6 @@ # Stop Loss -The `stoploss` configuration parameter is loss in percentage that should trigger a sale. +The `stoploss` configuration parameter is loss as ratio that should trigger a sale. For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. Most of the strategy files already include the optimal `stoploss` value. From b2025597aa3f97f9f6a6b14e0402534dc8e2cdcc Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:15:48 +0200 Subject: [PATCH 048/191] Build-commands should write timeframe instead of ticker interval --- freqtrade/commands/build_config_commands.py | 4 ++-- freqtrade/templates/base_config.json.j2 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 87098f53c..0c98b2e55 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -75,8 +75,8 @@ def ask_user_config() -> Dict[str, Any]: }, { "type": "text", - "name": "ticker_interval", - "message": "Please insert your timeframe (ticker interval):", + "name": "timeframe", + "message": "Please insert your desired timeframe (e.g. 5m):", "default": "5m", }, { diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 6d3174347..e47c32309 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -4,7 +4,7 @@ "stake_amount": {{ stake_amount }}, "tradable_balance_ratio": 0.99, "fiat_display_currency": "{{ fiat_display_currency }}", - "ticker_interval": "{{ ticker_interval }}", + "timeframe": "{{ timeframe }}", "dry_run": {{ dry_run | lower }}, "cancel_open_orders_on_exit": false, "unfilledtimeout": { From 009ea0639f90535a0f350311231c217d1664deab Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:33:26 +0200 Subject: [PATCH 049/191] Exchange some occurances of ticker_interval --- freqtrade/commands/arguments.py | 6 ++--- freqtrade/commands/cli_options.py | 4 +-- freqtrade/commands/list_commands.py | 4 +-- freqtrade/constants.py | 2 ++ tests/conftest.py | 2 +- tests/exchange/test_exchange.py | 8 +++--- tests/optimize/test_backtest_detail.py | 2 +- tests/optimize/test_backtesting.py | 34 +++++++++++++------------- tests/optimize/test_edge_cli.py | 8 +++--- tests/optimize/test_hyperopt.py | 8 +++--- 10 files changed, 40 insertions(+), 38 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 1b7bbfeb5..36e3dedf0 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -15,7 +15,7 @@ ARGS_STRATEGY = ["strategy", "strategy_path"] ARGS_TRADE = ["db_url", "sd_notify", "dry_run"] -ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", +ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "max_open_trades", "stake_amount", "fee"] ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", @@ -59,10 +59,10 @@ ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchang ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url", "trade_source", "export", "exportfilename", - "timerange", "ticker_interval", "no_trades"] + "timerange", "timeframe", "no_trades"] ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", - "trade_source", "ticker_interval"] + "trade_source", "timeframe"] ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index ee9208c33..3ed2f81d1 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -110,8 +110,8 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', ), # Optimize common - "ticker_interval": Arg( - '-i', '--ticker-interval', + "timeframe": Arg( + '-i', '--timeframe', '--ticker-interval', help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).', ), "timerange": Arg( diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index e5131f9b2..b29aabe25 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -102,8 +102,8 @@ def start_list_timeframes(args: Dict[str, Any]) -> None: Print ticker intervals (timeframes) available on Exchange """ config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) - # Do not use ticker_interval set in the config - config['ticker_interval'] = None + # Do not use timeframe set in the config + config['timeframe'] = None # Init exchange exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1984d4866..511c2993d 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -72,6 +72,7 @@ CONF_SCHEMA = { 'properties': { 'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1}, 'ticker_interval': {'type': 'string'}, + 'timeframe': {'type': 'string'}, 'stake_currency': {'type': 'string'}, 'stake_amount': { 'type': ['number', 'string'], @@ -303,6 +304,7 @@ CONF_SCHEMA = { SCHEMA_TRADE_REQUIRED = [ 'exchange', + 'timeframe', 'max_open_trades', 'stake_currency', 'stake_amount', diff --git a/tests/conftest.py b/tests/conftest.py index 971f7a5fa..f62b18f98 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -247,7 +247,7 @@ def default_conf(testdatadir): "stake_currency": "BTC", "stake_amount": 0.001, "fiat_display_currency": "USD", - "ticker_interval": '5m', + "timeframe": '5m', "dry_run": True, "cancel_open_orders_on_exit": False, "minimal_roi": { diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 25aecba5c..6df94f356 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1350,7 +1350,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ # exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), "_async_get_candle_history", "fetch_ohlcv", - pair='ABCD/BTC', timeframe=default_conf['ticker_interval']) + pair='ABCD/BTC', timeframe=default_conf['timeframe']) api_mock = MagicMock() with pytest.raises(OperationalException, @@ -1480,7 +1480,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) sort_mock = mocker.patch('freqtrade.exchange.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the OHLCV data sort - res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) + res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe']) assert res[0] == 'ETH/BTC' res_ohlcv = res[2] @@ -1517,9 +1517,9 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na # Reset sort mock sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the OHLCV data sort - res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) + res = await exchange._async_get_candle_history('ETH/BTC', default_conf['timeframe']) assert res[0] == 'ETH/BTC' - assert res[1] == default_conf['ticker_interval'] + assert res[1] == default_conf['timeframe'] res_ohlcv = res[2] # Sorted not called again - data is already in order assert sort_mock.call_count == 0 diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index e7bc76c1d..9b3043086 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -360,7 +360,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: """ default_conf["stoploss"] = data.stop_loss default_conf["minimal_roi"] = data.roi - default_conf["ticker_interval"] = tests_timeframe + default_conf["timeframe"] = tests_timeframe default_conf["trailing_stop"] = data.trailing_stop default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached # Only add this to configuration If it's necessary diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ace82d28b..407604d9c 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -81,7 +81,7 @@ def load_data_test(what, testdatadir): def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None: patch_exchange(mocker) - config['ticker_interval'] = '1m' + config['timeframe'] = '1m' backtesting = Backtesting(config) data = load_data_test(contour, testdatadir) @@ -165,7 +165,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config + assert 'timeframe' in config assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'position_stacking' not in config @@ -189,7 +189,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', - '--ticker-interval', '1m', + '--timeframe', '1m', '--enable-position-stacking', '--disable-max-market-positions', '--timerange', ':100', @@ -208,8 +208,8 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> assert config['runmode'] == RunMode.BACKTEST assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config - assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + assert 'timeframe' in config + assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...', caplog) assert 'position_stacking' in config @@ -288,7 +288,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> None: patch_exchange(mocker) - del default_conf['ticker_interval'] + del default_conf['timeframe'] default_conf['strategy_list'] = ['DefaultStrategy', 'SampleStrategy'] @@ -337,7 +337,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) - default_conf['ticker_interval'] = '1m' + default_conf['timeframe'] = '1m' default_conf['datadir'] = testdatadir default_conf['export'] = None default_conf['timerange'] = '-1510694220' @@ -367,7 +367,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) - default_conf['ticker_interval'] = "1m" + default_conf['timeframe'] = "1m" default_conf['datadir'] = testdatadir default_conf['export'] = None default_conf['timerange'] = '20180101-20180102' @@ -387,7 +387,7 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=[])) - default_conf['ticker_interval'] = "1m" + default_conf['timeframe'] = "1m" default_conf['datadir'] = testdatadir default_conf['export'] = None default_conf['timerange'] = '20180101-20180102' @@ -534,7 +534,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC', datadir=testdatadir) - default_conf['ticker_interval'] = '1m' + default_conf['timeframe'] = '1m' backtesting = Backtesting(default_conf) backtesting.strategy.advise_buy = _trend_alternate # Override backtesting.strategy.advise_sell = _trend_alternate # Override @@ -573,7 +573,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) # Remove data for one pair from the beginning of the data data[pair] = data[pair][tres:].reset_index() - default_conf['ticker_interval'] = '5m' + default_conf['timeframe'] = '5m' backtesting = Backtesting(default_conf) backtesting.strategy.advise_buy = _trend_alternate_hold # Override @@ -623,7 +623,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', str(testdatadir), - '--ticker-interval', '1m', + '--timeframe', '1m', '--timerange', '1510694220-1510700340', '--enable-position-stacking', '--disable-max-market-positions' @@ -632,7 +632,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): start_backtesting(args) # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Parameter --timerange detected: 1510694220-1510700340 ...', f'Using data directory: {testdatadir} ...', @@ -676,7 +676,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): '--config', 'config.json', '--datadir', str(testdatadir), '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--ticker-interval', '1m', + '--timeframe', '1m', '--timerange', '1510694220-1510700340', '--enable-position-stacking', '--disable-max-market-positions', @@ -695,7 +695,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Parameter --timerange detected: 1510694220-1510700340 ...', f'Using data directory: {testdatadir} ...', @@ -765,7 +765,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat '--config', 'config.json', '--datadir', str(testdatadir), '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), - '--ticker-interval', '1m', + '--timeframe', '1m', '--timerange', '1510694220-1510700340', '--enable-position-stacking', '--disable-max-market-positions', @@ -778,7 +778,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat # check the logs, that will contain the backtest result exists = [ - 'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Parameter --timerange detected: 1510694220-1510700340 ...', f'Using data directory: {testdatadir} ...', diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index a5e468542..acec51f66 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -29,7 +29,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config + assert 'timeframe' in config assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'timerange' not in config @@ -48,7 +48,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', - '--ticker-interval', '1m', + '--timeframe', '1m', '--timerange', ':100', '--stoplosses=-0.01,-0.10,-0.001' ] @@ -62,8 +62,8 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N assert 'datadir' in config assert config['runmode'] == RunMode.EDGE assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config - assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + assert 'timeframe' in config + assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...', caplog) assert 'timerange' in config diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 90e047954..f5b3b8909 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -94,7 +94,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config + assert 'timeframe' in config assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'position_stacking' not in config @@ -136,8 +136,8 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo assert config['runmode'] == RunMode.HYPEROPT assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config - assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + assert 'timeframe' in config + assert log_has('Parameter -i/--ticker-interval detected ... Using timeframe: 1m ...', caplog) assert 'position_stacking' in config @@ -544,7 +544,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: ) patch_exchange(mocker) # Co-test loading timeframe from strategy - del default_conf['ticker_interval'] + del default_conf['timeframe'] default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, From 898def7f6ca3a58163ad7811dca75c547bb13730 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:39:01 +0200 Subject: [PATCH 050/191] Remove ticker_interval from exchange --- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7ef0d7750..3f1cdc568 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -115,7 +115,7 @@ class Exchange: if validate: # Check if timeframe is available - self.validate_timeframes(config.get('ticker_interval')) + self.validate_timeframes(config.get('timeframe')) # Initial markets load self._load_markets() diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 6df94f356..0d924882f 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -578,7 +578,7 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): ('5m'), ("1m"), ("15m"), ("1h") ]) def test_validate_timeframes(default_conf, mocker, timeframe): - default_conf["ticker_interval"] = timeframe + default_conf["timeframe"] = timeframe api_mock = MagicMock() id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock @@ -596,7 +596,7 @@ def test_validate_timeframes(default_conf, mocker, timeframe): def test_validate_timeframes_failed(default_conf, mocker): - default_conf["ticker_interval"] = "3m" + default_conf["timeframe"] = "3m" api_mock = MagicMock() id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock @@ -613,7 +613,7 @@ def test_validate_timeframes_failed(default_conf, mocker): with pytest.raises(OperationalException, match=r"Invalid timeframe '3m'. This exchange supports.*"): Exchange(default_conf) - default_conf["ticker_interval"] = "15s" + default_conf["timeframe"] = "15s" with pytest.raises(OperationalException, match=r"Timeframes < 1m are currently not supported by Freqtrade."): @@ -621,7 +621,7 @@ def test_validate_timeframes_failed(default_conf, mocker): def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): - default_conf["ticker_interval"] = "3m" + default_conf["timeframe"] = "3m" api_mock = MagicMock() id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock @@ -641,7 +641,7 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker): - default_conf["ticker_interval"] = "3m" + default_conf["timeframe"] = "3m" api_mock = MagicMock() id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock @@ -662,7 +662,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker): def test_validate_timeframes_not_in_config(default_conf, mocker): - del default_conf["ticker_interval"] + del default_conf["timeframe"] api_mock = MagicMock() id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock From b2c241e607317baf147806824c838952632e5722 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:43:20 +0200 Subject: [PATCH 051/191] Replace ticker_interval in all rpc files --- freqtrade/rpc/rpc.py | 3 ++- freqtrade/rpc/rpc_manager.py | 4 ++-- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc.py | 2 ++ tests/rpc/test_rpc_apiserver.py | 2 ++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 6addf18ba..e7d4a3be8 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -101,7 +101,8 @@ class RPC: 'trailing_stop_positive': config.get('trailing_stop_positive'), 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'), - 'ticker_interval': config['ticker_interval'], + 'ticker_interval': config['timeframe'], # DEPRECATED + 'timeframe': config['timeframe'], 'exchange': config['exchange']['name'], 'strategy': config['strategy'], 'forcebuy_enabled': config.get('forcebuy_enable', False), diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 670275991..2cb44fec8 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -72,7 +72,7 @@ class RPCManager: minimal_roi = config['minimal_roi'] stoploss = config['stoploss'] trailing_stop = config['trailing_stop'] - ticker_interval = config['ticker_interval'] + timeframe = config['timeframe'] exchange_name = config['exchange']['name'] strategy_name = config.get('strategy', '') self.send_msg({ @@ -81,7 +81,7 @@ class RPCManager: f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' f'*Minimum ROI:* `{minimal_roi}`\n' f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n' - f'*Ticker Interval:* `{ticker_interval}`\n' + f'*Timeframe:* `{timeframe}`\n' f'*Strategy:* `{strategy_name}`' }) self.send_msg({ diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7eef18dfc..17354cdb0 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -639,7 +639,7 @@ class Telegram(RPC): f"*Max open Trades:* `{val['max_open_trades']}`\n" f"*Minimum ROI:* `{val['minimal_roi']}`\n" f"{sl_info}" - f"*Ticker Interval:* `{val['ticker_interval']}`\n" + f"*Timeframe:* `{val['timeframe']}`\n" f"*Strategy:* `{val['strategy']}`\n" f"*Current state:* `{val['state']}`" ) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 7b7824310..4950c4ea7 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -66,6 +66,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'max_rate': ANY, 'strategy': ANY, 'ticker_interval': ANY, + 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, 'close_date_hum': None, @@ -120,6 +121,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'max_rate': ANY, 'strategy': ANY, 'ticker_interval': ANY, + 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, 'close_date_hum': None, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index c7f937b0c..bccd12e18 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -544,6 +544,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'sell_order_status': None, 'strategy': 'DefaultStrategy', 'ticker_interval': 5, + 'timeframe': 5, 'exchange': 'bittrex', }] @@ -659,6 +660,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'sell_order_status': None, 'strategy': None, 'ticker_interval': None, + 'timeframe': None, 'exchange': 'bittrex', } From 950f358982b881d0f8f7b5d4e665506380fe8f90 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:47:27 +0200 Subject: [PATCH 052/191] Replace occurances in test files --- tests/commands/test_build_config.py | 4 +- tests/config_test_comments.json | 2 +- tests/data/test_dataprovider.py | 40 +++++++++---------- tests/data/test_history.py | 6 +-- tests/optimize/test_hyperopt.py | 4 +- tests/strategy/test_interface.py | 12 +++--- tests/strategy/test_strategy.py | 8 ++-- tests/test_arguments.py | 2 +- tests/test_configuration.py | 2 +- tests/test_freqtradebot.py | 2 +- tests/test_main.py | 12 +++--- tests/test_persistence.py | 2 + tests/test_plotting.py | 2 +- tests/testdata/backtest-result_test copy.json | 7 ++++ 14 files changed, 57 insertions(+), 48 deletions(-) create mode 100644 tests/testdata/backtest-result_test copy.json diff --git a/tests/commands/test_build_config.py b/tests/commands/test_build_config.py index d4ebe1de2..69b277e3b 100644 --- a/tests/commands/test_build_config.py +++ b/tests/commands/test_build_config.py @@ -44,7 +44,7 @@ def test_start_new_config(mocker, caplog, exchange): 'stake_currency': 'USDT', 'stake_amount': 100, 'fiat_display_currency': 'EUR', - 'ticker_interval': '15m', + 'timeframe': '15m', 'dry_run': True, 'exchange_name': exchange, 'exchange_key': 'sampleKey', @@ -68,7 +68,7 @@ def test_start_new_config(mocker, caplog, exchange): result = rapidjson.loads(wt_mock.call_args_list[0][0][0], parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS) assert result['exchange']['name'] == exchange - assert result['ticker_interval'] == '15m' + assert result['timeframe'] == '15m' def test_start_new_config_exists(mocker, caplog): diff --git a/tests/config_test_comments.json b/tests/config_test_comments.json index 8f41b08fa..b224d7602 100644 --- a/tests/config_test_comments.json +++ b/tests/config_test_comments.json @@ -9,7 +9,7 @@ "fiat_display_currency": "USD", // C++-style comment "amount_reserve_percent" : 0.05, // And more, tabs before this comment "dry_run": false, - "ticker_interval": "5m", + "timeframe": "5m", "trailing_stop": false, "trailing_stop_positive": 0.005, "trailing_stop_positive_offset": 0.0051, diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index c2d6e82f1..718060c5e 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -12,7 +12,7 @@ from tests.conftest import get_patched_exchange def test_ohlcv(mocker, default_conf, ohlcv_history): default_conf["runmode"] = RunMode.DRY_RUN - timeframe = default_conf["ticker_interval"] + timeframe = default_conf["timeframe"] exchange = get_patched_exchange(mocker, default_conf) exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history @@ -53,47 +53,47 @@ def test_historic_ohlcv(mocker, default_conf, ohlcv_history): def test_get_pair_dataframe(mocker, default_conf, ohlcv_history): default_conf["runmode"] = RunMode.DRY_RUN - ticker_interval = default_conf["ticker_interval"] + timeframe = default_conf["timeframe"] exchange = get_patched_exchange(mocker, default_conf) - exchange._klines[("XRP/BTC", ticker_interval)] = ohlcv_history - exchange._klines[("UNITTEST/BTC", ticker_interval)] = ohlcv_history + exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history + exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.DRY_RUN - assert ohlcv_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)) - assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) - assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval) is not ohlcv_history - assert not dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval).empty - assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty + assert ohlcv_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", timeframe)) + assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame) + assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe) is not ohlcv_history + assert not dp.get_pair_dataframe("UNITTEST/BTC", timeframe).empty + assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty # Test with and without parameter - assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)\ + assert dp.get_pair_dataframe("UNITTEST/BTC", timeframe)\ .equals(dp.get_pair_dataframe("UNITTEST/BTC")) default_conf["runmode"] = RunMode.LIVE dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.LIVE - assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) - assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty + assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame) + assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty historymock = MagicMock(return_value=ohlcv_history) mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) default_conf["runmode"] = RunMode.BACKTEST dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.BACKTEST - assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) - # assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty + assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", timeframe), DataFrame) + # assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty def test_available_pairs(mocker, default_conf, ohlcv_history): exchange = get_patched_exchange(mocker, default_conf) - ticker_interval = default_conf["ticker_interval"] - exchange._klines[("XRP/BTC", ticker_interval)] = ohlcv_history - exchange._klines[("UNITTEST/BTC", ticker_interval)] = ohlcv_history + timeframe = default_conf["timeframe"] + exchange._klines[("XRP/BTC", timeframe)] = ohlcv_history + exchange._klines[("UNITTEST/BTC", timeframe)] = ohlcv_history dp = DataProvider(default_conf, exchange) assert len(dp.available_pairs) == 2 - assert dp.available_pairs == [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval), ] + assert dp.available_pairs == [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe), ] def test_refresh(mocker, default_conf, ohlcv_history): @@ -101,8 +101,8 @@ def test_refresh(mocker, default_conf, ohlcv_history): mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock) exchange = get_patched_exchange(mocker, default_conf, id="binance") - ticker_interval = default_conf["ticker_interval"] - pairs = [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval)] + timeframe = default_conf["timeframe"] + pairs = [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe)] pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")] diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 6fd4d9569..c52163bbc 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -354,7 +354,7 @@ def test_init(default_conf, mocker) -> None: assert {} == load_data( datadir=Path(''), pairs=[], - timeframe=default_conf['ticker_interval'] + timeframe=default_conf['timeframe'] ) @@ -363,13 +363,13 @@ def test_init_with_refresh(default_conf, mocker) -> None: refresh_data( datadir=Path(''), pairs=[], - timeframe=default_conf['ticker_interval'], + timeframe=default_conf['timeframe'], exchange=exchange ) assert {} == load_data( datadir=Path(''), pairs=[], - timeframe=default_conf['ticker_interval'] + timeframe=default_conf['timeframe'] ) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index f5b3b8909..4f5b3983a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -117,7 +117,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo '--config', 'config.json', '--hyperopt', 'DefaultHyperOpt', '--datadir', '/foo/bar', - '--ticker-interval', '1m', + '--timeframe', '1m', '--timerange', ':100', '--enable-position-stacking', '--disable-max-market-positions', @@ -137,7 +137,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config - assert log_has('Parameter -i/--ticker-interval detected ... Using timeframe: 1m ...', + assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...', caplog) assert 'position_stacking' in config diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index e5539099b..59b4d5902 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -54,12 +54,12 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): def test_get_signal_empty(default_conf, mocker, caplog): - assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], + assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame()) assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() - assert (False, False) == _STRATEGY.get_signal('bar', default_conf['ticker_interval'], + assert (False, False) == _STRATEGY.get_signal('bar', default_conf['timeframe'], []) assert log_has('Empty candle (OHLCV) data for pair bar', caplog) @@ -70,7 +70,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ohlcv_his _STRATEGY, '_analyze_ticker_internal', side_effect=ValueError('xyz') ) - assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], + assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'], ohlcv_history) assert log_has_re(r'Strategy caused the following exception: xyz.*', caplog) @@ -83,7 +83,7 @@ def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ohlcv_history) ) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], + assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], ohlcv_history) assert log_has('Empty dataframe for pair xyz', caplog) @@ -104,7 +104,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): return_value=mocked_history ) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], + assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], ohlcv_history) assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog) @@ -124,7 +124,7 @@ def test_assert_df_raise(default_conf, mocker, caplog, ohlcv_history): _STRATEGY, 'assert_df', side_effect=StrategyError('Dataframe returned...') ) - assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], + assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], ohlcv_history) assert log_has('Unable to analyze candle (OHLCV) data for pair xyz: Dataframe returned...', caplog) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 13ca68bf0..5cb59cad0 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -106,7 +106,7 @@ def test_strategy(result, default_conf): assert default_conf['stoploss'] == -0.10 assert strategy.ticker_interval == '5m' - assert default_conf['ticker_interval'] == '5m' + assert default_conf['timeframe'] == '5m' df_indicators = strategy.advise_indicators(result, metadata=metadata) assert 'adx' in df_indicators @@ -176,19 +176,19 @@ def test_strategy_override_trailing_stop_positive(caplog, default_conf): caplog) -def test_strategy_override_ticker_interval(caplog, default_conf): +def test_strategy_override_timeframe(caplog, default_conf): caplog.set_level(logging.INFO) default_conf.update({ 'strategy': 'DefaultStrategy', - 'ticker_interval': 60, + 'timeframe': 60, 'stake_currency': 'ETH' }) strategy = StrategyResolver.load_strategy(default_conf) assert strategy.ticker_interval == 60 assert strategy.stake_currency == 'ETH' - assert log_has("Override strategy 'ticker_interval' with value in config file: 60.", + assert log_has("Override strategy 'timeframe' with value in config file: 60.", caplog) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 0052a61d0..457683598 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -131,7 +131,7 @@ def test_parse_args_backtesting_custom() -> None: assert call_args["verbosity"] == 0 assert call_args["command"] == 'backtesting' assert call_args["func"] is not None - assert call_args["ticker_interval"] == '1m' + assert call_args["timeframe"] == '1m' assert type(call_args["strategy_list"]) is list assert len(call_args["strategy_list"]) == 2 diff --git a/tests/test_configuration.py b/tests/test_configuration.py index edcbe4516..05074c258 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -87,7 +87,7 @@ def test_load_config_file_error_range(default_conf, mocker, caplog) -> None: assert isinstance(x, str) assert (x == '{"max_open_trades": 1, "stake_currency": "BTC", ' '"stake_amount": .001, "fiat_display_currency": "USD", ' - '"ticker_interval": "5m", "dry_run": true, ') + '"timeframe": "5m", "dry_run": true, ') def test__args_to_config(caplog): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5e951b585..442917bb6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -924,7 +924,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None: assert refresh_mock.call_count == 1 assert ("BTC/ETH", "1m") in refresh_mock.call_args[0][0] assert ("ETH/USDT", "1h") in refresh_mock.call_args[0][0] - assert ("ETH/BTC", default_conf["ticker_interval"]) in refresh_mock.call_args[0][0] + assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0] @pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", [ diff --git a/tests/test_main.py b/tests/test_main.py index 11d0ede3a..5700df1ae 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -35,12 +35,12 @@ def test_parse_args_backtesting(mocker) -> None: main(['backtesting']) assert backtesting_mock.call_count == 1 call_args = backtesting_mock.call_args[0][0] - assert call_args["config"] == ['config.json'] - assert call_args["verbosity"] == 0 - assert call_args["command"] == 'backtesting' - assert call_args["func"] is not None - assert callable(call_args["func"]) - assert call_args["ticker_interval"] is None + assert call_args['config'] == ['config.json'] + assert call_args['verbosity'] == 0 + assert call_args['command'] == 'backtesting' + assert call_args['func'] is not None + assert callable(call_args['func']) + assert call_args['timeframe'] is None def test_main_start_hyperopt(mocker) -> None: diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 3b1041fd8..c15936cf6 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -772,6 +772,7 @@ def test_to_json(default_conf, fee): 'max_rate': None, 'strategy': None, 'ticker_interval': None, + 'timeframe': None, 'exchange': 'bittrex', } @@ -829,6 +830,7 @@ def test_to_json(default_conf, fee): 'sell_order_status': None, 'strategy': None, 'ticker_interval': None, + 'timeframe': None, 'exchange': 'bittrex', } diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 5bb113784..af872d0c1 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -47,7 +47,7 @@ def generate_empty_figure(): def test_init_plotscript(default_conf, mocker, testdatadir): default_conf['timerange'] = "20180110-20180112" default_conf['trade_source'] = "file" - default_conf['ticker_interval'] = "5m" + default_conf['timeframe'] = "5m" default_conf["datadir"] = testdatadir default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" ret = init_plotscript(default_conf) diff --git a/tests/testdata/backtest-result_test copy.json b/tests/testdata/backtest-result_test copy.json new file mode 100644 index 000000000..0395830d4 --- /dev/null +++ b/tests/testdata/backtest-result_test copy.json @@ -0,0 +1,7 @@ +{ + "ASDF": {, + "trades": [], + "metrics":[], + } +} +[["TRX/BTC",0.03990025,1515568500.0,1515568800.0,27,5,9.64e-05,0.00010074887218045112,false,"roi"],["ADA/BTC",0.03990025,1515568500.0,1515569400.0,27,15,4.756e-05,4.9705563909774425e-05,false,"roi"],["XLM/BTC",0.03990025,1515569100.0,1515569700.0,29,10,3.339e-05,3.489631578947368e-05,false,"roi"],["TRX/BTC",0.03990025,1515569100.0,1515570000.0,29,15,9.696e-05,0.00010133413533834584,false,"roi"],["ETH/BTC",-0.0,1515569700.0,1515573300.0,31,60,0.0943,0.09477268170426063,false,"roi"],["XMR/BTC",0.00997506,1515570000.0,1515571800.0,32,30,0.02719607,0.02760503345864661,false,"roi"],["ZEC/BTC",0.0,1515572100.0,1515578100.0,39,100,0.04634952,0.046581848421052625,false,"roi"],["NXT/BTC",-0.0,1515595500.0,1515599400.0,117,65,3.066e-05,3.081368421052631e-05,false,"roi"],["LTC/BTC",0.0,1515602100.0,1515604500.0,139,40,0.0168999,0.016984611278195488,false,"roi"],["ETH/BTC",-0.0,1515602400.0,1515604800.0,140,40,0.09132568,0.0917834528320802,false,"roi"],["ETH/BTC",-0.0,1515610200.0,1515613500.0,166,55,0.08898003,0.08942604518796991,false,"roi"],["ETH/BTC",0.0,1515622500.0,1515625200.0,207,45,0.08560008,0.08602915308270676,false,"roi"],["ETC/BTC",0.00997506,1515624600.0,1515626400.0,214,30,0.00249083,0.0025282860902255634,false,"roi"],["NXT/BTC",-0.0,1515626100.0,1515629700.0,219,60,3.022e-05,3.037147869674185e-05,false,"roi"],["ETC/BTC",0.01995012,1515627600.0,1515629100.0,224,25,0.002437,0.0024980776942355883,false,"roi"],["ZEC/BTC",0.00997506,1515628800.0,1515630900.0,228,35,0.04771803,0.04843559436090225,false,"roi"],["XLM/BTC",-0.10448878,1515642000.0,1515644700.0,272,45,3.651e-05,3.2859000000000005e-05,false,"stop_loss"],["ETH/BTC",0.00997506,1515642900.0,1515644700.0,275,30,0.08824105,0.08956798308270676,false,"roi"],["ETC/BTC",-0.0,1515643200.0,1515646200.0,276,50,0.00243,0.002442180451127819,false,"roi"],["ZEC/BTC",0.01995012,1515645000.0,1515646500.0,282,25,0.04545064,0.046589753784461146,false,"roi"],["XLM/BTC",0.01995012,1515645000.0,1515646200.0,282,20,3.372e-05,3.456511278195488e-05,false,"roi"],["XMR/BTC",0.01995012,1515646500.0,1515647700.0,287,20,0.02644,0.02710265664160401,false,"roi"],["ETH/BTC",-0.0,1515669600.0,1515672000.0,364,40,0.08812,0.08856170426065162,false,"roi"],["XMR/BTC",-0.0,1515670500.0,1515672900.0,367,40,0.02683577,0.026970285137844607,false,"roi"],["ADA/BTC",0.01995012,1515679200.0,1515680700.0,396,25,4.919e-05,5.04228320802005e-05,false,"roi"],["ETH/BTC",-0.0,1515698700.0,1515702900.0,461,70,0.08784896,0.08828930566416039,false,"roi"],["ADA/BTC",-0.0,1515710100.0,1515713400.0,499,55,5.105e-05,5.130588972431077e-05,false,"roi"],["XLM/BTC",0.00997506,1515711300.0,1515713100.0,503,30,3.96e-05,4.019548872180451e-05,false,"roi"],["NXT/BTC",-0.0,1515711300.0,1515713700.0,503,40,2.885e-05,2.899461152882205e-05,false,"roi"],["XMR/BTC",0.00997506,1515713400.0,1515715500.0,510,35,0.02645,0.026847744360902256,false,"roi"],["ZEC/BTC",-0.0,1515714900.0,1515719700.0,515,80,0.048,0.04824060150375939,false,"roi"],["XLM/BTC",0.01995012,1515791700.0,1515793200.0,771,25,4.692e-05,4.809593984962405e-05,false,"roi"],["ETC/BTC",-0.0,1515804900.0,1515824400.0,815,325,0.00256966,0.0025825405012531327,false,"roi"],["ADA/BTC",0.0,1515840900.0,1515843300.0,935,40,6.262e-05,6.293388471177944e-05,false,"roi"],["XLM/BTC",0.0,1515848700.0,1516025400.0,961,2945,4.73e-05,4.753709273182957e-05,false,"roi"],["ADA/BTC",-0.0,1515850200.0,1515854700.0,966,75,6.063e-05,6.0933909774436085e-05,false,"roi"],["TRX/BTC",-0.0,1515850800.0,1515886200.0,968,590,0.00011082,0.00011137548872180449,false,"roi"],["ADA/BTC",-0.0,1515856500.0,1515858900.0,987,40,5.93e-05,5.9597243107769415e-05,false,"roi"],["ZEC/BTC",-0.0,1515861000.0,1515863400.0,1002,40,0.04850003,0.04874313791979949,false,"roi"],["ETH/BTC",-0.0,1515881100.0,1515911100.0,1069,500,0.09825019,0.09874267215538847,false,"roi"],["ADA/BTC",0.0,1515889200.0,1515970500.0,1096,1355,6.018e-05,6.048165413533834e-05,false,"roi"],["ETH/BTC",-0.0,1515933900.0,1515936300.0,1245,40,0.09758999,0.0980791628822055,false,"roi"],["ETC/BTC",0.00997506,1515943800.0,1515945600.0,1278,30,0.00311,0.0031567669172932328,false,"roi"],["ETC/BTC",-0.0,1515962700.0,1515968100.0,1341,90,0.00312401,0.003139669197994987,false,"roi"],["LTC/BTC",0.0,1515972900.0,1515976200.0,1375,55,0.0174679,0.017555458395989976,false,"roi"],["DASH/BTC",-0.0,1515973500.0,1515975900.0,1377,40,0.07346846,0.07383672295739348,false,"roi"],["ETH/BTC",-0.0,1515983100.0,1515985500.0,1409,40,0.097994,0.09848519799498745,false,"roi"],["ETH/BTC",-0.0,1516000800.0,1516003200.0,1468,40,0.09659,0.09707416040100249,false,"roi"],["TRX/BTC",0.00997506,1516004400.0,1516006500.0,1480,35,9.987e-05,0.00010137180451127818,false,"roi"],["ETH/BTC",0.0,1516018200.0,1516071000.0,1526,880,0.0948969,0.09537257368421052,false,"roi"],["DASH/BTC",-0.0,1516025400.0,1516038000.0,1550,210,0.071,0.07135588972431077,false,"roi"],["ZEC/BTC",-0.0,1516026600.0,1516029000.0,1554,40,0.04600501,0.046235611553884705,false,"roi"],["TRX/BTC",-0.0,1516039800.0,1516044300.0,1598,75,9.438e-05,9.485308270676691e-05,false,"roi"],["XMR/BTC",-0.0,1516041300.0,1516043700.0,1603,40,0.03040001,0.030552391002506264,false,"roi"],["ADA/BTC",-0.10448878,1516047900.0,1516091100.0,1625,720,5.837e-05,5.2533e-05,false,"stop_loss"],["ZEC/BTC",-0.0,1516048800.0,1516053600.0,1628,80,0.046036,0.04626675689223057,false,"roi"],["ETC/BTC",-0.0,1516062600.0,1516065000.0,1674,40,0.0028685,0.0028828784461152877,false,"roi"],["DASH/BTC",0.0,1516065300.0,1516070100.0,1683,80,0.06731755,0.0676549813283208,false,"roi"],["ETH/BTC",0.0,1516088700.0,1516092000.0,1761,55,0.09217614,0.09263817578947368,false,"roi"],["LTC/BTC",0.01995012,1516091700.0,1516092900.0,1771,20,0.0165,0.016913533834586467,false,"roi"],["TRX/BTC",0.03990025,1516091700.0,1516092000.0,1771,5,7.953e-05,8.311781954887218e-05,false,"roi"],["ZEC/BTC",-0.0,1516092300.0,1516096200.0,1773,65,0.045202,0.04542857644110275,false,"roi"],["ADA/BTC",0.00997506,1516094100.0,1516095900.0,1779,30,5.248e-05,5.326917293233082e-05,false,"roi"],["XMR/BTC",0.0,1516094100.0,1516096500.0,1779,40,0.02892318,0.02906815834586466,false,"roi"],["ADA/BTC",0.01995012,1516096200.0,1516097400.0,1786,20,5.158e-05,5.287273182957392e-05,false,"roi"],["ZEC/BTC",0.00997506,1516097100.0,1516099200.0,1789,35,0.04357584,0.044231115789473675,false,"roi"],["XMR/BTC",0.00997506,1516097100.0,1516098900.0,1789,30,0.02828232,0.02870761804511278,false,"roi"],["ADA/BTC",0.00997506,1516110300.0,1516112400.0,1833,35,5.362e-05,5.4426315789473676e-05,false,"roi"],["ADA/BTC",-0.0,1516123800.0,1516127100.0,1878,55,5.302e-05,5.328576441102756e-05,false,"roi"],["ETH/BTC",0.00997506,1516126500.0,1516128300.0,1887,30,0.09129999,0.09267292218045112,false,"roi"],["XLM/BTC",0.01995012,1516126500.0,1516127700.0,1887,20,3.808e-05,3.903438596491228e-05,false,"roi"],["XMR/BTC",0.00997506,1516129200.0,1516131000.0,1896,30,0.02811012,0.028532828571428567,false,"roi"],["ETC/BTC",-0.10448878,1516137900.0,1516141500.0,1925,60,0.00258379,0.002325411,false,"stop_loss"],["NXT/BTC",-0.10448878,1516137900.0,1516142700.0,1925,80,2.559e-05,2.3031e-05,false,"stop_loss"],["TRX/BTC",-0.10448878,1516138500.0,1516141500.0,1927,50,7.62e-05,6.858e-05,false,"stop_loss"],["LTC/BTC",0.03990025,1516141800.0,1516142400.0,1938,10,0.0151,0.015781203007518795,false,"roi"],["ETC/BTC",0.03990025,1516141800.0,1516142100.0,1938,5,0.00229844,0.002402129022556391,false,"roi"],["ETC/BTC",0.03990025,1516142400.0,1516142700.0,1940,5,0.00235676,0.00246308,false,"roi"],["DASH/BTC",0.01995012,1516142700.0,1516143900.0,1941,20,0.0630692,0.06464988170426066,false,"roi"],["NXT/BTC",0.03990025,1516143000.0,1516143300.0,1942,5,2.2e-05,2.2992481203007514e-05,false,"roi"],["ADA/BTC",0.00997506,1516159800.0,1516161600.0,1998,30,4.974e-05,5.048796992481203e-05,false,"roi"],["TRX/BTC",0.01995012,1516161300.0,1516162500.0,2003,20,7.108e-05,7.28614536340852e-05,false,"roi"],["ZEC/BTC",-0.0,1516181700.0,1516184100.0,2071,40,0.04327,0.04348689223057644,false,"roi"],["ADA/BTC",-0.0,1516184400.0,1516208400.0,2080,400,4.997e-05,5.022047619047618e-05,false,"roi"],["DASH/BTC",-0.0,1516185000.0,1516188300.0,2082,55,0.06836818,0.06871087764411027,false,"roi"],["XLM/BTC",-0.0,1516185000.0,1516187400.0,2082,40,3.63e-05,3.648195488721804e-05,false,"roi"],["XMR/BTC",-0.0,1516192200.0,1516226700.0,2106,575,0.0281,0.02824085213032581,false,"roi"],["ETH/BTC",-0.0,1516192500.0,1516208100.0,2107,260,0.08651001,0.08694364413533832,false,"roi"],["ADA/BTC",-0.0,1516251600.0,1516254900.0,2304,55,5.633e-05,5.6612355889724306e-05,false,"roi"],["DASH/BTC",0.00997506,1516252800.0,1516254900.0,2308,35,0.06988494,0.07093584135338346,false,"roi"],["ADA/BTC",-0.0,1516260900.0,1516263300.0,2335,40,5.545e-05,5.572794486215538e-05,false,"roi"],["LTC/BTC",-0.0,1516266000.0,1516268400.0,2352,40,0.01633527,0.016417151052631574,false,"roi"],["ETC/BTC",-0.0,1516293600.0,1516296000.0,2444,40,0.00269734,0.0027108605012531326,false,"roi"],["XLM/BTC",0.01995012,1516298700.0,1516300200.0,2461,25,4.475e-05,4.587155388471177e-05,false,"roi"],["NXT/BTC",0.00997506,1516299900.0,1516301700.0,2465,30,2.79e-05,2.8319548872180444e-05,false,"roi"],["ZEC/BTC",0.0,1516306200.0,1516308600.0,2486,40,0.04439326,0.04461578260651629,false,"roi"],["XLM/BTC",0.0,1516311000.0,1516322100.0,2502,185,4.49e-05,4.51250626566416e-05,false,"roi"],["XMR/BTC",-0.0,1516312500.0,1516338300.0,2507,430,0.02855,0.028693107769423555,false,"roi"],["ADA/BTC",0.0,1516313400.0,1516315800.0,2510,40,5.796e-05,5.8250526315789473e-05,false,"roi"],["ZEC/BTC",0.0,1516319400.0,1516321800.0,2530,40,0.04340323,0.04362079005012531,false,"roi"],["ZEC/BTC",0.0,1516380300.0,1516383300.0,2733,50,0.04454455,0.04476783095238095,false,"roi"],["ADA/BTC",-0.0,1516382100.0,1516391700.0,2739,160,5.62e-05,5.648170426065162e-05,false,"roi"],["XLM/BTC",-0.0,1516382400.0,1516392900.0,2740,175,4.339e-05,4.360749373433584e-05,false,"roi"],["TRX/BTC",0.0,1516423500.0,1516469700.0,2877,770,0.0001009,0.00010140576441102757,false,"roi"],["ETC/BTC",-0.0,1516423800.0,1516461300.0,2878,625,0.00270505,0.002718609147869674,false,"roi"],["XMR/BTC",-0.0,1516423800.0,1516431600.0,2878,130,0.03000002,0.030150396040100245,false,"roi"],["ADA/BTC",-0.0,1516438800.0,1516441200.0,2928,40,5.46e-05,5.4873684210526304e-05,false,"roi"],["XMR/BTC",-0.10448878,1516472700.0,1516852200.0,3041,6325,0.03082222,0.027739998000000002,false,"stop_loss"],["ETH/BTC",-0.0,1516487100.0,1516490100.0,3089,50,0.08969999,0.09014961401002504,false,"roi"],["LTC/BTC",0.0,1516503000.0,1516545000.0,3142,700,0.01632501,0.01640683962406015,false,"roi"],["DASH/BTC",-0.0,1516530000.0,1516532400.0,3232,40,0.070538,0.07089157393483708,false,"roi"],["ADA/BTC",-0.0,1516549800.0,1516560300.0,3298,175,5.301e-05,5.3275714285714276e-05,false,"roi"],["XLM/BTC",0.0,1516551600.0,1516554000.0,3304,40,3.955e-05,3.9748245614035085e-05,false,"roi"],["ETC/BTC",0.00997506,1516569300.0,1516571100.0,3363,30,0.00258505,0.002623922932330827,false,"roi"],["XLM/BTC",-0.0,1516569300.0,1516571700.0,3363,40,3.903e-05,3.922563909774435e-05,false,"roi"],["ADA/BTC",-0.0,1516581300.0,1516617300.0,3403,600,5.236e-05,5.262245614035087e-05,false,"roi"],["TRX/BTC",0.0,1516584600.0,1516587000.0,3414,40,9.028e-05,9.073253132832079e-05,false,"roi"],["ETC/BTC",-0.0,1516623900.0,1516631700.0,3545,130,0.002687,0.002700468671679198,false,"roi"],["XLM/BTC",-0.0,1516626900.0,1516629300.0,3555,40,4.168e-05,4.1888922305764405e-05,false,"roi"],["TRX/BTC",0.00997506,1516629600.0,1516631400.0,3564,30,8.821e-05,8.953646616541353e-05,false,"roi"],["ADA/BTC",-0.0,1516636500.0,1516639200.0,3587,45,5.172e-05,5.1979248120300745e-05,false,"roi"],["NXT/BTC",0.01995012,1516637100.0,1516638300.0,3589,20,3.026e-05,3.101839598997494e-05,false,"roi"],["DASH/BTC",0.0,1516650600.0,1516666200.0,3634,260,0.07064,0.07099408521303258,false,"roi"],["LTC/BTC",0.0,1516656300.0,1516658700.0,3653,40,0.01644483,0.01652726022556391,false,"roi"],["XLM/BTC",0.00997506,1516665900.0,1516667700.0,3685,30,4.331e-05,4.3961278195488714e-05,false,"roi"],["NXT/BTC",0.01995012,1516672200.0,1516673700.0,3706,25,3.2e-05,3.2802005012531326e-05,false,"roi"],["ETH/BTC",0.0,1516681500.0,1516684500.0,3737,50,0.09167706,0.09213659413533835,false,"roi"],["DASH/BTC",0.0,1516692900.0,1516698000.0,3775,85,0.0692498,0.06959691679197995,false,"roi"],["NXT/BTC",0.0,1516704600.0,1516712700.0,3814,135,3.182e-05,3.197949874686716e-05,false,"roi"],["ZEC/BTC",-0.0,1516705500.0,1516723500.0,3817,300,0.04088,0.04108491228070175,false,"roi"],["ADA/BTC",-0.0,1516719300.0,1516721700.0,3863,40,5.15e-05,5.175814536340851e-05,false,"roi"],["ETH/BTC",0.0,1516725300.0,1516752300.0,3883,450,0.09071698,0.09117170170426064,false,"roi"],["NXT/BTC",-0.0,1516728300.0,1516733100.0,3893,80,3.128e-05,3.1436791979949865e-05,false,"roi"],["TRX/BTC",-0.0,1516738500.0,1516744800.0,3927,105,9.555e-05,9.602894736842104e-05,false,"roi"],["ZEC/BTC",-0.0,1516746600.0,1516749000.0,3954,40,0.04080001,0.041004521328320796,false,"roi"],["ADA/BTC",-0.0,1516751400.0,1516764900.0,3970,225,5.163e-05,5.1888796992481196e-05,false,"roi"],["ZEC/BTC",0.0,1516753200.0,1516758600.0,3976,90,0.04040781,0.04061035541353383,false,"roi"],["ADA/BTC",-0.0,1516776300.0,1516778700.0,4053,40,5.132e-05,5.157724310776942e-05,false,"roi"],["ADA/BTC",0.03990025,1516803300.0,1516803900.0,4143,10,5.198e-05,5.432496240601503e-05,false,"roi"],["NXT/BTC",-0.0,1516805400.0,1516811700.0,4150,105,3.054e-05,3.069308270676692e-05,false,"roi"],["TRX/BTC",0.0,1516806600.0,1516810500.0,4154,65,9.263e-05,9.309431077694235e-05,false,"roi"],["ADA/BTC",-0.0,1516833600.0,1516836300.0,4244,45,5.514e-05,5.5416390977443596e-05,false,"roi"],["XLM/BTC",0.0,1516841400.0,1516843800.0,4270,40,4.921e-05,4.9456666666666664e-05,false,"roi"],["ETC/BTC",0.0,1516868100.0,1516882500.0,4359,240,0.0026,0.002613032581453634,false,"roi"],["XMR/BTC",-0.0,1516875900.0,1516896900.0,4385,350,0.02799871,0.028139054411027563,false,"roi"],["ZEC/BTC",-0.0,1516878000.0,1516880700.0,4392,45,0.04078902,0.0409934762406015,false,"roi"],["NXT/BTC",-0.0,1516885500.0,1516887900.0,4417,40,2.89e-05,2.904486215538847e-05,false,"roi"],["ZEC/BTC",-0.0,1516886400.0,1516889100.0,4420,45,0.041103,0.041309030075187964,false,"roi"],["XLM/BTC",0.00997506,1516895100.0,1516896900.0,4449,30,5.428e-05,5.5096240601503756e-05,false,"roi"],["XLM/BTC",-0.0,1516902300.0,1516922100.0,4473,330,5.414e-05,5.441137844611528e-05,false,"roi"],["ZEC/BTC",-0.0,1516914900.0,1516917300.0,4515,40,0.04140777,0.0416153277443609,false,"roi"],["ETC/BTC",0.0,1516932300.0,1516934700.0,4573,40,0.00254309,0.002555837318295739,false,"roi"],["ADA/BTC",-0.0,1516935300.0,1516979400.0,4583,735,5.607e-05,5.6351052631578935e-05,false,"roi"],["ETC/BTC",0.0,1516947000.0,1516958700.0,4622,195,0.00253806,0.0025507821052631577,false,"roi"],["ZEC/BTC",-0.0,1516951500.0,1516960500.0,4637,150,0.0415,0.04170802005012531,false,"roi"],["XLM/BTC",0.00997506,1516960500.0,1516962300.0,4667,30,5.321e-05,5.401015037593984e-05,false,"roi"],["XMR/BTC",-0.0,1516982700.0,1516985100.0,4741,40,0.02772046,0.02785940967418546,false,"roi"],["ETH/BTC",0.0,1517009700.0,1517012100.0,4831,40,0.09461341,0.09508766268170425,false,"roi"],["XLM/BTC",-0.0,1517013300.0,1517016600.0,4843,55,5.615e-05,5.643145363408521e-05,false,"roi"],["ADA/BTC",-0.07877175,1517013900.0,1517287500.0,4845,4560,5.556e-05,5.144e-05,true,"force_sell"],["DASH/BTC",-0.0,1517020200.0,1517052300.0,4866,535,0.06900001,0.06934587471177944,false,"roi"],["ETH/BTC",-0.0,1517034300.0,1517036700.0,4913,40,0.09449985,0.09497353345864659,false,"roi"],["ZEC/BTC",-0.04815133,1517046000.0,1517287200.0,4952,4020,0.0410697,0.03928809,true,"force_sell"],["XMR/BTC",-0.0,1517053500.0,1517056200.0,4977,45,0.0285,0.02864285714285714,false,"roi"],["XMR/BTC",-0.0,1517056500.0,1517066700.0,4987,170,0.02866372,0.02880739779448621,false,"roi"],["ETH/BTC",-0.0,1517068200.0,1517071800.0,5026,60,0.095381,0.09585910025062655,false,"roi"],["DASH/BTC",-0.0,1517072700.0,1517075100.0,5041,40,0.06759092,0.06792972160401002,false,"roi"],["ETC/BTC",-0.0,1517096400.0,1517101500.0,5120,85,0.00258501,0.002597967443609022,false,"roi"],["DASH/BTC",-0.0,1517106300.0,1517127000.0,5153,345,0.06698502,0.0673207845112782,false,"roi"],["DASH/BTC",-0.0,1517135100.0,1517157000.0,5249,365,0.0677177,0.06805713709273183,false,"roi"],["XLM/BTC",0.0,1517171700.0,1517175300.0,5371,60,5.215e-05,5.2411403508771925e-05,false,"roi"],["ETC/BTC",0.00997506,1517176800.0,1517178600.0,5388,30,0.00273809,0.002779264285714285,false,"roi"],["ETC/BTC",0.00997506,1517184000.0,1517185800.0,5412,30,0.00274632,0.002787618045112782,false,"roi"],["LTC/BTC",0.0,1517192100.0,1517194800.0,5439,45,0.01622478,0.016306107218045113,false,"roi"],["DASH/BTC",-0.0,1517195100.0,1517197500.0,5449,40,0.069,0.06934586466165413,false,"roi"],["TRX/BTC",-0.0,1517203200.0,1517208900.0,5476,95,8.755e-05,8.798884711779448e-05,false,"roi"],["DASH/BTC",-0.0,1517209200.0,1517253900.0,5496,745,0.06825763,0.06859977350877192,false,"roi"],["DASH/BTC",-0.0,1517255100.0,1517257500.0,5649,40,0.06713892,0.06747545593984962,false,"roi"],["TRX/BTC",-0.0199116,1517268600.0,1517287500.0,5694,315,8.934e-05,8.8e-05,true,"force_sell"]] From 18913db99267a699a89b9fdddc6509980cff6775 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:47:36 +0200 Subject: [PATCH 053/191] Replace ticker_interval with timeframe in sample configs --- config.json.example | 2 +- config_binance.json.example | 2 +- config_full.json.example | 2 +- config_kraken.json.example | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index d37a6b336..545fcf1d4 100644 --- a/config.json.example +++ b/config.json.example @@ -4,7 +4,7 @@ "stake_amount": 0.05, "tradable_balance_ratio": 0.99, "fiat_display_currency": "USD", - "ticker_interval": "5m", + "timeframe": "5m", "dry_run": false, "cancel_open_orders_on_exit": false, "trailing_stop": false, diff --git a/config_binance.json.example b/config_binance.json.example index 5d7b6b656..98dada647 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -4,7 +4,7 @@ "stake_amount": 0.05, "tradable_balance_ratio": 0.99, "fiat_display_currency": "USD", - "ticker_interval": "5m", + "timeframe": "5m", "dry_run": true, "cancel_open_orders_on_exit": false, "trailing_stop": false, diff --git a/config_full.json.example b/config_full.json.example index 481742817..66b0d6a0d 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -9,7 +9,7 @@ "last_stake_amount_min_ratio": 0.5, "dry_run": false, "cancel_open_orders_on_exit": false, - "ticker_interval": "5m", + "timeframe": "5m", "trailing_stop": false, "trailing_stop_positive": 0.005, "trailing_stop_positive_offset": 0.0051, diff --git a/config_kraken.json.example b/config_kraken.json.example index 54fbf4a00..602e2ccf3 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -4,7 +4,7 @@ "stake_amount": 10, "tradable_balance_ratio": 0.99, "fiat_display_currency": "EUR", - "ticker_interval": "5m", + "timeframe": "5m", "dry_run": true, "cancel_open_orders_on_exit": false, "trailing_stop": false, From cadc50ce9b7dbedf50478cbd9431e5bdffa4ac8c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:49:40 +0200 Subject: [PATCH 054/191] Replace more occurances of ticker_interval with timeframe --- freqtrade/commands/pairlist_commands.py | 2 +- freqtrade/data/converter.py | 4 ++-- freqtrade/data/dataprovider.py | 4 ++-- freqtrade/freqtradebot.py | 6 +++--- freqtrade/optimize/backtesting.py | 6 +++--- freqtrade/optimize/hyperopt_interface.py | 8 +++++--- freqtrade/pairlist/pairlistmanager.py | 4 ++-- freqtrade/plot/plotting.py | 6 +++--- freqtrade/resolvers/hyperopt_resolver.py | 5 +++-- freqtrade/resolvers/strategy_resolver.py | 2 +- freqtrade/strategy/interface.py | 2 +- freqtrade/templates/base_strategy.py.j2 | 4 ++-- tests/strategy/test_strategy.py | 2 +- 13 files changed, 29 insertions(+), 26 deletions(-) diff --git a/freqtrade/commands/pairlist_commands.py b/freqtrade/commands/pairlist_commands.py index bf0b217a5..dffe0c82e 100644 --- a/freqtrade/commands/pairlist_commands.py +++ b/freqtrade/commands/pairlist_commands.py @@ -25,7 +25,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None: results = {} for curr in quote_currencies: config['stake_currency'] = curr - # Do not use ticker_interval set in the config + # Do not use timeframe set in the config pairlists = PairListManager(exchange, config) pairlists.refresh_pairlist() results[curr] = pairlists.whitelist diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 0ef7955a4..cfc7bc903 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -236,12 +236,12 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: from freqtrade.data.history.idatahandler import get_datahandler src = get_datahandler(config['datadir'], convert_from) trg = get_datahandler(config['datadir'], convert_to) - timeframes = config.get('timeframes', [config.get('ticker_interval')]) + timeframes = config.get('timeframes', [config.get('timeframe')]) logger.info(f"Converting candle (OHLCV) for timeframe {timeframes}") if 'pairs' not in config: config['pairs'] = [] - # Check timeframes or fall back to ticker_interval. + # Check timeframes or fall back to timeframe. for timeframe in timeframes: config['pairs'].extend(src.ohlcv_get_pairs(config['datadir'], timeframe)) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index a01344364..058ca42da 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -55,7 +55,7 @@ class DataProvider: Use False only for read-only operations (where the dataframe is not modified) """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): - return self._exchange.klines((pair, timeframe or self._config['ticker_interval']), + return self._exchange.klines((pair, timeframe or self._config['timeframe']), copy=copy) else: return DataFrame() @@ -67,7 +67,7 @@ class DataProvider: :param timeframe: timeframe to get data for """ return load_pair_history(pair=pair, - timeframe=timeframe or self._config['ticker_interval'], + timeframe=timeframe or self._config['timeframe'], datadir=self._config['datadir'] ) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d4afa1d60..627971c31 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -547,7 +547,7 @@ class FreqtradeBot: exchange=self.exchange.id, open_order_id=order_id, strategy=self.strategy.get_strategy_name(), - ticker_interval=timeframe_to_minutes(self.config['ticker_interval']) + ticker_interval=timeframe_to_minutes(self.config['timeframe']) ) # Update fees if order is closed @@ -780,7 +780,7 @@ class FreqtradeBot: self.update_trade_state(trade, stoploss_order, sl_order=True) # Lock pair for one candle to prevent immediate rebuys self.strategy.lock_pair(trade.pair, - timeframe_to_next_date(self.config['ticker_interval'])) + timeframe_to_next_date(self.config['timeframe'])) self._notify_sell(trade, "stoploss") return True @@ -1090,7 +1090,7 @@ class FreqtradeBot: Trade.session.flush() # Lock pair for one candle to prevent immediate rebuys - self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['ticker_interval'])) + self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['timeframe'])) self._notify_sell(trade, order_type) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3bf211d99..9a48338f1 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -94,10 +94,10 @@ class Backtesting: self.strategylist.append(StrategyResolver.load_strategy(self.config)) validate_config_consistency(self.config) - if "ticker_interval" not in self.config: + if "timeframe" not in self.config: raise OperationalException("Timeframe (ticker interval) needs to be set in either " - "configuration or as cli argument `--ticker-interval 5m`") - self.timeframe = str(self.config.get('ticker_interval')) + "configuration or as cli argument `--timeframe 5m`") + self.timeframe = str(self.config.get('timeframe')) self.timeframe_min = timeframe_to_minutes(self.timeframe) # Get maximum required startup period diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index b3cedef2c..20209d8a9 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -37,7 +37,8 @@ class IHyperOpt(ABC): self.config = config # Assign ticker_interval to be used in hyperopt - IHyperOpt.ticker_interval = str(config['ticker_interval']) + IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECTED + IHyperOpt.timeframe = str(config['timeframe']) @staticmethod def buy_strategy_generator(params: Dict[str, Any]) -> Callable: @@ -218,9 +219,10 @@ class IHyperOpt(ABC): # Why do I still need such shamanic mantras in modern python? def __getstate__(self): state = self.__dict__.copy() - state['ticker_interval'] = self.ticker_interval + state['timeframe'] = self.timeframe return state def __setstate__(self, state): self.__dict__.update(state) - IHyperOpt.ticker_interval = state['ticker_interval'] + IHyperOpt.ticker_interval = state['timeframe'] + IHyperOpt.timeframe = state['timeframe'] diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index f532f2cd0..81e52768e 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -131,6 +131,6 @@ class PairListManager(): def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes: """ - Create list of pair tuples with (pair, ticker_interval) + Create list of pair tuples with (pair, timeframe) """ - return [(pair, timeframe or self._config['ticker_interval']) for pair in pairs] + return [(pair, timeframe or self._config['timeframe']) for pair in pairs] diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index f1d114e2b..f9e526967 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -45,7 +45,7 @@ def init_plotscript(config): data = load_data( datadir=config.get("datadir"), pairs=pairs, - timeframe=config.get('ticker_interval', '5m'), + timeframe=config.get('timeframe', '5m'), timerange=timerange, data_format=config.get('dataformat_ohlcv', 'json'), ) @@ -487,7 +487,7 @@ def load_and_plot_trades(config: Dict[str, Any]): plot_config=strategy.plot_config if hasattr(strategy, 'plot_config') else {} ) - store_plot_file(fig, filename=generate_plot_filename(pair, config['ticker_interval']), + store_plot_file(fig, filename=generate_plot_filename(pair, config['timeframe']), directory=config['user_data_dir'] / "plot") logger.info('End of plotting process. %s plots generated', pair_counter) @@ -515,6 +515,6 @@ def plot_profit(config: Dict[str, Any]) -> None: # Create an average close price of all the pairs that were involved. # this could be useful to gauge the overall market trend fig = generate_profit_graph(plot_elements["pairs"], plot_elements["ohlcv"], - trades, config.get('ticker_interval', '5m')) + trades, config.get('timeframe', '5m')) store_plot_file(fig, filename='freqtrade-profit-plot.html', directory=config['user_data_dir'] / "plot", auto_open=True) diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index ddf461252..633363134 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -77,8 +77,9 @@ class HyperOptLossResolver(IResolver): config, kwargs={}, extra_dir=config.get('hyperopt_path')) - # Assign ticker_interval to be used in hyperopt - hyperoptloss.__class__.ticker_interval = str(config['ticker_interval']) + # Assign timeframe to be used in hyperopt + hyperoptloss.__class__.ticker_interval = str(config['timeframe']) + hyperoptloss.__class__.timeframe = str(config['timeframe']) if not hasattr(hyperoptloss, 'hyperopt_loss_function'): raise OperationalException( diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index abd6a4195..99cf2d785 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -54,7 +54,7 @@ class StrategyResolver(IResolver): # Check if we need to override configuration # (Attribute name, default, subkey) attributes = [("minimal_roi", {"0": 10.0}, None), - ("ticker_interval", None, None), + ("timeframe", None, None), ("stoploss", None, None), ("trailing_stop", None, None), ("trailing_stop_positive", None, None), diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ed2344a53..17c4aa5ca 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -62,7 +62,7 @@ class IStrategy(ABC): Attributes you can use: minimal_roi -> Dict: Minimal ROI designed for the strategy stoploss -> float: optimal stoploss designed for the strategy - ticker_interval -> str: value of the timeframe (ticker interval) to use with the strategy + timeframe -> str: value of the timeframe (ticker interval) to use with the strategy """ # Strategy interface version # Default to version 2 diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index c37164568..ce2c6d5c0 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -51,8 +51,8 @@ class {{ strategy }}(IStrategy): # trailing_stop_positive = 0.01 # trailing_stop_positive_offset = 0.0 # Disabled / not configured - # Optimal ticker interval for the strategy. - ticker_interval = '5m' + # Optimal timeframe for the strategy. + timeframe = '5m' # Run "populate_indicators()" only for new candle. process_only_new_candles = False diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 5cb59cad0..59ce8c5b8 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -186,7 +186,7 @@ def test_strategy_override_timeframe(caplog, default_conf): }) strategy = StrategyResolver.load_strategy(default_conf) - assert strategy.ticker_interval == 60 + assert strategy.timeframe == 60 assert strategy.stake_currency == 'ETH' assert log_has("Override strategy 'timeframe' with value in config file: 60.", caplog) From 388573800c673e1fe383fd83938c85c06fa49db2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 20:52:33 +0200 Subject: [PATCH 055/191] Update configuration messages --- freqtrade/configuration/configuration.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 7edd9bca1..139e42084 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -204,9 +204,9 @@ class Configuration: def _process_optimize_options(self, config: Dict[str, Any]) -> None: # This will override the strategy configuration - self._args_to_config(config, argname='ticker_interval', - logstring='Parameter -i/--ticker-interval detected ... ' - 'Using ticker_interval: {} ...') + self._args_to_config(config, argname='timeframe', + logstring='Parameter -i/--timeframe detected ... ' + 'Using timeframe: {} ...') self._args_to_config(config, argname='position_stacking', logstring='Parameter --enable-position-stacking detected ...') @@ -242,8 +242,8 @@ class Configuration: self._args_to_config(config, argname='strategy_list', logstring='Using strategy list of {} strategies', logfun=len) - self._args_to_config(config, argname='ticker_interval', - logstring='Overriding ticker interval with Command line argument') + self._args_to_config(config, argname='timeframe', + logstring='Overriding timeframe with Command line argument') self._args_to_config(config, argname='export', logstring='Parameter --export detected: {} ...') From 947903a4acac05fbda301e0dc90040488a772b28 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 09:36:04 +0200 Subject: [PATCH 056/191] Use timeframe from within strategy --- freqtrade/edge/edge_positioning.py | 4 ++-- freqtrade/freqtradebot.py | 8 ++++---- tests/edge/test_edge.py | 10 +++++----- tests/strategy/strats/default_strategy.py | 2 +- tests/strategy/test_default_strategy.py | 1 + tests/strategy/test_strategy.py | 1 + tests/test_configuration.py | 12 ++++++------ 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index c19d4552a..dd4ea35bb 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -100,14 +100,14 @@ class Edge: datadir=self.config['datadir'], pairs=pairs, exchange=self.exchange, - timeframe=self.strategy.ticker_interval, + timeframe=self.strategy.timeframe, timerange=self._timerange, ) data = load_data( datadir=self.config['datadir'], pairs=pairs, - timeframe=self.strategy.ticker_interval, + timeframe=self.strategy.timeframe, timerange=self._timerange, startup_candles=self.strategy.startup_candle_count, data_format=self.config.get('dataformat_ohlcv', 'json'), diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 627971c31..c52fe18d1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -421,8 +421,8 @@ class FreqtradeBot: # running get_signal on historical data fetched (buy, sell) = self.strategy.get_signal( - pair, self.strategy.ticker_interval, - self.dataprovider.ohlcv(pair, self.strategy.ticker_interval)) + pair, self.strategy.timeframe, + self.dataprovider.ohlcv(pair, self.strategy.timeframe)) if buy and not sell: stake_amount = self.get_trade_stake_amount(pair) @@ -696,8 +696,8 @@ class FreqtradeBot: if (config_ask_strategy.get('use_sell_signal', True) or config_ask_strategy.get('ignore_roi_if_buy_signal', False)): (buy, sell) = self.strategy.get_signal( - trade.pair, self.strategy.ticker_interval, - self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval)) + trade.pair, self.strategy.timeframe, + self.dataprovider.ohlcv(trade.pair, self.strategy.timeframe)) if config_ask_strategy.get('use_order_book', False): # logger.debug('Order book %s',orderBook) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 163ceff4b..cf9cb6fe1 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -27,7 +27,7 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, #################################################################### tests_start_time = arrow.get(2018, 10, 3) -ticker_interval_in_minute = 60 +timeframe_in_minute = 60 _ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7} # Helpers for this test file @@ -49,7 +49,7 @@ def _build_dataframe(buy_ohlc_sell_matrice): 'date': tests_start_time.shift( minutes=( ohlc[0] * - ticker_interval_in_minute)).timestamp * + timeframe_in_minute)).timestamp * 1000, 'buy': ohlc[1], 'open': ohlc[2], @@ -70,7 +70,7 @@ def _build_dataframe(buy_ohlc_sell_matrice): def _time_on_candle(number): return np.datetime64(tests_start_time.shift( - minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms') + minutes=(number * timeframe_in_minute)).timestamp * 1000, 'ms') # End helper functions @@ -262,7 +262,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', NEOBTC = [ [ - tests_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, + tests_start_time.shift(minutes=(x * timeframe_in_minute)).timestamp * 1000, math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base - 0.0001, @@ -274,7 +274,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', base = 0.002 LTCBTC = [ [ - tests_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000, + tests_start_time.shift(minutes=(x * timeframe_in_minute)).timestamp * 1000, math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base - 0.0001, diff --git a/tests/strategy/strats/default_strategy.py b/tests/strategy/strats/default_strategy.py index 7ea55d3f9..98842ff7c 100644 --- a/tests/strategy/strats/default_strategy.py +++ b/tests/strategy/strats/default_strategy.py @@ -29,7 +29,7 @@ class DefaultStrategy(IStrategy): stoploss = -0.10 # Optimal ticker interval for the strategy - ticker_interval = '5m' + timeframe = '5m' # Optional order type mapping order_types = { diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 0b8ea9f85..315f80440 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -19,6 +19,7 @@ def test_default_strategy(result): assert type(strategy.minimal_roi) is dict assert type(strategy.stoploss) is float assert type(strategy.ticker_interval) is str + assert type(strategy.timeframe) is str indicators = strategy.populate_indicators(result, metadata) assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 59ce8c5b8..1bb45f28c 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -105,6 +105,7 @@ def test_strategy(result, default_conf): assert strategy.stoploss == -0.10 assert default_conf['stoploss'] == -0.10 + assert strategy.timeframe == '5m' assert strategy.ticker_interval == '5m' assert default_conf['timeframe'] == '5m' diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 05074c258..8e79f4297 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -401,8 +401,8 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> assert 'datadir' in config assert 'user_data_dir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config - assert not log_has('Parameter -i/--ticker-interval detected ...', caplog) + assert 'timeframe' in config + assert not log_has('Parameter -i/--timeframe detected ...', caplog) assert 'position_stacking' not in config assert not log_has('Parameter --enable-position-stacking detected ...', caplog) @@ -448,8 +448,8 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non assert log_has('Using user-data directory: {} ...'.format(Path("/tmp/freqtrade")), caplog) assert 'user_data_dir' in config - assert 'ticker_interval' in config - assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + assert 'timeframe' in config + assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...', caplog) assert 'position_stacking' in config @@ -494,8 +494,8 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) - assert 'ticker_interval' in config - assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', + assert 'timeframe' in config + assert log_has('Parameter -i/--timeframe detected ... Using timeframe: 1m ...', caplog) assert 'strategy_list' in config From 3e895ae74aa3d754a0d8a2bc477c1c6da50b7baa Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 09:41:42 +0200 Subject: [PATCH 057/191] Some more replacements of ticker_interval --- freqtrade/optimize/hyperopt_loss_interface.py | 1 + freqtrade/persistence.py | 1 + tests/data/test_dataprovider.py | 2 +- tests/test_configuration.py | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_interface.py b/freqtrade/optimize/hyperopt_loss_interface.py index 879a9f0e9..c2607a9a8 100644 --- a/freqtrade/optimize/hyperopt_loss_interface.py +++ b/freqtrade/optimize/hyperopt_loss_interface.py @@ -15,6 +15,7 @@ class IHyperOptLoss(ABC): Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.) """ ticker_interval: str + timeframe: str @staticmethod @abstractmethod diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 111ccfe2a..363ce35ad 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -288,6 +288,7 @@ class Trade(_DECL_BASE): 'max_rate': self.max_rate, 'strategy': self.strategy, 'ticker_interval': self.ticker_interval, + 'timeframe': self.ticker_interval, 'open_order_id': self.open_order_id, 'exchange': self.exchange, } diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index 718060c5e..def3ad535 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -104,7 +104,7 @@ def test_refresh(mocker, default_conf, ohlcv_history): timeframe = default_conf["timeframe"] pairs = [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe)] - pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")] + pairs_non_trad = [("ETH/USDT", timeframe), ("BTC/TUSD", "1h")] dp = DataProvider(default_conf, exchange) dp.refresh(pairs) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 8e79f4297..1ed97f728 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -87,7 +87,7 @@ def test_load_config_file_error_range(default_conf, mocker, caplog) -> None: assert isinstance(x, str) assert (x == '{"max_open_trades": 1, "stake_currency": "BTC", ' '"stake_amount": .001, "fiat_display_currency": "USD", ' - '"timeframe": "5m", "dry_run": true, ') + '"timeframe": "5m", "dry_run": true, "cance') def test__args_to_config(caplog): From 09fe3c6f5e1f9f0a42882782e3caac6607e34600 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 09:50:56 +0200 Subject: [PATCH 058/191] create compatibility code --- freqtrade/configuration/deprecated_settings.py | 6 ++++++ freqtrade/constants.py | 1 - freqtrade/resolvers/strategy_resolver.py | 11 +++++++++++ freqtrade/strategy/interface.py | 4 ++-- tests/strategy/test_default_strategy.py | 1 - 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 3999ea422..2d5dba9ca 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -67,3 +67,9 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: "'tradable_balance_ratio' and remove 'capital_available_percentage' " "from the edge configuration." ) + if 'ticker_interval' in config: + logger.warning( + "DEPRECATED: " + "Please use 'timeframe' instead of 'ticker_interval." + ) + config['timeframe'] = config['ticker_interval'] diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 511c2993d..3afb4b0f1 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -71,7 +71,6 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1}, - 'ticker_interval': {'type': 'string'}, 'timeframe': {'type': 'string'}, 'stake_currency': {'type': 'string'}, 'stake_amount': { diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 99cf2d785..26bce01ca 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -50,6 +50,14 @@ class StrategyResolver(IResolver): if 'ask_strategy' not in config: config['ask_strategy'] = {} + if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'): + # Assign ticker_interval to timeframe to keep compatibility + if 'timeframe' not in config: + logger.warning( + "DEPRECATED: Please migrate to using timeframe instead of ticker_interval." + ) + strategy.timeframe = strategy.ticker_interval + # Set attributes # Check if we need to override configuration # (Attribute name, default, subkey) @@ -80,6 +88,9 @@ class StrategyResolver(IResolver): StrategyResolver._override_attribute_helper(strategy, config, attribute, default) + # Assign deprecated variable - to not break users code relying on this. + strategy.ticker_interval = strategy.timeframe + # Loop this list again to have output combined for attribute, _, subkey in attributes: if subkey and attribute in config[subkey]: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 17c4aa5ca..7bf750249 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -85,8 +85,8 @@ class IStrategy(ABC): trailing_stop_positive_offset: float = 0.0 trailing_only_offset_is_reached = False - # associated ticker interval - ticker_interval: str + # associated timeframe + timeframe: str # Optional order types order_types: Dict = { diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 315f80440..df7e35197 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -18,7 +18,6 @@ def test_default_strategy(result): metadata = {'pair': 'ETH/BTC'} assert type(strategy.minimal_roi) is dict assert type(strategy.stoploss) is float - assert type(strategy.ticker_interval) is str assert type(strategy.timeframe) is str indicators = strategy.populate_indicators(result, metadata) assert type(indicators) is DataFrame From af0f29e6b7b6647ce8e651d35b4a558fee09b578 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 10:02:24 +0200 Subject: [PATCH 059/191] Update persistence to use timeframe --- docs/sql_cheatsheet.md | 2 +- freqtrade/freqtradebot.py | 2 +- freqtrade/persistence.py | 19 ++++++++++++------- tests/test_persistence.py | 7 ++++--- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 895a0536a..b261904d7 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -70,7 +70,7 @@ CREATE TABLE trades min_rate FLOAT, sell_reason VARCHAR, strategy VARCHAR, - ticker_interval INTEGER, + timeframe INTEGER, PRIMARY KEY (id), CHECK (is_open IN (0, 1)) ); diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c52fe18d1..a8483051e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -547,7 +547,7 @@ class FreqtradeBot: exchange=self.exchange.id, open_order_id=order_id, strategy=self.strategy.get_strategy_name(), - ticker_interval=timeframe_to_minutes(self.config['timeframe']) + timeframe=timeframe_to_minutes(self.config['timeframe']) ) # Update fees if order is closed diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 363ce35ad..f0b97be1e 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -86,7 +86,7 @@ def check_migrate(engine) -> None: logger.debug(f'trying {table_back_name}') # Check for latest column - if not has_column(cols, 'sell_order_status'): + if not has_column(cols, 'timeframe'): logger.info(f'Running database migration - backup available as {table_back_name}') fee_open = get_column_def(cols, 'fee_open', 'fee') @@ -107,7 +107,12 @@ def check_migrate(engine) -> None: min_rate = get_column_def(cols, 'min_rate', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') - ticker_interval = get_column_def(cols, 'ticker_interval', 'null') + # If ticker-interval existed use that, else null. + if has_column(cols, 'ticker_interval'): + timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') + else: + timeframe = get_column_def(cols, 'timeframe', 'null') + open_trade_price = get_column_def(cols, 'open_trade_price', f'amount * open_rate * (1 + {fee_open})') close_profit_abs = get_column_def( @@ -133,7 +138,7 @@ def check_migrate(engine) -> None: stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stoploss_order_id, stoploss_last_update, max_rate, min_rate, sell_reason, sell_order_status, strategy, - ticker_interval, open_trade_price, close_profit_abs + timeframe, open_trade_price, close_profit_abs ) select id, lower(exchange), case @@ -155,7 +160,7 @@ def check_migrate(engine) -> None: {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {sell_order_status} sell_order_status, - {strategy} strategy, {ticker_interval} ticker_interval, + {strategy} strategy, {timeframe} timeframe, {open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs from {table_back_name} """) @@ -232,7 +237,7 @@ class Trade(_DECL_BASE): sell_reason = Column(String, nullable=True) sell_order_status = Column(String, nullable=True) strategy = Column(String, nullable=True) - ticker_interval = Column(Integer, nullable=True) + timeframe = Column(Integer, nullable=True) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -287,8 +292,8 @@ class Trade(_DECL_BASE): 'min_rate': self.min_rate, 'max_rate': self.max_rate, 'strategy': self.strategy, - 'ticker_interval': self.ticker_interval, - 'timeframe': self.ticker_interval, + 'ticker_interval': self.timeframe, + 'timeframe': self.timeframe, 'open_order_id': self.open_order_id, 'exchange': self.exchange, } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index c15936cf6..bc5b315a1 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -469,6 +469,7 @@ def test_migrate_old(mocker, default_conf, fee): assert trade.fee_open_currency is None assert trade.fee_close_cost is None assert trade.fee_close_currency is None + assert trade.timeframe is None trade = Trade.query.filter(Trade.id == 2).first() assert trade.close_rate is not None @@ -512,11 +513,11 @@ def test_migrate_new(mocker, default_conf, fee, caplog): );""" insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee, open_rate, stake_amount, amount, open_date, - stop_loss, initial_stop_loss, max_rate) + stop_loss, initial_stop_loss, max_rate, ticker_interval) VALUES ('binance', 'ETC/BTC', 1, {fee}, 0.00258580, {stake}, {amount}, '2019-11-28 12:44:24.000000', - 0.0, 0.0, 0.0) + 0.0, 0.0, 0.0, '5m') """.format(fee=fee.return_value, stake=default_conf.get("stake_amount"), amount=amount @@ -554,7 +555,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.initial_stop_loss == 0.0 assert trade.sell_reason is None assert trade.strategy is None - assert trade.ticker_interval is None + assert trade.timeframe == '5m' assert trade.stoploss_order_id is None assert trade.stoploss_last_update is None assert log_has("trying trades_bak1", caplog) From f9bb1a7f22ee086042e37d563ee67d90a12577a1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 10:02:55 +0200 Subject: [PATCH 060/191] Update more occurances of ticker_interval --- freqtrade/data/btanalysis.py | 4 ++-- freqtrade/optimize/hyperopt_loss_interface.py | 1 - freqtrade/templates/sample_strategy.py | 2 +- tests/optimize/test_backtesting.py | 4 ++-- tests/optimize/test_hyperopt.py | 3 ++- tests/strategy/test_default_strategy.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index f98135c27..5948d933c 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -103,7 +103,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: "open_rate", "close_rate", "amount", "duration", "sell_reason", "fee_open", "fee_close", "open_rate_requested", "close_rate_requested", "stake_amount", "max_rate", "min_rate", "id", "exchange", - "stop_loss", "initial_stop_loss", "strategy", "ticker_interval"] + "stop_loss", "initial_stop_loss", "strategy", "timeframe"] trades = pd.DataFrame([(t.pair, t.open_date.replace(tzinfo=timezone.utc), @@ -121,7 +121,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: t.min_rate, t.id, t.exchange, t.stop_loss, t.initial_stop_loss, - t.strategy, t.ticker_interval + t.strategy, t.timeframe ) for t in Trade.get_trades().all()], columns=columns) diff --git a/freqtrade/optimize/hyperopt_loss_interface.py b/freqtrade/optimize/hyperopt_loss_interface.py index c2607a9a8..48407a8a8 100644 --- a/freqtrade/optimize/hyperopt_loss_interface.py +++ b/freqtrade/optimize/hyperopt_loss_interface.py @@ -14,7 +14,6 @@ class IHyperOptLoss(ABC): Interface for freqtrade hyperopt Loss functions. Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.) """ - ticker_interval: str timeframe: str @staticmethod diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index f78489173..e269848d2 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -53,7 +53,7 @@ class SampleStrategy(IStrategy): # trailing_stop_positive_offset = 0.0 # Disabled / not configured # Optimal ticker interval for the strategy. - ticker_interval = '5m' + timeframe = '5m' # Run "populate_indicators()" only for new candle. process_only_new_candles = False diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 407604d9c..342d7689f 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -286,7 +286,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: assert not backtesting.strategy.order_types["stoploss_on_exchange"] -def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> None: +def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: patch_exchange(mocker) del default_conf['timeframe'] default_conf['strategy_list'] = ['DefaultStrategy', @@ -453,7 +453,7 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None: t["close_rate"], 6) < round(ln.iloc[0]["high"], 6)) -def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) -> None: +def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: default_conf['ask_strategy']['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) patch_exchange(mocker) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 4f5b3983a..564725709 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -197,7 +197,8 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: "Using populate_sell_trend from the strategy.", caplog) assert log_has("Hyperopt class does not provide populate_buy_trend() method. " "Using populate_buy_trend from the strategy.", caplog) - assert hasattr(x, "ticker_interval") + assert hasattr(x, "ticker_interval") # DEPRECATED + assert hasattr(x, "timeframe") def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None: diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index df7e35197..1b1648db9 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -6,7 +6,7 @@ from .strats.default_strategy import DefaultStrategy def test_default_strategy_structure(): assert hasattr(DefaultStrategy, 'minimal_roi') assert hasattr(DefaultStrategy, 'stoploss') - assert hasattr(DefaultStrategy, 'ticker_interval') + assert hasattr(DefaultStrategy, 'timeframe') assert hasattr(DefaultStrategy, 'populate_indicators') assert hasattr(DefaultStrategy, 'populate_buy_trend') assert hasattr(DefaultStrategy, 'populate_sell_trend') From febc95dcdf95d9ee6a080d25684491320f24f236 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 10:03:23 +0200 Subject: [PATCH 061/191] Update documentation to remove ticker_interval --- docs/bot-usage.md | 25 ++++++++++++------------- docs/configuration.md | 4 ++-- docs/hyperopt.md | 8 ++++---- docs/plotting.md | 15 ++++++++------- docs/strategy-customization.md | 14 +++++++------- docs/strategy_analysis_example.md | 4 ++-- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index b1649374a..b5af60f2b 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -72,7 +72,6 @@ Strategy arguments: Specify strategy class name which will be used by the bot. --strategy-path PATH Specify additional strategy lookup path. -. ``` @@ -197,7 +196,7 @@ Backtesting also uses the config specified via `-c/--config`. ``` usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] - [--strategy-path PATH] [-i TICKER_INTERVAL] + [--strategy-path PATH] [-i TIMEFRAME] [--timerange TIMERANGE] [--max-open-trades INT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT] [--eps] [--dmmp] @@ -206,7 +205,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] optional arguments: -h, --help show this help message and exit - -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE @@ -280,7 +279,7 @@ to find optimal parameter values for your strategy. ``` usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH] - [-i TICKER_INTERVAL] [--timerange TIMERANGE] + [-i TIMEFRAME] [--timerange TIMERANGE] [--max-open-trades INT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT] [--hyperopt NAME] [--hyperopt-path PATH] [--eps] @@ -292,7 +291,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] optional arguments: -h, --help show this help message and exit - -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE @@ -323,7 +322,7 @@ optional arguments: --print-all Print all results, not only the best ones. --no-color Disable colorization of hyperopt results. May be useful if you are redirecting output to a file. - --print-json Print best results in JSON format. + --print-json Print output in JSON format. -j JOBS, --job-workers JOBS The number of concurrently running jobs for hyperoptimization (hyperopt worker processes). If -1 @@ -341,11 +340,11 @@ optional arguments: class (IHyperOptLoss). Different functions can generate completely different results, since the target for optimization is different. Built-in - Hyperopt-loss-functions are: - DefaultHyperOptLoss, OnlyProfitHyperOptLoss, - SharpeHyperOptLoss, SharpeHyperOptLossDaily, - SortinoHyperOptLoss, SortinoHyperOptLossDaily. - (default: `DefaultHyperOptLoss`). + Hyperopt-loss-functions are: DefaultHyperOptLoss, + OnlyProfitHyperOptLoss, SharpeHyperOptLoss, + SharpeHyperOptLossDaily, SortinoHyperOptLoss, + SortinoHyperOptLossDaily.(default: + `DefaultHyperOptLoss`). Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -378,13 +377,13 @@ To know your trade expectancy and winrate against historical data, you can use E ``` usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH] - [-i TICKER_INTERVAL] [--timerange TIMERANGE] + [-i TIMEFRAME] [--timerange TIMERANGE] [--max-open-trades INT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT] [--stoplosses STOPLOSS_RANGE] optional arguments: -h, --help show this help message and exit - -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE diff --git a/docs/configuration.md b/docs/configuration.md index a1cb45e0f..4dbcc6d2a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,7 +47,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
**Datatype:** Boolean | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
**Datatype:** Float (as ratio) | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
**Datatype:** Positive Float as ratio. -| `ticker_interval` | The timeframe (ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float @@ -125,7 +125,7 @@ The following parameters can be set in either configuration file or strategy. Values set in the configuration file always overwrite values set in the strategy. * `minimal_roi` -* `ticker_interval` +* `timeframe` * `stoploss` * `trailing_stop` * `trailing_stop_positive` diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 8efc51a39..a4d36530c 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -124,9 +124,9 @@ To avoid naming collisions in the search-space, please prefix all sell-spaces wi #### Using timeframe as a part of the Strategy -The Strategy class exposes the timeframe (ticker interval) value as the `self.ticker_interval` attribute. -The same value is available as class-attribute `HyperoptName.ticker_interval`. -In the case of the linked sample-value this would be `SampleHyperOpt.ticker_interval`. +The Strategy class exposes the timeframe value as the `self.timeframe` attribute. +The same value is available as class-attribute `HyperoptName.timeframe`. +In the case of the linked sample-value this would be `SampleHyperOpt.timeframe`. ## Solving a Mystery @@ -403,7 +403,7 @@ As stated in the comment, you can also use it as the value of the `minimal_roi` #### Default ROI Search Space -If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point): +If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the timeframe used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point): | # step | 1m | | 5m | | 1h | | 1d | | | ------ | ------ | ----------------- | -------- | ----------- | ---------- | ----------------- | ------------ | ----------------- | diff --git a/docs/plotting.md b/docs/plotting.md index be83065a6..d3a2df1c1 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -31,7 +31,7 @@ usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--plot-limit INT] [--db-url PATH] [--trade-source {DB,file}] [--export EXPORT] [--export-filename PATH] - [--timerange TIMERANGE] [-i TICKER_INTERVAL] + [--timerange TIMERANGE] [-i TIMEFRAME] [--no-trades] optional arguments: @@ -65,7 +65,7 @@ optional arguments: _today.json` --timerange TIMERANGE Specify what timerange of data to use. - -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`). --no-trades Skip using trades from backtesting file and DB. @@ -227,7 +227,7 @@ usage: freqtrade plot-profit [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-p PAIRS [PAIRS ...]] [--timerange TIMERANGE] [--export EXPORT] [--export-filename PATH] [--db-url PATH] - [--trade-source {DB,file}] [-i TICKER_INTERVAL] + [--trade-source {DB,file}] [-i TIMEFRAME] optional arguments: -h, --help show this help message and exit @@ -250,7 +250,7 @@ optional arguments: --trade-source {DB,file} Specify the source for trades (Can be DB or file (backtest file)) Default: file - -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`). @@ -261,9 +261,10 @@ Common arguments: details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. -d PATH, --datadir PATH Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 7197b0fba..ca0d9a9a3 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -248,7 +248,7 @@ minimal_roi = { While technically not completely disabled, this would sell once the trade reaches 10000% Profit. -To use times based on candle duration (ticker_interval or timeframe), the following snippet can be handy. +To use times based on candle duration (timeframe), the following snippet can be handy. This will allow you to change the ticket_interval for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...) ``` python @@ -256,12 +256,12 @@ from freqtrade.exchange import timeframe_to_minutes class AwesomeStrategy(IStrategy): - ticker_interval = "1d" - ticker_interval_mins = timeframe_to_minutes(ticker_interval) + timeframe = "1d" + timeframe_mins = timeframe_to_minutes(timeframe) minimal_roi = { "0": 0.05, # 5% for the first 3 candles - str(ticker_interval_mins * 3)): 0.02, # 2% after 3 candles - str(ticker_interval_mins * 6)): 0.01, # 1% After 6 candles + str(timeframe_mins * 3)): 0.02, # 2% after 3 candles + str(timeframe_mins * 6)): 0.01, # 1% After 6 candles } ``` @@ -290,7 +290,7 @@ Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported Please note that the same buy/sell signals may work well with one timeframe, but not with the others. -This setting is accessible within the strategy methods as the `self.ticker_interval` attribute. +This setting is accessible within the strategy methods as the `self.timeframe` attribute. ### Metadata dict @@ -400,7 +400,7 @@ This is where calling `self.dp.current_whitelist()` comes in handy. class SampleStrategy(IStrategy): # strategy init stuff... - ticker_interval = '5m' + timeframe = '5m' # more strategy init stuff.. diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index d26d684ce..6b4ad567f 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -18,7 +18,7 @@ config = Configuration.from_files([]) # config = Configuration.from_files(["config.json"]) # Define some constants -config["ticker_interval"] = "5m" +config["timeframe"] = "5m" # Name of the strategy class config["strategy"] = "SampleStrategy" # Location of the data @@ -33,7 +33,7 @@ pair = "BTC_USDT" from freqtrade.data.history import load_pair_history candles = load_pair_history(datadir=data_location, - timeframe=config["ticker_interval"], + timeframe=config["timeframe"], pair=pair) # Confirm success From 33b7046260fc500fe5ae2c60ab999f67ac7f0ff4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 10:06:26 +0200 Subject: [PATCH 062/191] Update more documentation --- README.md | 5 +++-- docs/backtesting.md | 10 +++++----- docs/bot-usage.md | 25 +++++++++++++++++++------ docs/edge.md | 2 +- docs/hyperopt.md | 2 +- docs/strategy-customization.md | 2 +- docs/utils.md | 2 +- freqtrade/commands/arguments.py | 2 +- 8 files changed, 32 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cfb384702..7e0acde46 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,8 @@ positional arguments: new-hyperopt Create new hyperopt new-strategy Create new strategy download-data Download backtesting data. - convert-data Convert candle (OHLCV) data from one format to another. + convert-data Convert candle (OHLCV) data from one format to + another. convert-trade-data Convert trade data from one format to another. backtesting Backtesting module. edge Edge module. @@ -94,7 +95,7 @@ positional arguments: list-markets Print markets on exchange. list-pairs Print pairs on exchange. list-strategies Print available strategies. - list-timeframes Print available ticker intervals (timeframes) for the exchange. + list-timeframes Print available timeframes for the exchange. show-trades Show trades. test-pairlist Test your pairlist configuration. plot-dataframe Plot candles with indicators. diff --git a/docs/backtesting.md b/docs/backtesting.md index 9b2997510..51b2e953b 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -12,7 +12,7 @@ real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting). Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/` by default. -If no data is available for the exchange / pair / timeframe (ticker interval) combination, backtesting will ask you to download them first using `freqtrade download-data`. +If no data is available for the exchange / pair / timeframe combination, backtesting will ask you to download them first using `freqtrade download-data`. For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation. The result of backtesting will confirm if your bot has better odds of making a profit than a loss. @@ -35,7 +35,7 @@ freqtrade backtesting #### With 1 min candle (OHLCV) data ```bash -freqtrade backtesting --ticker-interval 1m +freqtrade backtesting --timeframe 1m ``` #### Using a different on-disk historical candle (OHLCV) data source @@ -58,7 +58,7 @@ Where `-s SampleStrategy` refers to the class name within the strategy file `sam #### Comparing multiple Strategies ```bash -freqtrade backtesting --strategy-list SampleStrategy1 AwesomeStrategy --ticker-interval 5m +freqtrade backtesting --strategy-list SampleStrategy1 AwesomeStrategy --timeframe 5m ``` Where `SampleStrategy1` and `AwesomeStrategy` refer to class names of strategies. @@ -228,13 +228,13 @@ You can then load the trades to perform further analysis as shown in our [data a To compare multiple strategies, a list of Strategies can be provided to backtesting. -This is limited to 1 timeframe (ticker interval) value per run. However, data is only loaded once from disk so if you have multiple +This is limited to 1 timeframe value per run. However, data is only loaded once from disk so if you have multiple strategies you'd like to compare, this will give a nice runtime boost. All listed Strategies need to be in the same directory. ``` bash -freqtrade backtesting --timerange 20180401-20180410 --ticker-interval 5m --strategy-list Strategy001 Strategy002 --export trades +freqtrade backtesting --timerange 20180401-20180410 --timeframe 5m --strategy-list Strategy001 Strategy002 --export trades ``` This will save the results to `user_data/backtest_results/backtest-result-.json`, injecting the strategy-name into the target filename. diff --git a/docs/bot-usage.md b/docs/bot-usage.md index b5af60f2b..40ff3d82b 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -9,22 +9,35 @@ This page explains the different parameters of the bot and how to run it. ``` usage: freqtrade [-h] [-V] - {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} + {trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit} ... Free, open source crypto trading bot positional arguments: - {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} + {trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit} trade Trade module. + create-userdir Create user-data directory. + new-config Create new config + new-hyperopt Create new hyperopt + new-strategy Create new strategy + download-data Download backtesting data. + convert-data Convert candle (OHLCV) data from one format to + another. + convert-trade-data Convert trade data from one format to another. backtesting Backtesting module. edge Edge module. hyperopt Hyperopt module. - create-userdir Create user-data directory. + hyperopt-list List Hyperopt results + hyperopt-show Show details of Hyperopt results list-exchanges Print available exchanges. - list-timeframes Print available ticker intervals (timeframes) for the - exchange. - download-data Download backtesting data. + list-hyperopts Print available hyperopt classes. + list-markets Print markets on exchange. + list-pairs Print pairs on exchange. + list-strategies Print available strategies. + list-timeframes Print available timeframes for the exchange. + show-trades Show trades. + test-pairlist Test your pairlist configuration. plot-dataframe Plot candles with indicators. plot-profit Generate plot showing profits. diff --git a/docs/edge.md b/docs/edge.md index 029844c0b..28a7f14cb 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -156,7 +156,7 @@ Edge module has following configuration options: | `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 | `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 (ticker interval). As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).
*Defaults to `1440` (one day).*
**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 ## Running Edge independently diff --git a/docs/hyperopt.md b/docs/hyperopt.md index a4d36530c..c9c87ead3 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -412,7 +412,7 @@ If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace f | 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 | | 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 | -These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe (ticker interval) used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used. +These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used. If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index ca0d9a9a3..70013c821 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -142,7 +142,7 @@ By letting the bot know how much history is needed, backtest trades can start at Let's try to backtest 1 month (January 2019) of 5m candles using the an example strategy with EMA100, as above. ``` bash -freqtrade backtesting --timerange 20190101-20190201 --ticker-interval 5m +freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m ``` Assuming `startup_candle_count` is set to 100, backtesting knows it needs 100 candles to generate valid buy signals. It will load data from `20190101 - (100 * 5m)` - which is ~2019-12-31 15:30:00. diff --git a/docs/utils.md b/docs/utils.md index 7ed31376f..793c84a93 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -62,7 +62,7 @@ $ freqtrade new-config --config config_binance.json ? Please insert your stake currency: BTC ? Please insert your stake amount: 0.05 ? Please insert max_open_trades (Integer or 'unlimited'): 3 -? Please insert your timeframe (ticker interval): 5m +? Please insert your desired timeframe (e.g. 5m): 5m ? Please insert your display Currency (for reporting): USD ? Select exchange binance ? Do you want to enable Telegram? No diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 36e3dedf0..72f2a02f0 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -318,7 +318,7 @@ class Arguments: # Add list-timeframes subcommand list_timeframes_cmd = subparsers.add_parser( 'list-timeframes', - help='Print available ticker intervals (timeframes) for the exchange.', + help='Print available timeframes for the exchange.', parents=[_common_parser], ) list_timeframes_cmd.set_defaults(func=start_list_timeframes) From 8e1a664a48f7b315907a9bbdd3b7f8fd790a8211 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 10:11:50 +0200 Subject: [PATCH 063/191] Add test for deprecation updating --- tests/strategy/strats/legacy_strategy.py | 1 + tests/strategy/test_strategy.py | 2 ++ tests/test_configuration.py | 12 ++++++++++++ 3 files changed, 15 insertions(+) diff --git a/tests/strategy/strats/legacy_strategy.py b/tests/strategy/strats/legacy_strategy.py index 89ce3f8cb..9cbce0ad5 100644 --- a/tests/strategy/strats/legacy_strategy.py +++ b/tests/strategy/strats/legacy_strategy.py @@ -31,6 +31,7 @@ class TestStrategyLegacy(IStrategy): stoploss = -0.10 # Optimal ticker interval for the strategy + # Keep the legacy value here to test compatibility ticker_interval = '5m' def populate_indicators(self, dataframe: DataFrame) -> DataFrame: diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 1bb45f28c..f2cf11712 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -370,6 +370,8 @@ def test_call_deprecated_function(result, monkeypatch, default_conf): assert strategy._buy_fun_len == 2 assert strategy._sell_fun_len == 2 assert strategy.INTERFACE_VERSION == 1 + assert strategy.timeframe == '5m' + assert strategy.ticker_interval == '5m' indicator_df = strategy.advise_indicators(result, metadata=metadata) assert isinstance(indicator_df, DataFrame) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 1ed97f728..9602f6389 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1137,3 +1137,15 @@ def test_process_deprecated_setting(mocker, default_conf, caplog): 'sectionB', 'deprecated_setting') assert not log_has_re('DEPRECATED', caplog) assert default_conf['sectionA']['new_setting'] == 'valA' + + +def test_process_deprecated_ticker_interval(mocker, default_conf, caplog): + message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." + process_temporary_deprecated_settings(default_conf) + assert not log_has(message, caplog) + + del default_conf['timeframe'] + default_conf['ticker_interval'] = '15m' + process_temporary_deprecated_settings(default_conf) + assert log_has(message, caplog) + assert default_conf['ticker_interval'] == '15m' From a8005819c97461055bf29a0bae4a1e662a313c28 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 10:19:27 +0200 Subject: [PATCH 064/191] Add class-level attributes to hyperopt and strategy --- freqtrade/optimize/hyperopt_interface.py | 3 ++- freqtrade/strategy/interface.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 20209d8a9..00353cbf4 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -31,7 +31,8 @@ class IHyperOpt(ABC): Class attributes you can use: ticker_interval -> int: value of the ticker interval to use for the strategy """ - ticker_interval: str + ticker_interval: str # deprecated + timeframe: str def __init__(self, config: dict) -> None: self.config = config diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7bf750249..f9f3a3678 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -86,6 +86,7 @@ class IStrategy(ABC): trailing_only_offset_is_reached = False # associated timeframe + ticker_interval: str # DEPRECATED timeframe: str # Optional order types From b106c886309f446861bd01b4062e3e285c23a3ff Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 13:08:21 +0200 Subject: [PATCH 065/191] Add test case for strategy overwriting --- tests/strategy/test_strategy.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index f2cf11712..85cb8c132 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -358,8 +358,9 @@ def test_deprecate_populate_indicators(result, default_conf): @pytest.mark.filterwarnings("ignore:deprecated") -def test_call_deprecated_function(result, monkeypatch, default_conf): +def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): default_location = Path(__file__).parent / "strats" + del default_conf['timeframe'] default_conf.update({'strategy': 'TestStrategyLegacy', 'strategy_path': default_location}) strategy = StrategyResolver.load_strategy(default_conf) @@ -385,6 +386,9 @@ def test_call_deprecated_function(result, monkeypatch, default_conf): assert isinstance(selldf, DataFrame) assert 'sell' in selldf + assert log_has('DEPRECATED: Please migrate to using timeframe instead of ticker_interval.', + caplog) + def test_strategy_interface_versioning(result, monkeypatch, default_conf): default_conf.update({'strategy': 'DefaultStrategy'}) From 9995a5899fb16dcfe1e13a7525077142a4152076 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 2 Jun 2020 16:25:22 +0300 Subject: [PATCH 066/191] Fix merge --- freqtrade/persistence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index a3b394769..bb9db703d 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -108,7 +108,7 @@ def check_migrate(engine) -> None: sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') # If ticker-interval existed use that, else null. - if has_column(cols, '): + if has_column(cols, 'ticker_interval'): timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') else: timeframe = get_column_def(cols, 'timeframe', 'null') From edf8e39bc11c6c37b06459ac9d9bdfc38f86eb9a Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 2 Jun 2020 17:57:45 +0300 Subject: [PATCH 067/191] Fix tests after merge --- tests/rpc/test_rpc.py | 2 -- tests/rpc/test_rpc_apiserver.py | 4 +--- tests/test_persistence.py | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 9a55c7639..44d54bad6 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -65,7 +65,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, - 'ticker_interval': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, @@ -124,7 +123,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, - 'ticker_interval': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index b3859c4e6..5547ee004 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -322,7 +322,7 @@ def test_api_show_config(botclient, mocker): assert_response(rc) assert 'dry_run' in rc.json assert rc.json['exchange'] == 'bittrex' - assert rc.json['ticker_interval'] == '5m' + assert rc.json['timeframe'] == '5m' assert rc.json['state'] == 'running' assert not rc.json['trailing_stop'] @@ -547,7 +547,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'sell_reason': None, 'sell_order_status': None, 'strategy': 'DefaultStrategy', - 'ticker_interval': 5, 'timeframe': 5, 'exchange': 'bittrex', }] @@ -671,7 +670,6 @@ def test_api_forcebuy(botclient, mocker, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, - 'ticker_interval': None, 'timeframe': None, 'exchange': 'bittrex', } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 69ee014c5..d55f24719 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -776,7 +776,6 @@ def test_to_json(default_conf, fee): 'min_rate': None, 'max_rate': None, 'strategy': None, - 'ticker_interval': None, 'timeframe': None, 'exchange': 'bittrex', } @@ -838,7 +837,6 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, - 'ticker_interval': None, 'timeframe': None, 'exchange': 'bittrex', } From 85fedf95e85a871579731a55911694d406660bb4 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 2 Jun 2020 18:43:37 +0300 Subject: [PATCH 068/191] Make mypy happy --- 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 6275b0eda..397e0c1a7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -890,7 +890,7 @@ class Exchange: logger.debug(f"_async_get_trade_history(), pair: {pair}, " f"since: {since}, until: {until}, from_id: {from_id}") - if not until: + if until is None: exchange_msec = ccxt.Exchange.milliseconds() logger.debug(f"Exchange milliseconds: {exchange_msec}") until = exchange_msec From f4c2bb13468d0edaf78dc3ab31b89c8876851e35 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 2 Jun 2020 19:37:08 +0300 Subject: [PATCH 069/191] Fix crash in #3404 --- freqtrade/data/converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 0ef7955a4..f73775432 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -197,7 +197,7 @@ def trades_to_ohlcv(trades: List, timeframe: str) -> DataFrame: df_new['date'] = df_new.index # Drop 0 volume rows df_new = df_new.dropna() - return df_new[DEFAULT_DATAFRAME_COLUMNS] + return df_new.loc[:, DEFAULT_DATAFRAME_COLUMNS] def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to: str, erase: bool): From 1a5dba9a79a50e567bbc16da3da63a82294c7802 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 19:39:17 +0200 Subject: [PATCH 070/191] Revert "Fix tests after merge" This reverts commit edf8e39bc11c6c37b06459ac9d9bdfc38f86eb9a. --- tests/rpc/test_rpc.py | 2 ++ tests/rpc/test_rpc_apiserver.py | 4 +++- tests/test_persistence.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 44d54bad6..9a55c7639 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -65,6 +65,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, + 'ticker_interval': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, @@ -123,6 +124,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, + 'ticker_interval': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 5547ee004..b3859c4e6 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -322,7 +322,7 @@ def test_api_show_config(botclient, mocker): assert_response(rc) assert 'dry_run' in rc.json assert rc.json['exchange'] == 'bittrex' - assert rc.json['timeframe'] == '5m' + assert rc.json['ticker_interval'] == '5m' assert rc.json['state'] == 'running' assert not rc.json['trailing_stop'] @@ -547,6 +547,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'sell_reason': None, 'sell_order_status': None, 'strategy': 'DefaultStrategy', + 'ticker_interval': 5, 'timeframe': 5, 'exchange': 'bittrex', }] @@ -670,6 +671,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, + 'ticker_interval': None, 'timeframe': None, 'exchange': 'bittrex', } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index d55f24719..69ee014c5 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -776,6 +776,7 @@ def test_to_json(default_conf, fee): 'min_rate': None, 'max_rate': None, 'strategy': None, + 'ticker_interval': None, 'timeframe': None, 'exchange': 'bittrex', } @@ -837,6 +838,7 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, + 'ticker_interval': None, 'timeframe': None, 'exchange': 'bittrex', } From 02fca141a03fccf9fd49dffc3ba8b30e84c6dfbc Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 19:43:15 +0200 Subject: [PATCH 071/191] Readd ticker_interval to trade api response --- freqtrade/persistence.py | 1 + tests/rpc/test_rpc_apiserver.py | 1 + 2 files changed, 2 insertions(+) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index bb9db703d..628c9cf22 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -258,6 +258,7 @@ class Trade(_DECL_BASE): 'amount': round(self.amount, 8), 'stake_amount': round(self.stake_amount, 8), 'strategy': self.strategy, + 'ticker_interval': self.timeframe, # DEPRECATED 'timeframe': self.timeframe, 'fee_open': self.fee_open, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index b3859c4e6..daee0186a 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -323,6 +323,7 @@ def test_api_show_config(botclient, mocker): assert 'dry_run' in rc.json assert rc.json['exchange'] == 'bittrex' assert rc.json['ticker_interval'] == '5m' + assert rc.json['timeframe'] == '5m' assert rc.json['state'] == 'running' assert not rc.json['trailing_stop'] From 48117666fe9ef6fa9510e5daad86dd3a895a0ce1 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 2 Jun 2020 21:09:23 +0300 Subject: [PATCH 072/191] Update freqtrade/exchange/exchange.py Co-authored-by: Matthias --- freqtrade/exchange/exchange.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 397e0c1a7..031e464b7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -891,9 +891,8 @@ class Exchange: f"since: {since}, until: {until}, from_id: {from_id}") if until is None: - exchange_msec = ccxt.Exchange.milliseconds() - logger.debug(f"Exchange milliseconds: {exchange_msec}") - until = exchange_msec + until = ccxt.Exchange.milliseconds() + logger.debug(f"Exchange milliseconds: {until}") if self._trades_pagination == 'time': return await self._async_get_trade_history_time( From ea954b43389feef6ca0323c95f858df82b17cce6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 20:54:14 +0200 Subject: [PATCH 073/191] Add failing test with testcase from incident Full problem in #3431 --- tests/exchange/test_exchange.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 25aecba5c..32163f696 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2192,12 +2192,18 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: 'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944), ({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561, 'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 0.00074305), - # TODO: More tests here! # Rate included in return - return as is ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, 0.01), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 0.005), + # 0.1% filled - no costs (kraken - #3431) + ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, + 'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None), + ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, + 'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, 0.0), + ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, + 'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None), ]) def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None: mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081}) From a2551daf12c68434449ea512433482cfc2736696 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 20:55:12 +0200 Subject: [PATCH 074/191] Fix ZeroDivision problem where cost is 0.0 --- freqtrade/exchange/exchange.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 7ef0d7750..4acee710a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1104,9 +1104,12 @@ class Exchange: order['fee']['cost'] / safe_value_fallback(order, order, 'filled', 'amount'), 8) elif fee_curr in self.get_pair_quote_currency(order['symbol']): # Quote currency - divide by cost - return round(order['fee']['cost'] / order['cost'], 8) + return round(order['fee']['cost'] / order['cost'], 8) if order['cost'] else None else: # If Fee currency is a different currency + if not order['cost']: + # If cost is None or 0.0 -> falsy, return None + return None try: comb = self.get_valid_pair_combination(fee_curr, self._config['stake_currency']) tick = self.fetch_ticker(comb) From ad61673d6feec44a91c48cf805381cca28f11cee Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jun 2020 21:10:12 +0200 Subject: [PATCH 075/191] Fix missing key in test order --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index 971f7a5fa..1cd3bcc0f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1590,6 +1590,7 @@ def buy_order_fee(): 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), 'price': 0.245441, 'amount': 8.0, + 'cost': 1.963528, 'remaining': 90.99181073, 'status': 'closed', 'fee': None From 78dea19ffb4e0efa9fa7594fadfddd49010a06e7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Mar 2020 20:57:12 +0100 Subject: [PATCH 076/191] Implement first version of FTX stop --- freqtrade/exchange/ftx.py | 53 +++++++++++++++++ tests/exchange/test_ftx.py | 106 ++++++++++++++++++++++++++++++++++ tests/exchange/test_kraken.py | 10 ++-- 3 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 tests/exchange/test_ftx.py diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 75915122b..d06d49e5a 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -2,6 +2,10 @@ import logging from typing import Dict +import ccxt + +from freqtrade.exceptions import (DependencyException, InvalidOrderException, + OperationalException, TemporaryError) from freqtrade.exchange import Exchange logger = logging.getLogger(__name__) @@ -10,5 +14,54 @@ logger = logging.getLogger(__name__) class Ftx(Exchange): _ft_has: Dict = { + "stoploss_on_exchange": True, "ohlcv_candle_limit": 1500, } + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['type'] == 'stop' and stop_loss > float(order['price']) + + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + """ + Creates a stoploss market order. + Stoploss market orders is the only stoploss type supported by kraken. + """ + + ordertype = "stop" + + stop_price = self.price_to_precision(pair, stop_price) + + if self._config['dry_run']: + dry_order = self.dry_run_order( + pair, ordertype, "sell", amount, stop_price) + 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', + amount=amount, price=stop_price, params=params) + logger.info('stoploss order added for %s. ' + 'stop price: %s.', pair, stop_price) + return order + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to create {ordertype} sell order on market {pair}.' + f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' + f'Message: {e}') from e + except ccxt.InvalidOrder as e: + raise InvalidOrderException( + f'Could not create {ordertype} sell order on market {pair}. ' + f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' + f'Message: {e}') from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py new file mode 100644 index 000000000..f17d5b42e --- /dev/null +++ b/tests/exchange/test_ftx.py @@ -0,0 +1,106 @@ +# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement +# pragma pylint: disable=protected-access +from random import randint +from unittest.mock import MagicMock + +import ccxt +import pytest + +from freqtrade.exceptions import (DependencyException, InvalidOrderException, + OperationalException, TemporaryError) +from tests.conftest import get_patched_exchange + + +STOPLOSS_ORDERTYPE = 'stop' + + +def test_stoploss_order_ftx(default_conf, mocker): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') + + # stoploss_on_exchange_limit_ratio is irrelevant for ftx 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={}) + + 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 + 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 + + # test exception handling + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + with pytest.raises(InvalidOrderException): + api_mock.create_order = MagicMock( + side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + with pytest.raises(TemporaryError): + api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + with pytest.raises(OperationalException, match=r".*DeadBeef.*"): + api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + +def test_stoploss_order_dry_run_ftx(default_conf, mocker): + api_mock = MagicMock() + default_conf['dry_run'] = True + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') + + api_mock.create_order.reset_mock() + + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + assert 'id' in order + assert 'info' in order + assert 'type' in order + + assert order['type'] == STOPLOSS_ORDERTYPE + assert order['price'] == 220 + assert order['amount'] == 1 + + +def test_stoploss_adjust_ftx(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='ftx') + order = { + 'type': STOPLOSS_ORDERTYPE, + 'price': 1500, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case ... + order['type'] = 'stop_loss_limit' + assert not exchange.stoploss_adjust(1501, order) diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index d63dd66cc..0950979cf 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -11,6 +11,8 @@ from freqtrade.exceptions import (DependencyException, InvalidOrderException, from tests.conftest import get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers +STOPLOSS_ORDERTYPE = 'stop-loss' + def test_buy_kraken_trading_agreement(default_conf, mocker): api_mock = MagicMock() @@ -159,7 +161,6 @@ def test_get_balances_prod(default_conf, mocker): def test_stoploss_order_kraken(default_conf, mocker): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) - order_type = 'stop-loss' api_mock.create_order = MagicMock(return_value={ 'id': order_id, @@ -187,7 +188,7 @@ def test_stoploss_order_kraken(default_conf, mocker): 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'] == order_type + assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE 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 @@ -218,7 +219,6 @@ def test_stoploss_order_kraken(default_conf, mocker): def test_stoploss_order_dry_run_kraken(default_conf, mocker): api_mock = MagicMock() - order_type = 'stop-loss' default_conf['dry_run'] = True mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) @@ -233,7 +233,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): assert 'info' in order assert 'type' in order - assert order['type'] == order_type + assert order['type'] == STOPLOSS_ORDERTYPE assert order['price'] == 220 assert order['amount'] == 1 @@ -241,7 +241,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): def test_stoploss_adjust_kraken(mocker, default_conf): exchange = get_patched_exchange(mocker, default_conf, id='kraken') order = { - 'type': 'stop-loss', + 'type': STOPLOSS_ORDERTYPE, 'price': 1500, } assert exchange.stoploss_adjust(1501, order) From 68a59fd26d720efa3d22f9f67bbafac8ce2d280b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Mar 2020 20:58:05 +0100 Subject: [PATCH 077/191] Add Hint to suggest this is still broken --- freqtrade/exchange/ftx.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index d06d49e5a..97257530e 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -28,7 +28,8 @@ class Ftx(Exchange): def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ Creates a stoploss market order. - Stoploss market orders is the only stoploss type supported by kraken. + Stoploss market orders is the only stoploss type supported by ftx. + TODO: This doesnot work yet as the order cannot be aquired via fetch_orders - so Freqtrade assumes the order as always missing. """ ordertype = "stop" From a808fb3b1068baa7a1c522ac4840ac93d8438b07 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 15:32:52 +0100 Subject: [PATCH 078/191] versionbump ccxt to first version that has FTX implemented correctly --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 20963a15f..6d832e3f5 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ setup(name='freqtrade', tests_require=['pytest', 'pytest-asyncio', 'pytest-cov', 'pytest-mock', ], install_requires=[ # from requirements-common.txt - 'ccxt>=1.18.1080', + 'ccxt>=1.24.96', 'SQLAlchemy', 'python-telegram-bot', 'arrow', From d90d6ed5d01283040b61416925a19dd1541fe037 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 15:50:33 +0100 Subject: [PATCH 079/191] Add ftx to tested exchanges --- tests/exchange/test_exchange.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 32163f696..d8950dd09 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -25,7 +25,7 @@ from freqtrade.resolvers.exchange_resolver import ExchangeResolver from tests.conftest import get_patched_exchange, log_has, log_has_re # Make sure to always keep one exchange here which is NOT subclassed!! -EXCHANGES = ['bittrex', 'binance', 'kraken', ] +EXCHANGES = ['bittrex', 'binance', 'kraken', 'ftx'] # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines @@ -1258,7 +1258,8 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls - since = 5 * 60 * 500 * 1.8 + + since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8 ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000)) assert exchange._async_get_candle_history.call_count == 2 From f83c1c5abf5d2b20ec29adfc84c85d3bcca6911d Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 17:01:11 +0100 Subject: [PATCH 080/191] Use get_stoploss_order and cancel_stoploss_order This allows exchanges to use stoploss which don't have the same endpoints --- freqtrade/exchange/exchange.py | 8 ++++++- freqtrade/exchange/ftx.py | 44 ++++++++++++++++++++++++++++++++++ freqtrade/freqtradebot.py | 10 ++++---- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 038fc22bc..e666b07ec 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -79,7 +79,7 @@ class Exchange: if config['dry_run']: logger.info('Instance is running with dry_run enabled') - + logger.info(f"Using CCXT {ccxt.__version__}") exchange_config = config['exchange'] # Deep merge ft_has with default ft_has options @@ -952,6 +952,9 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + # Assign method to get_stoploss_order to allow easy overriding in other classes + cancel_stoploss_order = cancel_order + def is_cancel_order_result_suitable(self, corder) -> bool: if not isinstance(corder, dict): return False @@ -1004,6 +1007,9 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + # Assign method to get_stoploss_order to allow easy overriding in other classes + get_stoploss_order = get_order + @retrier def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: """ diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 97257530e..70f140ac5 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -7,6 +7,7 @@ import ccxt from freqtrade.exceptions import (DependencyException, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange +from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -66,3 +67,46 @@ class Ftx(Exchange): f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e + + @retrier + def get_stoploss_order(self, order_id: str, pair: str) -> Dict: + if self._config['dry_run']: + try: + order = self._dry_run_open_orders[order_id] + return order + except KeyError as e: + # Gracefully handle errors with dry-run orders. + raise InvalidOrderException( + f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e + try: + orders = self._api.fetch_orders('BNB/USD', None, params={'type': 'stop'}) + + order = [order for order in orders if order['id'] == order_id] + if len(order) == 1: + return order[0] + else: + raise InvalidOrderException(f"Could not get Stoploss Order for id {order_id}") + + except ccxt.InvalidOrder as e: + raise InvalidOrderException( + f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + + @retrier + def cancel_stoploss_order(self, order_id: str, pair: str) -> None: + if self._config['dry_run']: + return + try: + return self._api.cancel_order(order_id, pair, params={'type': 'stop'}) + except ccxt.InvalidOrder as e: + raise InvalidOrderException( + f'Could not cancel order. Message: {e}') from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a74b0a5a1..4e4fe6e11 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -774,13 +774,13 @@ class FreqtradeBot: try: # First we check if there is already a stoploss on exchange - stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \ + stoploss_order = self.exchange.get_stoploss_order(trade.stoploss_order_id, trade.pair) \ if trade.stoploss_order_id else None except InvalidOrderException as exception: logger.warning('Unable to fetch stoploss order: %s', exception) # We check if stoploss order is fulfilled - if stoploss_order and stoploss_order['status'] == 'closed': + if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'): trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value self.update_trade_state(trade, stoploss_order, sl_order=True) # Lock pair for one candle to prevent immediate rebuys @@ -807,7 +807,7 @@ class FreqtradeBot: return False # If stoploss order is canceled for some reason we add it - if stoploss_order and stoploss_order['status'] == 'canceled': + if stoploss_order and stoploss_order['status'] in ('canceled', 'cancelled'): if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss, rate=trade.stop_loss): return False @@ -840,7 +840,7 @@ class FreqtradeBot: logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) ' 'in order to add another one ...', order['id']) try: - self.exchange.cancel_order(order['id'], trade.pair) + self.exchange.cancel_stoploss_order(order['id'], trade.pair) except InvalidOrderException: logger.exception(f"Could not cancel stoploss order {order['id']} " f"for pair {trade.pair}") @@ -1068,7 +1068,7 @@ class FreqtradeBot: # First cancelling stoploss on exchange ... if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: try: - self.exchange.cancel_order(trade.stoploss_order_id, trade.pair) + self.exchange.cancel_stoploss_order(trade.stoploss_order_id, trade.pair) except InvalidOrderException: logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") From cf50c1cb7be618f48ab9268be506a5b18765d871 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 17:01:45 +0100 Subject: [PATCH 081/191] Add tests for new exchange methods --- tests/exchange/test_exchange.py | 51 +++++++++++++++++++++++++++++++++ tests/exchange/test_ftx.py | 35 +++++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d8950dd09..c06b934ba 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1734,6 +1734,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} + assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -1818,6 +1819,25 @@ def test_cancel_order(default_conf, mocker, exchange_name): order_id='_', pair='TKN/BTC') +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_cancel_stoploss_order(default_conf, mocker, exchange_name): + default_conf['dry_run'] = False + api_mock = MagicMock() + api_mock.cancel_order = MagicMock(return_value=123) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC') == 123 + + with pytest.raises(InvalidOrderException): + api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange.cancel_stoploss_order(order_id='_', pair='TKN/BTC') + assert api_mock.cancel_order.call_count == 1 + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + "cancel_stoploss_order", "cancel_order", + order_id='_', pair='TKN/BTC') + + @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_order(default_conf, mocker, exchange_name): default_conf['dry_run'] = True @@ -1847,6 +1867,37 @@ def test_get_order(default_conf, mocker, exchange_name): order_id='_', pair='TKN/BTC') +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_stoploss_order(default_conf, mocker, exchange_name): + # Don't test FTX here - that needs a seperate test + if exchange_name == 'ftx': + return + default_conf['dry_run'] = True + order = MagicMock() + order.myid = 123 + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + exchange._dry_run_open_orders['X'] = order + assert exchange.get_stoploss_order('X', 'TKN/BTC').myid == 123 + + with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'): + exchange.get_stoploss_order('Y', 'TKN/BTC') + + default_conf['dry_run'] = False + api_mock = MagicMock() + api_mock.fetch_order = MagicMock(return_value=456) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.get_stoploss_order('X', 'TKN/BTC') == 456 + + with pytest.raises(InvalidOrderException): + api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange.get_stoploss_order(order_id='_', pair='TKN/BTC') + assert api_mock.fetch_order.call_count == 1 + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + 'get_stoploss_order', 'fetch_order', + order_id='_', pair='TKN/BTC') + @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_name(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index f17d5b42e..2b75e5324 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -9,7 +9,7 @@ import pytest from freqtrade.exceptions import (DependencyException, InvalidOrderException, OperationalException, TemporaryError) from tests.conftest import get_patched_exchange - +from .test_exchange import ccxt_exceptionhandlers STOPLOSS_ORDERTYPE = 'stop' @@ -104,3 +104,36 @@ def test_stoploss_adjust_ftx(mocker, default_conf): # Test with invalid order case ... order['type'] = 'stop_loss_limit' assert not exchange.stoploss_adjust(1501, order) + + +def test_get_stoploss_order(default_conf, mocker): + default_conf['dry_run'] = True + order = MagicMock() + order.myid = 123 + exchange = get_patched_exchange(mocker, default_conf, id='ftx') + exchange._dry_run_open_orders['X'] = order + assert exchange.get_stoploss_order('X', 'TKN/BTC').myid == 123 + + with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'): + exchange.get_stoploss_order('Y', 'TKN/BTC') + + default_conf['dry_run'] = False + api_mock = MagicMock() + api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': '456'}]) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') + assert exchange.get_stoploss_order('X', 'TKN/BTC')['status'] == '456' + + api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}]) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') + with pytest.raises(InvalidOrderException, match=r"Could not get Stoploss Order for id X"): + exchange.get_stoploss_order('X', 'TKN/BTC')['status'] + + with pytest.raises(InvalidOrderException): + api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') + exchange.get_stoploss_order(order_id='_', pair='TKN/BTC') + assert api_mock.fetch_orders.call_count == 1 + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx', + 'get_stoploss_order', 'fetch_orders', + order_id='_', pair='TKN/BTC') From 3174f37b41b515a886673501055ce4fe4d394947 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2020 17:02:47 +0100 Subject: [PATCH 082/191] adapt tests to use stoploss_* methods --- tests/test_freqtradebot.py | 33 ++++++++++++++++++--------------- tests/test_integration.py | 4 ++-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 487e3a60e..dc5a5c7d3 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1126,7 +1126,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, trade.stoploss_order_id = 100 hanging_stoploss_order = MagicMock(return_value={'status': 'open'}) - mocker.patch('freqtrade.exchange.Exchange.get_order', hanging_stoploss_order) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', hanging_stoploss_order) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id == 100 @@ -1139,7 +1139,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, trade.stoploss_order_id = 100 canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'}) - mocker.patch('freqtrade.exchange.Exchange.get_order', canceled_stoploss_order) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', canceled_stoploss_order) stoploss.reset_mock() assert freqtrade.handle_stoploss_on_exchange(trade) is False @@ -1164,7 +1164,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, 'average': 2, 'amount': limit_buy_order['amount'], }) - mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog) assert trade.stoploss_order_id is None @@ -1183,7 +1183,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog, # It should try to add stoploss order trade.stoploss_order_id = 100 stoploss.reset_mock() - mocker.patch('freqtrade.exchange.Exchange.get_order', side_effect=InvalidOrderException()) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', + side_effect=InvalidOrderException()) mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) freqtrade.handle_stoploss_on_exchange(trade) assert stoploss.call_count == 1 @@ -1214,7 +1215,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog, buy=MagicMock(return_value={'id': limit_buy_order['id']}), sell=MagicMock(return_value={'id': limit_sell_order['id']}), get_fee=fee, - get_order=MagicMock(return_value={'status': 'canceled'}), + get_stoploss_order=MagicMock(return_value={'status': 'canceled'}), stoploss=MagicMock(side_effect=DependencyException()), ) freqtrade = FreqtradeBot(default_conf) @@ -1331,7 +1332,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, } }) - mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging) # stoploss initially at 5% assert freqtrade.handle_trade(trade) is False @@ -1346,7 +1347,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog, cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss_order_mock) # stoploss should not be updated as the interval is 60 seconds @@ -1429,8 +1430,9 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c 'stopPrice': '0.1' } } - mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', + side_effect=InvalidOrderException()) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog) @@ -1439,7 +1441,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c # Fail creating stoploss order caplog.clear() - cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_order", MagicMock()) + cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_stoploss_order", MagicMock()) mocker.patch("freqtrade.exchange.Exchange.stoploss", side_effect=DependencyException()) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert cancel_mock.call_count == 1 @@ -1510,7 +1512,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, } }) - mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging) # stoploss initially at 20% as edge dictated it. assert freqtrade.handle_trade(trade) is False @@ -1519,7 +1521,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, cancel_order_mock = MagicMock() stoploss_order_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock) + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) # price goes down 5% @@ -2632,7 +2634,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException()) + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', + side_effect=InvalidOrderException()) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) sellmock = MagicMock() patch_exchange(mocker) @@ -2680,7 +2683,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, stoploss=stoploss, - cancel_order=cancel_order, + cancel_stoploss_order=cancel_order, ) freqtrade = FreqtradeBot(default_conf) @@ -2771,7 +2774,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f "fee": None, "trades": None }) - mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_executed) + mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_executed) freqtrade.exit_positions(trades) assert trade.stoploss_order_id is None diff --git a/tests/test_integration.py b/tests/test_integration.py index 1396e86f5..57960503e 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -62,8 +62,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, get_fee=fee, amount_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y, - get_order=stoploss_order_mock, - cancel_order=cancel_order_mock, + get_stoploss_order=stoploss_order_mock, + cancel_stoploss_order=cancel_order_mock, ) mocker.patch.multiple( From 11ebdefd09ef1d587e326b0d7d23020f32d71ebb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 18 Apr 2020 19:52:21 +0200 Subject: [PATCH 083/191] Fix bug after rebase --- freqtrade/exchange/ftx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 70f140ac5..0bd32b581 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -97,9 +97,9 @@ class Ftx(Exchange): raise OperationalException(e) from e @retrier - def cancel_stoploss_order(self, order_id: str, pair: str) -> None: + def cancel_stoploss_order(self, order_id: str, pair: str) -> Dict: if self._config['dry_run']: - return + return {} try: return self._api.cancel_order(order_id, pair, params={'type': 'stop'}) except ccxt.InvalidOrder as e: From b58fd179f231ed304d8d47d85c8d43169218b0a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 10:05:14 +0200 Subject: [PATCH 084/191] Don't hardcode pair ... --- freqtrade/exchange/ftx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 0bd32b581..1bc97c4d3 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -79,7 +79,7 @@ class Ftx(Exchange): raise InvalidOrderException( f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e try: - orders = self._api.fetch_orders('BNB/USD', None, params={'type': 'stop'}) + orders = self._api.fetch_orders(pair, None, params={'type': 'stop'}) order = [order for order in orders if order['id'] == order_id] if len(order) == 1: From 1d9aeef792a4eaa100646f04aaf6e5d47a2169b9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 10:36:23 +0200 Subject: [PATCH 085/191] Support stop order in persistence --- freqtrade/persistence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 823bf6dc0..2e02c3999 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -374,7 +374,7 @@ class Trade(_DECL_BASE): elif order_type in ('market', 'limit') and order['side'] == 'sell': self.close(order['price']) logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self) - elif order_type in ('stop_loss_limit', 'stop-loss'): + elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'): self.stoploss_order_id = None self.close_rate_requested = self.stop_loss logger.info('%s is hit for %s.', order_type.upper(), self) From 77a62b845a1d5c13146966e3609f40e99f39fbf2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 11:24:23 +0200 Subject: [PATCH 086/191] Fix some comments --- freqtrade/exchange/ftx.py | 2 +- tests/exchange/test_exchange.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 1bc97c4d3..20d643a83 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -30,7 +30,6 @@ class Ftx(Exchange): """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by ftx. - TODO: This doesnot work yet as the order cannot be aquired via fetch_orders - so Freqtrade assumes the order as always missing. """ ordertype = "stop" @@ -46,6 +45,7 @@ class Ftx(Exchange): params = self._params.copy() amount = self.amount_to_precision(pair, amount) + # set orderPrice to place limit order (?) order = self._api.create_order(symbol=pair, type=ordertype, side='sell', amount=amount, price=stop_price, params=params) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index c06b934ba..dc272de36 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1898,6 +1898,7 @@ def test_get_stoploss_order(default_conf, mocker, exchange_name): 'get_stoploss_order', 'fetch_order', order_id='_', pair='TKN/BTC') + @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_name(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) From f0eb0bc350a876c514deb63e331cedac6707a884 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Jun 2020 11:33:40 +0200 Subject: [PATCH 087/191] Support limit orders --- freqtrade/exchange/ftx.py | 12 +++++++++--- tests/exchange/test_ftx.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 20d643a83..73347f1eb 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -28,9 +28,13 @@ class Ftx(Exchange): def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ - Creates a stoploss market order. - Stoploss market orders is the only stoploss type supported by ftx. + Creates a stoploss order. + depending on order_types.stoploss configuration, uses 'market' or limit order. + + Limit orders are defined by having orderPrice set, otherwise a market order is used. """ + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + limit_rate = stop_price * limit_price_pct ordertype = "stop" @@ -43,9 +47,11 @@ class Ftx(Exchange): try: params = self._params.copy() + if order_types.get('stoploss', 'market') == 'limit': + # set orderPrice to place limit order, otherwise it's a market order + params['orderPrice'] = limit_rate amount = self.amount_to_precision(pair, amount) - # set orderPrice to place limit order (?) order = self._api.create_order(symbol=pair, type=ordertype, side='sell', amount=amount, price=stop_price, params=params) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 2b75e5324..bead63096 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -34,6 +34,14 @@ def test_stoploss_order_ftx(default_conf, mocker): # stoploss_on_exchange_limit_ratio is irrelevant for ftx 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_args_list[0][1]['symbol'] == 'ETH/BTC' + assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE + 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'] == 190 + assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] + assert api_mock.create_order.call_count == 1 api_mock.create_order.reset_mock() @@ -48,6 +56,22 @@ def test_stoploss_order_ftx(default_conf, mocker): 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 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params'] + + api_mock.create_order.reset_mock() + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={'stoploss': 'limit'}) + + 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 + 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 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params'] + assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8 # test exception handling with pytest.raises(DependencyException): From 0dc1a8e0379852c01289e851cb36a4c9f13b6327 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Jun 2020 19:40:30 +0200 Subject: [PATCH 088/191] Add profit sum to api response --- freqtrade/rpc/rpc.py | 20 +++++++++++++++----- freqtrade/rpc/telegram.py | 13 +++++++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 91b35e9ad..d3fef7041 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -283,8 +283,9 @@ class RPC: # Prepare data to display profit_closed_coin_sum = round(sum(profit_closed_coin), 8) - profit_closed_percent = (round(mean(profit_closed_ratio) * 100, 2) if profit_closed_ratio - else 0.0) + profit_closed_ratio_mean = mean(profit_closed_ratio) if profit_closed_ratio else 0.0 + profit_closed_ratio_sum = sum(profit_closed_ratio) if profit_closed_ratio else 0.0 + profit_closed_fiat = self._fiat_converter.convert_amount( profit_closed_coin_sum, stake_currency, @@ -292,7 +293,8 @@ class RPC: ) if self._fiat_converter else 0 profit_all_coin_sum = round(sum(profit_all_coin), 8) - profit_all_percent = round(mean(profit_all_ratio) * 100, 2) if profit_all_ratio else 0.0 + profit_all_ratio_mean = mean(profit_all_ratio) if profit_all_ratio else 0.0 + profit_all_ratio_sum = sum(profit_all_ratio) if profit_all_ratio else 0.0 profit_all_fiat = self._fiat_converter.convert_amount( profit_all_coin_sum, stake_currency, @@ -304,10 +306,18 @@ class RPC: num = float(len(durations) or 1) return { 'profit_closed_coin': profit_closed_coin_sum, - 'profit_closed_percent': profit_closed_percent, + 'profit_closed_percent': round(profit_closed_ratio_mean * 100, 2), # DEPRECATED + 'profit_closed_percent_mean': round(profit_closed_ratio_mean * 100, 2), + 'profit_closed_ratio_mean': profit_closed_ratio_mean, + 'profit_closed_percent_sum': round(profit_closed_ratio_sum * 100, 2), + 'profit_closed_ratio_sum': profit_closed_ratio_sum, 'profit_closed_fiat': profit_closed_fiat, 'profit_all_coin': profit_all_coin_sum, - 'profit_all_percent': profit_all_percent, + 'profit_all_percent': round(profit_all_ratio_mean * 100, 2), # DEPRECATED + 'profit_all_percent_mean': round(profit_all_ratio_mean * 100, 2), + 'profit_all_ratio_mean': profit_all_ratio_mean, + 'profit_all_percent_sum': round(profit_all_ratio_sum * 100, 2), + 'profit_all_ratio_sum': profit_all_ratio_sum, 'profit_all_fiat': profit_all_fiat, 'trade_count': len(trades), 'closed_trade_count': len([t for t in trades if not t.is_open]), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8fa973d73..02c580ebd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -315,10 +315,12 @@ class Telegram(RPC): stake_cur, fiat_disp_cur) profit_closed_coin = stats['profit_closed_coin'] - profit_closed_percent = stats['profit_closed_percent'] + profit_closed_percent_mean = stats['profit_closed_percent_mean'] + profit_closed_percent_sum = stats['profit_closed_percent_sum'] profit_closed_fiat = stats['profit_closed_fiat'] profit_all_coin = stats['profit_all_coin'] - profit_all_percent = stats['profit_all_percent'] + profit_all_percent_mean = stats['profit_all_percent_mean'] + profit_all_percent_sum = stats['profit_all_percent_sum'] profit_all_fiat = stats['profit_all_fiat'] trade_count = stats['trade_count'] first_trade_date = stats['first_trade_date'] @@ -333,13 +335,16 @@ class Telegram(RPC): if stats['closed_trade_count'] > 0: markdown_msg = ("*ROI:* Closed trades\n" f"∙ `{profit_closed_coin:.8f} {stake_cur} " - f"({profit_closed_percent:.2f}%)`\n" + f"({profit_closed_percent_mean:.2f}%) " + f"({profit_closed_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n" f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") else: markdown_msg = "`No closed trade` \n" markdown_msg += (f"*ROI:* All trades\n" - f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" + f"∙ `{profit_all_coin:.8f} {stake_cur} " + f"({profit_all_percent_mean:.2f}%) " + f"({profit_all_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n" f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" f"*Total Trade Count:* `{trade_count}`\n" f"*First Trade opened:* `{first_trade_date}`\n" From 6997524a04f27334bf383b3e07a6267b4f8d6923 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Jun 2020 19:40:49 +0200 Subject: [PATCH 089/191] Fix tests for additional info --- tests/rpc/test_rpc_apiserver.py | 8 ++++++++ tests/rpc/test_rpc_telegram.py | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cacf49f62..70a06557f 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -428,9 +428,17 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li 'profit_all_coin': 6.217e-05, 'profit_all_fiat': 0, 'profit_all_percent': 6.2, + 'profit_all_percent_mean': 6.2, + 'profit_all_ratio_mean': 0.06201058, + 'profit_all_percent_sum': 6.2, + 'profit_all_ratio_sum': 0.06201058, 'profit_closed_coin': 6.217e-05, 'profit_closed_fiat': 0, 'profit_closed_percent': 6.2, + 'profit_closed_ratio_mean': 0.06201058, + 'profit_closed_percent_mean': 6.2, + 'profit_closed_ratio_sum': 0.06201058, + 'profit_closed_percent_sum': 6.2, 'trade_count': 1, 'closed_trade_count': 1, } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f81127c4c..4895d67e4 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -434,7 +434,8 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert msg_mock.call_count == 1 assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] - assert '∙ `-0.00000500 BTC (-0.50%)`' in msg_mock.call_args_list[-1][0][0] + assert ('∙ `-0.00000500 BTC (-0.50%) (-0.5 \N{GREEK CAPITAL LETTER SIGMA}%)`' + in msg_mock.call_args_list[-1][0][0]) msg_mock.reset_mock() # Update the ticker with a market going up @@ -447,10 +448,12 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0] - assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`' + in msg_mock.call_args_list[-1][0][0]) assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] - assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`' + in msg_mock.call_args_list[-1][0][0]) assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] From 2f07d21629b8aade68a5916303652133bf57d3cd Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 3 Jun 2020 20:20:39 +0200 Subject: [PATCH 090/191] Update documentation with FTX Stoploss on exchange --- docs/configuration.md | 12 ++++++++---- docs/exchanges.md | 5 +++++ docs/stoploss.md | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index da0f015e8..467190463 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -272,7 +272,7 @@ the static list of pairs) if we should buy. ### Understand order_types -The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds. +The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`, `emergencysell`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoplosses using using market orders. It also allows to set the @@ -288,8 +288,12 @@ If this is configured, the following 4 values (`buy`, `sell`, `stoploss` and `emergencysell` is an optional value, which defaults to `market` and is used when creating stoploss on exchange orders fails. The below is the default which is used if this is not configured in either strategy or configuration file. -Since `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the stoploss_price and the Limit price. -`stoploss` defines the stop-price - and limit should be slightly below this. This defaults to 0.99 / 1% (configurable via `stoploss_on_exchange_limit_ratio`). +Not all Exchanges support `stoploss_on_exchange`. If an exchange supports both limit and market stoploss orders, then the value of `stoploss` will be used to determine the stoploss type. + +If `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the stoploss_price and the Limit price. +`stoploss` defines the stop-price - and limit should be slightly below this. + +This defaults to 0.99 / 1% (configurable via `stoploss_on_exchange_limit_ratio`). Calculation example: we bought the asset at 100$. Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the stoploss will happen between 95$ and 94.05$. @@ -331,7 +335,7 @@ Configuration: 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 order. + 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 "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. diff --git a/docs/exchanges.md b/docs/exchanges.md index 81f017023..cd210eb69 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -64,6 +64,11 @@ print(res) ## FTX +!!! 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. + + ### Using subaccounts To use subaccounts with FTX, you need to edit the configuration and add the following: diff --git a/docs/stoploss.md b/docs/stoploss.md index 0e43817ec..cd90a71b4 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -27,7 +27,7 @@ So this parameter will tell the bot how often it should update the stoploss orde This same logic will reapply a stoploss order on the exchange should you cancel it accidentally. !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit) and Kraken (stop-loss-market) as of now. + 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. ## Static Stop Loss From 5c5dc6fffe67decf8df285cfc28733c0bd07567a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 4 Jun 2020 06:56:30 +0200 Subject: [PATCH 091/191] Update test to reflect real trade after one cycle --- tests/rpc/test_rpc_apiserver.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cacf49f62..8743ec7ba 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -495,6 +495,10 @@ def test_api_status(botclient, mocker, ticker, fee, markets): assert rc.json == [] ftbot.enter_positions() + trades = Trade.get_open_trades() + trades[0].open_order_id = None + ftbot.exit_positions(trades) + rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) assert len(rc.json) == 1 @@ -509,25 +513,26 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'close_rate': None, 'current_profit': -0.00408133, 'current_profit_pct': -0.41, + 'current_profit_abs': -4.09e-06, 'current_rate': 1.099e-05, 'open_date': ANY, 'open_date_hum': 'just now', 'open_timestamp': ANY, - 'open_order': '(limit buy rem=0.00000000)', + 'open_order': None, 'open_rate': 1.098e-05, 'pair': 'ETH/BTC', 'stake_amount': 0.001, - 'stop_loss': 0.0, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, + 'stop_loss': 9.882e-06, + 'stop_loss_abs': 9.882e-06, + 'stop_loss_pct': -10.0, + 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, + 'stoploss_last_update': ANY, + 'stoploss_last_update_timestamp': ANY, + 'initial_stop_loss': 9.882e-06, + 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_pct': -10.0, + 'initial_stop_loss_ratio': -0.1, 'trade_id': 1, 'close_rate_requested': None, 'current_rate': 1.099e-05, @@ -539,9 +544,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'fee_open_currency': None, 'open_date': ANY, 'is_open': True, - 'max_rate': 0.0, - 'min_rate': None, - 'open_order_id': ANY, + 'max_rate': 1.099e-05, + 'min_rate': 1.098e-05, + 'open_order_id': None, 'open_rate_requested': 1.098e-05, 'open_trade_price': 0.0010025, 'sell_reason': None, From 412b50dac5471e5392395c771298fbb118dcc8bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 4 Jun 2020 06:56:59 +0200 Subject: [PATCH 092/191] Add current stoploss calculations --- freqtrade/rpc/rpc.py | 13 +++++++++++++ tests/rpc/test_rpc_apiserver.py | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 91b35e9ad..dd6b084a2 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -130,6 +130,14 @@ class RPC: except DependencyException: current_rate = NAN current_profit = trade.calc_profit_ratio(current_rate) + current_profit_abs = trade.calc_profit(current_rate) + # Calculate guaranteed profit (in case of trailing stop) + stoploss_entrypoint_dist = trade.open_rate - trade.stop_loss + stoploss_entrypoint_dist_ratio = stoploss_entrypoint_dist / trade.stop_loss + # calculate distance to stoploss + stoploss_current_dist = trade.stop_loss - current_rate + stoploss_current_dist_ratio = stoploss_current_dist / current_rate + fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%' if trade.close_profit is not None else None) trade_dict = trade.to_json() @@ -140,6 +148,11 @@ class RPC: current_rate=current_rate, current_profit=current_profit, current_profit_pct=round(current_profit * 100, 2), + current_profit_abs=current_profit_abs, + stoploss_current_dist=stoploss_current_dist, + stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), + stoploss_entrypoint_dist=stoploss_entrypoint_dist, + stoploss_entrypoint_dist_ratio=round(stoploss_entrypoint_dist_ratio, 8), open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8743ec7ba..906e9f2fe 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -533,6 +533,10 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'initial_stop_loss_abs': 9.882e-06, 'initial_stop_loss_pct': -10.0, 'initial_stop_loss_ratio': -0.1, + 'stoploss_current_dist': -1.1080000000000002e-06, + 'stoploss_current_dist_ratio': -0.10081893, + 'stoploss_entrypoint_dist': 1.0980000000000003e-06, + 'stoploss_entrypoint_dist_ratio': 0.11111111, 'trade_id': 1, 'close_rate_requested': None, 'current_rate': 1.099e-05, From 7bd55aa2f1c6c60c6b50ffd7d8965146c633f4e7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 4 Jun 2020 07:04:32 +0200 Subject: [PATCH 093/191] Use correct calcuation for "locked in profit" --- freqtrade/rpc/rpc.py | 8 ++++---- tests/rpc/test_rpc_apiserver.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index dd6b084a2..fc774ded2 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -132,8 +132,8 @@ class RPC: current_profit = trade.calc_profit_ratio(current_rate) current_profit_abs = trade.calc_profit(current_rate) # Calculate guaranteed profit (in case of trailing stop) - stoploss_entrypoint_dist = trade.open_rate - trade.stop_loss - stoploss_entrypoint_dist_ratio = stoploss_entrypoint_dist / trade.stop_loss + stoploss_entry_dist = trade.calc_profit(trade.stop_loss) + stoploss_entry_dist_ratio = trade.calc_profit_ratio(trade.stop_loss) # calculate distance to stoploss stoploss_current_dist = trade.stop_loss - current_rate stoploss_current_dist_ratio = stoploss_current_dist / current_rate @@ -151,8 +151,8 @@ class RPC: current_profit_abs=current_profit_abs, stoploss_current_dist=stoploss_current_dist, stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), - stoploss_entrypoint_dist=stoploss_entrypoint_dist, - stoploss_entrypoint_dist_ratio=round(stoploss_entrypoint_dist_ratio, 8), + stoploss_entry_dist=stoploss_entry_dist, + stoploss_entry_dist_ratio=round(stoploss_entry_dist_ratio, 8), open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 906e9f2fe..3555057a9 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -535,8 +535,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'initial_stop_loss_ratio': -0.1, 'stoploss_current_dist': -1.1080000000000002e-06, 'stoploss_current_dist_ratio': -0.10081893, - 'stoploss_entrypoint_dist': 1.0980000000000003e-06, - 'stoploss_entrypoint_dist_ratio': 0.11111111, + 'stoploss_entry_dist': -0.00010475, + 'stoploss_entry_dist_ratio': -0.10448878, 'trade_id': 1, 'close_rate_requested': None, 'current_rate': 1.099e-05, From 6a88eb603bde0eea17aa18bd318e50345b70baed Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 4 Jun 2020 07:20:50 +0200 Subject: [PATCH 094/191] Update failing test --- tests/rpc/test_rpc.py | 73 +++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 1de73ada9..ef1c1bc16 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -42,8 +42,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: rpc._rpc_trade_status() freqtradebot.enter_positions() + trades = Trade.get_open_trades() + trades[0].open_order_id = None + freqtradebot.exit_positions(trades) + results = rpc._rpc_trade_status() - assert { + assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', @@ -54,11 +58,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'fee_open': ANY, 'fee_open_cost': ANY, 'fee_open_currency': ANY, - 'fee_close': ANY, + 'fee_close': fee.return_value, 'fee_close_cost': ANY, 'fee_close_currency': ANY, 'open_rate_requested': ANY, - 'open_trade_price': ANY, + 'open_trade_price': 0.0010025, 'close_rate_requested': ANY, 'sell_reason': ANY, 'sell_order_status': ANY, @@ -80,28 +84,32 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_profit_abs': None, 'current_profit': -0.00408133, 'current_profit_pct': -0.41, - 'stop_loss': 0.0, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, + 'current_profit_abs': -4.09e-06, + 'stop_loss': 9.882e-06, + 'stop_loss_abs': 9.882e-06, + 'stop_loss_pct': -10.0, + 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'open_order': '(limit buy rem=0.00000000)', + 'stoploss_last_update': ANY, + 'stoploss_last_update_timestamp': ANY, + 'initial_stop_loss': 9.882e-06, + 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_pct': -10.0, + 'initial_stop_loss_ratio': -0.1, + 'stoploss_current_dist': -1.1080000000000002e-06, + 'stoploss_current_dist_ratio': -0.10081893, + 'stoploss_entry_dist': -0.00010475, + 'stoploss_entry_dist_ratio': -0.10448878, + 'open_order': None, 'exchange': 'bittrex', - - } == results[0] + } mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', MagicMock(side_effect=DependencyException("Pair 'ETH/BTC' not available"))) results = rpc._rpc_trade_status() assert isnan(results[0]['current_profit']) assert isnan(results[0]['current_rate']) - assert { + assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', @@ -112,7 +120,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'fee_open': ANY, 'fee_open_cost': ANY, 'fee_open_currency': ANY, - 'fee_close': ANY, + 'fee_close': fee.return_value, 'fee_close_cost': ANY, 'fee_close_currency': ANY, 'open_rate_requested': ANY, @@ -138,20 +146,25 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_profit_abs': None, 'current_profit': ANY, 'current_profit_pct': ANY, - 'stop_loss': 0.0, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, + 'current_profit_abs': ANY, + 'stop_loss': 9.882e-06, + 'stop_loss_abs': 9.882e-06, + 'stop_loss_pct': -10.0, + 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'open_order': '(limit buy rem=0.00000000)', + 'stoploss_last_update': ANY, + 'stoploss_last_update_timestamp': ANY, + 'initial_stop_loss': 9.882e-06, + 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_pct': -10.0, + 'initial_stop_loss_ratio': -0.1, + 'stoploss_current_dist': ANY, + 'stoploss_current_dist_ratio': ANY, + 'stoploss_entry_dist': -0.00010475, + 'stoploss_entry_dist_ratio': -0.10448878, + 'open_order': None, 'exchange': 'bittrex', - } == results[0] + } def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: From b27348490d3d6883701c0f9dafe9ab0885f29034 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 5 Jun 2020 13:59:00 +0200 Subject: [PATCH 095/191] Add rate-limiting note for Kraken datadownload --- docs/exchanges.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index 81f017023..075329a07 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -30,6 +30,15 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f The Kraken API does only provide 720 historic candles, which is sufficient for Freqtrade dry-run and live trade modes, but is a problem for backtesting. To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data. +Due to the heavy rate-limiting applied by Kraken, the follwoing configuration section should be used to download data: + +``` json + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 3100 + }, +``` + ## Bittrex ### Order types From ff289a71776239e2ac0dd35e2fb40cfa44a83e83 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Fri, 5 Jun 2020 19:08:54 +0200 Subject: [PATCH 096/191] Updated tests to work with Telegram emojis --- tests/rpc/test_rpc_telegram.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f81127c4c..2cecc35d1 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1221,8 +1221,9 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None: 'amount': 1333.3333333333335, 'open_date': arrow.utcnow().shift(hours=-1) }) + '🔵'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ - == '*Bittrex:* Buying ETH/BTC\n' \ + == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \ @@ -1243,8 +1244,9 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: 'exchange': 'Bittrex', 'pair': 'ETH/BTC', }) + '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ - == ('*Bittrex:* Cancelling Open Buy Order for ETH/BTC') + == ('\N{WARNING SIGN} *Bittrex:* Cancelling Open Buy Order for ETH/BTC') def test_send_msg_sell_notification(default_conf, mocker) -> None: @@ -1276,8 +1278,9 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'open_date': arrow.utcnow().shift(hours=-1), 'close_date': arrow.utcnow(), }) + '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ - == ('*Binance:* Selling KEY/ETH\n' + == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' @@ -1305,7 +1308,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'close_date': arrow.utcnow(), }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Selling KEY/ETH\n' + == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' @@ -1334,8 +1337,9 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'pair': 'KEY/ETH', 'reason': 'Cancelled on exchange' }) + '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ - == ('*Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: Cancelled on exchange') + == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: Cancelled on exchange') msg_mock.reset_mock() telegram.send_msg({ @@ -1345,7 +1349,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'reason': 'timeout' }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout') + == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout') # Reset singleton function to avoid random breaks telegram._fiat_converter.convert_amount = old_convamount @@ -1379,7 +1383,8 @@ def test_warning_notification(default_conf, mocker) -> None: 'type': RPCMessageType.WARNING_NOTIFICATION, 'status': 'message' }) - assert msg_mock.call_args[0][0] == '*Warning:* `message`' + '⚠'.encode('ascii', 'namereplace') + assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`' def test_custom_notification(default_conf, mocker) -> None: @@ -1437,8 +1442,9 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'amount': 1333.3333333333335, 'open_date': arrow.utcnow().shift(hours=-1) }) + '🔵'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ - == '*Bittrex:* Buying ETH/BTC\n' \ + == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \ @@ -1473,8 +1479,9 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'close_date': arrow.utcnow(), }) + '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ - == '*Binance:* Selling KEY/ETH\n' \ + == '\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00007500`\n' \ '*Current Rate:* `0.00003201`\n' \ From 080efd1102835367f135827c9a7dc4be1623be84 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Fri, 5 Jun 2020 19:09:49 +0200 Subject: [PATCH 097/191] Added unicoded emoji's to Telegram messages --- freqtrade/rpc/telegram.py | 61 +++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8fa973d73..e496d3fa6 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -19,7 +19,6 @@ logger = logging.getLogger(__name__) logger.debug('Included module rpc.telegram ...') - MAX_TELEGRAM_MESSAGE_LENGTH = 4096 @@ -29,6 +28,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: :param command_handler: Telegram CommandHandler :return: decorated function """ + def wrapper(self, *args, **kwargs): """ Decorator logic """ update = kwargs.get('update') or args[0] @@ -126,6 +126,12 @@ class Telegram(RPC): def send_msg(self, msg: Dict[str, Any]) -> None: """ Send a message to telegram channel """ + '🔵'.encode('ascii', 'namereplace') + '🚀'.encode('ascii', 'namereplace') + '✳'.encode('ascii', 'namereplace') + '❌'.encode('ascii', 'namereplace') + '⚠'.encode('ascii', 'namereplace') + if msg['type'] == RPCMessageType.BUY_NOTIFICATION: if self._fiat_converter: msg['stake_amount_fiat'] = self._fiat_converter.convert_amount( @@ -133,7 +139,7 @@ class Telegram(RPC): else: msg['stake_amount_fiat'] = 0 - message = ("*{exchange}:* Buying {pair}\n" + message = ("\N{LARGE BLUE CIRCLE} *{exchange}:* Buying {pair}\n" "*Amount:* `{amount:.8f}`\n" "*Open Rate:* `{limit:.8f}`\n" "*Current Rate:* `{current_rate:.8f}`\n" @@ -144,7 +150,7 @@ class Telegram(RPC): message += ")`" elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION: - message = "*{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg) + message = "\N{WARNING SIGN} *{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg) elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) @@ -153,33 +159,44 @@ class Telegram(RPC): microsecond=0) - msg['open_date'].replace(microsecond=0) msg['duration_min'] = msg['duration'].total_seconds() / 60 - message = ("*{exchange}:* Selling {pair}\n" - "*Amount:* `{amount:.8f}`\n" - "*Open Rate:* `{open_rate:.8f}`\n" - "*Current Rate:* `{current_rate:.8f}`\n" - "*Close Rate:* `{limit:.8f}`\n" - "*Sell Reason:* `{sell_reason}`\n" - "*Duration:* `{duration} ({duration_min:.1f} min)`\n" - "*Profit:* `{profit_percent:.2f}%`").format(**msg) + if float(msg['profit_percent']) >= 5.0: + message = ("\N{ROCKET} *{exchange}:* Selling {pair}\n").format(**msg) + + elif float(msg['profit_percent']) >= 0.0: + message = "\N{EIGHT SPOKED ASTERISK} *{exchange}:* Selling {pair}\n" + + elif msg['sell_reason'] == "stop_loss": + message = ("\N{WARNING SIGN} *{exchange}:* Selling {pair}\n").format(**msg) + + else: + message = ("\N{CROSS MARK} *{exchange}:* Selling {pair}\n").format(**msg) + + message += ("*Amount:* `{amount:.8f}`\n" + "*Open Rate:* `{open_rate:.8f}`\n" + "*Current Rate:* `{current_rate:.8f}`\n" + "*Close Rate:* `{limit:.8f}`\n" + "*Sell Reason:* `{sell_reason}`\n" + "*Duration:* `{duration} ({duration_min:.1f} min)`\n" + "*Profit:* `{profit_percent:.2f}%`").format(**msg) # Check if all sell properties are available. # This might not be the case if the message origin is triggered by /forcesell if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency']) - and self._fiat_converter): + and self._fiat_converter): msg['profit_fiat'] = self._fiat_converter.convert_amount( msg['profit_amount'], msg['stake_currency'], msg['fiat_currency']) message += (' `({gain}: {profit_amount:.8f} {stake_currency}' ' / {profit_fiat:.3f} {fiat_currency})`').format(**msg) elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION: - message = ("*{exchange}:* Cancelling Open Sell Order " + message = ("\N{WARNING SIGN} *{exchange}:* Cancelling Open Sell Order " "for {pair}. Reason: {reason}").format(**msg) elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION: message = '*Status:* `{status}`'.format(**msg) elif msg['type'] == RPCMessageType.WARNING_NOTIFICATION: - message = '*Warning:* `{status}`'.format(**msg) + message = '\N{WARNING SIGN} *Warning:* `{status}`'.format(**msg) elif msg['type'] == RPCMessageType.CUSTOM_NOTIFICATION: message = '{status}'.format(**msg) @@ -222,8 +239,8 @@ class Telegram(RPC): # Adding initial stoploss only if it is different from stoploss "*Initial Stoploss:* `{initial_stop_loss:.8f}` " + ("`({initial_stop_loss_pct:.2f}%)`") if ( - r['stop_loss'] != r['initial_stop_loss'] - and r['initial_stop_loss_pct'] is not None) else "", + r['stop_loss'] != r['initial_stop_loss'] + and r['initial_stop_loss_pct'] is not None) else "", # Adding stoploss and stoploss percentage only if it is not None "*Stoploss:* `{stop_loss:.8f}` " + @@ -363,14 +380,14 @@ class Telegram(RPC): "This mode is still experimental!\n" "Starting capital: " f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n" - ) + ) for currency in result['currencies']: if currency['est_stake'] > 0.0001: curr_output = "*{currency}:*\n" \ - "\t`Available: {free: .8f}`\n" \ - "\t`Balance: {balance: .8f}`\n" \ - "\t`Pending: {used: .8f}`\n" \ - "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency) + "\t`Available: {free: .8f}`\n" \ + "\t`Balance: {balance: .8f}`\n" \ + "\t`Pending: {used: .8f}`\n" \ + "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency) else: curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency) @@ -587,7 +604,7 @@ class Telegram(RPC): "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ "regardless of profit`\n" \ - f"{forcebuy_text if self._config.get('forcebuy_enable', False) else '' }" \ + f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" \ "*/performance:* `Show performance of each finished trade grouped by pair`\n" \ "*/daily :* `Shows profit or loss per day, over the last n days`\n" \ "*/count:* `Show number of trades running compared to allowed number of trades`" \ From 4c6a7a354dfae8d0a60f8243aefa91f400a01604 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Fri, 5 Jun 2020 20:04:11 +0200 Subject: [PATCH 098/191] Removed '.encode' lines, unessecary --- freqtrade/rpc/telegram.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e496d3fa6..6b1450472 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -126,12 +126,6 @@ class Telegram(RPC): def send_msg(self, msg: Dict[str, Any]) -> None: """ Send a message to telegram channel """ - '🔵'.encode('ascii', 'namereplace') - '🚀'.encode('ascii', 'namereplace') - '✳'.encode('ascii', 'namereplace') - '❌'.encode('ascii', 'namereplace') - '⚠'.encode('ascii', 'namereplace') - if msg['type'] == RPCMessageType.BUY_NOTIFICATION: if self._fiat_converter: msg['stake_amount_fiat'] = self._fiat_converter.convert_amount( From 08b9abed3aeb722357ced69fe67489acc05e3eaa Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Fri, 5 Jun 2020 20:05:55 +0200 Subject: [PATCH 099/191] Removed '.encode', unecessary --- tests/rpc/test_rpc_telegram.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 2cecc35d1..129bdf9d1 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1221,7 +1221,6 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None: 'amount': 1333.3333333333335, 'open_date': arrow.utcnow().shift(hours=-1) }) - '🔵'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \ '*Amount:* `1333.33333333`\n' \ @@ -1244,7 +1243,6 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: 'exchange': 'Bittrex', 'pair': 'ETH/BTC', }) - '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ == ('\N{WARNING SIGN} *Bittrex:* Cancelling Open Buy Order for ETH/BTC') @@ -1278,7 +1276,6 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'open_date': arrow.utcnow().shift(hours=-1), 'close_date': arrow.utcnow(), }) - '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' '*Amount:* `1333.33333333`\n' @@ -1337,7 +1334,6 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'pair': 'KEY/ETH', 'reason': 'Cancelled on exchange' }) - '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: Cancelled on exchange') @@ -1383,7 +1379,6 @@ def test_warning_notification(default_conf, mocker) -> None: 'type': RPCMessageType.WARNING_NOTIFICATION, 'status': 'message' }) - '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`' @@ -1442,7 +1437,6 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'amount': 1333.3333333333335, 'open_date': arrow.utcnow().shift(hours=-1) }) - '🔵'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \ '*Amount:* `1333.33333333`\n' \ @@ -1479,7 +1473,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'close_date': arrow.utcnow(), }) - '⚠'.encode('ascii', 'namereplace') assert msg_mock.call_args[0][0] \ == '\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' \ '*Amount:* `1333.33333333`\n' \ From 6694ac50779619eec4f761a3f28175833d4080c9 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Fri, 5 Jun 2020 20:10:52 +0200 Subject: [PATCH 100/191] Splitted a line that was too long, resulting in flake8 error --- tests/rpc/test_rpc_telegram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 129bdf9d1..15fe0eaf9 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1335,7 +1335,8 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'reason': 'Cancelled on exchange' }) assert msg_mock.call_args[0][0] \ - == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: Cancelled on exchange') + == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. ' + 'Reason: Cancelled on exchange') msg_mock.reset_mock() telegram.send_msg({ From f34bcc5fd3b629acfaeb55cd51854b1e719f44f8 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Fri, 5 Jun 2020 20:15:22 +0200 Subject: [PATCH 101/191] Splitted a line that was too long, resulting in error for flake8 --- freqtrade/rpc/telegram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 6b1450472..7dbd9cd83 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -144,7 +144,8 @@ class Telegram(RPC): message += ")`" elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION: - message = "\N{WARNING SIGN} *{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg) + message = "\N{WARNING SIGN} *{exchange}:* " \ + "Cancelling Open Buy Order for {pair}".format(**msg) elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) From 5f9994c9ed1d7749412bf8201932f44e4e4b4c69 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 5 Jun 2020 20:24:21 +0200 Subject: [PATCH 102/191] Reduce verbosity of sell-rate fetching --- freqtrade/freqtradebot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a74b0a5a1..5104e4f95 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -702,11 +702,10 @@ class FreqtradeBot: self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval)) if config_ask_strategy.get('use_order_book', False): - # logger.debug('Order book %s',orderBook) order_book_min = config_ask_strategy.get('order_book_min', 1) order_book_max = config_ask_strategy.get('order_book_max', 1) - logger.info(f'Using order book between {order_book_min} and {order_book_max} ' - f'for selling {trade.pair}...') + logger.debug(f'Using order book between {order_book_min} and {order_book_max} ' + f'for selling {trade.pair}...') order_book = self._order_book_gen(trade.pair, f"{config_ask_strategy['price_side']}s", order_book_min=order_book_min, From 8c32d691c7ee4cee9ff860748b3ebee2c1187388 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 5 Jun 2020 20:31:40 +0200 Subject: [PATCH 103/191] Add information about bid and ask strategy to /showconfig --- freqtrade/rpc/rpc.py | 2 ++ freqtrade/rpc/telegram.py | 4 +++- tests/rpc/test_rpc_apiserver.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 91b35e9ad..a5005f35e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -105,6 +105,8 @@ class RPC: 'exchange': config['exchange']['name'], 'strategy': config['strategy'], 'forcebuy_enabled': config.get('forcebuy_enable', False), + 'ask_strategy': config.get('ask_strategy', {}), + 'bid_strategy': config.get('bid_strategy', {}), 'state': str(self._freqtrade.state) } return val diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8fa973d73..09a99ad85 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -636,13 +636,15 @@ class Telegram(RPC): else: sl_info = f"*Stoploss:* `{val['stoploss']}`\n" - + import json self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Exchange:* `{val['exchange']}`\n" f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n" f"*Max open Trades:* `{val['max_open_trades']}`\n" f"*Minimum ROI:* `{val['minimal_roi']}`\n" + f"*Ask strategy:* ```\n{val['ask_strategy']}```\n" + f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n" f"{sl_info}" f"*Ticker Interval:* `{val['ticker_interval']}`\n" f"*Strategy:* `{val['strategy']}`\n" diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cacf49f62..6a850ecc5 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -325,6 +325,8 @@ def test_api_show_config(botclient, mocker): assert rc.json['ticker_interval'] == '5m' assert rc.json['state'] == 'running' assert not rc.json['trailing_stop'] + assert 'bid_strategy' in rc.json + assert 'ask_strategy' in rc.json def test_api_daily(botclient, mocker, ticker, fee, markets): From 1a5cd85900f232ee24022dd82b9ebb2732b88f4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 6 Jun 2020 13:15:01 +0200 Subject: [PATCH 104/191] Fix typo Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 075329a07..aef356ea9 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -30,7 +30,7 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f The Kraken API does only provide 720 historic candles, which is sufficient for Freqtrade dry-run and live trade modes, but is a problem for backtesting. To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data. -Due to the heavy rate-limiting applied by Kraken, the follwoing configuration section should be used to download data: +Due to the heavy rate-limiting applied by Kraken, the following configuration section should be used to download data: ``` json "ccxt_async_config": { From b2316cdd00fd1f946a8bd07cb55e39e0a8720859 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 6 Jun 2020 13:32:06 +0200 Subject: [PATCH 105/191] Extract sell_smoij logic into it's own function --- freqtrade/rpc/telegram.py | 41 +++++++++++++++++++--------------- tests/rpc/test_rpc_telegram.py | 23 +++++++++++++++++++ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7dbd9cd83..0dc57e5b9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -154,25 +154,16 @@ class Telegram(RPC): microsecond=0) - msg['open_date'].replace(microsecond=0) msg['duration_min'] = msg['duration'].total_seconds() / 60 - if float(msg['profit_percent']) >= 5.0: - message = ("\N{ROCKET} *{exchange}:* Selling {pair}\n").format(**msg) + msg['emoij'] = self._get_sell_emoij(msg) - elif float(msg['profit_percent']) >= 0.0: - message = "\N{EIGHT SPOKED ASTERISK} *{exchange}:* Selling {pair}\n" - - elif msg['sell_reason'] == "stop_loss": - message = ("\N{WARNING SIGN} *{exchange}:* Selling {pair}\n").format(**msg) - - else: - message = ("\N{CROSS MARK} *{exchange}:* Selling {pair}\n").format(**msg) - - message += ("*Amount:* `{amount:.8f}`\n" - "*Open Rate:* `{open_rate:.8f}`\n" - "*Current Rate:* `{current_rate:.8f}`\n" - "*Close Rate:* `{limit:.8f}`\n" - "*Sell Reason:* `{sell_reason}`\n" - "*Duration:* `{duration} ({duration_min:.1f} min)`\n" - "*Profit:* `{profit_percent:.2f}%`").format(**msg) + message = ("{emoij} *{exchange}:* Selling {pair}\n" + "*Amount:* `{amount:.8f}`\n" + "*Open Rate:* `{open_rate:.8f}`\n" + "*Current Rate:* `{current_rate:.8f}`\n" + "*Close Rate:* `{limit:.8f}`\n" + "*Sell Reason:* `{sell_reason}`\n" + "*Duration:* `{duration} ({duration_min:.1f} min)`\n" + "*Profit:* `{profit_percent:.2f}%`").format(**msg) # Check if all sell properties are available. # This might not be the case if the message origin is triggered by /forcesell @@ -201,6 +192,20 @@ class Telegram(RPC): self._send_msg(message) + def _get_sell_emoij(self, msg): + """ + Get emoji for sell-side + """ + + if float(msg['profit_percent']) >= 5.0: + return "\N{ROCKET}" + elif float(msg['profit_percent']) >= 0.0: + return "\N{EIGHT SPOKED ASTERISK}" + elif msg['sell_reason'] == "stop_loss": + return"\N{WARNING SIGN}" + else: + return "\N{CROSS MARK}" + @authorized_only def _status(self, update: Update, context: CallbackContext) -> None: """ diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 15fe0eaf9..7fea19fa9 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1485,6 +1485,29 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: '*Profit:* `-57.41%`' +@pytest.mark.parametrize('msg,expected', [ + ({'profit_percent': 20.1, 'sell_reason': 'roi'}, "\N{ROCKET}"), + ({'profit_percent': 5.1, 'sell_reason': 'roi'}, "\N{ROCKET}"), + ({'profit_percent': 2.56, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"), + ({'profit_percent': 1.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"), + ({'profit_percent': 0.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"), + ({'profit_percent': -5.0, 'sell_reason': 'stop_loss'}, "\N{WARNING SIGN}"), + ({'profit_percent': -2.0, 'sell_reason': 'sell_signal'}, "\N{CROSS MARK}"), +]) +def test__sell_emoji(default_conf, mocker, msg, expected): + del default_conf['fiat_display_currency'] + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) + + assert telegram._get_sell_emoij(msg) == expected + + def test__send_msg(default_conf, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) bot = MagicMock() From 172ca761f2c472bc405e2933f0c4b0b81612e668 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Sat, 6 Jun 2020 15:38:42 +0200 Subject: [PATCH 106/191] Fixed typo 'emoij' --- freqtrade/rpc/telegram.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0dc57e5b9..09be7793e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -154,9 +154,9 @@ class Telegram(RPC): microsecond=0) - msg['open_date'].replace(microsecond=0) msg['duration_min'] = msg['duration'].total_seconds() / 60 - msg['emoij'] = self._get_sell_emoij(msg) + msg['emoji'] = self._get_sell_emoji(msg) - message = ("{emoij} *{exchange}:* Selling {pair}\n" + message = ("{emoji} *{exchange}:* Selling {pair}\n" "*Amount:* `{amount:.8f}`\n" "*Open Rate:* `{open_rate:.8f}`\n" "*Current Rate:* `{current_rate:.8f}`\n" @@ -192,7 +192,7 @@ class Telegram(RPC): self._send_msg(message) - def _get_sell_emoij(self, msg): + def _get_sell_emoji(self, msg): """ Get emoji for sell-side """ From d20762aa01c9f3e2ab61dca97d260737e93c6c4c Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Sat, 6 Jun 2020 15:46:19 +0200 Subject: [PATCH 107/191] Fixed typo 'emoij' in test file too --- tests/rpc/test_rpc_telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 7fea19fa9..423f27441 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1505,7 +1505,7 @@ def test__sell_emoji(default_conf, mocker, msg, expected): freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) - assert telegram._get_sell_emoij(msg) == expected + assert telegram._get_sell_emoji(msg) == expected def test__send_msg(default_conf, mocker) -> None: From 3bd38171f80c7b420d259f573861a7fed8a6d031 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 6 Jun 2020 17:19:44 +0200 Subject: [PATCH 108/191] DOn't use json.dumps - it's not necessary --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 09a99ad85..2ea697ed4 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -636,7 +636,7 @@ class Telegram(RPC): else: sl_info = f"*Stoploss:* `{val['stoploss']}`\n" - import json + self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Exchange:* `{val['exchange']}`\n" @@ -644,7 +644,7 @@ class Telegram(RPC): f"*Max open Trades:* `{val['max_open_trades']}`\n" f"*Minimum ROI:* `{val['minimal_roi']}`\n" f"*Ask strategy:* ```\n{val['ask_strategy']}```\n" - f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n" + f"*Bid strategy:* ```\n{val['bid_strategy']}```\n" f"{sl_info}" f"*Ticker Interval:* `{val['ticker_interval']}`\n" f"*Strategy:* `{val['strategy']}`\n" From 8d8cf5a2fde3ea20f66991b1eb978e84d514e9d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 6 Jun 2020 17:28:00 +0200 Subject: [PATCH 109/191] Improve code formatting of telegram --- freqtrade/rpc/telegram.py | 72 +++++++++++++++++----------------- tests/rpc/test_rpc_telegram.py | 41 +++++++++---------- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 965318f64..894955eb5 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -144,8 +144,8 @@ class Telegram(RPC): message += ")`" elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION: - message = "\N{WARNING SIGN} *{exchange}:* " \ - "Cancelling Open Buy Order for {pair}".format(**msg) + message = ("\N{WARNING SIGN} *{exchange}:* " + "Cancelling Open Buy Order for {pair}".format(**msg)) elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) @@ -388,11 +388,11 @@ class Telegram(RPC): ) for currency in result['currencies']: if currency['est_stake'] > 0.0001: - curr_output = "*{currency}:*\n" \ - "\t`Available: {free: .8f}`\n" \ - "\t`Balance: {balance: .8f}`\n" \ - "\t`Pending: {used: .8f}`\n" \ - "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency) + curr_output = ("*{currency}:*\n" + "\t`Available: {free: .8f}`\n" + "\t`Balance: {balance: .8f}`\n" + "\t`Pending: {used: .8f}`\n" + "\t`Est. {stake}: {est_stake: .8f}`\n").format(**currency) else: curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency) @@ -403,9 +403,9 @@ class Telegram(RPC): else: output += curr_output - output += "\n*Estimated Value*:\n" \ - "\t`{stake}: {total: .8f}`\n" \ - "\t`{symbol}: {value: .2f}`\n".format(**result) + output += ("\n*Estimated Value*:\n" + "\t`{stake}: {total: .8f}`\n" + "\t`{symbol}: {value: .2f}`\n").format(**result) self._send_msg(output) except RPCException as e: self._send_msg(str(e)) @@ -598,32 +598,32 @@ class Telegram(RPC): :param update: message update :return: None """ - forcebuy_text = "*/forcebuy []:* `Instantly buys the given pair. " \ - "Optionally takes a rate at which to buy.` \n" - message = "*/start:* `Starts the trader`\n" \ - "*/stop:* `Stops the trader`\n" \ - "*/status [table]:* `Lists all open trades`\n" \ - " *table :* `will display trades in a table`\n" \ - " `pending buy orders are marked with an asterisk (*)`\n" \ - " `pending sell orders are marked with a double asterisk (**)`\n" \ - "*/profit:* `Lists cumulative profit from all finished trades`\n" \ - "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ - "regardless of profit`\n" \ - f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" \ - "*/performance:* `Show performance of each finished trade grouped by pair`\n" \ - "*/daily :* `Shows profit or loss per day, over the last n days`\n" \ - "*/count:* `Show number of trades running compared to allowed number of trades`" \ - "\n" \ - "*/balance:* `Show account balance per currency`\n" \ - "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \ - "*/reload_conf:* `Reload configuration file` \n" \ - "*/show_config:* `Show running configuration` \n" \ - "*/whitelist:* `Show current whitelist` \n" \ - "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " \ - "to the blacklist.` \n" \ - "*/edge:* `Shows validated pairs by Edge if it is enabled` \n" \ - "*/help:* `This help message`\n" \ - "*/version:* `Show version`" + forcebuy_text = ("*/forcebuy []:* `Instantly buys the given pair. " + "Optionally takes a rate at which to buy.` \n") + message = ("*/start:* `Starts the trader`\n" + "*/stop:* `Stops the trader`\n" + "*/status [table]:* `Lists all open trades`\n" + " *table :* `will display trades in a table`\n" + " `pending buy orders are marked with an asterisk (*)`\n" + " `pending sell orders are marked with a double asterisk (**)`\n" + "*/profit:* `Lists cumulative profit from all finished trades`\n" + "*/forcesell |all:* `Instantly sells the given trade or all trades, " + "regardless of profit`\n" + f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" + "*/performance:* `Show performance of each finished trade grouped by pair`\n" + "*/daily :* `Shows profit or loss per day, over the last n days`\n" + "*/count:* `Show number of trades running compared to allowed number of trades`" + "\n" + "*/balance:* `Show account balance per currency`\n" + "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" + "*/reload_conf:* `Reload configuration file` \n" + "*/show_config:* `Show running configuration` \n" + "*/whitelist:* `Show current whitelist` \n" + "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " + "to the blacklist.` \n" + "*/edge:* `Shows validated pairs by Edge if it is enabled` \n" + "*/help:* `This help message`\n" + "*/version:* `Show version`") self._send_msg(message) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 21b9df81c..b18106ee5 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -71,10 +71,10 @@ def test_init(default_conf, mocker, caplog) -> None: assert start_polling.dispatcher.add_handler.call_count > 0 assert start_polling.start_polling.call_count == 1 - message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \ - "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \ - "['performance'], ['daily'], ['count'], ['reload_conf'], ['show_config'], " \ - "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]" + message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " + "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " + "['performance'], ['daily'], ['count'], ['reload_conf'], ['show_config'], " + "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]") assert log_has(message_str, caplog) @@ -1016,9 +1016,8 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: msg_mock.reset_mock() telegram._count(update=update, context=MagicMock()) - msg = '
  current    max    total stake\n---------  -----  -------------\n' \
-          '        1      {}          {}
'\ - .format( + msg = ('
  current    max    total stake\n---------  -----  -------------\n'
+           '        1      {}          {}
').format( default_conf['max_open_trades'], default_conf['stake_amount'] ) @@ -1441,12 +1440,11 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'amount': 1333.3333333333335, 'open_date': arrow.utcnow().shift(hours=-1) }) - assert msg_mock.call_args[0][0] \ - == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \ - '*Amount:* `1333.33333333`\n' \ - '*Open Rate:* `0.00001099`\n' \ - '*Current Rate:* `0.00001099`\n' \ - '*Total:* `(0.001000 BTC)`' + assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' + '*Amount:* `1333.33333333`\n' + '*Open Rate:* `0.00001099`\n' + '*Current Rate:* `0.00001099`\n' + '*Total:* `(0.001000 BTC)`') def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: @@ -1477,15 +1475,14 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'close_date': arrow.utcnow(), }) - assert msg_mock.call_args[0][0] \ - == '\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' \ - '*Amount:* `1333.33333333`\n' \ - '*Open Rate:* `0.00007500`\n' \ - '*Current Rate:* `0.00003201`\n' \ - '*Close Rate:* `0.00003201`\n' \ - '*Sell Reason:* `stop_loss`\n' \ - '*Duration:* `2:35:03 (155.1 min)`\n' \ - '*Profit:* `-57.41%`' + assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' + '*Amount:* `1333.33333333`\n' + '*Open Rate:* `0.00007500`\n' + '*Current Rate:* `0.00003201`\n' + '*Close Rate:* `0.00003201`\n' + '*Sell Reason:* `stop_loss`\n' + '*Duration:* `2:35:03 (155.1 min)`\n' + '*Profit:* `-57.41%`') @pytest.mark.parametrize('msg,expected', [ From db4576c50b97e89c524b2de69998e1431d963204 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 10:09:39 +0200 Subject: [PATCH 110/191] Use json for *strategy dump --- freqtrade/rpc/telegram.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 894955eb5..eb53fc68f 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -3,6 +3,7 @@ """ This module manage Telegram communication """ +import json import logging from typing import Any, Callable, Dict @@ -665,8 +666,8 @@ class Telegram(RPC): f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n" f"*Max open Trades:* `{val['max_open_trades']}`\n" f"*Minimum ROI:* `{val['minimal_roi']}`\n" - f"*Ask strategy:* ```\n{val['ask_strategy']}```\n" - f"*Bid strategy:* ```\n{val['bid_strategy']}```\n" + f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n" + f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n" f"{sl_info}" f"*Ticker Interval:* `{val['ticker_interval']}`\n" f"*Strategy:* `{val['strategy']}`\n" From a6f67247520a9fb52271852cefc80dd05716977d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 11:29:14 +0200 Subject: [PATCH 111/191] Reorder functions in optimize_report --- freqtrade/optimize/optimize_reports.py | 136 +++++++++++++------------ 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index c148f0f44..d70a80bb3 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -113,25 +113,6 @@ def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, max_open_t return tabular_data -def generate_text_table(pair_results: List[Dict[str, Any]], stake_currency: str) -> str: - """ - Generates and returns a text table for the given backtest data and the results dataframe - :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row - :param stake_currency: stake-currency - used to correctly name headers - :return: pretty printed table with tabulate as string - """ - - headers = _get_line_header('Pair', stake_currency) - floatfmt = _get_line_floatfmt() - output = [[ - t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], - t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'] - ] for t in pair_results] - # Ignore type as floatfmt does allow tuples but mypy does not know that - return tabulate(output, headers=headers, - floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore - - def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]: """ Generate small table outlining Backtest results @@ -166,33 +147,6 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List return tabular_data -def generate_text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], - stake_currency: str) -> str: - """ - Generate small table outlining Backtest results - :param sell_reason_stats: Sell reason metrics - :param stake_currency: Stakecurrency used - :return: pretty printed table with tabulate as string - """ - headers = [ - 'Sell Reason', - 'Sells', - 'Wins', - 'Draws', - 'Losses', - 'Avg Profit %', - 'Cum Profit %', - f'Tot Profit {stake_currency}', - 'Tot Profit %', - ] - - output = [[ - t['sell_reason'], t['trades'], t['wins'], t['draws'], t['losses'], - t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], t['profit_pct_total'], - ] for t in sell_reason_stats] - return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") - - def generate_strategy_metrics(stake_currency: str, max_open_trades: int, all_results: Dict) -> List[Dict]: """ @@ -209,26 +163,6 @@ def generate_strategy_metrics(stake_currency: str, max_open_trades: int, return tabular_data -def generate_text_table_strategy(strategy_results, stake_currency: str) -> str: - """ - Generate summary table per strategy - :param stake_currency: stake-currency - used to correctly name headers - :param max_open_trades: Maximum allowed open trades used for backtest - :param all_results: Dict of containing results for all strategies - :return: pretty printed table with tabulate as string - """ - floatfmt = _get_line_floatfmt() - headers = _get_line_header('Strategy', stake_currency) - - output = [[ - t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], - t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'] - ] for t in strategy_results] - # Ignore type as floatfmt does allow tuples but mypy does not know that - return tabulate(output, headers=headers, - floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore - - def generate_edge_table(results: dict) -> str: floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', 'd', 'd') @@ -288,6 +222,76 @@ def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], return result +### +# Start output section +### + +def generate_text_table(pair_results: List[Dict[str, Any]], stake_currency: str) -> str: + """ + Generates and returns a text table for the given backtest data and the results dataframe + :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row + :param stake_currency: stake-currency - used to correctly name headers + :return: pretty printed table with tabulate as string + """ + + headers = _get_line_header('Pair', stake_currency) + floatfmt = _get_line_floatfmt() + output = [[ + t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], + t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'] + ] for t in pair_results] + # Ignore type as floatfmt does allow tuples but mypy does not know that + return tabulate(output, headers=headers, + floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") + + +def generate_text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], + stake_currency: str) -> str: + """ + Generate small table outlining Backtest results + :param sell_reason_stats: Sell reason metrics + :param stake_currency: Stakecurrency used + :return: pretty printed table with tabulate as string + """ + headers = [ + 'Sell Reason', + 'Sells', + 'Wins', + 'Draws', + 'Losses', + 'Avg Profit %', + 'Cum Profit %', + f'Tot Profit {stake_currency}', + 'Tot Profit %', + ] + + output = [[ + t['sell_reason'], t['trades'], t['wins'], t['draws'], t['losses'], + t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], t['profit_pct_total'], + ] for t in sell_reason_stats] + return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") + + +def generate_text_table_strategy(strategy_results, stake_currency: str) -> str: + """ + Generate summary table per strategy + :param stake_currency: stake-currency - used to correctly name headers + :param max_open_trades: Maximum allowed open trades used for backtest + :param all_results: Dict of containing results for all strategies + :return: pretty printed table with tabulate as string + """ + floatfmt = _get_line_floatfmt() + headers = _get_line_header('Strategy', stake_currency) + + output = [[ + t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], + t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'] + ] for t in strategy_results] + # Ignore type as floatfmt does allow tuples but mypy does not know that + return tabulate(output, headers=headers, + floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") + + def show_backtest_results(config: Dict, backtest_stats: Dict): stake_currency = config['stake_currency'] From 499c6772d170f30fb16a5410fd32f3c63c3dcfbd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 11:31:33 +0200 Subject: [PATCH 112/191] Rename tabulate methods they don't "generate" anything --- freqtrade/optimize/optimize_reports.py | 12 +++++------- tests/optimize/test_backtesting.py | 2 +- tests/optimize/test_optimize_reports.py | 14 +++++++------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index d70a80bb3..789ad7d46 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -245,8 +245,7 @@ def generate_text_table(pair_results: List[Dict[str, Any]], stake_currency: str) floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") -def generate_text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], - stake_currency: str) -> str: +def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str: """ Generate small table outlining Backtest results :param sell_reason_stats: Sell reason metrics @@ -272,7 +271,7 @@ def generate_text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") -def generate_text_table_strategy(strategy_results, stake_currency: str) -> str: +def text_table_strategy(strategy_results, stake_currency: str) -> str: """ Generate summary table per strategy :param stake_currency: stake-currency - used to correctly name headers @@ -304,9 +303,8 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) print(table) - table = generate_text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], - stake_currency=stake_currency, - ) + table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], + stake_currency=stake_currency) if isinstance(table, str): print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) print(table) @@ -322,7 +320,7 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): if len(backtest_stats['strategy']) > 1: # Print Strategy summary table - table = generate_text_table_strategy(backtest_stats['strategy_comparison'], stake_currency) + table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency) print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) print(table) print('=' * len(table.splitlines()[0])) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 40c106975..04c8a4b0e 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -666,7 +666,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): mocker.patch.multiple('freqtrade.optimize.optimize_reports', generate_text_table=gen_table_mock, - generate_text_table_strategy=gen_strattable_mock, + text_table_strategy=gen_strattable_mock, generate_pair_metrics=MagicMock(), generate_sell_reason_stats=sell_reason_mock, generate_strategy_metrics=gen_strat_summary, diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 8bef6e2cc..b4105af7d 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -7,8 +7,8 @@ from arrow import Arrow from freqtrade.edge import PairInfo from freqtrade.optimize.optimize_reports import ( generate_pair_metrics, generate_edge_table, generate_sell_reason_stats, - generate_text_table, generate_text_table_sell_reason, generate_strategy_metrics, - generate_text_table_strategy, store_backtest_result) + generate_text_table, text_table_sell_reason, generate_strategy_metrics, + text_table_strategy, store_backtest_result) from freqtrade.strategy.interface import SellType from tests.conftest import patch_exchange @@ -69,7 +69,7 @@ def test_generate_pair_metrics(default_conf, mocker): pytest.approx(pair_results[-1]['profit_sum_pct']) == pair_results[-1]['profit_sum'] * 100) -def test_generate_text_table_sell_reason(default_conf): +def test_text_table_sell_reason(default_conf): results = pd.DataFrame( { @@ -97,8 +97,8 @@ def test_generate_text_table_sell_reason(default_conf): sell_reason_stats = generate_sell_reason_stats(max_open_trades=2, results=results) - assert generate_text_table_sell_reason(sell_reason_stats=sell_reason_stats, - stake_currency='BTC') == result_str + assert text_table_sell_reason(sell_reason_stats=sell_reason_stats, + stake_currency='BTC') == result_str def test_generate_sell_reason_stats(default_conf): @@ -136,7 +136,7 @@ def test_generate_sell_reason_stats(default_conf): assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2) -def test_generate_text_table_strategy(default_conf, mocker): +def test_text_table_strategy(default_conf, mocker): results = {} results['TestStrategy1'] = pd.DataFrame( { @@ -178,7 +178,7 @@ def test_generate_text_table_strategy(default_conf, mocker): max_open_trades=2, all_results=results) - assert generate_text_table_strategy(strategy_results, 'BTC') == result_str + assert text_table_strategy(strategy_results, 'BTC') == result_str def test_generate_edge_table(edge_conf, mocker): From 070913f32729c053eb150db1d473c2c5c027e37d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 11:35:02 +0200 Subject: [PATCH 113/191] Rename text_table generation --- freqtrade/optimize/optimize_reports.py | 6 +++--- tests/optimize/test_backtesting.py | 18 +++++++++--------- tests/optimize/test_optimize_reports.py | 7 +++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 789ad7d46..739640bc5 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -226,7 +226,7 @@ def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], # Start output section ### -def generate_text_table(pair_results: List[Dict[str, Any]], stake_currency: str) -> str: +def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: str) -> str: """ Generates and returns a text table for the given backtest data and the results dataframe :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row @@ -298,7 +298,7 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): # Print results print(f"Result for strategy {strategy}") - table = generate_text_table(results['results_per_pair'], stake_currency=stake_currency) + table = text_table_bt_results(results['results_per_pair'], stake_currency=stake_currency) if isinstance(table, str): print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '=')) print(table) @@ -309,7 +309,7 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) print(table) - table = generate_text_table(results['left_open_trades'], stake_currency=stake_currency) + table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency) if isinstance(table, str): print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '=')) print(table) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 04c8a4b0e..1c9810ec9 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -659,17 +659,17 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) - gen_table_mock = MagicMock() + text_table_mock = MagicMock() sell_reason_mock = MagicMock() - gen_strattable_mock = MagicMock() - gen_strat_summary = MagicMock() + strattable_mock = MagicMock() + strat_summary = MagicMock() mocker.patch.multiple('freqtrade.optimize.optimize_reports', - generate_text_table=gen_table_mock, - text_table_strategy=gen_strattable_mock, + text_table_bt_results=text_table_mock, + text_table_strategy=strattable_mock, generate_pair_metrics=MagicMock(), generate_sell_reason_stats=sell_reason_mock, - generate_strategy_metrics=gen_strat_summary, + generate_strategy_metrics=strat_summary, ) patched_configuration_load_config_file(mocker, default_conf) @@ -690,10 +690,10 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): start_backtesting(args) # 2 backtests, 4 tables assert backtestmock.call_count == 2 - assert gen_table_mock.call_count == 4 - assert gen_strattable_mock.call_count == 1 + assert text_table_mock.call_count == 4 + assert strattable_mock.call_count == 1 assert sell_reason_mock.call_count == 2 - assert gen_strat_summary.call_count == 1 + assert strat_summary.call_count == 1 # check the logs, that will contain the backtest result exists = [ diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index b4105af7d..175405e4c 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -7,13 +7,13 @@ from arrow import Arrow from freqtrade.edge import PairInfo from freqtrade.optimize.optimize_reports import ( generate_pair_metrics, generate_edge_table, generate_sell_reason_stats, - generate_text_table, text_table_sell_reason, generate_strategy_metrics, + text_table_bt_results, text_table_sell_reason, generate_strategy_metrics, text_table_strategy, store_backtest_result) from freqtrade.strategy.interface import SellType from tests.conftest import patch_exchange -def test_generate_text_table(default_conf, mocker): +def test_text_table_bt_results(default_conf, mocker): results = pd.DataFrame( { @@ -40,8 +40,7 @@ def test_generate_text_table(default_conf, mocker): pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', max_open_trades=2, results=results) - assert generate_text_table(pair_results, - stake_currency='BTC') == result_str + assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str def test_generate_pair_metrics(default_conf, mocker): From 04779411f5066c221ce7fd91a237f0caba9fbd81 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 14:32:01 +0200 Subject: [PATCH 114/191] Add docstring to backtest_stats --- freqtrade/optimize/optimize_reports.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 739640bc5..9bcbb4dd7 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -190,7 +190,14 @@ def generate_edge_table(results: dict) -> str: def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], - all_results: Dict[str, DataFrame]): + all_results: Dict[str, DataFrame]) -> Dict[str, Any]: + """ + :param config: Configuration object used for backtest + :param btdata: Backtest data + :param all_results: backtest result - dictionary with { Strategy: results}. + :return: + Dictionary containing results per strategy and a stratgy summary. + """ stake_currency = config['stake_currency'] max_open_trades = config['max_open_trades'] result: Dict[str, Any] = {'strategy': {}} From 3f9ab0846d6b5197094fddb50f15c5fdfe409688 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 15:17:35 +0200 Subject: [PATCH 115/191] Rename profitperc to profit_percent --- freqtrade/data/btanalysis.py | 15 ++++++++------- freqtrade/plot/plotting.py | 14 +++++++------- tests/data/test_btanalysis.py | 2 +- tests/test_plotting.py | 4 ++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index f98135c27..6c498a470 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -16,7 +16,7 @@ from freqtrade.persistence import Trade logger = logging.getLogger(__name__) # must align with columns in backtest.py -BT_DATA_COLUMNS = ["pair", "profitperc", "open_time", "close_time", "index", "duration", +BT_DATA_COLUMNS = ["pair", "profit_percent", "open_time", "close_time", "index", "duration", "open_rate", "close_rate", "open_at_end", "sell_reason"] @@ -99,7 +99,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS) persistence.init(db_url, clean_open_orders=False) - columns = ["pair", "open_time", "close_time", "profit", "profitperc", + columns = ["pair", "open_time", "close_time", "profit", "profit_percent", "open_rate", "close_rate", "amount", "duration", "sell_reason", "fee_open", "fee_close", "open_rate_requested", "close_rate_requested", "stake_amount", "max_rate", "min_rate", "id", "exchange", @@ -190,7 +190,7 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, """ Adds a column `col_name` with the cumulative profit for the given trades array. :param df: DataFrame with date index - :param trades: DataFrame containing trades (requires columns close_time and profitperc) + :param trades: DataFrame containing trades (requires columns close_time and profit_percent) :param col_name: Column name that will be assigned the results :param timeframe: Timeframe used during the operations :return: Returns df with one additional column, col_name, containing the cumulative profit. @@ -201,7 +201,8 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, from freqtrade.exchange import timeframe_to_minutes timeframe_minutes = timeframe_to_minutes(timeframe) # Resample to timeframe to make sure trades match candles - _trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_time')[['profitperc']].sum() + _trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_time' + )[['profit_percent']].sum() df.loc[:, col_name] = _trades_sum.cumsum() # Set first value to 0 df.loc[df.iloc[0].name, col_name] = 0 @@ -211,13 +212,13 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_time', - value_col: str = 'profitperc' + value_col: str = 'profit_percent' ) -> Tuple[float, pd.Timestamp, pd.Timestamp]: """ Calculate max drawdown and the corresponding close dates - :param trades: DataFrame containing trades (requires columns close_time and profitperc) + :param trades: DataFrame containing trades (requires columns close_time and profit_percent) :param date_col: Column in DataFrame to use for dates (defaults to 'close_time') - :param value_col: Column in DataFrame to use for values (defaults to 'profitperc') + :param value_col: Column in DataFrame to use for values (defaults to 'profit_percent') :return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time :raise: ValueError if trade-dataframe was found empty. """ diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index f1d114e2b..d519c5f4e 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -162,7 +162,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: # Trades can be empty if trades is not None and len(trades) > 0: # Create description for sell summarizing the trade - trades['desc'] = trades.apply(lambda row: f"{round(row['profitperc'] * 100, 1)}%, " + trades['desc'] = trades.apply(lambda row: f"{round(row['profit_percent'] * 100, 1)}%, " f"{row['sell_reason']}, {row['duration']} min", axis=1) trade_buys = go.Scatter( @@ -181,9 +181,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: ) trade_sells = go.Scatter( - x=trades.loc[trades['profitperc'] > 0, "close_time"], - y=trades.loc[trades['profitperc'] > 0, "close_rate"], - text=trades.loc[trades['profitperc'] > 0, "desc"], + x=trades.loc[trades['profit_percent'] > 0, "close_time"], + y=trades.loc[trades['profit_percent'] > 0, "close_rate"], + text=trades.loc[trades['profit_percent'] > 0, "desc"], mode='markers', name='Sell - Profit', marker=dict( @@ -194,9 +194,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: ) ) trade_sells_loss = go.Scatter( - x=trades.loc[trades['profitperc'] <= 0, "close_time"], - y=trades.loc[trades['profitperc'] <= 0, "close_rate"], - text=trades.loc[trades['profitperc'] <= 0, "desc"], + x=trades.loc[trades['profit_percent'] <= 0, "close_time"], + y=trades.loc[trades['profit_percent'] <= 0, "close_rate"], + text=trades.loc[trades['profit_percent'] <= 0, "desc"], mode='markers', name='Sell - Loss', marker=dict( diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 50cf9db3d..b65db7fd8 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -47,7 +47,7 @@ def test_load_trades_from_db(default_conf, fee, mocker): assert isinstance(trades, DataFrame) assert "pair" in trades.columns assert "open_time" in trades.columns - assert "profitperc" in trades.columns + assert "profit_percent" in trades.columns for col in BT_DATA_COLUMNS: if col not in ['index', 'open_at_end']: diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 5bb113784..150329c52 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -124,7 +124,7 @@ def test_plot_trades(testdatadir, caplog): trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit') assert isinstance(trade_sell, go.Scatter) assert trade_sell.yaxis == 'y' - assert len(trades.loc[trades['profitperc'] > 0]) == len(trade_sell.x) + assert len(trades.loc[trades['profit_percent'] > 0]) == len(trade_sell.x) assert trade_sell.marker.color == 'green' assert trade_sell.marker.symbol == 'square-open' assert trade_sell.text[0] == '4.0%, roi, 15 min' @@ -132,7 +132,7 @@ def test_plot_trades(testdatadir, caplog): trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss') assert isinstance(trade_sell_loss, go.Scatter) assert trade_sell_loss.yaxis == 'y' - assert len(trades.loc[trades['profitperc'] <= 0]) == len(trade_sell_loss.x) + assert len(trades.loc[trades['profit_percent'] <= 0]) == len(trade_sell_loss.x) assert trade_sell_loss.marker.color == 'red' assert trade_sell_loss.marker.symbol == 'square-open' assert trade_sell_loss.text[5] == '-10.4%, stop_loss, 720 min' From 0f373e6bb9332500f00b735f4965201c8f34b031 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 15:02:56 +0200 Subject: [PATCH 116/191] Update unrelated tests --- tests/test_persistence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index ae639511b..fe2912486 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -298,7 +298,7 @@ def test_calc_profit(limit_buy_order, limit_sell_order, fee): fee_close=fee.return_value, exchange='bittrex', ) - trade.open_order_id = 'profit_percent' + trade.open_order_id = 'something' trade.update(limit_buy_order) # Buy @ 0.00001099 # Custom closing rate and regular fee rate @@ -332,7 +332,7 @@ def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee): fee_close=fee.return_value, exchange='bittrex', ) - trade.open_order_id = 'profit_percent' + trade.open_order_id = 'something' trade.update(limit_buy_order) # Buy @ 0.00001099 # Get percent of profit with a custom rate (Higher than open rate) From 68395d27454ee307680d381f16652c02ca8d2964 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 15:13:59 +0200 Subject: [PATCH 117/191] Use bracket notation to query results in hyperopt --- docs/advanced-hyperopt.md | 4 ++-- freqtrade/optimize/default_hyperopt_loss.py | 4 ++-- freqtrade/optimize/hyperopt_loss_onlyprofit.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md index 25b4bd900..5fc674b03 100644 --- a/docs/advanced-hyperopt.md +++ b/docs/advanced-hyperopt.md @@ -63,8 +63,8 @@ class SuperDuperHyperOptLoss(IHyperOptLoss): * 0.25: Avoiding trade loss * 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above """ - total_profit = results.profit_percent.sum() - trade_duration = results.trade_duration.mean() + total_profit = results['profit_percent'].sum() + trade_duration = results['trade_duration'].mean() trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8) profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) diff --git a/freqtrade/optimize/default_hyperopt_loss.py b/freqtrade/optimize/default_hyperopt_loss.py index 4ab9fbe44..9e780d0ea 100644 --- a/freqtrade/optimize/default_hyperopt_loss.py +++ b/freqtrade/optimize/default_hyperopt_loss.py @@ -42,8 +42,8 @@ class DefaultHyperOptLoss(IHyperOptLoss): * 0.25: Avoiding trade loss * 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above """ - total_profit = results.profit_percent.sum() - trade_duration = results.trade_duration.mean() + total_profit = results['profit_percent'].sum() + trade_duration = results['trade_duration'].mean() trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8) profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) diff --git a/freqtrade/optimize/hyperopt_loss_onlyprofit.py b/freqtrade/optimize/hyperopt_loss_onlyprofit.py index a1c50e727..43176dbad 100644 --- a/freqtrade/optimize/hyperopt_loss_onlyprofit.py +++ b/freqtrade/optimize/hyperopt_loss_onlyprofit.py @@ -34,5 +34,5 @@ class OnlyProfitHyperOptLoss(IHyperOptLoss): """ Objective function, returns smaller number for better results. """ - total_profit = results.profit_percent.sum() + total_profit = results['profit_percent'].sum() return 1 - total_profit / EXPECTED_MAX_PROFIT From a75b94f1437ad33664de4beb7c55ef7f77b5cd69 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 15:30:41 +0200 Subject: [PATCH 118/191] use bracket notation for dataframe access --- freqtrade/optimize/optimize_reports.py | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 9bcbb4dd7..d89860a73 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -65,25 +65,25 @@ def _generate_result_line(result: DataFrame, max_open_trades: int, first_column: """ return { 'key': first_column, - 'trades': len(result.index), - 'profit_mean': result.profit_percent.mean(), - 'profit_mean_pct': result.profit_percent.mean() * 100.0, - 'profit_sum': result.profit_percent.sum(), - 'profit_sum_pct': result.profit_percent.sum() * 100.0, - 'profit_total_abs': result.profit_abs.sum(), - 'profit_total_pct': result.profit_percent.sum() * 100.0 / max_open_trades, + 'trades': len(result), + 'profit_mean': result['profit_percent'].mean(), + 'profit_mean_pct': result['profit_percent'].mean() * 100.0, + 'profit_sum': result['profit_percent'].sum(), + 'profit_sum_pct': result['profit_percent'].sum() * 100.0, + 'profit_total_abs': result['profit_abs'].sum(), + 'profit_total_pct': result['profit_percent'].sum() * 100.0 / max_open_trades, 'duration_avg': str(timedelta( - minutes=round(result.trade_duration.mean())) + minutes=round(result['trade_duration'].mean())) ) if not result.empty else '0:00', # 'duration_max': str(timedelta( - # minutes=round(result.trade_duration.max())) + # minutes=round(result['trade_duration'].max())) # ) if not result.empty else '0:00', # 'duration_min': str(timedelta( - # minutes=round(result.trade_duration.min())) + # minutes=round(result['trade_duration'].min())) # ) if not result.empty else '0:00', - 'wins': len(result[result.profit_abs > 0]), - 'draws': len(result[result.profit_abs == 0]), - 'losses': len(result[result.profit_abs < 0]), + 'wins': len(result[result['profit_abs'] > 0]), + 'draws': len(result[result['profit_abs'] == 0]), + 'losses': len(result[result['profit_abs'] < 0]), } @@ -102,8 +102,8 @@ def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, max_open_t tabular_data = [] for pair in data: - result = results[results.pair == pair] - if skip_nan and result.profit_abs.isnull().all(): + result = results[results['pair'] == pair] + if skip_nan and result['profit_abs'].isnull().all(): continue tabular_data.append(_generate_result_line(result, max_open_trades, pair)) From 54226b45b1913383da8cc2922421ce01f83ffeac Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 16:02:08 +0200 Subject: [PATCH 119/191] Add test verifying failure --- tests/optimize/test_backtesting.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 40c106975..b1e9dec56 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -401,6 +401,33 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> Backtesting(default_conf) + +def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.data.history.get_timerange', get_timerange) + patch_exchange(mocker) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest') + mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist', + PropertyMock(return_value=['XRP/BTC'])) + mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.refresh_pairlist') + + default_conf['ticker_interval'] = "1m" + default_conf['datadir'] = testdatadir + default_conf['export'] = None + # Use stoploss from strategy + del default_conf['stoploss'] + default_conf['timerange'] = '20180101-20180102' + + default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}] + with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'): + Backtesting(default_conf) + + default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}, ] + Backtesting(default_conf) + + def test_backtest(default_conf, fee, mocker, testdatadir) -> None: default_conf['ask_strategy']['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) From 72ae4b15002801a47ad4b3ae0576b414d37020f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Jun 2020 16:06:20 +0200 Subject: [PATCH 120/191] Load pairlist after strategy to use strategy-config fail in certain conditions when using strategy-list Fix #3363 --- freqtrade/optimize/backtesting.py | 33 +++++++++++++++++------------- tests/optimize/test_backtesting.py | 7 ++++++- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index b47b38ea4..0c5bb1a0c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -65,20 +65,6 @@ class Backtesting: self.strategylist: List[IStrategy] = [] self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) - self.pairlists = PairListManager(self.exchange, self.config) - if 'VolumePairList' in self.pairlists.name_list: - raise OperationalException("VolumePairList not allowed for backtesting.") - - self.pairlists.refresh_pairlist() - - if len(self.pairlists.whitelist) == 0: - raise OperationalException("No pair in whitelist.") - - if config.get('fee'): - self.fee = config['fee'] - else: - self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0]) - if self.config.get('runmode') != RunMode.HYPEROPT: self.dataprovider = DataProvider(self.config, self.exchange) IStrategy.dp = self.dataprovider @@ -101,6 +87,25 @@ class Backtesting: self.timeframe = str(self.config.get('ticker_interval')) self.timeframe_min = timeframe_to_minutes(self.timeframe) + self.pairlists = PairListManager(self.exchange, self.config) + if 'VolumePairList' in self.pairlists.name_list: + raise OperationalException("VolumePairList not allowed for backtesting.") + + if len(self.strategylist) > 1 and 'PrecisionFilter' in self.pairlists.name_list: + raise OperationalException( + "PrecisionFilter not allowed for backtesting multiple strategies." + ) + + self.pairlists.refresh_pairlist() + + if len(self.pairlists.whitelist) == 0: + raise OperationalException("No pair in whitelist.") + + if config.get('fee'): + self.fee = config['fee'] + else: + self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0]) + # Get maximum required startup period self.required_startup = max([strat.startup_candle_count for strat in self.strategylist]) # Load one (first) strategy diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index b1e9dec56..fc03223d2 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -401,7 +401,6 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> Backtesting(default_conf) - def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None: mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) @@ -427,6 +426,12 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}, ] Backtesting(default_conf) + # Multiple strategies + default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy'] + with pytest.raises(OperationalException, + match='PrecisionFilter not allowed for backtesting multiple strategies.'): + Backtesting(default_conf) + def test_backtest(default_conf, fee, mocker, testdatadir) -> None: default_conf['ask_strategy']['use_sell_signal'] = False From bb07746bd5a6a6fcf2843bfe30d85ea5f5e9c16c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:08:19 +0000 Subject: [PATCH 121/191] Bump pytest-mock from 3.1.0 to 3.1.1 Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/pytest-dev/pytest-mock/releases) - [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.1.0...v3.1.1) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e05231630..6393d2478 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,7 @@ mypy==0.770 pytest==5.4.2 pytest-asyncio==0.12.0 pytest-cov==2.9.0 -pytest-mock==3.1.0 +pytest-mock==3.1.1 pytest-random-order==1.0.4 # Convert jupyter notebooks to markdown documents From 9f482d598ef3b66d3b170b09383f07abe0e9fa50 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:09:38 +0000 Subject: [PATCH 122/191] Bump ccxt from 1.29.5 to 1.29.52 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.29.5 to 1.29.52. - [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.29.5...1.29.52) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 07bc5caa3..dab3a5da4 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.29.5 +ccxt==1.29.52 SQLAlchemy==1.3.17 python-telegram-bot==12.7 arrow==0.15.6 From 1c8243e9b31628c5b3f56f15f10937d61c878fb5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:10:07 +0000 Subject: [PATCH 123/191] Bump numpy from 1.18.4 to 1.18.5 Bumps [numpy](https://github.com/numpy/numpy) from 1.18.4 to 1.18.5. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/master/doc/HOWTO_RELEASE.rst.txt) - [Commits](https://github.com/numpy/numpy/compare/v1.18.4...v1.18.5) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f5d09db4d..2c68b8f2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Load common requirements -r requirements-common.txt -numpy==1.18.4 +numpy==1.18.5 pandas==1.0.4 From 81ed1d6f358cc6243edcdf2e4144e94917add5ba Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:10:29 +0000 Subject: [PATCH 124/191] Bump mkdocs-material from 5.2.2 to 5.2.3 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.2.2 to 5.2.3. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.2.2...5.2.3) Signed-off-by: dependabot-preview[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 9997fa854..b8ea338de 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==5.2.2 +mkdocs-material==5.2.3 mdx_truly_sane_lists==1.2 From 4b5aee7d1ce36d8f3e9c5c567e0eeca271cbcb08 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:21:08 +0000 Subject: [PATCH 125/191] Bump pytest from 5.4.2 to 5.4.3 Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.4.2 to 5.4.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/5.4.2...5.4.3) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 6393d2478..0bb54679d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ flake8==3.8.2 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 mypy==0.770 -pytest==5.4.2 +pytest==5.4.3 pytest-asyncio==0.12.0 pytest-cov==2.9.0 pytest-mock==3.1.1 From 6029593c431bd11d9742c01d3468c8dfc24bf880 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:49:08 +0000 Subject: [PATCH 126/191] Bump mypy from 0.770 to 0.780 Bumps [mypy](https://github.com/python/mypy) from 0.770 to 0.780. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.770...v0.780) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0bb54679d..d62b768c1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==2.0.0 flake8==3.8.2 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 -mypy==0.770 +mypy==0.780 pytest==5.4.3 pytest-asyncio==0.12.0 pytest-cov==2.9.0 From ab0003f56517a51e358e24245f53ea054032d151 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 9 Jun 2020 14:33:57 +0200 Subject: [PATCH 127/191] fix #3463 by explicitly failing if no stoploss is defined --- freqtrade/pairlist/PrecisionFilter.py | 6 +++++- tests/pairlist/test_pairlist.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index 0331347be..45baf656c 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -5,7 +5,7 @@ import logging from typing import Any, Dict from freqtrade.pairlist.IPairList import IPairList - +from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) @@ -17,6 +17,10 @@ class PrecisionFilter(IPairList): pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + if 'stoploss' not in self._config: + raise OperationalException( + 'PrecisionFilter can only work with stoploss defined. Please add the ' + 'stoploss key to your configuration (overwrites eventual strategy settings).') self._stoploss = self._config['stoploss'] self._enabled = self._stoploss != 0 diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 421f06911..07f853342 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -362,6 +362,17 @@ 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: + whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}] + del whitelist_conf['stoploss'] + + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + + with pytest.raises(OperationalException, + match=r"PrecisionFilter can only work with stoploss defined\..*"): + PairListManager(MagicMock, whitelist_conf) + + def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}] From 05deb5ba05361df908e22c18ac02c222f9298a29 Mon Sep 17 00:00:00 2001 From: Mister Render Date: Tue, 9 Jun 2020 16:08:20 +0000 Subject: [PATCH 128/191] Fixed typo and missing { This should help with copy pasting the pairlists code block. Also fixed minor typo on line 594 (was: "I selects" and is now: "It selects") --- docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index da0f015e8..c31af1cc9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -591,7 +591,7 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis #### Volume Pair List -`VolumePairList` employs sorting/filtering of pairs by their trading volume. I selects `number_assets` top pairs with sorting based on the `sort_key` (which can only be `quoteVolume`). +`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`). When used in the chain of Pairlist Handlers in a non-leading position (after StaticPairList and other Pairlist Filters), `VolumePairList` considers outputs of previous Pairlist Handlers, adding its sorting/selection of the pairs by the trading volume. @@ -609,7 +609,7 @@ The `refresh_period` setting allows to define the period (in seconds), at which "number_assets": 20, "sort_key": "quoteVolume", "refresh_period": 1800, -], +}], ``` #### PrecisionFilter From bd942992ef80bff0c7768ffe7ee57c8965e40265 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 9 Jun 2020 20:47:52 +0200 Subject: [PATCH 129/191] Add documentation about pricing related to market orders --- docs/configuration.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index c31af1cc9..035a65abf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -333,6 +333,9 @@ Configuration: !!! Note If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new 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. @@ -459,6 +462,9 @@ Prices are always retrieved right before an order is placed, either by querying !!! Note Orderbook data used by Freqtrade are the data retrieved from exchange by the ccxt's function `fetch_order_book()`, i.e. are usually data from the L2-aggregated orderbook, while the ticker data are the structures returned by the ccxt's `fetch_ticker()`/`fetch_tickers()` functions. Refer to the ccxt library [documentation](https://github.com/ccxt/ccxt/wiki/Manual#market-data) for more details. +!!! Warning "Using market orders" + Please read the section [Market order pricing](#market-order-pricing) section when using market orders. + ### Buy price #### Check depth of market @@ -553,6 +559,29 @@ A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price. +### Market order pricing + +When using market orders, prices should be configured to use the "correct" side of the orderbook to allow realistic pricing detection. +Assuming both buy and sell are using market orders, a configuration similar to the following might be used + +``` jsonc + "order_types": { + "buy": "market", + "sell": "market" + // ... + }, + "bid_strategy": { + "price_side": "ask", + //... + }, + "ask_strategy":{ + "price_side": "bid", + //... + }, +``` + +Obviously, if only one side is using limit orders, different pricing combinations can be used. + ## Pairlists and Pairlist Handlers Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings. From 6744f8f052d66e1f7a49eff81c6ff57e65fec0cf Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 10 Jun 2020 01:22:55 +0300 Subject: [PATCH 130/191] Remove _load_async_markets --- freqtrade/exchange/exchange.py | 17 +++-------------- tests/exchange/test_exchange.py | 28 ---------------------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 038fc22bc..773f7a0a4 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -256,32 +256,21 @@ class Exchange: "Please check your config.json") raise OperationalException(f'Exchange {name} does not provide a sandbox api') - def _load_async_markets(self, reload: bool = False) -> None: - try: - if self._api_async: - asyncio.get_event_loop().run_until_complete( - self._api_async.load_markets(reload=reload)) - - except ccxt.BaseError as e: - logger.warning('Could not load async markets. Reason: %s', e) - return - def _load_markets(self) -> None: - """ Initialize markets both sync and async """ + """ Initialize markets """ try: self._api.load_markets() - self._load_async_markets() self._last_markets_refresh = arrow.utcnow().timestamp except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) def _reload_markets(self) -> None: - """Reload markets both sync and async, if refresh interval has passed""" + """Reload markets if refresh interval has passed""" # Check whether markets have to be reloaded if (self._last_markets_refresh > 0) and ( self._last_markets_refresh + self.markets_refresh_interval > arrow.utcnow().timestamp): - return None + return logger.debug("Performing scheduled market reload..") try: self._api.load_markets(reload=True) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 32163f696..31efc65ff 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -130,7 +130,6 @@ def test_init_exception(default_conf, mocker): def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock())) - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') @@ -318,19 +317,6 @@ def test_set_sandbox_exception(default_conf, mocker): exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname') -def test__load_async_markets(default_conf, mocker, caplog): - exchange = get_patched_exchange(mocker, default_conf) - exchange._api_async.load_markets = get_mock_coro(None) - exchange._load_async_markets() - assert exchange._api_async.load_markets.call_count == 1 - caplog.set_level(logging.DEBUG) - - exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef")) - exchange._load_async_markets() - - assert log_has('Could not load async markets. Reason: deadbeef', caplog) - - def test__load_markets(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() @@ -338,7 +324,6 @@ def test__load_markets(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) assert log_has('Unable to initialize markets. Reason: SomeError', caplog) @@ -406,7 +391,6 @@ def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') Exchange(default_conf) @@ -420,7 +404,6 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') with pytest.raises(OperationalException, match=r'XRP is not available as stake on .*' 'Available currencies are: BTC, ETH, USDT'): @@ -470,7 +453,6 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -483,7 +465,6 @@ def test_validate_pairs_not_available(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') with pytest.raises(OperationalException, match=r'not available'): Exchange(default_conf) @@ -498,7 +479,6 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'): Exchange(default_conf) @@ -517,7 +497,6 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -535,7 +514,6 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -551,7 +529,6 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -567,7 +544,6 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): @@ -742,7 +718,6 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') - mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') @@ -1934,7 +1909,6 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()), - _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), validate_stakecurrency=MagicMock() @@ -1968,7 +1942,6 @@ def test_merge_ft_has_dict(default_conf, mocker): def test_get_valid_pair_combination(default_conf, mocker, markets): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()), - _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), markets=PropertyMock(return_value=markets)) @@ -2041,7 +2014,6 @@ def test_get_markets(default_conf, mocker, markets, expected_keys): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()), - _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), markets=PropertyMock(return_value=markets)) From 7d451638a84e9d709291a4038cb06f760bfa9c80 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 10 Jun 2020 01:39:23 +0300 Subject: [PATCH 131/191] Make _reload_markets() public --- freqtrade/exchange/exchange.py | 4 ++-- freqtrade/freqtradebot.py | 4 ++-- tests/exchange/test_exchange.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 773f7a0a4..6802a6b9e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -264,8 +264,8 @@ class Exchange: except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) - def _reload_markets(self) -> None: - """Reload markets if refresh interval has passed""" + def reload_markets(self) -> None: + """Reload markets if refresh interval has passed """ # Check whether markets have to be reloaded if (self._last_markets_refresh > 0) and ( self._last_markets_refresh + self.markets_refresh_interval diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5104e4f95..8a66957c3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -139,8 +139,8 @@ class FreqtradeBot: :return: True if one or more trades has been created or closed, False otherwise """ - # Check whether markets have to be reloaded - self.exchange._reload_markets() + # Check whether markets have to be reloaded and reload them when it's needed + self.exchange.reload_markets() # Query trades from persistence layer trades = Trade.get_open_trades() diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 31efc65ff..9914188b8 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -337,7 +337,7 @@ def test__load_markets(default_conf, mocker, caplog): assert ex.markets == expected_return -def test__reload_markets(default_conf, mocker, caplog): +def test_reload_markets(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) initial_markets = {'ETH/BTC': {}} @@ -356,17 +356,17 @@ def test__reload_markets(default_conf, mocker, caplog): assert exchange.markets == initial_markets # less than 10 minutes have passed, no reload - exchange._reload_markets() + exchange.reload_markets() assert exchange.markets == initial_markets # more than 10 minutes have passed, reload is executed exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60 - exchange._reload_markets() + exchange.reload_markets() assert exchange.markets == updated_markets assert log_has('Performing scheduled market reload..', caplog) -def test__reload_markets_exception(default_conf, mocker, caplog): +def test_reload_markets_exception(default_conf, mocker, caplog): caplog.set_level(logging.DEBUG) api_mock = MagicMock() @@ -375,7 +375,7 @@ def test__reload_markets_exception(default_conf, mocker, caplog): exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") # less than 10 minutes have passed, no reload - exchange._reload_markets() + exchange.reload_markets() assert exchange._last_markets_refresh == 0 assert log_has_re(r"Could not reload markets.*", caplog) From 0067a3ab7ceb929b2ded3a7916a064f759f45c69 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 10 Jun 2020 06:30:29 +0300 Subject: [PATCH 132/191] Change logging level --- 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 6802a6b9e..e974655cb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -190,7 +190,7 @@ class Exchange: def markets(self) -> Dict: """exchange ccxt markets""" if not self._api.markets: - logger.warning("Markets were not loaded. Loading them now..") + logger.info("Markets were not loaded. Loading them now..") self._load_markets() return self._api.markets From a198b91b873ca428ccb19dc5244a47104c228a2b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Jun 2020 06:36:35 +0200 Subject: [PATCH 133/191] align Spaces in commented config section Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 035a65abf..cb5b6c3ea 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -572,11 +572,11 @@ Assuming both buy and sell are using market orders, a configuration similar to t }, "bid_strategy": { "price_side": "ask", - //... + // ... }, "ask_strategy":{ "price_side": "bid", - //... + // ... }, ``` From ac92834693fe5e9a3c165c3529c7dbfc4f1a3798 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Tue, 9 Jun 2020 23:03:55 +0200 Subject: [PATCH 134/191] reload_conf & reload_config now both accepted, code is more consistent now --- docs/rest-api.md | 6 +++--- docs/stoploss.md | 2 +- docs/strategy-customization.md | 2 +- docs/telegram-usage.md | 8 ++++---- freqtrade/rpc/api_server.py | 10 +++++----- freqtrade/rpc/rpc.py | 8 ++++---- freqtrade/rpc/telegram.py | 12 +++++++----- freqtrade/state.py | 2 +- freqtrade/worker.py | 2 +- tests/rpc/test_rpc.py | 2 +- tests/rpc/test_rpc_apiserver.py | 6 +++--- tests/rpc/test_rpc_telegram.py | 13 +++++++------ tests/test_main.py | 4 ++-- 13 files changed, 40 insertions(+), 37 deletions(-) diff --git a/docs/rest-api.md b/docs/rest-api.md index ed5f355b4..33f62f884 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -110,7 +110,7 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `start` | | Starts the trader | `stop` | | Stops the trader | `stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. -| `reload_conf` | | Reloads the configuration file +| `reload_config` | | Reloads the configuration file | `show_config` | | Shows part of the current configuration with relevant settings to operation | `status` | | Lists all open trades | `count` | | Displays number of trades used and available @@ -174,7 +174,7 @@ profit Returns the profit summary :returns: json object -reload_conf +reload_config Reload configuration :returns: json object @@ -196,7 +196,7 @@ stop stopbuy Stop buying (but handle sells gracefully). - use reload_conf to reset + use reload_config to reset :returns: json object version diff --git a/docs/stoploss.md b/docs/stoploss.md index 0e43817ec..7ebe98ee6 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -101,7 +101,7 @@ Simplified example: ## Changing stoploss on open trades -A stoploss on an open trade can be changed by changing the value in the configuration or strategy and use the `/reload_conf` command (alternatively, completely stopping and restarting the bot also works). +A stoploss on an open trade can be changed by changing the value in the configuration or strategy and use the `/reload_config` command (alternatively, completely stopping and restarting the bot also works). The new stoploss value will be applied to open trades (and corresponding log-messages will be generated). diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 7197b0fba..92e4453d2 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -557,7 +557,7 @@ Locks can also be lifted manually, by calling `self.unlock_pair(pair)`. To verify if a pair is currently locked, use `self.is_pair_locked(pair)`. !!! Note - Locked pairs are not persisted, so a restart of the bot, or calling `/reload_conf` will reset locked pairs. + Locked pairs are not persisted, so a restart of the bot, or calling `/reload_config` will reset locked pairs. !!! Warning Locking pairs is not functioning during backtesting. diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index f683ae8da..f423a9376 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -52,7 +52,7 @@ official commands. You can ask at any moment for help with `/help`. | `/start` | | Starts the trader | `/stop` | | Stops the trader | `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. -| `/reload_conf` | | Reloads the configuration file +| `/reload_config` | | Reloads the configuration file | `/show_config` | | Shows part of the current configuration with relevant settings to operation | `/status` | | Lists all open trades | `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**) @@ -85,14 +85,14 @@ Below, example of Telegram message you will receive for each command. ### /stopbuy -> **status:** `Setting max_open_trades to 0. Run /reload_conf to reset.` +> **status:** `Setting max_open_trades to 0. Run /reload_config to reset.` Prevents the bot from opening new trades by temporarily setting "max_open_trades" to 0. Open trades will be handled via their regular rules (ROI / Sell-signal, stoploss, ...). After this, give the bot time to close off open trades (can be checked via `/status table`). Once all positions are sold, run `/stop` to completely stop the bot. -`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command. +`/reload_config` resets "max_open_trades" to the value set in the configuration and resets this command. !!! Warning The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset. @@ -209,7 +209,7 @@ Shows the current whitelist Shows the current blacklist. If Pair is set, then this pair will be added to the pairlist. Also supports multiple pairs, seperated by a space. -Use `/reload_conf` to reset the blacklist. +Use `/reload_config` to reset the blacklist. > Using blacklist `StaticPairList` with 2 pairs >`DODGE/BTC`, `HOT/BTC`. diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 9d0899ccd..f424bea92 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -172,8 +172,8 @@ class ApiServer(RPC): self.app.add_url_rule(f'{BASE_URI}/stop', 'stop', view_func=self._stop, methods=['POST']) self.app.add_url_rule(f'{BASE_URI}/stopbuy', 'stopbuy', view_func=self._stopbuy, methods=['POST']) - self.app.add_url_rule(f'{BASE_URI}/reload_conf', 'reload_conf', - view_func=self._reload_conf, methods=['POST']) + self.app.add_url_rule(f'{BASE_URI}/reload_config', 'reload_config', + view_func=self._reload_config, methods=['POST']) # Info commands self.app.add_url_rule(f'{BASE_URI}/balance', 'balance', view_func=self._balance, methods=['GET']) @@ -304,12 +304,12 @@ class ApiServer(RPC): @require_login @rpc_catch_errors - def _reload_conf(self): + def _reload_config(self): """ - Handler for /reload_conf. + Handler for /reload_config. Triggers a config file reload """ - msg = self._rpc_reload_conf() + msg = self._rpc_reload_config() return self.rest_dump(msg) @require_login diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 00a170ee3..e4c96cf3b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -418,9 +418,9 @@ class RPC: return {'status': 'already stopped'} - def _rpc_reload_conf(self) -> Dict[str, str]: - """ Handler for reload_conf. """ - self._freqtrade.state = State.RELOAD_CONF + def _rpc_reload_config(self) -> Dict[str, str]: + """ Handler for reload_config. """ + self._freqtrade.state = State.RELOAD_CONFIG return {'status': 'reloading config ...'} def _rpc_stopbuy(self) -> Dict[str, str]: @@ -431,7 +431,7 @@ class RPC: # Set 'max_open_trades' to 0 self._freqtrade.config['max_open_trades'] = 0 - return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} + return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} def _rpc_forcesell(self, trade_id: str) -> Dict[str, str]: """ diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index eb53fc68f..e86f35687 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -95,7 +95,9 @@ class Telegram(RPC): CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), - CommandHandler('reload_conf', self._reload_conf), + CommandHandler('reload_conf', self._reload_config), + CommandHandler('reload_config', self._reload_config), + CommandHandler('show_conf', self._reload_config), CommandHandler('show_config', self._show_config), CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), @@ -436,15 +438,15 @@ class Telegram(RPC): self._send_msg('Status: `{status}`'.format(**msg)) @authorized_only - def _reload_conf(self, update: Update, context: CallbackContext) -> None: + def _reload_config(self, update: Update, context: CallbackContext) -> None: """ - Handler for /reload_conf. + Handler for /reload_config. Triggers a config file reload :param bot: telegram bot :param update: message update :return: None """ - msg = self._rpc_reload_conf() + msg = self._rpc_reload_config() self._send_msg('Status: `{status}`'.format(**msg)) @authorized_only @@ -617,7 +619,7 @@ class Telegram(RPC): "\n" "*/balance:* `Show account balance per currency`\n" "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" - "*/reload_conf:* `Reload configuration file` \n" + "*/reload_config:* `Reload configuration file` \n" "*/show_config:* `Show running configuration` \n" "*/whitelist:* `Show current whitelist` \n" "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " diff --git a/freqtrade/state.py b/freqtrade/state.py index 38784c6a4..8ddff71d9 100644 --- a/freqtrade/state.py +++ b/freqtrade/state.py @@ -12,7 +12,7 @@ class State(Enum): """ RUNNING = 1 STOPPED = 2 - RELOAD_CONF = 3 + RELOAD_CONFIG = 3 def __str__(self): return f"{self.name.lower()}" diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 3f5ab734e..5bdb166c2 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -71,7 +71,7 @@ class Worker: state = None while True: state = self._worker(old_state=state) - if state == State.RELOAD_CONF: + if state == State.RELOAD_CONFIG: self._reconfigure() def _worker(self, old_state: Optional[State]) -> State: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index ef1c1bc16..88c82c5ea 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -592,7 +592,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None: assert freqtradebot.config['max_open_trades'] != 0 result = rpc._rpc_stopbuy() - assert {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} == result + assert {'status': 'No more buy will occur from now. Run /reload_config to reset.'} == result assert freqtradebot.config['max_open_trades'] == 0 diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 9b247fefc..a6d38cc9f 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -251,10 +251,10 @@ def test_api_cleanup(default_conf, mocker, caplog): def test_api_reloadconf(botclient): ftbot, client = botclient - rc = client_post(client, f"{BASE_URI}/reload_conf") + rc = client_post(client, f"{BASE_URI}/reload_config") assert_response(rc) assert rc.json == {'status': 'reloading config ...'} - assert ftbot.state == State.RELOAD_CONF + assert ftbot.state == State.RELOAD_CONFIG def test_api_stopbuy(botclient): @@ -263,7 +263,7 @@ def test_api_stopbuy(botclient): rc = client_post(client, f"{BASE_URI}/stopbuy") assert_response(rc) - assert rc.json == {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} + assert rc.json == {'status': 'No more buy will occur from now. Run /reload_config to reset.'} assert ftbot.config['max_open_trades'] == 0 diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index b18106ee5..afe007347 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -73,8 +73,9 @@ def test_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " - "['performance'], ['daily'], ['count'], ['reload_conf'], ['show_config'], " - "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]") + "['performance'], ['daily'], ['count'], ['reload_conf'], ['reload_config'], " + "['show_conf'], ['show_config'], ['stopbuy'], ['whitelist'], " + "['blacklist'], ['edge'], ['help'], ['version']]") assert log_has(message_str, caplog) @@ -666,11 +667,11 @@ def test_stopbuy_handle(default_conf, update, mocker) -> None: telegram._stopbuy(update=update, context=MagicMock()) assert freqtradebot.config['max_open_trades'] == 0 assert msg_mock.call_count == 1 - assert 'No more buy will occur from now. Run /reload_conf to reset.' \ + assert 'No more buy will occur from now. Run /reload_config to reset.' \ in msg_mock.call_args_list[0][0][0] -def test_reload_conf_handle(default_conf, update, mocker) -> None: +def test_reload_config_handle(default_conf, update, mocker) -> None: msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -683,8 +684,8 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None: freqtradebot.state = State.RUNNING assert freqtradebot.state == State.RUNNING - telegram._reload_conf(update=update, context=MagicMock()) - assert freqtradebot.state == State.RELOAD_CONF + telegram._reload_config(update=update, context=MagicMock()) + assert freqtradebot.state == State.RELOAD_CONFIG assert msg_mock.call_count == 1 assert 'reloading config' in msg_mock.call_args_list[0][0][0] diff --git a/tests/test_main.py b/tests/test_main.py index 11d0ede3a..48cc47104 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -141,12 +141,12 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None: assert log_has_re(r'SIGINT.*', caplog) -def test_main_reload_conf(mocker, default_conf, caplog) -> None: +def test_main_reload_config(mocker, default_conf, caplog) -> None: patch_exchange(mocker) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock()) # Simulate Running, reload, running workflow worker_mock = MagicMock(side_effect=[State.RUNNING, - State.RELOAD_CONF, + State.RELOAD_CONFIG, State.RUNNING, OperationalException("Oh snap!")]) mocker.patch('freqtrade.worker.Worker._worker', worker_mock) From 04fa59769596506c4ff958b0554aaa0ca42c4a1d Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Wed, 10 Jun 2020 16:28:37 +0200 Subject: [PATCH 135/191] Test with multiple commands in one line --- freqtrade/rpc/telegram.py | 8 ++++---- tests/rpc/test_rpc_telegram.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e86f35687..51130e653 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -95,10 +95,10 @@ class Telegram(RPC): CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), - CommandHandler('reload_conf', self._reload_config), - CommandHandler('reload_config', self._reload_config), - CommandHandler('show_conf', self._reload_config), - CommandHandler('show_config', self._show_config), + #CommandHandler('reload_conf', self._reload_config), + CommandHandler(('reload_config' or 'reload_con'), self._reload_config), + #CommandHandler('show_conf', self._reload_config), + CommandHandler('show_config' or 'show_conf', self._show_config), CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), CommandHandler('blacklist', self._blacklist), diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index afe007347..ce0c9d1f0 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -73,8 +73,8 @@ def test_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " - "['performance'], ['daily'], ['count'], ['reload_conf'], ['reload_config'], " - "['show_conf'], ['show_config'], ['stopbuy'], ['whitelist'], " + "['performance'], ['daily'], ['count'], ['reload_config'], " + "['show_config'], ['stopbuy'], ['whitelist'], " "['blacklist'], ['edge'], ['help'], ['version']]") assert log_has(message_str, caplog) From 043397c5d7d70d2e7c3821b892486413a8a68a32 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Wed, 10 Jun 2020 16:34:46 +0200 Subject: [PATCH 136/191] reload_conf & reload_config now both accepted, code is more consistent now --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 51130e653..e7a8971ec 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -96,7 +96,7 @@ class Telegram(RPC): CommandHandler('daily', self._daily), CommandHandler('count', self._count), #CommandHandler('reload_conf', self._reload_config), - CommandHandler(('reload_config' or 'reload_con'), self._reload_config), + CommandHandler(('reload_config' or 'reload_conf'), self._reload_config), #CommandHandler('show_conf', self._reload_config), CommandHandler('show_config' or 'show_conf', self._show_config), CommandHandler('stopbuy', self._stopbuy), From 8c9dea988cf0665744dba54c7b9c0b4c2dfd3cfe Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Wed, 10 Jun 2020 16:55:47 +0200 Subject: [PATCH 137/191] Now supports both commands & fixed test --- freqtrade/rpc/telegram.py | 6 ++---- tests/rpc/test_rpc_telegram.py | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e7a8971ec..006baee60 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -95,10 +95,8 @@ class Telegram(RPC): CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), - #CommandHandler('reload_conf', self._reload_config), - CommandHandler(('reload_config' or 'reload_conf'), self._reload_config), - #CommandHandler('show_conf', self._reload_config), - CommandHandler('show_config' or 'show_conf', self._show_config), + CommandHandler(['reload_config', 'reload_conf'], self._reload_config), + CommandHandler(['show_config', 'show_conf'], self._show_config), CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), CommandHandler('blacklist', self._blacklist), diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ce0c9d1f0..b19c6d9ee 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -72,10 +72,9 @@ def test_init(default_conf, mocker, caplog) -> None: assert start_polling.start_polling.call_count == 1 message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " - "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " - "['performance'], ['daily'], ['count'], ['reload_config'], " - "['show_config'], ['stopbuy'], ['whitelist'], " - "['blacklist'], ['edge'], ['help'], ['version']]") + "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['performance'], " + "['daily'], ['count'], ['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " + "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]") assert log_has(message_str, caplog) From 4f643f8481af5ed983615c9a88b9296cc9a21b92 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Wed, 10 Jun 2020 17:12:21 +0200 Subject: [PATCH 138/191] Fix Flake8 error: line too long --- tests/rpc/test_rpc_telegram.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index b19c6d9ee..0a4352f5b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -72,9 +72,10 @@ def test_init(default_conf, mocker, caplog) -> None: assert start_polling.start_polling.call_count == 1 message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " - "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['performance'], " - "['daily'], ['count'], ['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " - "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]") + "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " + "['performance'], ['daily'], ['count'], ['reload_config', 'reload_conf'], " + "['show_config', 'show_conf'], ['stopbuy'], ['whitelist'], ['blacklist'], " + "['edge'], ['help'], ['version']]") assert log_has(message_str, caplog) From 69ac5c1ac7a6178113b8f6df8650661f6f3fa284 Mon Sep 17 00:00:00 2001 From: Felipe Lambert Date: Mon, 8 Jun 2020 14:55:28 -0300 Subject: [PATCH 139/191] change hyperopt return to better copy to strategy file --- freqtrade/optimize/hyperopt.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 3a28de785..58bcbd208 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -12,7 +12,7 @@ from math import ceil from collections import OrderedDict from operator import itemgetter from pathlib import Path -from pprint import pprint +from pprint import pformat from typing import Any, Dict, List, Optional import rapidjson @@ -244,11 +244,21 @@ class Hyperopt: def _params_pretty_print(params, space: str, header: str) -> None: if space in params: space_params = Hyperopt._space_params(params, space, 5) + print(f"\n # {header}") if space == 'stoploss': - print(header, space_params.get('stoploss')) + print(" stoploss =", space_params.get('stoploss')) + elif space == 'roi': + minimal_roi_result = rapidjson.dumps( + OrderedDict( + (str(k), v) for k, v in space_params.items() + ), + default=str, indent=4, number_mode=rapidjson.NM_NATIVE) + minimal_roi_result = minimal_roi_result.replace("\n", "\n ") + print(f" minimal_roi = {minimal_roi_result}") else: - print(header) - pprint(space_params, indent=4) + params_result = pformat(space_params, indent=4).replace("}", "\n}") + params_result = params_result.replace("{", "{\n ").replace("\n", "\n ") + print(f" {space}_params = {params_result}") @staticmethod def _space_params(params, space: str, r: int = None) -> Dict: From a7cd68121beb33cb40520b94439344879c1b7114 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Jun 2020 19:44:34 +0200 Subject: [PATCH 140/191] Have rest-client use new reload_config endpoint --- scripts/rest_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/rest_client.py b/scripts/rest_client.py index b26c32479..1f96bcb69 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -80,18 +80,18 @@ class FtRestClient(): return self._post("stop") def stopbuy(self): - """Stop buying (but handle sells gracefully). Use `reload_conf` to reset. + """Stop buying (but handle sells gracefully). Use `reload_config` to reset. :return: json object """ return self._post("stopbuy") - def reload_conf(self): + def reload_config(self): """Reload configuration. :return: json object """ - return self._post("reload_conf") + return self._post("reload_config") def balance(self): """Get the account balance. From c66ca957d9a6c5adc2560042d802c29c966988c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Jun 2020 19:57:47 +0200 Subject: [PATCH 141/191] Add test verifying this behaviour --- tests/pairlist/test_pairlist.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 421f06911..c67f7ae1c 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -421,6 +421,23 @@ def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist assert log_message in caplog.text +@pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) +def test__whitelist_for_active_markets_empty(mocker, whitelist_conf, markets, pairlist, tickers): + whitelist_conf['pairlists'][0]['method'] = pairlist + + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=None), + get_tickers=tickers + ) + # Assign starting whitelist + pairlist_handler = freqtrade.pairlists._pairlist_handlers[0] + with pytest.raises(OperationalException, match=r'Markets not loaded.*'): + pairlist_handler._whitelist_for_active_markets(['ETH/BTC']) + + def test_volumepairlist_invalid_sortvalue(mocker, markets, whitelist_conf): whitelist_conf['pairlists'][0].update({"sort_key": "asdf"}) From 1e7826f3922748449554d01cfb81c7c366c5babb Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 10 Jun 2020 19:57:59 +0200 Subject: [PATCH 142/191] Explicitly raise OperationalException if markets are not loaded correctly --- freqtrade/pairlist/IPairList.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index f48a7dcfd..fd25e0766 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -150,6 +150,9 @@ class IPairList(ABC): black_listed """ markets = self._exchange.markets + if not markets: + raise OperationalException( + 'Markets not loaded. Make sure that exchange is initialized correctly.') sanitized_whitelist: List[str] = [] for pair in pairlist: From ab2f5579d8ad638d10a222ff1f84fdff41306c4e Mon Sep 17 00:00:00 2001 From: CoDaXe Date: Thu, 11 Jun 2020 20:34:14 -0400 Subject: [PATCH 143/191] Update strategy-customization.md Fix typo "the an" --- docs/strategy-customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 92e4453d2..be41b196e 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -139,7 +139,7 @@ By letting the bot know how much history is needed, backtest trades can start at #### Example -Let's try to backtest 1 month (January 2019) of 5m candles using the an example strategy with EMA100, as above. +Let's try to backtest 1 month (January 2019) of 5m candles using an example strategy with EMA100, as above. ``` bash freqtrade backtesting --timerange 20190101-20190201 --ticker-interval 5m From 9615614e489d194155a896c6cb8c4ca86522ce8f Mon Sep 17 00:00:00 2001 From: CoDaXe Date: Fri, 12 Jun 2020 13:13:10 -0400 Subject: [PATCH 144/191] Update hyperopt.md Wrong flag name --- docs/hyperopt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 8efc51a39..f66534726 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -265,7 +265,7 @@ freqtrade hyperopt --timerange 20180401-20180501 Hyperopt can reuse `populate_indicators`, `populate_buy_trend`, `populate_sell_trend` from your strategy, assuming these methods are **not** in your custom hyperopt file, and a strategy is provided. ```bash -freqtrade hyperopt --strategy SampleStrategy --customhyperopt SampleHyperopt +freqtrade hyperopt --strategy SampleStrategy --hyperopt SampleHyperopt ``` ### Running Hyperopt with Smaller Search Space From 9890e26aeb7d1664088b1dc3cad3db1eba69a633 Mon Sep 17 00:00:00 2001 From: John Duong Date: Fri, 12 Jun 2020 22:10:18 -0700 Subject: [PATCH 145/191] fix SQL cheatsheet query (#3475) --- docs/sql_cheatsheet.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 895a0536a..88eb0d3d4 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -101,7 +101,7 @@ SET is_open=0, close_date=, close_rate=, close_profit=close_rate/open_rate-1, - close_profit_abs = (amount * * (1 - fee_close) - (amount * open_rate * 1 - fee_open), + close_profit_abs = (amount * * (1 - fee_close) - (amount * open_rate * 1 - fee_open)), sell_reason= WHERE id=; ``` @@ -114,7 +114,7 @@ SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496, - close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * open_rate * 1 - fee_open) + close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * open_rate * 1 - fee_open)) sell_reason='force_sell' WHERE id=31; ``` From 37bc2d28ad4572234940689fa938a48732d11207 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 13 Jun 2020 13:34:29 +0300 Subject: [PATCH 146/191] Revert "Remove _load_async_markets" This reverts commit 6744f8f052d66e1f7a49eff81c6ff57e65fec0cf. --- freqtrade/exchange/exchange.py | 17 ++++++++++++++--- tests/exchange/test_exchange.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e974655cb..bd44f56f2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -256,21 +256,32 @@ class Exchange: "Please check your config.json") raise OperationalException(f'Exchange {name} does not provide a sandbox api') + def _load_async_markets(self, reload: bool = False) -> None: + try: + if self._api_async: + asyncio.get_event_loop().run_until_complete( + self._api_async.load_markets(reload=reload)) + + except ccxt.BaseError as e: + logger.warning('Could not load async markets. Reason: %s', e) + return + def _load_markets(self) -> None: - """ Initialize markets """ + """ Initialize markets both sync and async """ try: self._api.load_markets() + self._load_async_markets() self._last_markets_refresh = arrow.utcnow().timestamp except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) def reload_markets(self) -> None: - """Reload markets if refresh interval has passed """ + """Reload markets both sync and async if refresh interval has passed """ # Check whether markets have to be reloaded if (self._last_markets_refresh > 0) and ( self._last_markets_refresh + self.markets_refresh_interval > arrow.utcnow().timestamp): - return + return None logger.debug("Performing scheduled market reload..") try: self._api.load_markets(reload=True) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 9914188b8..2b63eee23 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -130,6 +130,7 @@ def test_init_exception(default_conf, mocker): def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock())) + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') @@ -317,6 +318,19 @@ def test_set_sandbox_exception(default_conf, mocker): exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname') +def test__load_async_markets(default_conf, mocker, caplog): + exchange = get_patched_exchange(mocker, default_conf) + exchange._api_async.load_markets = get_mock_coro(None) + exchange._load_async_markets() + assert exchange._api_async.load_markets.call_count == 1 + caplog.set_level(logging.DEBUG) + + exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef")) + exchange._load_async_markets() + + assert log_has('Could not load async markets. Reason: deadbeef', caplog) + + def test__load_markets(default_conf, mocker, caplog): caplog.set_level(logging.INFO) api_mock = MagicMock() @@ -324,6 +338,7 @@ def test__load_markets(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) assert log_has('Unable to initialize markets. Reason: SomeError', caplog) @@ -391,6 +406,7 @@ def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') Exchange(default_conf) @@ -404,6 +420,7 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') with pytest.raises(OperationalException, match=r'XRP is not available as stake on .*' 'Available currencies are: BTC, ETH, USDT'): @@ -453,6 +470,7 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -465,6 +483,7 @@ def test_validate_pairs_not_available(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') with pytest.raises(OperationalException, match=r'not available'): Exchange(default_conf) @@ -479,6 +498,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'): Exchange(default_conf) @@ -497,6 +517,7 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog): }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -514,6 +535,7 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -529,6 +551,7 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') Exchange(default_conf) @@ -544,6 +567,7 @@ def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"): @@ -718,6 +742,7 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_async_markets') mocker.patch('freqtrade.exchange.Exchange.validate_pairs') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') @@ -1909,6 +1934,7 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), validate_stakecurrency=MagicMock() @@ -1942,6 +1968,7 @@ def test_merge_ft_has_dict(default_conf, mocker): def test_get_valid_pair_combination(default_conf, mocker, markets): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), markets=PropertyMock(return_value=markets)) @@ -2014,6 +2041,7 @@ def test_get_markets(default_conf, mocker, markets, expected_keys): mocker.patch.multiple('freqtrade.exchange.Exchange', _init_ccxt=MagicMock(return_value=MagicMock()), + _load_async_markets=MagicMock(), validate_pairs=MagicMock(), validate_timeframes=MagicMock(), markets=PropertyMock(return_value=markets)) From 3d9b1077612c272267363c72a1653ec39ffdaa83 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sat, 13 Jun 2020 17:12:37 +0300 Subject: [PATCH 147/191] Changes after review --- freqtrade/optimize/hyperopt.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 58bcbd208..1136fc4a7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -230,6 +230,9 @@ class Hyperopt: if space in ['buy', 'sell']: result_dict.setdefault('params', {}).update(space_params) elif space == 'roi': + # TODO: get rid of OrderedDict when support for python 3.6 will be + # dropped (dicts keep the order as the language feature) + # Convert keys in min_roi dict to strings because # rapidjson cannot dump dicts with integer keys... # OrderedDict is used to keep the numeric order of the items @@ -244,21 +247,24 @@ class Hyperopt: def _params_pretty_print(params, space: str, header: str) -> None: if space in params: space_params = Hyperopt._space_params(params, space, 5) - print(f"\n # {header}") + params_result = f"\n# {header}\n" if space == 'stoploss': - print(" stoploss =", space_params.get('stoploss')) + params_result += f"stoploss = {space_params.get('stoploss')}" elif space == 'roi': minimal_roi_result = rapidjson.dumps( + # TODO: get rid of OrderedDict when support for python 3.6 will be + # dropped (dicts keep the order as the language feature) OrderedDict( (str(k), v) for k, v in space_params.items() ), default=str, indent=4, number_mode=rapidjson.NM_NATIVE) - minimal_roi_result = minimal_roi_result.replace("\n", "\n ") - print(f" minimal_roi = {minimal_roi_result}") + params_result += f"minimal_roi = {minimal_roi_result}" else: - params_result = pformat(space_params, indent=4).replace("}", "\n}") - params_result = params_result.replace("{", "{\n ").replace("\n", "\n ") - print(f" {space}_params = {params_result}") + params_result += f"{space}_params = {pformat(space_params, indent=4)}" + params_result = params_result.replace("}", "\n}").replace("{", "{\n ") + + params_result = params_result.replace("\n", "\n ") + print(params_result) @staticmethod def _space_params(params, space: str, r: int = None) -> Dict: From ea77edce0519bdd7664fd20fd65416f85bdbaada Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sat, 13 Jun 2020 18:54:54 +0300 Subject: [PATCH 148/191] Make flake happy --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 1136fc4a7..153ae3861 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -251,9 +251,9 @@ class Hyperopt: if space == 'stoploss': params_result += f"stoploss = {space_params.get('stoploss')}" elif space == 'roi': - minimal_roi_result = rapidjson.dumps( # TODO: get rid of OrderedDict when support for python 3.6 will be # dropped (dicts keep the order as the language feature) + minimal_roi_result = rapidjson.dumps( OrderedDict( (str(k), v) for k, v in space_params.items() ), From f6f7c99b9c558a6410b98b4b884a5c952cce074a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Jun 2020 06:31:05 +0200 Subject: [PATCH 149/191] Adjust typography and add missing space Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- freqtrade/exchange/ftx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 73347f1eb..f16db96f5 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -60,7 +60,7 @@ class Ftx(Exchange): return order except ccxt.InsufficientFunds as e: raise DependencyException( - f'Insufficient funds to create {ordertype} sell order on market {pair}.' + f'Insufficient funds to create {ordertype} sell order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: @@ -91,7 +91,7 @@ class Ftx(Exchange): if len(order) == 1: return order[0] else: - raise InvalidOrderException(f"Could not get Stoploss Order for id {order_id}") + raise InvalidOrderException(f"Could not get stoploss order for id {order_id}") except ccxt.InvalidOrder as e: raise InvalidOrderException( From 534c242d1b44f0cda20202da85632936fc1e6ab3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Jun 2020 06:33:08 +0200 Subject: [PATCH 150/191] Apply typography to test too --- tests/exchange/test_ftx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index bead63096..75e98740c 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -149,7 +149,7 @@ def test_get_stoploss_order(default_conf, mocker): api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}]) exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') - with pytest.raises(InvalidOrderException, match=r"Could not get Stoploss Order for id X"): + with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"): exchange.get_stoploss_order('X', 'TKN/BTC')['status'] with pytest.raises(InvalidOrderException): From 837aedb0c7622826a2e5b7f2fc2bf105e43d472c Mon Sep 17 00:00:00 2001 From: muletman <66912721+muletman@users.noreply.github.com> Date: Sun, 14 Jun 2020 17:03:18 +0100 Subject: [PATCH 151/191] Docs: Run multiple instances for new users This is my proposition of contribution for how new users could get up and running with multiple instances of the bot, based on the conversation I had on Slack with @hroff-1902 It appeared to me that the transparent creation and usage of the sqlite databases, and the necessity to create other databases to run multiple bots at the same time was not so straightforward to me in the first place, despite browsing through the docs. It is evident now ;) but that will maybe save time for devs if any other new user come on slack with the same issue. Thanks --- HowToRunMultipleInstances.md | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 HowToRunMultipleInstances.md diff --git a/HowToRunMultipleInstances.md b/HowToRunMultipleInstances.md new file mode 100644 index 000000000..a5ec4c10c --- /dev/null +++ b/HowToRunMultipleInstances.md @@ -0,0 +1,51 @@ +# How to run multiple instances of freqtrade simultaneously + +This page is meant to be a quick tip for new users on how to run multiple bots a the same time, on the same computer (or other devices). + +In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allow you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would finish unexpectedly for one or another reason. + +As for various other things, upon docker or manual install, [freqtrade will create by default two different databases, one for dry-run, and the other for live trades.](https://www.freqtrade.io/en/latest/docker/#create-your-database-file) + +By default, executing the trade command in the command line interface, without specifying any database (`--db-url`) argument, freqtrade will store your trades and performance history in one of these two default databases, depending if dry-run mode is enabled or not. These databases are actual .sqlite files which are stored in your main freqtrade folder, under the name `tradesv3.dryrun.sqlite` for the dry-run mode, and `tradesv3.sqlite` for the live mode. + +The optional argument to the trade command used to specify the path of these files is `--db-url`. So when you are starting a bot with only the config and strategy arguments in dry-run mode for instance : + +``` +freqtrade trade -c MyConfig.json -s MyStrategy +``` + +is equivalent to : + +``` +freqtrade trade -c MyConfig.json -s MyStrategy --db-url sqlite:///tradesv3.dryrun.sqlite +``` + +That means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases. Even if you are using the same configuration file and strategy. + + If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So for example if you want to test your custom strategy in BTC vs USDT, you could type in one terminal : + +``` +freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.dryrun.sqlite +``` + +and in the other + +``` +freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.dryrun.sqlite +``` + +Conversely, if you wish to do the same thing in production mode, you will also have to create at least one new database (in addition to the default one) and specify the path to the "live" databases, for example : + +``` +freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.live.sqlite +``` + +and in the other + +``` +freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.live.sqlite +``` + +For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the following page : + +https://www.freqtrade.io/en/latest/sql_cheatsheet/ From d337fb6c6a4a3ca3e3f7bfb542b6bf7db0a788a8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Jun 2020 06:35:31 +0200 Subject: [PATCH 152/191] Update some comments --- freqtrade/commands/pairlist_commands.py | 1 - freqtrade/optimize/hyperopt_interface.py | 4 ++-- freqtrade/resolvers/strategy_resolver.py | 2 +- tests/strategy/test_strategy.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/freqtrade/commands/pairlist_commands.py b/freqtrade/commands/pairlist_commands.py index dffe0c82e..77bcb04b4 100644 --- a/freqtrade/commands/pairlist_commands.py +++ b/freqtrade/commands/pairlist_commands.py @@ -25,7 +25,6 @@ def start_test_pairlist(args: Dict[str, Any]) -> None: results = {} for curr in quote_currencies: config['stake_currency'] = curr - # Do not use timeframe set in the config pairlists = PairListManager(exchange, config) pairlists.refresh_pairlist() results[curr] = pairlists.whitelist diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 00353cbf4..65069b984 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -31,14 +31,14 @@ class IHyperOpt(ABC): Class attributes you can use: ticker_interval -> int: value of the ticker interval to use for the strategy """ - ticker_interval: str # deprecated + ticker_interval: str # DEPRECATED timeframe: str def __init__(self, config: dict) -> None: self.config = config # Assign ticker_interval to be used in hyperopt - IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECTED + IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED IHyperOpt.timeframe = str(config['timeframe']) @staticmethod diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 26bce01ca..121a04877 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -54,7 +54,7 @@ class StrategyResolver(IResolver): # Assign ticker_interval to timeframe to keep compatibility if 'timeframe' not in config: logger.warning( - "DEPRECATED: Please migrate to using timeframe instead of ticker_interval." + "DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'." ) strategy.timeframe = strategy.ticker_interval diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 85cb8c132..240f3d8ec 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -386,7 +386,7 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert isinstance(selldf, DataFrame) assert 'sell' in selldf - assert log_has('DEPRECATED: Please migrate to using timeframe instead of ticker_interval.', + assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.", caplog) From 1853350c7d344a0860d2f2d371ca211dd5f3bb4b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2020 09:01:05 +0000 Subject: [PATCH 153/191] Bump mkdocs-material from 5.2.3 to 5.3.0 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.2.3 to 5.3.0. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.2.3...5.3.0) Signed-off-by: dependabot-preview[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 b8ea338de..666cf5ac4 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==5.2.3 +mkdocs-material==5.3.0 mdx_truly_sane_lists==1.2 From d66050522c30da1d7b39fee9ff1ac5b906f39587 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2020 09:02:14 +0000 Subject: [PATCH 154/191] Bump ccxt from 1.29.52 to 1.30.2 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.29.52 to 1.30.2. - [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.29.52...1.30.2) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index dab3a5da4..a6420d76a 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.29.52 +ccxt==1.30.2 SQLAlchemy==1.3.17 python-telegram-bot==12.7 arrow==0.15.6 From f38f3643f55157e52523df7651df79002b319433 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2020 09:02:34 +0000 Subject: [PATCH 155/191] Bump pytest-cov from 2.9.0 to 2.10.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.9.0 to 2.10.0. - [Release notes](https://github.com/pytest-dev/pytest-cov/releases) - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.9.0...v2.10.0) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d62b768c1..d8d09a0c5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,7 +10,7 @@ flake8-tidy-imports==4.1.0 mypy==0.780 pytest==5.4.3 pytest-asyncio==0.12.0 -pytest-cov==2.9.0 +pytest-cov==2.10.0 pytest-mock==3.1.1 pytest-random-order==1.0.4 From df90d631fb3b32327dbae36d0e4d0b25205e7d98 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2020 10:05:20 +0000 Subject: [PATCH 156/191] Bump flake8 from 3.8.2 to 3.8.3 Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.8.2 to 3.8.3. - [Release notes](https://gitlab.com/pycqa/flake8/tags) - [Commits](https://gitlab.com/pycqa/flake8/compare/3.8.2...3.8.3) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d8d09a0c5..840eff15f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ -r requirements-hyperopt.txt coveralls==2.0.0 -flake8==3.8.2 +flake8==3.8.3 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 mypy==0.780 From e24ffebe691b3e90ac4c660edbb702aca1ca53c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Jun 2020 19:24:33 +0200 Subject: [PATCH 157/191] Move Multiple instances section to advanced-setup.md --- HowToRunMultipleInstances.md | 51 ----------------------------------- docs/advanced-setup.md | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 51 deletions(-) delete mode 100644 HowToRunMultipleInstances.md diff --git a/HowToRunMultipleInstances.md b/HowToRunMultipleInstances.md deleted file mode 100644 index a5ec4c10c..000000000 --- a/HowToRunMultipleInstances.md +++ /dev/null @@ -1,51 +0,0 @@ -# How to run multiple instances of freqtrade simultaneously - -This page is meant to be a quick tip for new users on how to run multiple bots a the same time, on the same computer (or other devices). - -In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allow you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would finish unexpectedly for one or another reason. - -As for various other things, upon docker or manual install, [freqtrade will create by default two different databases, one for dry-run, and the other for live trades.](https://www.freqtrade.io/en/latest/docker/#create-your-database-file) - -By default, executing the trade command in the command line interface, without specifying any database (`--db-url`) argument, freqtrade will store your trades and performance history in one of these two default databases, depending if dry-run mode is enabled or not. These databases are actual .sqlite files which are stored in your main freqtrade folder, under the name `tradesv3.dryrun.sqlite` for the dry-run mode, and `tradesv3.sqlite` for the live mode. - -The optional argument to the trade command used to specify the path of these files is `--db-url`. So when you are starting a bot with only the config and strategy arguments in dry-run mode for instance : - -``` -freqtrade trade -c MyConfig.json -s MyStrategy -``` - -is equivalent to : - -``` -freqtrade trade -c MyConfig.json -s MyStrategy --db-url sqlite:///tradesv3.dryrun.sqlite -``` - -That means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases. Even if you are using the same configuration file and strategy. - - If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So for example if you want to test your custom strategy in BTC vs USDT, you could type in one terminal : - -``` -freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.dryrun.sqlite -``` - -and in the other - -``` -freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.dryrun.sqlite -``` - -Conversely, if you wish to do the same thing in production mode, you will also have to create at least one new database (in addition to the default one) and specify the path to the "live" databases, for example : - -``` -freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.live.sqlite -``` - -and in the other - -``` -freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.live.sqlite -``` - -For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the following page : - -https://www.freqtrade.io/en/latest/sql_cheatsheet/ diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 95480a2c6..9848d04c8 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -4,6 +4,58 @@ This page explains some advanced tasks and configuration options that can be per If you do not know what things mentioned here mean, you probably do not need it. +## Running multiple instances of Freqtrade + +This page is meant to be a quick tip for new users on how to run multiple bots a the same time, on the same computer (or other devices). + +In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allow you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would finish unexpectedly for one or another reason. + +As for various other things, upon docker or manual install, [freqtrade will create by default two different databases, one for dry-run, and the other for live trades.](https://www.freqtrade.io/en/latest/docker/#create-your-database-file) + +By default, executing the trade command in the command line interface, without specifying any database (`--db-url`) argument, freqtrade will store your trades and performance history in one of these two default databases, depending if dry-run mode is enabled or not. These databases are actual .sqlite files which are stored in your main freqtrade folder, under the name `tradesv3.dryrun.sqlite` for the dry-run mode, and `tradesv3.sqlite` for the live mode. + +The optional argument to the trade command used to specify the path of these files is `--db-url`. So when you are starting a bot with only the config and strategy arguments in dry-run mode for instance : + +``` bash +freqtrade trade -c MyConfig.json -s MyStrategy +``` + +is equivalent to: + +``` bash +freqtrade trade -c MyConfig.json -s MyStrategy --db-url sqlite:///tradesv3.dryrun.sqlite +``` + +That means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases. Even if you are using the same configuration file and strategy. + + If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So for example if you want to test your custom strategy in BTC vs USDT, you could type in one terminal : + +``` bash +freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.dryrun.sqlite +``` + +and in the other + +``` bash +freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.dryrun.sqlite +``` + +Conversely, if you wish to do the same thing in production mode, you will also have to create at least one new database (in addition to the default one) and specify the path to the "live" databases, for example : + +``` bash +freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.live.sqlite +``` + +and in the other + +``` bash +freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.live.sqlite +``` + +For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the following page: + +https://www.freqtrade.io/en/latest/sql_cheatsheet/ + ## Configure the bot running as a systemd service Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup. From 61ce18a4abb17b3c5c7d46701b62a4b4504410ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Jun 2020 19:35:57 +0200 Subject: [PATCH 158/191] Slightly reword certain things - add link in FAQ --- docs/advanced-setup.md | 56 ++++++++++++++++++++---------------------- docs/faq.md | 4 +++ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 9848d04c8..21e428fa7 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -6,55 +6,51 @@ If you do not know what things mentioned here mean, you probably do not need it. ## Running multiple instances of Freqtrade -This page is meant to be a quick tip for new users on how to run multiple bots a the same time, on the same computer (or other devices). +This section will show you how to run multiple bots a the same time, on the same computer (or other devices). -In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allow you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would finish unexpectedly for one or another reason. +### Things to consider -As for various other things, upon docker or manual install, [freqtrade will create by default two different databases, one for dry-run, and the other for live trades.](https://www.freqtrade.io/en/latest/docker/#create-your-database-file) +* use different database files. +* use different telegram Bots (requires 2 different configuration files). +* use different ports (*applies only when webserver is enabled). -By default, executing the trade command in the command line interface, without specifying any database (`--db-url`) argument, freqtrade will store your trades and performance history in one of these two default databases, depending if dry-run mode is enabled or not. These databases are actual .sqlite files which are stored in your main freqtrade folder, under the name `tradesv3.dryrun.sqlite` for the dry-run mode, and `tradesv3.sqlite` for the live mode. +#### Different database files -The optional argument to the trade command used to specify the path of these files is `--db-url`. So when you are starting a bot with only the config and strategy arguments in dry-run mode for instance : +In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allows you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would be restarted or would be terminated unexpectently. + +Freqtrade will, by default, use seperate database files for dry-run and live bots (this assumes no database-url is given in either configuration nor via command line argument). +For live trading mode, the default database will be `tradesv3.sqlite`, and for dry-run, it will be `tradesv3.dryrun.sqlite`. + +The optional argument to the trade command used to specify the path of these files is `--db-url`, which requires a valid SQLAlchemy url. +So when you are starting a bot with only the config and strategy arguments in dry-run mode, the following 2 commands would have the same outcome. ``` bash freqtrade trade -c MyConfig.json -s MyStrategy -``` - -is equivalent to: - -``` bash +# is equivalent to freqtrade trade -c MyConfig.json -s MyStrategy --db-url sqlite:///tradesv3.dryrun.sqlite ``` -That means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases. Even if you are using the same configuration file and strategy. +That means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases. - If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So for example if you want to test your custom strategy in BTC vs USDT, you could type in one terminal : +If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So to test your custom strategy in BTC and USDT, you could use the following commands (in 2 seperate terminals): ``` bash -freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.dryrun.sqlite +# Terminal 1: +freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesBTC.dryrun.sqlite +# Terminal 2: +freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesUSDT.dryrun.sqlite ``` -and in the other +Conversely, if you wish to do the same thing in production mode, you will also have to create at least one new database (in addition to the default one) and specify the path to the "live" databases, for example: ``` bash -freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.dryrun.sqlite +# Terminal 1: +freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesBTC.live.sqlite +# Terminal 2: +freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesUSDT.live.sqlite ``` -Conversely, if you wish to do the same thing in production mode, you will also have to create at least one new database (in addition to the default one) and specify the path to the "live" databases, for example : - -``` bash -freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite://path/tradesBTC.live.sqlite -``` - -and in the other - -``` bash -freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite://path/path/tradesUSDT.live.sqlite -``` - -For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the following page: - -https://www.freqtrade.io/en/latest/sql_cheatsheet/ +For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md) ## Configure the bot running as a systemd service diff --git a/docs/faq.md b/docs/faq.md index 8e8a1bf35..31c49171d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -45,6 +45,10 @@ the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-c You can use the `/forcesell all` command from Telegram. +### I want to run multiple bots on the same machine + +Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade). + ### I'm getting the "RESTRICTED_MARKET" message in the log Currently known to happen for US Bittrex users. From 5e4dd44155958487d5ae141953951f9471a3843f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 15 Jun 2020 20:55:06 +0200 Subject: [PATCH 159/191] Fix formatting --- docs/advanced-setup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 21e428fa7..d3f692f33 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -6,13 +6,13 @@ If you do not know what things mentioned here mean, you probably do not need it. ## Running multiple instances of Freqtrade -This section will show you how to run multiple bots a the same time, on the same computer (or other devices). +This section will show you how to run multiple bots a the same time, on the same machine. ### Things to consider * use different database files. * use different telegram Bots (requires 2 different configuration files). -* use different ports (*applies only when webserver is enabled). +* use different ports (applies only when webserver is enabled). #### Different database files From 9cc04c929ff13595d0e29ab72c75673233eaa648 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jun 2020 07:17:15 +0200 Subject: [PATCH 160/191] Fix documentation typo --- docs/data-download.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data-download.md b/docs/data-download.md index 903d62854..3fb775e69 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -109,7 +109,7 @@ The following command will convert all candle (OHLCV) data available in `~/.freq It'll also remove original json data files (`--erase` parameter). ``` bash -freqtrade convert-data --format-from json --format-to jsongz --data-dir ~/.freqtrade/data/binance -t 5m 15m --erase +freqtrade convert-data --format-from json --format-to jsongz --datadir ~/.freqtrade/data/binance -t 5m 15m --erase ``` #### Subcommand convert-trade data @@ -155,7 +155,7 @@ The following command will convert all available trade-data in `~/.freqtrade/dat It'll also remove original jsongz data files (`--erase` parameter). ``` bash -freqtrade convert-trade-data --format-from jsongz --format-to json --data-dir ~/.freqtrade/data/kraken --erase +freqtrade convert-trade-data --format-from jsongz --format-to json --datadir ~/.freqtrade/data/kraken --erase ``` ### Pairs file From 3517c86fa28786630bcb49c3c58bcddd41f0cf74 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 16 Jun 2020 16:02:38 +0200 Subject: [PATCH 161/191] Fail if both ticker_interval and timeframe are present in a configuration Otherwise the wrong might be used, as it's unclear which one the intend of the user is --- .../configuration/deprecated_settings.py | 5 +++++ tests/test_configuration.py | 20 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index cefc6ac14..03ed41ab8 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -72,4 +72,9 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: "DEPRECATED: " "Please use 'timeframe' instead of 'ticker_interval." ) + if 'timeframe' in config: + raise OperationalException( + "Both 'timeframe' and 'ticker_interval' detected." + "Please remove 'ticker_interval' from your configuration to continue operating." + ) config['timeframe'] = config['ticker_interval'] diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 689e62ab9..cccc87670 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1144,11 +1144,21 @@ def test_process_deprecated_setting(mocker, default_conf, caplog): def test_process_deprecated_ticker_interval(mocker, default_conf, caplog): message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." - process_temporary_deprecated_settings(default_conf) + config = deepcopy(default_conf) + process_temporary_deprecated_settings(config) assert not log_has(message, caplog) - del default_conf['timeframe'] - default_conf['ticker_interval'] = '15m' - process_temporary_deprecated_settings(default_conf) + del config['timeframe'] + config['ticker_interval'] = '15m' + process_temporary_deprecated_settings(config) assert log_has(message, caplog) - assert default_conf['ticker_interval'] == '15m' + assert config['ticker_interval'] == '15m' + + config = deepcopy(default_conf) + # Have both timeframe and ticker interval in config + # Can also happen when using ticker_interval in configuration, and --timeframe as cli argument + config['timeframe'] = '5m' + config['ticker_interval'] = '4h' + with pytest.raises(OperationalException, + match=r"Both 'timeframe' and 'ticker_interval' detected."): + process_temporary_deprecated_settings(config) From d4fb5af456771083cf3facfd296fc59001d59fdb Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 17 Jun 2020 07:23:20 +0200 Subject: [PATCH 162/191] Also reload async markets fixes #2876 - Logs and Empty ticker history for new pair --- freqtrade/exchange/exchange.py | 2 ++ tests/exchange/test_exchange.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 35c62db27..b62410c34 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -285,6 +285,8 @@ class Exchange: logger.debug("Performing scheduled market reload..") try: self._api.load_markets(reload=True) + # Also reload async markets to avoid issues with newly listed pairs + self._load_async_markets(reload=True) self._last_markets_refresh = arrow.utcnow().timestamp except ccxt.BaseError: logger.exception("Could not reload markets.") diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 762ee295e..c9a600d50 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -365,6 +365,7 @@ def test_reload_markets(default_conf, mocker, caplog): default_conf['exchange']['markets_refresh_interval'] = 10 exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance", mock_markets=False) + exchange._load_async_markets = MagicMock() exchange._last_markets_refresh = arrow.utcnow().timestamp updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} @@ -373,11 +374,13 @@ def test_reload_markets(default_conf, mocker, caplog): # less than 10 minutes have passed, no reload exchange.reload_markets() assert exchange.markets == initial_markets + assert exchange._load_async_markets.call_count == 0 # more than 10 minutes have passed, reload is executed exchange._last_markets_refresh = arrow.utcnow().timestamp - 15 * 60 exchange.reload_markets() assert exchange.markets == updated_markets + assert exchange._load_async_markets.call_count == 1 assert log_has('Performing scheduled market reload..', caplog) From e2465f979bd6641f0413f58719622e471afef807 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 17 Jun 2020 08:33:53 +0200 Subject: [PATCH 163/191] Correctly mock out async_reload --- tests/conftest.py | 1 + tests/exchange/test_exchange.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3ca431f40..a4106c767 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,6 +56,7 @@ def patched_configuration_load_config_file(mocker, config) -> None: def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> None: + mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index c9a600d50..700aff969 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -319,7 +319,12 @@ def test_set_sandbox_exception(default_conf, mocker): def test__load_async_markets(default_conf, mocker, caplog): - exchange = get_patched_exchange(mocker, default_conf) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt') + mocker.patch('freqtrade.exchange.Exchange.validate_pairs') + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') + mocker.patch('freqtrade.exchange.Exchange._load_markets') + mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') + exchange = Exchange(default_conf) exchange._api_async.load_markets = get_mock_coro(None) exchange._load_async_markets() assert exchange._api_async.load_markets.call_count == 1 From 0b8cac68befa83b2e705da8d350442b7a06cfe8c Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Wed, 17 Jun 2020 22:49:01 +0300 Subject: [PATCH 164/191] Improve advanced-setup.md --- docs/advanced-setup.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index d3f692f33..f03bc10c0 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -6,20 +6,20 @@ If you do not know what things mentioned here mean, you probably do not need it. ## Running multiple instances of Freqtrade -This section will show you how to run multiple bots a the same time, on the same machine. +This section will show you how to run multiple bots at the same time, on the same machine. ### Things to consider -* use different database files. -* use different telegram Bots (requires 2 different configuration files). -* use different ports (applies only when webserver is enabled). +* Use different database files. +* Use different Telegram bots (requires multiple different configuration files; applies only when Telegram is enabled). +* Use different ports (applies only when Freqtrade REST API webserver is enabled). -#### Different database files +### Different database files -In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allows you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would be restarted or would be terminated unexpectently. +In order to keep track of your trades, profits, etc., freqtrade is using a SQLite database where it stores various types of information such as the trades you performed in the past and the current position(s) you are holding at any time. This allows you to keep track of your profits, but most importantly, keep track of ongoing activity if the bot process would be restarted or would be terminated unexpectedly. -Freqtrade will, by default, use seperate database files for dry-run and live bots (this assumes no database-url is given in either configuration nor via command line argument). -For live trading mode, the default database will be `tradesv3.sqlite`, and for dry-run, it will be `tradesv3.dryrun.sqlite`. +Freqtrade will, by default, use separate database files for dry-run and live bots (this assumes no database-url is given in either configuration nor via command line argument). +For live trading mode, the default database will be `tradesv3.sqlite` and for dry-run it will be `tradesv3.dryrun.sqlite`. The optional argument to the trade command used to specify the path of these files is `--db-url`, which requires a valid SQLAlchemy url. So when you are starting a bot with only the config and strategy arguments in dry-run mode, the following 2 commands would have the same outcome. @@ -30,9 +30,9 @@ freqtrade trade -c MyConfig.json -s MyStrategy freqtrade trade -c MyConfig.json -s MyStrategy --db-url sqlite:///tradesv3.dryrun.sqlite ``` -That means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases. +It means that if you are running the trade command in two different terminals, for example to test your strategy both for trades in USDT and in another instance for trades in BTC, you will have to run them with different databases. -If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So to test your custom strategy in BTC and USDT, you could use the following commands (in 2 seperate terminals): +If you specify the URL of a database which does not exist, freqtrade will create one with the name you specified. So to test your custom strategy with BTC and USDT stake currencies, you could use the following commands (in 2 separate terminals): ``` bash # Terminal 1: @@ -50,7 +50,7 @@ freqtrade trade -c MyConfigBTC.json -s MyCustomStrategy --db-url sqlite:///user_ freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user_data/tradesUSDT.live.sqlite ``` -For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md) +For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md). ## Configure the bot running as a systemd service From dbf14ccf13d00be1687d4d3b45af252384bbec7f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:13:36 +0000 Subject: [PATCH 165/191] Bump mypy from 0.780 to 0.781 Bumps [mypy](https://github.com/python/mypy) from 0.780 to 0.781. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.780...v0.781) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 840eff15f..91b33c573 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==2.0.0 flake8==3.8.3 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 -mypy==0.780 +mypy==0.781 pytest==5.4.3 pytest-asyncio==0.12.0 pytest-cov==2.10.0 From 993333a61cdc99afbc04aa7e96ffcb582e181776 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:14:25 +0000 Subject: [PATCH 166/191] Bump pandas from 1.0.4 to 1.0.5 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.4 to 1.0.5. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md) - [Commits](https://github.com/pandas-dev/pandas/compare/v1.0.4...v1.0.5) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2c68b8f2c..8376e41aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ -r requirements-common.txt numpy==1.18.5 -pandas==1.0.4 +pandas==1.0.5 From 432c1b54bfc8332aab70f1a08ba491d08972b7a8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:14:57 +0000 Subject: [PATCH 167/191] Bump arrow from 0.15.6 to 0.15.7 Bumps [arrow](https://github.com/crsmithdev/arrow) from 0.15.6 to 0.15.7. - [Release notes](https://github.com/crsmithdev/arrow/releases) - [Changelog](https://github.com/crsmithdev/arrow/blob/master/CHANGELOG.rst) - [Commits](https://github.com/crsmithdev/arrow/compare/0.15.6...0.15.7) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index a6420d76a..767e993de 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -3,7 +3,7 @@ ccxt==1.30.2 SQLAlchemy==1.3.17 python-telegram-bot==12.7 -arrow==0.15.6 +arrow==0.15.7 cachetools==4.1.0 requests==2.23.0 urllib3==1.25.9 From dcc95d09339a104977a347499ada458f1855fba7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:15:33 +0000 Subject: [PATCH 168/191] Bump mkdocs-material from 5.3.0 to 5.3.2 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.3.0 to 5.3.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.3.0...5.3.2) Signed-off-by: dependabot-preview[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 666cf5ac4..7ddfc1dfb 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==5.3.0 +mkdocs-material==5.3.2 mdx_truly_sane_lists==1.2 From b29f12bfad2e526c4dd3ae2b1bc0fcebe5bc4a9f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:16:00 +0000 Subject: [PATCH 169/191] Bump scipy from 1.4.1 to 1.5.0 Bumps [scipy](https://github.com/scipy/scipy) from 1.4.1 to 1.5.0. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.4.1...v1.5.0) Signed-off-by: dependabot-preview[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index e1b3fef4f..aedfc0eaa 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.4.1 +scipy==1.5.0 scikit-learn==0.23.1 scikit-optimize==0.7.4 filelock==3.0.12 From 6d82e41dd1bcf966d89d4ae6d8d7104b8cb14809 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:17:07 +0000 Subject: [PATCH 170/191] Bump ccxt from 1.30.2 to 1.30.31 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.30.2 to 1.30.31. - [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.30.2...1.30.31) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index a6420d76a..2e90c6cc1 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.30.2 +ccxt==1.30.31 SQLAlchemy==1.3.17 python-telegram-bot==12.7 arrow==0.15.6 From 9af1dae53e40f35beb4fc41069a655c95449f52c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:48:54 +0000 Subject: [PATCH 171/191] Bump numpy from 1.18.5 to 1.19.0 Bumps [numpy](https://github.com/numpy/numpy) from 1.18.5 to 1.19.0. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/master/doc/HOWTO_RELEASE.rst.txt) - [Commits](https://github.com/numpy/numpy/compare/v1.18.5...v1.19.0) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8376e41aa..1e61d165f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Load common requirements -r requirements-common.txt -numpy==1.18.5 +numpy==1.19.0 pandas==1.0.5 From 1854e3053890552e3892757568c359219c6a3106 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 11:56:51 +0000 Subject: [PATCH 172/191] Bump requests from 2.23.0 to 2.24.0 Bumps [requests](https://github.com/psf/requests) from 2.23.0 to 2.24.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/master/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.23.0...v2.24.0) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 767e993de..3519eda69 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -5,7 +5,7 @@ SQLAlchemy==1.3.17 python-telegram-bot==12.7 arrow==0.15.7 cachetools==4.1.0 -requests==2.23.0 +requests==2.24.0 urllib3==1.25.9 wrapt==1.12.1 jsonschema==3.2.0 From f2807143c65c7bb3e9f46566cb1e3d2f43952a22 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 12:10:52 +0000 Subject: [PATCH 173/191] Bump ccxt from 1.30.2 to 1.30.34 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.30.2 to 1.30.34. - [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.30.2...1.30.34) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 767e993de..1c981aee3 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.30.2 +ccxt==1.30.34 SQLAlchemy==1.3.17 python-telegram-bot==12.7 arrow==0.15.7 From 3624aec059f0bfc5f8aa587c18fcc97cf7a5cd8b Mon Sep 17 00:00:00 2001 From: gambcl Date: Wed, 24 Jun 2020 15:21:28 +0100 Subject: [PATCH 174/191] Typos --- 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 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index fd25e0766..1cca00eba 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 requries tickers, an empty List is passed + If no Pairlist requires tickers, an empty List is passed as tickers argument to filter_pairlist """ diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index 0331347be..120f7c4e0 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -27,7 +27,7 @@ class PrecisionFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requries tickers, an empty List is passed + If no Pairlist requires tickers, an empty List is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py index b85d68269..29dd88a76 100644 --- a/freqtrade/pairlist/PriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -24,7 +24,7 @@ class PriceFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requries tickers, an empty List is passed + If no Pairlist requires tickers, an empty List is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/ShuffleFilter.py b/freqtrade/pairlist/ShuffleFilter.py index ba3792213..eb4f6dcc3 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 requries tickers, an empty List is passed + If no Pairlist requires tickers, an empty List is passed as tickers argument to filter_pairlist """ return False diff --git a/freqtrade/pairlist/SpreadFilter.py b/freqtrade/pairlist/SpreadFilter.py index 0147c0068..2527a3131 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 requries tickers, an empty List is passed + If no Pairlist requires tickers, an empty List is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index b5c1bc767..aa6268ba3 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -28,7 +28,7 @@ class StaticPairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requries tickers, an empty List is passed + If no Pairlist requires tickers, an empty List is passed as tickers argument to filter_pairlist """ return False diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index d32be3dc9..35dce93eb 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -54,7 +54,7 @@ class VolumePairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requries tickers, an empty List is passed + If no Pairlist requires tickers, an empty List is passed as tickers argument to filter_pairlist """ return True From 676006b99c0610fcbb975325b4544597bc0fcaa7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 24 Jun 2020 17:40:23 +0200 Subject: [PATCH 175/191] --dl-trades should also support increasing download span (by downloading the whole dataset again to avoid missing data in the middle). --- freqtrade/data/history/history_utils.py | 5 +++++ tests/conftest.py | 2 +- tests/data/test_history.py | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 4f3f75a87..58bd752ea 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -270,6 +270,11 @@ def _download_trades_history(exchange: Exchange, # DEFAULT_TRADES_COLUMNS: 0 -> timestamp # DEFAULT_TRADES_COLUMNS: 1 -> id + if trades and since < trades[0][0]: + # since is before the first trade + logger.info(f"Start earlier than available data. Redownloading trades for {pair}...") + trades = [] + from_id = trades[-1][1] if trades else None if trades and since < trades[-1][0]: # Reset since to the last available point diff --git a/tests/conftest.py b/tests/conftest.py index a4106c767..f2143e60e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1424,7 +1424,7 @@ def trades_for_order(): @pytest.fixture(scope="function") def trades_history(): - return [[1565798399463, '126181329', None, 'buy', 0.019627, 0.04, 0.00078508], + return [[1565798389463, '126181329', None, 'buy', 0.019627, 0.04, 0.00078508], [1565798399629, '126181330', None, 'buy', 0.019627, 0.244, 0.004788987999999999], [1565798399752, '126181331', None, 'sell', 0.019626, 0.011, 0.00021588599999999999], [1565798399862, '126181332', None, 'sell', 0.019626, 0.011, 0.00021588599999999999], diff --git a/tests/data/test_history.py b/tests/data/test_history.py index c52163bbc..c2eb2d715 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -557,6 +557,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad assert ght_mock.call_count == 1 # Check this in seconds - since we had to convert to seconds above too. assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time2 - 5 + assert ght_mock.call_args_list[0][1]['from_id'] is not None # clean files freshly downloaded _clean_test_file(file1) @@ -568,6 +569,27 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad pair='ETH/BTC') assert log_has_re('Failed to download historic trades for pair: "ETH/BTC".*', caplog) + file2 = testdatadir / 'XRP_ETH-trades.json.gz' + + _backup_file(file2, True) + + ght_mock.reset_mock() + mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', + ght_mock) + # Since before first start date + since_time = int(trades_history[0][0] // 1000) - 500 + timerange = TimeRange('date', None, since_time, 0) + + assert _download_trades_history(data_handler=data_handler, exchange=exchange, + pair='XRP/ETH', timerange=timerange) + + assert ght_mock.call_count == 1 + + assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time + assert ght_mock.call_args_list[0][1]['from_id'] is None + assert log_has_re(r'Start earlier than available data. Redownloading trades for.*', caplog) + _clean_test_file(file2) + def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog): From b77a105778842ee012a122ac5f9a8f218fbc3716 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 24 Jun 2020 20:32:19 +0200 Subject: [PATCH 176/191] Add CORS_origins key to configuration --- config.json.example | 1 + config_binance.json.example | 1 + config_full.json.example | 1 + config_kraken.json.example | 1 + docs/rest-api.md | 24 ++++++++++++++++++++++++ freqtrade/constants.py | 2 ++ freqtrade/rpc/api_server.py | 4 +++- freqtrade/templates/base_config.json.j2 | 1 + 8 files changed, 34 insertions(+), 1 deletion(-) diff --git a/config.json.example b/config.json.example index 9e3daa2b5..77a147d0c 100644 --- a/config.json.example +++ b/config.json.example @@ -82,6 +82,7 @@ "listen_port": 8080, "verbosity": "info", "jwt_secret_key": "somethingrandom", + "CORS_origins": [], "username": "", "password": "" }, diff --git a/config_binance.json.example b/config_binance.json.example index b45e69bba..82943749d 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -87,6 +87,7 @@ "listen_port": 8080, "verbosity": "info", "jwt_secret_key": "somethingrandom", + "CORS_origins": [], "username": "", "password": "" }, diff --git a/config_full.json.example b/config_full.json.example index 1fd1b44a5..3a8667da4 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -123,6 +123,7 @@ "listen_port": 8080, "verbosity": "info", "jwt_secret_key": "somethingrandom", + "CORS_origins": [], "username": "freqtrader", "password": "SuperSecurePassword" }, diff --git a/config_kraken.json.example b/config_kraken.json.example index 7e4001ff3..fb983a4a3 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -93,6 +93,7 @@ "listen_port": 8080, "verbosity": "info", "jwt_secret_key": "somethingrandom", + "CORS_origins": [], "username": "", "password": "" }, diff --git a/docs/rest-api.md b/docs/rest-api.md index 33f62f884..630c952b4 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -13,6 +13,7 @@ Sample configuration: "listen_port": 8080, "verbosity": "info", "jwt_secret_key": "somethingrandom", + "CORS_origins": [], "username": "Freqtrader", "password": "SuperSecret1!" }, @@ -232,3 +233,26 @@ Since the access token has a short timeout (15 min) - the `token/refresh` reques > curl -X POST --header "Authorization: Bearer ${refresh_token}"http://localhost:8080/api/v1/token/refresh {"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODkxMTk5NzQsIm5iZiI6MTU4OTExOTk3NCwianRpIjoiMDBjNTlhMWUtMjBmYS00ZTk0LTliZjAtNWQwNTg2MTdiZDIyIiwiZXhwIjoxNTg5MTIwODc0LCJpZGVudGl0eSI6eyJ1IjoiRnJlcXRyYWRlciJ9LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.1seHlII3WprjjclY6DpRhen0rqdF4j6jbvxIhUFaSbs"} ``` + +## CORS + +All web-based frontends are subject to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - Cross-Origin Resource Sharing. +Since most request to the Freqtrade API must be authenticated, a proper CORS policy is key to avoid security problems. +Also, the Standard disallows `*` CORS policies for requests with credentials, so this setting must be done appropriately. + +Users can configure this themselfs via the `CORS_origins` configuration setting. +It consists of a list of allowed sites that are allowed to consume resources from the bot's API. + +Assuming your Application would be deployed as `https://frequi.freqtrade.io/home/` - this would mean that the following configuration becomes necessary: + +```jsonc +{ + //... + "jwt_secret_key": "somethingrandom", + "CORS_origins": ["https://frequi.freqtrade.io"], + //... +} +``` + +!!! Note + We strongly recommend to also set `jwt_secret_key` to something random and known only to yourself to avoid unauthorized access to your bot. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 6741e0605..1f8944ed9 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -221,6 +221,8 @@ CONF_SCHEMA = { }, 'username': {'type': 'string'}, 'password': {'type': 'string'}, + 'jwt_secret_key': {'type': 'string'}, + 'CORS_origins': {'type': 'array', 'items': {'type': 'string'}}, 'verbosity': {'type': 'string', 'enum': ['error', 'info']}, }, 'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password'] diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index f424bea92..a2cef9a98 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -90,7 +90,9 @@ class ApiServer(RPC): self._config = freqtrade.config self.app = Flask(__name__) self._cors = CORS(self.app, - resources={r"/api/*": {"supports_credentials": True, }} + resources={r"/api/*": { + "supports_credentials": True, + "origins": self._config['api_server'].get('CORS_origins', [])}} ) # Setup the Flask-JWT-Extended extension diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 118ae348b..b362690f9 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -59,6 +59,7 @@ "listen_port": 8080, "verbosity": "info", "jwt_secret_key": "somethingrandom", + "CORS_origins": [], "username": "", "password": "" }, From 5423d8588ecb6bf1c9094f9ca89b19585fe3ddaf Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 24 Jun 2020 20:32:35 +0200 Subject: [PATCH 177/191] Test for cors settings --- tests/rpc/test_rpc_apiserver.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8e73eacf8..0acb31282 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -24,6 +24,7 @@ def botclient(default_conf, mocker): default_conf.update({"api_server": {"enabled": True, "listen_ip_address": "127.0.0.1", "listen_port": 8080, + "CORS_origins": ['http://example.com'], "username": _TEST_USER, "password": _TEST_PASS, }}) @@ -40,13 +41,13 @@ def client_post(client, url, data={}): content_type="application/json", data=data, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), - 'Origin': 'example.com'}) + 'Origin': 'http://example.com'}) def client_get(client, url): # Add fake Origin to ensure CORS kicks in return client.get(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), - 'Origin': 'example.com'}) + 'Origin': 'http://example.com'}) def assert_response(response, expected_code=200, needs_cors=True): @@ -54,6 +55,7 @@ def assert_response(response, expected_code=200, needs_cors=True): assert response.content_type == "application/json" if needs_cors: assert ('Access-Control-Allow-Credentials', 'true') in response.headers._list + assert ('Access-Control-Allow-Origin', 'http://example.com') in response.headers._list def test_api_not_found(botclient): @@ -110,7 +112,7 @@ def test_api_token_login(botclient): rc = client.get(f"{BASE_URI}/count", content_type="application/json", headers={'Authorization': f'Bearer {rc.json["access_token"]}', - 'Origin': 'example.com'}) + 'Origin': 'http://example.com'}) assert_response(rc) @@ -122,7 +124,7 @@ def test_api_token_refresh(botclient): content_type="application/json", data=None, headers={'Authorization': f'Bearer {rc.json["refresh_token"]}', - 'Origin': 'example.com'}) + 'Origin': 'http://example.com'}) assert_response(rc) assert 'access_token' in rc.json assert 'refresh_token' not in rc.json From ab7f5a2bcf41e3e0c988b5e66d8f02d6ed39a4b4 Mon Sep 17 00:00:00 2001 From: gambcl Date: Wed, 24 Jun 2020 23:58:12 +0100 Subject: [PATCH 178/191] Added pairslist AgeFilter --- config_full.json.example | 1 + docs/configuration.md | 14 +++++- freqtrade/constants.py | 3 +- freqtrade/pairlist/AgeFilter.py | 76 +++++++++++++++++++++++++++++++++ tests/pairlist/test_pairlist.py | 76 +++++++++++++++++++++++++++++++-- 5 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 freqtrade/pairlist/AgeFilter.py diff --git a/config_full.json.example b/config_full.json.example index 1fd1b44a5..5b8fa256b 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -64,6 +64,7 @@ "sort_key": "quoteVolume", "refresh_period": 1800 }, + {"method": "AgeFilter", "min_days_listed": 10}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "SpreadFilter", "max_spread_ratio": 0.005} diff --git a/docs/configuration.md b/docs/configuration.md index 8438d55da..e7a79361a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -592,7 +592,7 @@ Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler). -Additionaly, [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter) and [`SpreadFilter`](#spreadfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. +Additionaly, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter) and [`SpreadFilter`](#spreadfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler. @@ -602,6 +602,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) * [`PrecisionFilter`](#precisionfilter) * [`PriceFilter`](#pricefilter) * [`ShuffleFilter`](#shufflefilter) @@ -645,6 +646,16 @@ The `refresh_period` setting allows to define the period (in seconds), at which }], ``` +#### AgeFilter + +Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`). + +When pairs are first listed on an exchange they can suffer huge price drops and volatility +in the first few days while the pair goes through its price-discovery period. Bots can often +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. + #### PrecisionFilter Filters low-value coins which would not allow setting stoplosses. @@ -692,6 +703,7 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, "number_assets": 20, "sort_key": "quoteVolume", }, + {"method": "AgeFilter", "min_days_listed": 10}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 6741e0605..92824f4c4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -22,7 +22,8 @@ ORDERBOOK_SIDES = ['ask', 'bid'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', - 'PrecisionFilter', 'PriceFilter', 'ShuffleFilter', 'SpreadFilter'] + 'AgeFilter', 'PrecisionFilter', 'PriceFilter', + 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz'] DRY_RUN_WALLET = 1000 MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py new file mode 100644 index 000000000..a23682599 --- /dev/null +++ b/freqtrade/pairlist/AgeFilter.py @@ -0,0 +1,76 @@ +""" +Minimum age (days listed) pair list filter +""" +import logging +import arrow +from typing import Any, Dict + +from freqtrade.misc import plural +from freqtrade.pairlist.IPairList import IPairList + + +logger = logging.getLogger(__name__) + + +class AgeFilter(IPairList): + + # Checked symbols cache (dictionary of ticker symbol => timestamp) + _symbolsChecked: Dict[str, int] = {} + + 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._min_days_listed = pairlistconfig.get('min_days_listed', 10) + self._enabled = self._min_days_listed >= 1 + + @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 age less than " + f"{self._min_days_listed} {plural(self._min_days_listed, 'day')}.") + + def _validate_pair(self, ticker: dict) -> bool: + """ + Validate age for the ticker + :param ticker: ticker dict as returned from ccxt.load_markets() + :return: True if the pair can stay, False if it should be removed + """ + + # Check symbol in cache + if ticker['symbol'] in self._symbolsChecked: + return True + + since_ms = int(arrow.utcnow() + .floor('day') + .shift(days=-self._min_days_listed) + .float_timestamp) * 1000 + + daily_candles = self._exchange.get_historic_ohlcv(pair=ticker['symbol'], + timeframe='1d', + since_ms=since_ms) + + if daily_candles is not None: + if len(daily_candles) > self._min_days_listed: + # We have fetched at least the minimum required number of daily candles + # Add to cache, store the time we last checked this symbol + self._symbolsChecked[ticker['symbol']] = int(arrow.utcnow().float_timestamp) * 1000 + return True + else: + self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, " + f"because age is less than " + f"{self._min_days_listed} " + f"{plural(self._min_days_listed, 'day')}") + return False + return False diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index c67f7ae1c..87ecced21 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -57,6 +57,31 @@ def whitelist_conf_2(default_conf): return default_conf +@pytest.fixture(scope="function") +def whitelist_conf_3(default_conf): + default_conf['stake_currency'] = 'BTC' + default_conf['exchange']['pair_whitelist'] = [ + 'ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC', + 'BTT/BTC', 'HOT/BTC', 'FUEL/BTC', 'XRP/BTC' + ] + default_conf['exchange']['pair_blacklist'] = [ + 'BLK/BTC' + ] + default_conf['pairlists'] = [ + { + "method": "VolumePairList", + "number_assets": 5, + "sort_key": "quoteVolume", + "refresh_period": 0, + }, + { + "method": "AgeFilter", + "min_days_listed": 2 + } + ] + return default_conf + + @pytest.fixture(scope="function") def static_pl_conf(whitelist_conf): whitelist_conf['pairlists'] = [ @@ -220,11 +245,20 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # No pair for ETH, all handlers ([{"method": "StaticPairList"}, {"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + {"method": "AgeFilter", "min_days_listed": 2}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.03}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, {"method": "ShuffleFilter"}], "ETH", []), + # AgeFilter and VolumePairList (require 2 days only, all should pass age test) + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + {"method": "AgeFilter", "min_days_listed": 2}], + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']), + # AgeFilter and VolumePairList (require 10 days, all should fail age test) + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + {"method": "AgeFilter", "min_days_listed": 10}], + "BTC", []), # Precisionfilter and quote volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}], @@ -272,7 +306,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # ShuffleFilter, no seed ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "ShuffleFilter"}], - "USDT", 3), # whitelist_result is integer -- check only lenght of randomized pairlist + "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 # PrecisionFilter after StaticPairList ([{"method": "StaticPairList"}, {"method": "PrecisionFilter"}], @@ -307,8 +344,8 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "BTC", 'static_in_the_middle'), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, - pairlists, base_currency, whitelist_result, - caplog) -> None: + ohlcv_history_list, pairlists, base_currency, + whitelist_result, caplog) -> None: whitelist_conf['pairlists'] = pairlists whitelist_conf['stake_currency'] = base_currency @@ -324,8 +361,12 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers, - markets=PropertyMock(return_value=shitcoinmarkets), + markets=PropertyMock(return_value=shitcoinmarkets) ) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), + ) # Set whitelist_result to None if pairlist is invalid and should produce exception if whitelist_result == 'filter_at_the_beginning': @@ -346,6 +387,10 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t len(whitelist) == whitelist_result for pairlist in pairlists: + if pairlist['method'] == 'AgeFilter' and pairlist['min_days_listed'] and \ + len(ohlcv_history_list) <= pairlist['min_days_listed']: + assert log_has_re(r'^Removed .* from whitelist, because age is less than ' + r'.* day.*', caplog) if pairlist['method'] == 'PrecisionFilter' and whitelist_result: assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' r'would be <= stop limit.*', caplog) @@ -468,6 +513,29 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf +def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_history_list): + + 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, whitelist_conf_3) + assert freqtrade.exchange.get_historic_ohlcv.call_count == 0 + freqtrade.pairlists.refresh_pairlist() + assert freqtrade.exchange.get_historic_ohlcv.call_count > 0 + + previous_call_count = freqtrade.exchange.get_historic_ohlcv.call_count + freqtrade.pairlists.refresh_pairlist() + # Should not have increased since first call. + assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count + + def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog): mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) From da8e87660e35e50f9d955dce703cd4cdfe9d50ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 26 Jun 2020 06:39:47 +0200 Subject: [PATCH 179/191] Add missing data fillup to FAQ --- docs/faq.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 31c49171d..7e9551051 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -49,6 +49,16 @@ You can use the `/forcesell all` command from Telegram. Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade). +### I'm getting "Missing data fillup" messages in the log + +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 `` - and the exchange does only return candles with volume. +On low volume pairs, this is a rather common occurance. + +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. + +Independently of the reason, Freqtrade will fill up these candles with "empty" candles, where open, high, low and close are set to the previous candle close - and volume is empty. In a chart, this will look like a `_` - and is aligned with how exchanges usually represent 0 volume candles. + ### I'm getting the "RESTRICTED_MARKET" message in the log Currently known to happen for US Bittrex users. From 185fab7b57fc7e47e05e06022a2df1ef32f688aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jun 2020 15:26:55 +0200 Subject: [PATCH 180/191] Change some wordings in documentation Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/rest-api.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/rest-api.md b/docs/rest-api.md index 630c952b4..a8d902b53 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -237,13 +237,13 @@ Since the access token has a short timeout (15 min) - the `token/refresh` reques ## CORS All web-based frontends are subject to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - Cross-Origin Resource Sharing. -Since most request to the Freqtrade API must be authenticated, a proper CORS policy is key to avoid security problems. -Also, the Standard disallows `*` CORS policies for requests with credentials, so this setting must be done appropriately. +Since most of the requests to the Freqtrade API must be authenticated, a proper CORS policy is key to avoid security problems. +Also, the standard disallows `*` CORS policies for requests with credentials, so this setting must be set appropriately. -Users can configure this themselfs via the `CORS_origins` configuration setting. +Users can configure this themselves via the `CORS_origins` configuration setting. It consists of a list of allowed sites that are allowed to consume resources from the bot's API. -Assuming your Application would be deployed as `https://frequi.freqtrade.io/home/` - this would mean that the following configuration becomes necessary: +Assuming your application is deployed as `https://frequi.freqtrade.io/home/` - this would mean that the following configuration becomes necessary: ```jsonc { From e11d22a6a2d997da487a298d87b6de7927f33a10 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jun 2020 15:31:37 +0200 Subject: [PATCH 181/191] Apply suggestions from code review Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/faq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 7e9551051..151b2c054 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -52,12 +52,12 @@ Please look at the [advanced setup documentation Page](advanced-setup.md#running ### I'm getting "Missing data fillup" messages in the log 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 `` - and the exchange does only return candles with volume. +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. 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. -Independently of the reason, Freqtrade will fill up these candles with "empty" candles, where open, high, low and close are set to the previous candle close - and volume is empty. In a chart, this will look like a `_` - and is aligned with how exchanges usually represent 0 volume candles. +Irrespectively of the reason, Freqtrade will fill up these candles with "empty" candles, where open, high, low and close are set to the previous candle close - and volume is empty. In a chart, this will look like a `_` - and is aligned with how exchanges usually represent 0 volume candles. ### I'm getting the "RESTRICTED_MARKET" message in the log From fe0b17c70c82e429afbdf5a17e4242275548ad2f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 09:01:20 +0000 Subject: [PATCH 182/191] Bump progressbar2 from 3.51.3 to 3.51.4 Bumps [progressbar2](https://github.com/WoLpH/python-progressbar) from 3.51.3 to 3.51.4. - [Release notes](https://github.com/WoLpH/python-progressbar/releases) - [Changelog](https://github.com/WoLpH/python-progressbar/blob/develop/CHANGES.rst) - [Commits](https://github.com/WoLpH/python-progressbar/compare/v3.51.3...v3.51.4) Signed-off-by: dependabot-preview[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index aedfc0eaa..2784bc156 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -7,4 +7,4 @@ scikit-learn==0.23.1 scikit-optimize==0.7.4 filelock==3.0.12 joblib==0.15.1 -progressbar2==3.51.3 +progressbar2==3.51.4 From e06b00921416acdd8f05ab9a770ebe0a21689254 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 09:02:57 +0000 Subject: [PATCH 183/191] Bump plotly from 4.8.1 to 4.8.2 Bumps [plotly](https://github.com/plotly/plotly.py) from 4.8.1 to 4.8.2. - [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.8.1...v4.8.2) Signed-off-by: dependabot-preview[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index cb13a59bf..ec5af3dbf 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.8.1 +plotly==4.8.2 From 4e5910afba3a72efe0735a0f257b6328ff3c8f24 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 09:03:19 +0000 Subject: [PATCH 184/191] Bump mkdocs-material from 5.3.2 to 5.3.3 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.3.2 to 5.3.3. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.3.2...5.3.3) Signed-off-by: dependabot-preview[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 7ddfc1dfb..a0505c84b 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==5.3.2 +mkdocs-material==5.3.3 mdx_truly_sane_lists==1.2 From be2b326a6e208d7f0835c7c899c6649764cdd924 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 09:03:58 +0000 Subject: [PATCH 185/191] Bump pytest-asyncio from 0.12.0 to 0.14.0 Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.12.0 to 0.14.0. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.12.0...v0.14.0) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 91b33c573..7d68a0c3b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 mypy==0.781 pytest==5.4.3 -pytest-asyncio==0.12.0 +pytest-asyncio==0.14.0 pytest-cov==2.10.0 pytest-mock==3.1.1 pytest-random-order==1.0.4 From 9e1ce0c67ab1dbb505a35d1c3e1e24e5b946e9c7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 09:04:12 +0000 Subject: [PATCH 186/191] Bump sqlalchemy from 1.3.17 to 1.3.18 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.17 to 1.3.18. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/master/CHANGES) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 916b0e24b..06549d2bf 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,7 +1,7 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs ccxt==1.30.34 -SQLAlchemy==1.3.17 +SQLAlchemy==1.3.18 python-telegram-bot==12.7 arrow==0.15.7 cachetools==4.1.0 From 449d4625336096658e1cbd39f8c9ed6ef9677af4 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:12:58 +0000 Subject: [PATCH 187/191] Bump python-telegram-bot from 12.7 to 12.8 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 12.7 to 12.8. - [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/v12.7...v12.8) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 06549d2bf..06eddb536 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -2,7 +2,7 @@ # mainly used for Raspberry pi installs ccxt==1.30.34 SQLAlchemy==1.3.18 -python-telegram-bot==12.7 +python-telegram-bot==12.8 arrow==0.15.7 cachetools==4.1.0 requests==2.24.0 From c06b2802882ec70b7f725ca39818212d75e0e1d5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:14:27 +0000 Subject: [PATCH 188/191] Bump mypy from 0.781 to 0.782 Bumps [mypy](https://github.com/python/mypy) from 0.781 to 0.782. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.781...v0.782) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7d68a0c3b..ed4f8f713 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==2.0.0 flake8==3.8.3 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 -mypy==0.781 +mypy==0.782 pytest==5.4.3 pytest-asyncio==0.14.0 pytest-cov==2.10.0 From 8fb1683bdc1237ec55f2f5a47379b424ab6a5ea2 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:32:42 +0000 Subject: [PATCH 189/191] Bump cachetools from 4.1.0 to 4.1.1 Bumps [cachetools](https://github.com/tkem/cachetools) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/tkem/cachetools/releases) - [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst) - [Commits](https://github.com/tkem/cachetools/compare/v4.1.0...v4.1.1) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 06eddb536..4c0f93ef7 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -4,7 +4,7 @@ ccxt==1.30.34 SQLAlchemy==1.3.18 python-telegram-bot==12.8 arrow==0.15.7 -cachetools==4.1.0 +cachetools==4.1.1 requests==2.24.0 urllib3==1.25.9 wrapt==1.12.1 From a9064117a5b35c7d6a2f63a0edce3c9188fa27de Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 12:53:58 +0000 Subject: [PATCH 190/191] Bump ccxt from 1.30.34 to 1.30.48 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.30.34 to 1.30.48. - [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.30.34...1.30.48) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 4c0f93ef7..2948b8f35 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.30.34 +ccxt==1.30.48 SQLAlchemy==1.3.18 python-telegram-bot==12.8 arrow==0.15.7 From e41392a73f1c019cce70b959ba4990f10b533419 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jun 2020 06:44:40 +0200 Subject: [PATCH 191/191] Version bump to 2020.6 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 602aece94..1eb0f9bec 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2020.5' +__version__ = '2020.6' if __version__ == 'develop':