From ecddaa663b3c8be6af8f305efd5f48548e52d2b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Oct 2020 19:58:04 +0200 Subject: [PATCH 01/92] Convert timestamp to int_timestamp for all arrow occurances --- freqtrade/configuration/timerange.py | 8 ++++---- freqtrade/edge/edge_positioning.py | 4 ++-- freqtrade/exchange/exchange.py | 12 ++++++------ freqtrade/optimize/optimize_reports.py | 4 ++-- freqtrade/wallets.py | 4 ++-- tests/commands/test_commands.py | 5 +++-- tests/conftest.py | 12 ++++++------ tests/data/test_history.py | 4 ++-- tests/edge/test_edge.py | 12 ++++++------ tests/exchange/test_exchange.py | 23 ++++++++++++----------- 10 files changed, 45 insertions(+), 43 deletions(-) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index 151003999..32bbd02a0 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -52,11 +52,11 @@ class TimeRange: :return: None (Modifies the object in place) """ if (not self.starttype or (startup_candles - and min_date.timestamp >= self.startts)): + and min_date.int_timestamp >= self.startts)): # If no startts was defined, or backtest-data starts at the defined backtest-date logger.warning("Moving start-date by %s candles to account for startup time.", startup_candles) - self.startts = (min_date.timestamp + timeframe_secs * startup_candles) + self.startts = (min_date.int_timestamp + timeframe_secs * startup_candles) self.starttype = 'date' @staticmethod @@ -89,7 +89,7 @@ class TimeRange: if stype[0]: starts = rvals[index] if stype[0] == 'date' and len(starts) == 8: - start = arrow.get(starts, 'YYYYMMDD').timestamp + start = arrow.get(starts, 'YYYYMMDD').int_timestamp elif len(starts) == 13: start = int(starts) // 1000 else: @@ -98,7 +98,7 @@ class TimeRange: if stype[1]: stops = rvals[index] if stype[1] == 'date' and len(stops) == 8: - stop = arrow.get(stops, 'YYYYMMDD').timestamp + stop = arrow.get(stops, 'YYYYMMDD').int_timestamp elif len(stops) == 13: stop = int(stops) // 1000 else: diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index a40b63d67..037717c68 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -87,7 +87,7 @@ class Edge: heartbeat = self.edge_config.get('process_throttle_secs') if (self._last_updated > 0) and ( - self._last_updated + heartbeat > arrow.utcnow().timestamp): + self._last_updated + heartbeat > arrow.utcnow().int_timestamp): return False data: Dict[str, Any] = {} @@ -146,7 +146,7 @@ class Edge: # Fill missing, calculable columns, profit, duration , abs etc. trades_df = self._fill_calculable_fields(DataFrame(trades)) self._cached_pairs = self._process_expectancy(trades_df) - self._last_updated = arrow.utcnow().timestamp + self._last_updated = arrow.utcnow().int_timestamp return True diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index bbb94e61f..d6836ee73 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -291,7 +291,7 @@ class Exchange: try: self._api.load_markets() self._load_async_markets() - self._last_markets_refresh = arrow.utcnow().timestamp + self._last_markets_refresh = arrow.utcnow().int_timestamp except ccxt.BaseError as e: logger.warning('Unable to initialize markets. Reason: %s', e) @@ -300,14 +300,14 @@ class Exchange: # 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): + > arrow.utcnow().int_timestamp): return None 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 + self._last_markets_refresh = arrow.utcnow().int_timestamp except ccxt.BaseError: logger.exception("Could not reload markets.") @@ -501,7 +501,7 @@ class Exchange: 'side': side, 'remaining': _amount, 'datetime': arrow.utcnow().isoformat(), - 'timestamp': int(arrow.utcnow().timestamp * 1000), + 'timestamp': int(arrow.utcnow().int_timestamp * 1000), 'status': "closed" if ordertype == "market" else "open", 'fee': None, 'info': {} @@ -696,7 +696,7 @@ class Exchange: ) input_coroutines = [self._async_get_candle_history( pair, timeframe, since) for since in - range(since_ms, arrow.utcnow().timestamp * 1000, one_call)] + range(since_ms, arrow.utcnow().int_timestamp * 1000, one_call)] results = await asyncio.gather(*input_coroutines, return_exceptions=True) @@ -759,7 +759,7 @@ class Exchange: interval_in_sec = timeframe_to_seconds(timeframe) return not ((self._pairs_last_refresh_time.get((pair, timeframe), 0) - + interval_in_sec) >= arrow.utcnow().timestamp) + + interval_in_sec) >= arrow.utcnow().int_timestamp) @retrier_async async def _async_get_candle_history(self, pair: str, timeframe: str, diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 3db9a312a..c977a991b 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -268,9 +268,9 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], 'profit_total': results['profit_percent'].sum(), 'profit_total_abs': results['profit_abs'].sum(), 'backtest_start': min_date.datetime, - 'backtest_start_ts': min_date.timestamp * 1000, + 'backtest_start_ts': min_date.int_timestamp * 1000, 'backtest_end': max_date.datetime, - 'backtest_end_ts': max_date.timestamp * 1000, + 'backtest_end_ts': max_date.int_timestamp * 1000, 'backtest_days': backtest_days, 'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0, diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 21a9466e1..3680dd416 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -108,13 +108,13 @@ class Wallets: for trading operations, the latest balance is needed. :param require_update: Allow skipping an update if balances were recently refreshed """ - if (require_update or (self._last_wallet_refresh + 3600 < arrow.utcnow().timestamp)): + if (require_update or (self._last_wallet_refresh + 3600 < arrow.utcnow().int_timestamp)): if self._config['dry_run']: self._update_dry() else: self._update_live() logger.info('Wallets synced.') - self._last_wallet_refresh = arrow.utcnow().timestamp + self._last_wallet_refresh = arrow.utcnow().int_timestamp def get_all_balances(self) -> Dict[str, Any]: return self._wallets diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 5b125697c..bf845a2e1 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -579,7 +579,7 @@ def test_download_data_timerange(mocker, caplog, markets): start_download_data(get_args(args)) assert dl_mock.call_count == 1 # 20days ago - days_ago = arrow.get(arrow.utcnow().shift(days=-20).date()).timestamp + days_ago = arrow.get(arrow.utcnow().shift(days=-20).date()).int_timestamp assert dl_mock.call_args_list[0][1]['timerange'].startts == days_ago dl_mock.reset_mock() @@ -592,7 +592,8 @@ def test_download_data_timerange(mocker, caplog, markets): start_download_data(get_args(args)) assert dl_mock.call_count == 1 - assert dl_mock.call_args_list[0][1]['timerange'].startts == arrow.Arrow(2020, 1, 1).timestamp + assert dl_mock.call_args_list[0][1]['timerange'].startts == arrow.Arrow( + 2020, 1, 1).int_timestamp def test_download_data_no_markets(mocker, caplog): diff --git a/tests/conftest.py b/tests/conftest.py index 2153fd327..a99404ac2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -792,7 +792,7 @@ def limit_buy_order_open(): 'side': 'buy', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().timestamp, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -911,7 +911,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -932,7 +932,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': 'AZNPFF-4AC4N-7MKTAT', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'status': 'canceled', @@ -953,7 +953,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -974,7 +974,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1000,7 +1000,7 @@ def limit_sell_order_open(): 'side': 'sell', 'pair': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().timestamp, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001173, 'amount': 90.99181073, 'filled': 0.0, diff --git a/tests/data/test_history.py b/tests/data/test_history.py index c8324cf0b..bbc6e55b4 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -323,7 +323,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: start = arrow.get('2018-01-01T00:00:00') end = arrow.get('2018-01-11T00:00:00') data = load_data(testdatadir, '5m', ['UNITTEST/BTC'], startup_candles=20, - timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) + timerange=TimeRange('date', 'date', start.int_timestamp, end.int_timestamp)) assert log_has( 'Using indicator startup period: 20 ...', caplog ) @@ -339,7 +339,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: start = arrow.get('2018-01-10T00:00:00') end = arrow.get('2018-02-20T00:00:00') data = load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], - timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) + timerange=TimeRange('date', 'date', start.int_timestamp, end.int_timestamp)) # timedifference in 5 minutes td = ((end - start).total_seconds() // 60 // 5) + 1 assert td != len(data['UNITTEST/BTC']) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index a4bfa1085..f25dad35b 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -50,7 +50,7 @@ def _build_dataframe(buy_ohlc_sell_matrice): 'date': tests_start_time.shift( minutes=( ohlc[0] * - timeframe_in_minute)).timestamp * + timeframe_in_minute)).int_timestamp * 1000, 'buy': ohlc[1], 'open': ohlc[2], @@ -71,7 +71,7 @@ def _build_dataframe(buy_ohlc_sell_matrice): def _time_on_candle(number): return np.datetime64(tests_start_time.shift( - minutes=(number * timeframe_in_minute)).timestamp * 1000, 'ms') + minutes=(number * timeframe_in_minute)).int_timestamp * 1000, 'ms') # End helper functions @@ -251,7 +251,7 @@ def test_edge_heartbeat_calculate(mocker, edge_conf): heartbeat = edge_conf['edge']['process_throttle_secs'] # should not recalculate if heartbeat not reached - edge._last_updated = arrow.utcnow().timestamp - heartbeat + 1 + edge._last_updated = arrow.utcnow().int_timestamp - heartbeat + 1 assert edge.calculate() is False @@ -263,7 +263,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', NEOBTC = [ [ - tests_start_time.shift(minutes=(x * timeframe_in_minute)).timestamp * 1000, + tests_start_time.shift(minutes=(x * timeframe_in_minute)).int_timestamp * 1000, math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base - 0.0001, @@ -275,7 +275,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m', base = 0.002 LTCBTC = [ [ - tests_start_time.shift(minutes=(x * timeframe_in_minute)).timestamp * 1000, + tests_start_time.shift(minutes=(x * timeframe_in_minute)).int_timestamp * 1000, math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base - 0.0001, @@ -299,7 +299,7 @@ def test_edge_process_downloaded_data(mocker, edge_conf): assert edge.calculate() assert len(edge._cached_pairs) == 2 - assert edge._last_updated <= arrow.utcnow().timestamp + 2 + assert edge._last_updated <= arrow.utcnow().int_timestamp + 2 def test_edge_process_no_data(mocker, edge_conf, caplog): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 7be9c77ac..7df596098 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -385,7 +385,7 @@ def test_reload_markets(default_conf, mocker, caplog): 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 + exchange._last_markets_refresh = arrow.utcnow().int_timestamp updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}} assert exchange.markets == initial_markets @@ -396,7 +396,7 @@ def test_reload_markets(default_conf, mocker, caplog): 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._last_markets_refresh = arrow.utcnow().int_timestamp - 15 * 60 exchange.reload_markets() assert exchange.markets == updated_markets assert exchange._load_async_markets.call_count == 1 @@ -1264,7 +1264,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) ohlcv = [ [ - arrow.utcnow().timestamp * 1000, # unix timestamp ms + arrow.utcnow().int_timestamp * 1000, # unix timestamp ms 1, # open 2, # high 3, # low @@ -1281,7 +1281,8 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): # one_call calculation * 1.8 should do 2 calls since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8 - ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000)) + ret = exchange.get_historic_ohlcv(pair, "5m", int(( + arrow.utcnow().int_timestamp - since) * 1000)) assert exchange._async_get_candle_history.call_count == 2 # Returns twice the above OHLCV data @@ -1291,7 +1292,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: ohlcv = [ [ - (arrow.utcnow().timestamp - 1) * 1000, # unix timestamp ms + (arrow.utcnow().int_timestamp - 1) * 1000, # unix timestamp ms 1, # open 2, # high 3, # low @@ -1299,7 +1300,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: 5, # volume (in quote currency) ], [ - arrow.utcnow().timestamp * 1000, # unix timestamp ms + arrow.utcnow().int_timestamp * 1000, # unix timestamp ms 3, # open 1, # high 4, # low @@ -1345,7 +1346,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name): ohlcv = [ [ - arrow.utcnow().timestamp * 1000, # unix timestamp ms + arrow.utcnow().int_timestamp * 1000, # unix timestamp ms 1, # open 2, # high 3, # low @@ -1380,14 +1381,14 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_get_candle_history(pair, "5m", - (arrow.utcnow().timestamp - 2000) * 1000) + (arrow.utcnow().int_timestamp - 2000) * 1000) with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' r'historical candle \(OHLCV\) data\..*'): api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) await exchange._async_get_candle_history(pair, "5m", - (arrow.utcnow().timestamp - 2000) * 1000) + (arrow.utcnow().int_timestamp - 2000) * 1000) @pytest.mark.asyncio @@ -1599,13 +1600,13 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name, with pytest.raises(OperationalException, match=r'Could not fetch trade data*'): api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - await exchange._async_fetch_trades(pair, since=(arrow.utcnow().timestamp - 2000) * 1000) + await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' r'historical trade data\..*'): api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - await exchange._async_fetch_trades(pair, since=(arrow.utcnow().timestamp - 2000) * 1000) + await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) @pytest.mark.asyncio From 685d18940a52eff4534d1a87bdb440cabb413bf1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Oct 2020 08:13:31 +0200 Subject: [PATCH 02/92] specify min-version for arrow int_timestamp was introduced in this version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b57e8d2c..b47427709 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ setup(name='freqtrade', 'ccxt>=1.24.96', 'SQLAlchemy', 'python-telegram-bot', - 'arrow', + 'arrow>=0.17.0', 'cachetools', 'requests', 'urllib3', From adffd402ea6aff03719d21bf725448f8da0d8b27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Oct 2020 20:11:38 +0200 Subject: [PATCH 03/92] Replace some pointless occurances of arrow --- freqtrade/commands/data_commands.py | 4 ++-- freqtrade/data/dataprovider.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index 7102eee38..c12c20ddd 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -1,9 +1,9 @@ import logging import sys from collections import defaultdict +from datetime import datetime, timedelta from typing import Any, Dict, List -import arrow from freqtrade.configuration import TimeRange, setup_utils_configuration from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format @@ -29,7 +29,7 @@ def start_download_data(args: Dict[str, Any]) -> None: "You can only specify one or the other.") timerange = TimeRange() if 'days' in config: - time_since = arrow.utcnow().shift(days=-config['days']).strftime("%Y%m%d") + time_since = (datetime.now() - timedelta(days=config['days'])).strftime("%Y%m%d") timerange = TimeRange.parse_timerange(f'{time_since}-') if 'timerange' in config: diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 07dd94fc1..ba43044a1 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -8,7 +8,6 @@ import logging from datetime import datetime, timezone from typing import Any, Dict, List, Optional, Tuple -from arrow import Arrow from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe @@ -38,7 +37,7 @@ class DataProvider: :param timeframe: Timeframe to get data for :param dataframe: analyzed dataframe """ - self.__cached_pairs[(pair, timeframe)] = (dataframe, Arrow.utcnow().datetime) + self.__cached_pairs[(pair, timeframe)] = (dataframe, datetime.now(timezone.utc)) def add_pairlisthandler(self, pairlists) -> None: """ From fd6018f67aaa9620c237098f8139bcfb06832d62 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 21 Oct 2020 06:21:13 +0200 Subject: [PATCH 04/92] Fix dependency sorting --- freqtrade/commands/data_commands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index c12c20ddd..b28e0c357 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -4,7 +4,6 @@ from collections import defaultdict from datetime import datetime, timedelta from typing import Any, Dict, List - from freqtrade.configuration import TimeRange, setup_utils_configuration from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, From 38fc5d680b937c7aff9ed148ecab34804dd9dfa0 Mon Sep 17 00:00:00 2001 From: Matthias Spiller Date: Sat, 31 Oct 2020 10:31:58 +0000 Subject: [PATCH 05/92] Enable usage of devcontainer for macOS users --- .devcontainer/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 7b5e64609..20ec247d1 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -7,8 +7,8 @@ services: dockerfile: ".devcontainer/Dockerfile" volumes: # Allow git usage within container - - "/home/${USER}/.ssh:/home/ftuser/.ssh:ro" - - "/home/${USER}/.gitconfig:/home/ftuser/.gitconfig:ro" + - "${HOME}/.ssh:/home/ftuser/.ssh:ro" + - "${HOME}/.gitconfig:/home/ftuser/.gitconfig:ro" - ..:/freqtrade:cached # Persist bash-history - freqtrade-vscode-server:/home/ftuser/.vscode-server From 78874fa86573f810d0c57e248651fc8aa1d80286 Mon Sep 17 00:00:00 2001 From: Matthias Spiller Date: Sat, 31 Oct 2020 10:53:51 +0000 Subject: [PATCH 06/92] informative_pairs does not honor dataformat --- freqtrade/data/dataprovider.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 07dd94fc1..3ca38e865 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -88,7 +88,8 @@ class DataProvider: """ return load_pair_history(pair=pair, timeframe=timeframe or self._config['timeframe'], - datadir=self._config['datadir'] + datadir=self._config['datadir'], + data_format=self._config.get('dataformat_ohlcv', 'json') ) def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame: From 0d11f0bd75da23e2c285778a435cb7cf2f65bff4 Mon Sep 17 00:00:00 2001 From: Matthias Spiller Date: Sat, 31 Oct 2020 11:45:46 +0000 Subject: [PATCH 07/92] Add unit test for hdf5 dataformat for informative pairs --- tests/data/test_dataprovider.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index a64dce908..a3c57a77b 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -52,6 +52,31 @@ def test_historic_ohlcv(mocker, default_conf, ohlcv_history): assert historymock.call_args_list[0][1]["timeframe"] == "5m" +def test_historic_ohlcv_dataformat(mocker, default_conf, ohlcv_history): + hdf5loadmock = MagicMock(return_value=ohlcv_history) + jsonloadmock = MagicMock(return_value=ohlcv_history) + mocker.patch("freqtrade.data.history.hdf5datahandler.HDF5DataHandler._ohlcv_load", hdf5loadmock) + mocker.patch("freqtrade.data.history.jsondatahandler.JsonDataHandler._ohlcv_load", jsonloadmock) + + default_conf["runmode"] = RunMode.BACKTEST + exchange = get_patched_exchange(mocker, default_conf) + dp = DataProvider(default_conf, exchange) + data = dp.historic_ohlcv("UNITTEST/BTC", "5m") + assert isinstance(data, DataFrame) + hdf5loadmock.assert_not_called() + jsonloadmock.assert_called_once() + + # Swiching to dataformat hdf5 + hdf5loadmock.reset_mock() + jsonloadmock.reset_mock() + default_conf["dataformat_ohlcv"] = "hdf5" + dp = DataProvider(default_conf, exchange) + data = dp.historic_ohlcv("UNITTEST/BTC", "5m") + assert isinstance(data, DataFrame) + hdf5loadmock.assert_called_once() + jsonloadmock.assert_not_called() + + def test_get_pair_dataframe(mocker, default_conf, ohlcv_history): default_conf["runmode"] = RunMode.DRY_RUN timeframe = default_conf["timeframe"] From e73203acb85b16ef79bfebac6b0275ca72ddf8f9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Nov 2020 10:51:07 +0100 Subject: [PATCH 08/92] FIx bug with dmmp --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 47bb9edd9..883f7338c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -340,7 +340,7 @@ class Backtesting: # max_open_trades must be respected # don't open on the last row if ((position_stacking or len(open_trades[pair]) == 0) - and max_open_trades > 0 and open_trade_count_start < max_open_trades + and (max_open_trades <= 0 or open_trade_count_start < max_open_trades) and tmp != end_date and row[BUY_IDX] == 1 and row[SELL_IDX] != 1): # Enter trade From 81fb0c572614805a393fa5a10ad4389877f65498 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 05:54:57 +0000 Subject: [PATCH 09/92] Bump numpy from 1.19.2 to 1.19.3 Bumps [numpy](https://github.com/numpy/numpy) from 1.19.2 to 1.19.3. - [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.19.2...v1.19.3) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7d2017beb..e2687539e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.19.2 +numpy==1.19.3 pandas==1.1.3 ccxt==1.36.85 From 6c3753ac7f64c1d61e8b4ecfcace66db9c5d5946 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 05:55:07 +0000 Subject: [PATCH 10/92] Bump mkdocs-material from 6.1.0 to 6.1.2 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.0 to 6.1.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.0...6.1.2) Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index f30710a1f..47f0eff1c 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,3 +1,3 @@ -mkdocs-material==6.1.0 +mkdocs-material==6.1.2 mdx_truly_sane_lists==1.2 pymdown-extensions==8.0.1 From 21b22760a7087409cf3f91ea860f29329d831745 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 05:55:12 +0000 Subject: [PATCH 11/92] Bump pytest from 6.1.1 to 6.1.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.1.1 to 6.1.2. - [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/6.1.1...6.1.2) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 916bb2ec2..1c96a880a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ flake8==3.8.4 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 mypy==0.790 -pytest==6.1.1 +pytest==6.1.2 pytest-asyncio==0.14.0 pytest-cov==2.10.1 pytest-mock==3.3.1 From aed44ef6b36f071d784bda57c9f910e90c4d953a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 07:46:28 +0000 Subject: [PATCH 12/92] Bump pandas from 1.1.3 to 1.1.4 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.1.3 to 1.1.4. - [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.1.3...v1.1.4) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e2687539e..05520d4d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.19.3 -pandas==1.1.3 +pandas==1.1.4 ccxt==1.36.85 aiohttp==3.7.1 From 74d8a985e22d0b6d2881b4d3dcd989186f3ad7b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 08:44:26 +0000 Subject: [PATCH 13/92] Bump aiohttp from 3.7.1 to 3.7.2 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.7.1 to 3.7.2. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.7.1...v3.7.2) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 05520d4d9..3df54ad06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.19.3 pandas==1.1.4 ccxt==1.36.85 -aiohttp==3.7.1 +aiohttp==3.7.2 SQLAlchemy==1.3.20 python-telegram-bot==13.0 arrow==0.17.0 From d56da41679f022365ab6ac913989f09d88ff920d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 13:50:07 +0000 Subject: [PATCH 14/92] Bump ccxt from 1.36.85 to 1.37.14 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.36.85 to 1.37.14. - [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.36.85...1.37.14) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3df54ad06..afe48171c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.3 pandas==1.1.4 -ccxt==1.36.85 +ccxt==1.37.14 aiohttp==3.7.2 SQLAlchemy==1.3.20 python-telegram-bot==13.0 From cf89a773da498915cb7f08e590d1d061c3d46bc8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Nov 2020 07:34:21 +0100 Subject: [PATCH 15/92] Standardize trade api outputs there should be no difference between current_profit and close_profit it's always profit, and the information if it's a closed trade is available elsewhere --- freqtrade/persistence/models.py | 9 +++++++-- freqtrade/rpc/rpc.py | 13 +++++++------ tests/rpc/test_rpc.py | 8 +++++++- tests/rpc/test_rpc_apiserver.py | 7 +++++++ tests/test_persistence.py | 8 ++++++++ 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 7e6d967c1..3019d3d5f 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -295,8 +295,13 @@ class Trade(_DECL_BASE): tzinfo=timezone.utc).timestamp() * 1000) if self.close_date else None, 'close_rate': self.close_rate, 'close_rate_requested': self.close_rate_requested, - 'close_profit': self.close_profit, - 'close_profit_abs': self.close_profit_abs, + 'close_profit': self.close_profit, # Deprecated + 'close_profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None, + 'close_profit_abs': self.close_profit_abs, # Deprecated + + 'profit_ratio': self.close_profit, + 'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None, + 'profit_abs': self.close_profit_abs, 'sell_reason': self.sell_reason, 'sell_order_status': self.sell_order_status, diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 10aaf56fa..4370fe897 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -152,17 +152,18 @@ class RPC: 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() trade_dict.update(dict( base_currency=self._freqtrade.config['stake_currency'], close_profit=trade.close_profit if trade.close_profit is not None else None, - close_profit_pct=fmt_close_profit, current_rate=current_rate, - current_profit=current_profit, - current_profit_pct=round(current_profit * 100, 2), - current_profit_abs=current_profit_abs, + current_profit=current_profit, # Deprectated + current_profit_pct=round(current_profit * 100, 2), # Deprectated + current_profit_abs=current_profit_abs, # Deprectated + profit_ratio=current_profit, + profit_pct=round(current_profit * 100, 2), + profit_abs=current_profit_abs, + stoploss_current_dist=stoploss_current_dist, stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2), diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 977dfbc20..4a4f3053e 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -70,7 +70,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'max_rate': ANY, 'strategy': ANY, 'ticker_interval': ANY, - 'timeframe': ANY, + 'timeframe': 5, 'open_order_id': ANY, 'close_date': None, 'close_date_hum': None, @@ -87,6 +87,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'current_profit_abs': -4.09e-06, + 'profit_ratio': -0.00408133, + 'profit_pct': -0.41, + 'profit_abs': -4.09e-06, 'stop_loss': 9.882e-06, 'stop_loss_abs': 9.882e-06, 'stop_loss_pct': -10.0, @@ -152,6 +155,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'current_profit': ANY, 'current_profit_pct': ANY, 'current_profit_abs': ANY, + 'profit_ratio': ANY, + 'profit_pct': ANY, + 'profit_abs': ANY, 'stop_loss': 9.882e-06, 'stop_loss_abs': 9.882e-06, 'stop_loss_pct': -10.0, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 7b4e2e153..fd5d2fce2 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -639,6 +639,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'current_profit_abs': -4.09e-06, + 'profit_ratio': -0.00408133, + 'profit_pct': -0.41, + 'profit_abs': -4.09e-06, 'current_rate': 1.099e-05, 'open_date': ANY, 'open_date_hum': 'just now', @@ -791,8 +794,12 @@ def test_api_forcebuy(botclient, mocker, fee): 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'close_profit': None, + 'close_profit_pct': None, 'close_profit_abs': None, 'close_rate_requested': None, + 'profit_ratio': None, + 'profit_pct': None, + 'profit_abs': None, 'fee_close': 0.0025, 'fee_close_cost': None, 'fee_close_currency': None, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 4216565ac..b2d2b716c 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -816,7 +816,11 @@ def test_to_json(default_conf, fee): 'amount_requested': 123.0, 'stake_amount': 0.001, 'close_profit': None, + 'close_profit_pct': None, 'close_profit_abs': None, + 'profit_ratio': None, + 'profit_pct': None, + 'profit_abs': None, 'sell_reason': None, 'sell_order_status': None, 'stop_loss': None, @@ -880,7 +884,11 @@ def test_to_json(default_conf, fee): 'initial_stop_loss_pct': None, 'initial_stop_loss_ratio': None, 'close_profit': None, + 'close_profit_pct': None, 'close_profit_abs': None, + 'profit_ratio': None, + 'profit_pct': None, + 'profit_abs': None, 'close_rate_requested': None, 'fee_close': 0.0025, 'fee_close_cost': None, From d1dab2328379446b502287fdbba0deb842fcb68a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Nov 2020 08:25:47 +0100 Subject: [PATCH 16/92] Remove deprecated api fields --- freqtrade/persistence/models.py | 3 --- freqtrade/rpc/rpc.py | 1 - freqtrade/rpc/telegram.py | 4 ++-- tests/rpc/test_rpc.py | 6 ------ tests/rpc/test_rpc_apiserver.py | 7 ------- tests/test_persistence.py | 6 ------ 6 files changed, 2 insertions(+), 25 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 3019d3d5f..8160ffbbf 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -270,7 +270,6 @@ class Trade(_DECL_BASE): 'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, 'stake_amount': round(self.stake_amount, 8), 'strategy': self.strategy, - 'ticker_interval': self.timeframe, # DEPRECATED 'timeframe': self.timeframe, 'fee_open': self.fee_open, @@ -305,7 +304,6 @@ class Trade(_DECL_BASE): 'sell_reason': self.sell_reason, 'sell_order_status': self.sell_order_status, - '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, @@ -314,7 +312,6 @@ class Trade(_DECL_BASE): if self.stoploss_last_update else None), 'stoploss_last_update_timestamp': int(self.stoploss_last_update.replace( tzinfo=timezone.utc).timestamp() * 1000) if self.stoploss_last_update else None, - '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), diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 4370fe897..efeb361ae 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -110,7 +110,6 @@ 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['timeframe'], # DEPRECATED 'timeframe': config['timeframe'], 'timeframe_ms': timeframe_to_msecs(config['timeframe']), 'timeframe_min': timeframe_to_minutes(config['timeframe']), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 3dcb7ab72..9bbae871d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -251,14 +251,14 @@ class Telegram(RPC): if r['close_profit_pct'] is not None else ""), "*Current Profit:* `{current_profit_pct:.2f}%`", ] - if (r['stop_loss'] != r['initial_stop_loss'] + if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] and r['initial_stop_loss_pct'] is not None): # Adding initial stoploss only if it is different from stoploss lines.append("*Initial Stoploss:* `{initial_stop_loss:.8f}` " "`({initial_stop_loss_pct:.2f}%)`") # Adding stoploss and stoploss percentage only if it is not None - lines.append("*Stoploss:* `{stop_loss:.8f}` " + + lines.append("*Stoploss:* `{stop_loss_abs:.8f}` " + ("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else "")) lines.append("*Stoploss distance:* `{stoploss_current_dist:.8f}` " "`({stoploss_current_dist_pct:.2f}%)`") diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 4a4f3053e..23ca53e53 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -69,7 +69,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, - 'ticker_interval': ANY, 'timeframe': 5, 'open_order_id': ANY, 'close_date': None, @@ -90,14 +89,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': -0.00408133, 'profit_pct': -0.41, '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': 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, @@ -137,7 +134,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, @@ -158,14 +154,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'profit_ratio': ANY, 'profit_pct': ANY, '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': 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, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index fd5d2fce2..80de839e4 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -360,7 +360,6 @@ 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['timeframe_ms'] == 300000 assert rc.json['timeframe_min'] == 5 @@ -650,14 +649,12 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'open_rate': 1.098e-05, 'pair': 'ETH/BTC', 'stake_amount': 0.001, - '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': 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, @@ -685,7 +682,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', }] @@ -782,14 +778,12 @@ def test_api_forcebuy(botclient, mocker, fee): 'open_rate': 0.245441, '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, @@ -815,7 +809,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 b2d2b716c..41b99b34f 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -823,21 +823,18 @@ def test_to_json(default_conf, fee): 'profit_abs': None, '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, 'max_rate': None, 'strategy': None, - 'ticker_interval': None, 'timeframe': None, 'exchange': 'bittrex', } @@ -872,14 +869,12 @@ def test_to_json(default_conf, fee): 'amount': 100.0, 'amount_requested': 101.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, @@ -905,7 +900,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 b58d6d38b5e080d7bfcf06d9813f55f55bd55e69 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Nov 2020 08:34:12 +0100 Subject: [PATCH 17/92] Use correct fields in telegram --- freqtrade/rpc/telegram.py | 7 +++---- tests/rpc/test_rpc_telegram.py | 11 ++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 9bbae871d..31ec33b63 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -247,14 +247,13 @@ class Telegram(RPC): "*Open Rate:* `{open_rate:.8f}`", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Current Rate:* `{current_rate:.8f}`", - ("*Close Profit:* `{close_profit_pct}`" - if r['close_profit_pct'] is not None else ""), - "*Current Profit:* `{current_profit_pct:.2f}%`", + ("*Current Profit:* " if r['is_open'] else "*Close Profit: *") + + "`{profit_pct:.2f}%`", ] if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] and r['initial_stop_loss_pct'] is not None): # Adding initial stoploss only if it is different from stoploss - lines.append("*Initial Stoploss:* `{initial_stop_loss:.8f}` " + lines.append("*Initial Stoploss:* `{initial_stop_loss_abs:.8f}` " "`({initial_stop_loss_pct:.2f}%)`") # Adding stoploss and stoploss percentage only if it is not None diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f1246005f..8e81db106 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -163,16 +163,17 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'amount': 90.99181074, 'stake_amount': 90.99181074, 'close_profit_pct': None, - 'current_profit': -0.0059, - 'current_profit_pct': -0.59, - 'initial_stop_loss': 1.098e-05, - 'stop_loss': 1.099e-05, + 'profit': -0.0059, + 'profit_pct': -0.59, + 'initial_stop_loss_abs': 1.098e-05, + 'stop_loss_abs': 1.099e-05, 'sell_order_status': None, 'initial_stop_loss_pct': -0.05, 'stoploss_current_dist': 1e-08, 'stoploss_current_dist_pct': -0.02, 'stop_loss_pct': -0.01, - 'open_order': '(limit buy rem=0.00000000)' + 'open_order': '(limit buy rem=0.00000000)', + 'is_open': True }]), _status_table=status_table, _send_msg=msg_mock From 7d2bd00f0ca421857ba475838269397bc64ec536 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Nov 2020 09:23:07 +0100 Subject: [PATCH 18/92] Update forgotten arrow.timestamp occurance --- tests/exchange/test_exchange.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index af08d753e..e4452a83c 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1302,7 +1302,8 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): raise TimeoutError() exchange._async_get_candle_history = MagicMock(side_effect=mock_get_candle_hist_error) - ret = exchange.get_historic_ohlcv(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000)) + ret = exchange.get_historic_ohlcv(pair, "5m", int( + (arrow.utcnow().int_timestamp - since) * 1000)) assert log_has_re(r"Async code raised an exception: .*", caplog) From 2af1c80fd536a94aca53f45e5cd0f3c2f8a69781 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Nov 2020 11:26:02 +0100 Subject: [PATCH 19/92] Convert _rpc_show_config to static method --- freqtrade/rpc/api_server.py | 2 +- freqtrade/rpc/rpc.py | 20 ++++++++++++-------- freqtrade/rpc/telegram.py | 3 ++- tests/rpc/test_rpc_apiserver.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 3 ++- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index be21179ad..7f4773d57 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -329,7 +329,7 @@ class ApiServer(RPC): """ Prints the bot's version """ - return jsonify(self._rpc_show_config(self._config)) + return jsonify(RPC._rpc_show_config(self._config, self._freqtrade.state)) @require_login @rpc_catch_errors diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index efeb361ae..888dc11ec 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -93,7 +93,8 @@ class RPC: def send_msg(self, msg: Dict[str, str]) -> None: """ Sends a message to all registered rpc modules """ - def _rpc_show_config(self, config) -> Dict[str, Any]: + @staticmethod + def _rpc_show_config(config, botstate: State) -> Dict[str, Any]: """ Return a dict of config options. Explicitly does NOT return the full config to avoid leakage of sensitive @@ -104,21 +105,24 @@ class RPC: 'stake_currency': config['stake_currency'], 'stake_amount': config['stake_amount'], 'max_open_trades': config['max_open_trades'], - 'minimal_roi': config['minimal_roi'].copy(), - 'stoploss': config['stoploss'], - 'trailing_stop': config['trailing_stop'], + 'minimal_roi': config['minimal_roi'].copy() if 'minimal_roi' in config else {}, + 'stoploss': config.get('stoploss'), + 'trailing_stop': config.get('trailing_stop'), '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'), - 'timeframe': config['timeframe'], - 'timeframe_ms': timeframe_to_msecs(config['timeframe']), - 'timeframe_min': timeframe_to_minutes(config['timeframe']), + 'timeframe': config.get('timeframe'), + 'timeframe_ms': timeframe_to_msecs(config['timeframe'] + ) if 'timeframe' in config else '', + 'timeframe_min': timeframe_to_minutes(config['timeframe'] + ) if 'timeframe' in config else '', '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) if self._freqtrade else '', + 'state': str(botstate), + 'runmode': config['runmode'].value } return val diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 31ec33b63..31d5bbfbd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -775,7 +775,8 @@ class Telegram(RPC): :param update: message update :return: None """ - val = self._rpc_show_config(self._freqtrade.config) + val = RPC._rpc_show_config(self._freqtrade.config, self._freqtrade.state) + if val['trailing_stop']: sl_info = ( f"*Initial Stoploss:* `{val['stoploss']}`\n" diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 80de839e4..0dc43474f 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -14,7 +14,7 @@ from freqtrade.__init__ import __version__ from freqtrade.loggers import setup_logging, setup_logging_pre from freqtrade.persistence import PairLocks, Trade from freqtrade.rpc.api_server import BASE_URI, ApiServer -from freqtrade.state import State +from freqtrade.state import RunMode, State from tests.conftest import create_mock_trades, get_patched_freqtradebot, log_has, patch_get_signal @@ -26,7 +26,7 @@ _TEST_PASS = "SuperSecurePassword1!" def botclient(default_conf, mocker): setup_logging_pre() setup_logging(default_conf) - + default_conf['runmode'] = RunMode.DRY_RUN default_conf.update({"api_server": {"enabled": True, "listen_ip_address": "127.0.0.1", "listen_port": 8080, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 8e81db106..7885a251d 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -21,7 +21,7 @@ from freqtrade.loggers import setup_logging from freqtrade.persistence import PairLocks, Trade from freqtrade.rpc import RPCMessageType from freqtrade.rpc.telegram import Telegram, authorized_only -from freqtrade.state import State +from freqtrade.state import RunMode, State from freqtrade.strategy.interface import SellType from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, patch_exchange, patch_get_signal, patch_whitelist) @@ -1309,6 +1309,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None: _init=MagicMock(), _send_msg=msg_mock ) + default_conf['runmode'] = RunMode.DRY_RUN freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) From 5243214a36a880a1ee830e9fe1b89bf1fcece92f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 05:44:29 +0000 Subject: [PATCH 20/92] Bump mkdocs-material from 6.1.2 to 6.1.4 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.2 to 6.1.4. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.2...6.1.4) Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 47f0eff1c..f034b0b36 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,3 +1,3 @@ -mkdocs-material==6.1.2 +mkdocs-material==6.1.4 mdx_truly_sane_lists==1.2 pymdown-extensions==8.0.1 From 42d9e3a28f788e2fd7f59f42f620d6a991dc6ed2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 05:44:40 +0000 Subject: [PATCH 21/92] Bump numpy from 1.19.3 to 1.19.4 Bumps [numpy](https://github.com/numpy/numpy) from 1.19.3 to 1.19.4. - [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.19.3...v1.19.4) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afe48171c..818741207 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.19.3 +numpy==1.19.4 pandas==1.1.4 ccxt==1.37.14 From 6063f2f91f319593085fe0bab300df638fd08faf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 05:44:49 +0000 Subject: [PATCH 22/92] Bump questionary from 1.7.0 to 1.8.0 Bumps [questionary](https://github.com/tmbo/questionary) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/tmbo/questionary/releases) - [Commits](https://github.com/tmbo/questionary/compare/1.7.0...1.8.0) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afe48171c..2789ca16a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,5 +35,5 @@ flask-cors==3.0.9 # Support for colorized terminal output colorama==0.4.4 # Building config files interactively -questionary==1.7.0 +questionary==1.8.0 prompt-toolkit==3.0.8 From 88b2f3f0d1b914f76e1a9921b3547a81d2040ea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 05:44:51 +0000 Subject: [PATCH 23/92] Bump scipy from 1.5.3 to 1.5.4 Bumps [scipy](https://github.com/scipy/scipy) from 1.5.3 to 1.5.4. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.5.3...v1.5.4) Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 5b68c1ea1..7e480b8c9 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.5.3 +scipy==1.5.4 scikit-learn==0.23.2 scikit-optimize==0.8.1 filelock==3.0.12 From 59e846d554f36d1eb126f5ae0d0e52ad22a0a6af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 07:48:14 +0000 Subject: [PATCH 24/92] Bump ccxt from 1.37.14 to 1.37.41 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.37.14 to 1.37.41. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.37.14...1.37.41) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 37a57e3f5..4622afa15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.4 pandas==1.1.4 -ccxt==1.37.14 +ccxt==1.37.41 aiohttp==3.7.2 SQLAlchemy==1.3.20 python-telegram-bot==13.0 From 13da8f936812bbfd255c4dfce4dd451dc9134c46 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Mon, 9 Nov 2020 08:34:40 +0000 Subject: [PATCH 25/92] Added ConstPairList handler to skip validation of pairs if you want to backtest a pair that's not live any more, e.g. expiring contracts. --- freqtrade/constants.py | 2 +- freqtrade/pairlist/ConstPairList.py | 60 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 freqtrade/pairlist/ConstPairList.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index dc5384f6f..21308b2dc 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -23,7 +23,7 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] -AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', +AVAILABLE_PAIRLISTS = ['ConstPairList', 'StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] diff --git a/freqtrade/pairlist/ConstPairList.py b/freqtrade/pairlist/ConstPairList.py new file mode 100644 index 000000000..e5b009c55 --- /dev/null +++ b/freqtrade/pairlist/ConstPairList.py @@ -0,0 +1,60 @@ +""" +Const Pair List provider + +Provides pair white list as it configured in config without checking for active markets +""" +import logging +from typing import Any, Dict, List + +from freqtrade.exceptions import OperationalException +from freqtrade.pairlist.IPairList import IPairList + + +logger = logging.getLogger(__name__) + + +class ConstPairList(IPairList): + + def __init__(self, exchange, pairlistmanager, + config: Dict[str, Any], pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + if self._pairlist_pos != 0: + raise OperationalException(f"{self.name} can only be used in the first position " + "in the list of Pairlist Handlers.") + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return False + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + -> Please overwrite in subclasses + """ + return f"{self.name}" + + def gen_pairlist(self, cached_pairlist: List[str], tickers: Dict) -> List[str]: + """ + Generate the pairlist + :param cached_pairlist: Previously generated pairlist (cached) + :param tickers: Tickers (from exchange.get_tickers()). + :return: List of pairs + """ + return self._config['exchange']['pair_whitelist'] + + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + """ + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new whitelist + """ + return pairlist From 916776bb53642b25c4be09bca82ef0c1467798d8 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Mon, 9 Nov 2020 08:37:38 +0000 Subject: [PATCH 26/92] Option to skip exchange validation, required to backtest pairs that are not live on the exchange any more. --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 659ff59bc..baa379db1 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -115,7 +115,7 @@ class Exchange: logger.info('Using Exchange "%s"', self.name) - if validate: + if validate and not exchange_config.get('skip_validation'): # Check if timeframe is available self.validate_timeframes(config.get('timeframe')) From 2640dfee9387613480e66da00a4fac5b260c1e41 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Thu, 12 Nov 2020 11:27:30 +0000 Subject: [PATCH 27/92] Revert "Added ConstPairList handler to skip validation of pairs if you want to backtest a pair that's not live any more, e.g. expiring contracts." This reverts commit 13da8f936812bbfd255c4dfce4dd451dc9134c46. --- freqtrade/constants.py | 2 +- freqtrade/pairlist/ConstPairList.py | 60 ----------------------------- 2 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 freqtrade/pairlist/ConstPairList.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 21308b2dc..dc5384f6f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -23,7 +23,7 @@ ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] -AVAILABLE_PAIRLISTS = ['ConstPairList', 'StaticPairList', 'VolumePairList', +AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] diff --git a/freqtrade/pairlist/ConstPairList.py b/freqtrade/pairlist/ConstPairList.py deleted file mode 100644 index e5b009c55..000000000 --- a/freqtrade/pairlist/ConstPairList.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Const Pair List provider - -Provides pair white list as it configured in config without checking for active markets -""" -import logging -from typing import Any, Dict, List - -from freqtrade.exceptions import OperationalException -from freqtrade.pairlist.IPairList import IPairList - - -logger = logging.getLogger(__name__) - - -class ConstPairList(IPairList): - - def __init__(self, exchange, pairlistmanager, - config: Dict[str, Any], pairlistconfig: Dict[str, Any], - pairlist_pos: int) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - - if self._pairlist_pos != 0: - raise OperationalException(f"{self.name} can only be used in the first position " - "in the list of Pairlist Handlers.") - - @property - def needstickers(self) -> bool: - """ - Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed - as tickers argument to filter_pairlist - """ - return False - - def short_desc(self) -> str: - """ - Short whitelist method description - used for startup-messages - -> Please overwrite in subclasses - """ - return f"{self.name}" - - def gen_pairlist(self, cached_pairlist: List[str], tickers: Dict) -> List[str]: - """ - Generate the pairlist - :param cached_pairlist: Previously generated pairlist (cached) - :param tickers: Tickers (from exchange.get_tickers()). - :return: List of pairs - """ - return self._config['exchange']['pair_whitelist'] - - def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: - """ - Filters and sorts pairlist and returns the whitelist again. - Called on each bot iteration - please use internal caching if necessary - :param pairlist: pairlist to filter or sort - :param tickers: Tickers (from exchange.get_tickers()). May be cached. - :return: new whitelist - """ - return pairlist From 2424ac94c27c146a553aed362c04ac1850733381 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Thu, 12 Nov 2020 11:29:46 +0000 Subject: [PATCH 28/92] skip the check for active markets with flag for existing StaticPairList --- freqtrade/pairlist/StaticPairList.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index aa6268ba3..3b6440763 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -24,6 +24,8 @@ class StaticPairList(IPairList): raise OperationalException(f"{self.name} can only be used in the first position " "in the list of Pairlist Handlers.") + self._allow_inactive = self._pairlistconfig.get('allow_inactive', False) + @property def needstickers(self) -> bool: """ @@ -47,7 +49,10 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). :return: List of pairs """ - return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist']) + if self._allow_inactive: + return self._config['exchange']['pair_whitelist'] + else: + return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist']) def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ From 2d6bfe1592ab61258d523065cdf4b38cb3857521 Mon Sep 17 00:00:00 2001 From: Daniel Goller Date: Thu, 12 Nov 2020 11:32:45 +0000 Subject: [PATCH 29/92] only skip pair validation rather than all of it --- freqtrade/exchange/exchange.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index baa379db1..0e982a06f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -115,7 +115,7 @@ class Exchange: logger.info('Using Exchange "%s"', self.name) - if validate and not exchange_config.get('skip_validation'): + if validate: # Check if timeframe is available self.validate_timeframes(config.get('timeframe')) @@ -124,7 +124,8 @@ class Exchange: # Check if all pairs are available self.validate_stakecurrency(config['stake_currency']) - self.validate_pairs(config['exchange']['pair_whitelist']) + if not exchange_config.get('skip_pair_validation'): + self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {})) self.validate_required_startup_candles(config.get('startup_candle_count', 0)) From 4eb96cfc4f24bb2a7e5c3b93f4374b2d8a14c088 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Nov 2020 06:51:45 +0100 Subject: [PATCH 30/92] Allow locks to be gathered even when the bot is stopped --- freqtrade/rpc/rpc.py | 2 -- tests/rpc/test_rpc_telegram.py | 7 ------- 2 files changed, 9 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 888dc11ec..90564a19d 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -605,8 +605,6 @@ class RPC: def _rpc_locks(self) -> Dict[str, Any]: """ Returns the current locks""" - if self._freqtrade.state != State.RUNNING: - raise RPCException('trader is not running') locks = PairLocks.get_pair_locks(None) return { diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 7885a251d..ace44a34a 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1041,13 +1041,6 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None patch_get_signal(freqtradebot, (True, False)) telegram = Telegram(freqtradebot) - freqtradebot.state = State.STOPPED - telegram._locks(update=update, context=MagicMock()) - assert msg_mock.call_count == 1 - assert 'not running' in msg_mock.call_args_list[0][0][0] - msg_mock.reset_mock() - freqtradebot.state = State.RUNNING - PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason') PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef') From 08b52926c816331033087b47c2afd1cb047c4452 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Nov 2020 10:43:48 +0100 Subject: [PATCH 31/92] Catch asyncio.TimeoutError when reloading async markets --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 659ff59bc..e74f5668c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -282,7 +282,7 @@ class Exchange: asyncio.get_event_loop().run_until_complete( self._api_async.load_markets(reload=reload)) - except ccxt.BaseError as e: + except (asyncio.TimeoutError, ccxt.BaseError) as e: logger.warning('Could not load async markets. Reason: %s', e) return From 164105acf24503a7349621add1c3b178b74ec5ff Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Nov 2020 08:25:57 +0100 Subject: [PATCH 32/92] Adjust startup_candle_count of sample strategies --- freqtrade/templates/base_strategy.py.j2 | 2 +- freqtrade/templates/sample_strategy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index ce2c6d5c0..4a1b43e36 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -63,7 +63,7 @@ class {{ strategy }}(IStrategy): ignore_roi_if_buy_signal = False # Number of candles the strategy requires before producing valid signals - startup_candle_count: int = 20 + startup_candle_count: int = 30 # Optional order type mapping. order_types = { diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 103f68a43..44590dbbe 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -64,7 +64,7 @@ class SampleStrategy(IStrategy): ignore_roi_if_buy_signal = False # Number of candles the strategy requires before producing valid signals - startup_candle_count: int = 20 + startup_candle_count: int = 30 # Optional order type mapping. order_types = { From 05f0cc787c432235fb31255725645fb42bb32fd0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Nov 2020 09:28:00 +0100 Subject: [PATCH 33/92] Plotting should use startup_candles too closes #3943 --- freqtrade/plot/plotting.py | 18 ++++++++++++++---- tests/test_plotting.py | 3 ++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index a89732df5..f7d300593 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -9,9 +9,9 @@ from freqtrade.data.btanalysis import (calculate_max_drawdown, combine_dataframe create_cum_profit, extract_trades_of_period, load_trades) from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider -from freqtrade.data.history import load_data +from freqtrade.data.history import get_timerange, load_data from freqtrade.exceptions import OperationalException -from freqtrade.exchange import timeframe_to_prev_date +from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_seconds from freqtrade.misc import pair_to_filename from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.strategy import IStrategy @@ -29,7 +29,7 @@ except ImportError: exit(1) -def init_plotscript(config): +def init_plotscript(config, startup_candles: int = 0): """ Initialize objects needed for plotting :return: Dict with candle (OHLCV) data, trades and pairs @@ -48,9 +48,16 @@ def init_plotscript(config): pairs=pairs, timeframe=config.get('timeframe', '5m'), timerange=timerange, + startup_candles=startup_candles, data_format=config.get('dataformat_ohlcv', 'json'), ) + if startup_candles: + min_date, max_date = get_timerange(data) + logger.info(f"Loading data from {min_date} to {max_date}") + timerange.adjust_start_if_necessary(timeframe_to_seconds(config.get('timeframe', '5m')), + startup_candles, min_date) + no_trades = False filename = config.get('exportfilename') if config.get('no_trades', False): @@ -72,6 +79,7 @@ def init_plotscript(config): return {"ohlcv": data, "trades": trades, "pairs": pairs, + "timerange": timerange, } @@ -474,7 +482,8 @@ def load_and_plot_trades(config: Dict[str, Any]): exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) IStrategy.dp = DataProvider(config, exchange) - plot_elements = init_plotscript(config) + plot_elements = init_plotscript(config, strategy.startup_candle_count) + timerange = plot_elements['timerange'] trades = plot_elements['trades'] pair_counter = 0 for pair, data in plot_elements["ohlcv"].items(): @@ -482,6 +491,7 @@ def load_and_plot_trades(config: Dict[str, Any]): logger.info("analyse pair %s", pair) df_analyzed = strategy.analyze_ticker(data, {'pair': pair}) + df_analyzed = trim_dataframe(df_analyzed, timerange) trades_pair = trades.loc[trades['pair'] == pair] trades_pair = extract_trades_of_period(df_analyzed, trades_pair) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 401f66b60..d3f97013d 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -51,9 +51,10 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ohlcv" in ret assert "trades" in ret assert "pairs" in ret + assert 'timerange' in ret default_conf['pairs'] = ["TRX/BTC", "ADA/BTC"] - ret = init_plotscript(default_conf) + ret = init_plotscript(default_conf, 20) assert "ohlcv" in ret assert "TRX/BTC" in ret["ohlcv"] assert "ADA/BTC" in ret["ohlcv"] From 7243c8ee56852d1fd649ffcbc5bff28fd57e7e8a Mon Sep 17 00:00:00 2001 From: SamVerhaegen Date: Sun, 15 Nov 2020 13:06:05 +0100 Subject: [PATCH 34/92] Fix typo in windows installation docs. --- docs/windows_installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 0ef0f131f..924459a6d 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -32,7 +32,7 @@ python -m venv .env .env\Scripts\activate.ps1 # optionally install ta-lib from wheel # Eventually adjust the below filename to match the downloaded wheel -pip install build_helpes/TA_Lib‑0.4.19‑cp38‑cp38‑win_amd64.whl +pip install build_helpers/TA_Lib-0.4.19-cp38-cp38-win_amd64.whl pip install -r requirements.txt pip install -e . freqtrade From da16474b2561a12636e0440d2100b0bb691a8f2d Mon Sep 17 00:00:00 2001 From: Aleksey Savin Date: Sun, 15 Nov 2020 15:13:44 +0300 Subject: [PATCH 35/92] Update telegram-usage.md --- docs/telegram-usage.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index ce2d715a0..5def6efd2 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -35,12 +35,16 @@ Copy the API Token (`22222222:APITOKEN` in the above example) and keep use it fo Don't forget to start the conversation with your bot, by clicking `/START` button -### 2. Get your user id +### 2. Telegram user_id +#### Get your user id Talk to the [userinfobot](https://telegram.me/userinfobot) Get your "Id", you will use it for the config parameter `chat_id`. +#### Use Group id +You can use bots in telegram groups just adding them to it. You can find the group id by adding a [RawDataBot](https://telegram.me/rawdatabot) to it, group id is the `"chat":{"id":-1001332619709}` in the [RawDataBot](https://telegram.me/rawdatabot) message. Dont forget about "-" (minus symbol) in start of value if it is and use string type in config, for example: `"chat_id":"-1001332619709"`. + ## Control telegram noise Freqtrade provides means to control the verbosity of your telegram bot. From 7b4c1ec3ced1ebcc2f8be06e2602f7223d964d5d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Nov 2020 15:40:40 +0100 Subject: [PATCH 36/92] Small wording changes --- docs/telegram-usage.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 5def6efd2..09cf21223 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -36,6 +36,7 @@ Copy the API Token (`22222222:APITOKEN` in the above example) and keep use it fo Don't forget to start the conversation with your bot, by clicking `/START` button ### 2. Telegram user_id + #### Get your user id Talk to the [userinfobot](https://telegram.me/userinfobot) @@ -43,7 +44,20 @@ Talk to the [userinfobot](https://telegram.me/userinfobot) Get your "Id", you will use it for the config parameter `chat_id`. #### Use Group id -You can use bots in telegram groups just adding them to it. You can find the group id by adding a [RawDataBot](https://telegram.me/rawdatabot) to it, group id is the `"chat":{"id":-1001332619709}` in the [RawDataBot](https://telegram.me/rawdatabot) message. Dont forget about "-" (minus symbol) in start of value if it is and use string type in config, for example: `"chat_id":"-1001332619709"`. + +You can use bots in telegram groups by just adding them to the group. You can find the group id by first adding a [RawDataBot](https://telegram.me/rawdatabot) to your group. The Group id is shown as id in the `"chat"` section, which the RawDataBot will send to you: + +``` json +"chat":{ + "id":-1001332619709 +} +``` + +For the Freqtrade configuration, you can then use the the full value (including `-` if it's there) as string: + +```json + "chat_id": "-1001332619709" +``` ## Control telegram noise From 26176d4c91c20cb24a762d4eb8ab204a9951d1ac Mon Sep 17 00:00:00 2001 From: Samaoo Date: Sun, 15 Nov 2020 19:55:09 +0100 Subject: [PATCH 37/92] Update exchanges.md According to https://blog.kraken.com/post/5282/stop-loss-limit-take-profit-limit-two-new-advanced-orders-go-live-on-kraken/ Stop Loss Limit orders are enabled again --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index fcf7c1cad..ac386c937 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -23,7 +23,7 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f ## Kraken !!! Tip "Stoploss on Exchange" - Kraken supports `stoploss_on_exchange` and uses stop-loss-market orders. It provides great advantages, so we recommend to benefit from it, however since the resulting order is a stoploss-market order, sell-rates are not guaranteed, which makes this feature less secure than on other exchanges. This limitation is based on kraken's policy [source](https://blog.kraken.com/post/1234/announcement-delisting-pairs-and-temporary-suspension-of-advanced-order-types/) and [source2](https://blog.kraken.com/post/1494/kraken-enables-advanced-orders-and-adds-10-currency-pairs/) - which has stoploss-limit orders disabled. + Kraken supports `stoploss_on_exchange` and uses stop-loss-market orders. It provides great advantages, so we recommend to benefit from it. ### Historic Kraken data From ef4ab601a9fc2a059d0eaf3fd77c4bb7f63f8ac3 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Sun, 15 Nov 2020 20:02:19 +0100 Subject: [PATCH 38/92] Update exchanges.md --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index ac386c937..5d7505795 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -23,7 +23,7 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f ## Kraken !!! Tip "Stoploss on Exchange" - Kraken supports `stoploss_on_exchange` and uses stop-loss-market orders. It provides great advantages, so we recommend to benefit from it. + Kraken supports `stoploss_on_exchange`. It provides great advantages, so we recommend to benefit from it. ### Historic Kraken data From 6ebc2f38974eff34140d63b2955f1e1b54f82b3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Nov 2020 05:40:25 +0000 Subject: [PATCH 39/92] Bump mkdocs-material from 6.1.4 to 6.1.5 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.4 to 6.1.5. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.4...6.1.5) Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index f034b0b36..cd294bef6 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,3 +1,3 @@ -mkdocs-material==6.1.4 +mkdocs-material==6.1.5 mdx_truly_sane_lists==1.2 pymdown-extensions==8.0.1 From e52c181a2a2a8a369a69fdff2f758767bb42b969 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Nov 2020 05:40:35 +0000 Subject: [PATCH 40/92] Bump flask-jwt-extended from 3.24.1 to 3.25.0 Bumps [flask-jwt-extended](https://github.com/vimalloc/flask-jwt-extended) from 3.24.1 to 3.25.0. - [Release notes](https://github.com/vimalloc/flask-jwt-extended/releases) - [Commits](https://github.com/vimalloc/flask-jwt-extended/compare/3.24.1...3.25.0) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4622afa15..ea33cdf3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,7 +29,7 @@ sdnotify==0.3.2 # Api server flask==1.1.2 -flask-jwt-extended==3.24.1 +flask-jwt-extended==3.25.0 flask-cors==3.0.9 # Support for colorized terminal output From 23947cf30b0dcfc7aab066d48237e0dcd229fb6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Nov 2020 05:40:50 +0000 Subject: [PATCH 41/92] Bump requests from 2.24.0 to 2.25.0 Bumps [requests](https://github.com/psf/requests) from 2.24.0 to 2.25.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.24.0...v2.25.0) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4622afa15..e56dcf4d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ SQLAlchemy==1.3.20 python-telegram-bot==13.0 arrow==0.17.0 cachetools==4.1.1 -requests==2.24.0 +requests==2.25.0 urllib3==1.25.11 wrapt==1.12.1 jsonschema==3.2.0 From f092a923990515976cfccabc59010d8ccddfd5ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Nov 2020 07:51:10 +0000 Subject: [PATCH 42/92] Bump urllib3 from 1.25.11 to 1.26.2 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.11 to 1.26.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/master/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.11...1.26.2) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5ce060819..d60a0ebba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ python-telegram-bot==13.0 arrow==0.17.0 cachetools==4.1.1 requests==2.25.0 -urllib3==1.25.11 +urllib3==1.26.2 wrapt==1.12.1 jsonschema==3.2.0 TA-Lib==0.4.19 From 3f2addb729f0d462ec11a40197eba4f00c17706a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Nov 2020 09:17:22 +0000 Subject: [PATCH 43/92] Bump ccxt from 1.37.41 to 1.37.69 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.37.41 to 1.37.69. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.37.41...1.37.69) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d60a0ebba..67b69a5dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.4 pandas==1.1.4 -ccxt==1.37.41 +ccxt==1.37.69 aiohttp==3.7.2 SQLAlchemy==1.3.20 python-telegram-bot==13.0 From 9621734adc37eb6f6edbd24907e42d35628d70ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Nov 2020 06:53:38 +0100 Subject: [PATCH 44/92] Allow setting datafromat via configuration closes #3953 --- freqtrade/commands/cli_options.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 4769bccde..619a300ae 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -354,13 +354,11 @@ AVAILABLE_CLI_OPTIONS = { '--data-format-ohlcv', help='Storage format for downloaded candle (OHLCV) data. (default: `%(default)s`).', choices=constants.AVAILABLE_DATAHANDLERS, - default='json' ), "dataformat_trades": Arg( '--data-format-trades', help='Storage format for downloaded trades data. (default: `%(default)s`).', choices=constants.AVAILABLE_DATAHANDLERS, - default='jsongz' ), "exchange": Arg( '--exchange', From 4a215821cdc2c3017c15fff2082a2b5e58773c61 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Tue, 17 Nov 2020 14:07:24 +0100 Subject: [PATCH 45/92] Fix typo in windows installation docs --- docs/windows_installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 924459a6d..0c1eeef64 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -50,7 +50,7 @@ freqtrade error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools ``` -Unfortunately, many packages requiring compilation don't provide a pre-build wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. +Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first. From 854d0c481f0b7569f4e28418a64e088aeb6c0bd4 Mon Sep 17 00:00:00 2001 From: Samaoo Date: Tue, 17 Nov 2020 14:14:42 +0100 Subject: [PATCH 46/92] Update windows_installation.md --- docs/windows_installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 0c1eeef64..5341ce96b 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -52,6 +52,6 @@ error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. -The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first. +The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building C code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first. --- From 52c9a2c37f62196d43f09b41fb0a90dc187a37e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Nov 2020 07:30:28 +0100 Subject: [PATCH 47/92] Convert np to None when loading hdf5 trades to allow duplicate detection --- freqtrade/data/history/hdf5datahandler.py | 4 +++- tests/data/test_history.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py index f6cf9e0d9..00e41673d 100644 --- a/freqtrade/data/history/hdf5datahandler.py +++ b/freqtrade/data/history/hdf5datahandler.py @@ -3,6 +3,7 @@ import re from pathlib import Path from typing import List, Optional +import numpy as np import pandas as pd from freqtrade import misc @@ -175,7 +176,8 @@ class HDF5DataHandler(IDataHandler): if timerange.stoptype == 'date': where.append(f"timestamp < {timerange.stopts * 1e3}") - trades = pd.read_hdf(filename, key=key, mode="r", where=where) + trades: pd.DataFrame = pd.read_hdf(filename, key=key, mode="r", where=where) + trades[['id', 'type']] = trades[['id', 'type']].replace({np.nan: None}) return trades.values.tolist() def trades_purge(self, pair: str) -> bool: diff --git a/tests/data/test_history.py b/tests/data/test_history.py index bbc6e55b4..538a0840f 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -724,6 +724,8 @@ def test_hdf5datahandler_trades_load(testdatadir): trades2 = dh._trades_load('XRP/ETH', timerange) assert len(trades) > len(trades2) + # Check that ID is None (If it's nan, it's wrong) + assert trades2[0][2] is None # unfiltered load has trades before starttime assert len([t for t in trades if t[0] < timerange.startts * 1000]) >= 0 From f88fe5d950daf3ab68f9153648bf8d94f20593b0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Nov 2020 19:14:43 +0100 Subject: [PATCH 48/92] Document new "allow_inactive" option --- docs/includes/pairlists.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index ae4ec818d..aebd084ab 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -35,6 +35,10 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis ], ``` +By default, only currently enabled pairs are allowed. +To skip pair validation against active markets, set `"allow_inactive": true` within the `StaticPairList` configuration. +This can be useful for backtesting expired pairs (like quarterly spot-markets). + #### Volume Pair List `VolumePairList` employs sorting/filtering of pairs by their trading volume. It selects `number_assets` top pairs with sorting based on the `sort_key` (which can only be `quoteVolume`). From 97e58a42f4caf1803af411937123461bda7ca244 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Nov 2020 19:17:31 +0100 Subject: [PATCH 49/92] Update documentation with new options --- docs/configuration.md | 1 + docs/includes/pairlists.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 47362e525..080ddd046 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -87,6 +87,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict | `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict | `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
**Datatype:** Positive Integer +| `exchange.skip_pair_validation` | Skip pairlist validation on startup.
*Defaults to `false`
**Datatype:** Boolean | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
**Datatype:** Boolean | `pairlists` | Define one or more pairlists to be used. [More information below](#pairlists-and-pairlist-handlers).
*Defaults to `StaticPairList`.*
**Datatype:** List of Dicts diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index aebd084ab..e6a9fc1a8 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -38,6 +38,7 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis By default, only currently enabled pairs are allowed. To skip pair validation against active markets, set `"allow_inactive": true` within the `StaticPairList` configuration. This can be useful for backtesting expired pairs (like quarterly spot-markets). +This option must be configured along with `exchange.skip_pair_validation` in the exchange configuration. #### Volume Pair List From aa0c3dced8bac13b35e241f287d1d9a4cbcecd31 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Nov 2020 13:14:02 +0100 Subject: [PATCH 50/92] Improve order types documentation --- docs/configuration.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 080ddd046..56ba13414 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -314,22 +314,21 @@ Configuration: } ``` -!!! Note +!!! Note "Market order support" Not all exchanges support "market" orders. The following message will be shown if your exchange does not support market orders: - `"Exchange does not support market orders."` + `"Exchange does not support market orders."` and the bot will refuse to start. -!!! Note - Stoploss on exchange interval is not mandatory. Do not change its value if you are +!!! Warning "Using market orders" + Please carefully read the section [Market order pricing](#market-order-pricing) section when using market orders. + +!!! Note "Stoploss on exchange" + `stoploss_on_exchange_interval` is not mandatory. Do not change its value if you are unsure of what you are doing. For more information about how stoploss works please refer to [the stoploss documentation](stoploss.md). -!!! Note If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order. -!!! Warning "Using market orders" - Please read the section [Market order pricing](#market-order-pricing) section when using market orders. - !!! Warning "Warning: stoploss_on_exchange failures" If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however this is not advised. From 5ed85963a950eada9571b2f6f19130ff15878157 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 10:39:49 +0100 Subject: [PATCH 51/92] Allow forcebuy price to be a string by converting it to float fix #3970 --- freqtrade/rpc/api_server.py | 2 ++ freqtrade/rpc/rpc.py | 2 +- tests/rpc/test_rpc.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 7f4773d57..384d7c6c2 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -508,6 +508,8 @@ class ApiServer(RPC): """ asset = request.json.get("pair") price = request.json.get("price", None) + price = float(price) if price is not None else price + trade = self._rpc_forcebuy(asset, price) if trade: return jsonify(trade.to_json()) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 90564a19d..e608a2274 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -524,7 +524,7 @@ class RPC: stake_currency = self._freqtrade.config.get('stake_currency') if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency: raise RPCException( - f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only') + f'Wrong pair selected. Only pairs with stake-currency {stake_currency} allowed.') # check if valid pair # check if pair already has an open pair diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 23ca53e53..47e0f763d 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -868,7 +868,8 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> assert trade.open_rate == 0.0001 # Test buy pair not with stakes - with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'): + with pytest.raises(RPCException, + match=r'Wrong pair selected. Only pairs with stake-currency.*'): rpc._rpc_forcebuy('LTC/ETH', 0.0001) pair = 'XRP/BTC' From 83861fabdea798100c5322a9efe5db4f7a23c84a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 10:52:15 +0100 Subject: [PATCH 52/92] Fix #3967, move TradeList type to constants --- freqtrade/constants.py | 3 +++ freqtrade/data/converter.py | 10 +++++---- freqtrade/data/history/hdf5datahandler.py | 4 ++-- freqtrade/data/history/idatahandler.py | 5 +---- freqtrade/data/history/jsondatahandler.py | 4 ++-- tests/data/test_converter.py | 27 ++++++++++++++++++++++- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index dc5384f6f..3271dda39 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -365,3 +365,6 @@ CANCEL_REASON = { # List of pairs with their timeframes PairWithTimeframe = Tuple[str, str] ListPairsWithTimeframes = List[PairWithTimeframe] + +# Type for trades list +TradeList = List[List] diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 38fa670e9..09930950a 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -10,7 +10,7 @@ from typing import Any, Dict, List import pandas as pd from pandas import DataFrame, to_datetime -from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS +from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList logger = logging.getLogger(__name__) @@ -168,7 +168,7 @@ def trades_remove_duplicates(trades: List[List]) -> List[List]: return [i for i, _ in itertools.groupby(sorted(trades, key=itemgetter(0)))] -def trades_dict_to_list(trades: List[Dict]) -> List[List]: +def trades_dict_to_list(trades: List[Dict]) -> TradeList: """ Convert fetch_trades result into a List (to be more memory efficient). :param trades: List of trades, as returned by ccxt.fetch_trades. @@ -177,16 +177,18 @@ def trades_dict_to_list(trades: List[Dict]) -> List[List]: return [[t[col] for col in DEFAULT_TRADES_COLUMNS] for t in trades] -def trades_to_ohlcv(trades: List, timeframe: str) -> DataFrame: +def trades_to_ohlcv(trades: TradeList, timeframe: str) -> DataFrame: """ Converts trades list to OHLCV list - TODO: This should get a dedicated test :param trades: List of trades, as returned by ccxt.fetch_trades. :param timeframe: Timeframe to resample data to :return: OHLCV Dataframe. + :raises: Valueerror if no trades are provided """ from freqtrade.exchange import timeframe_to_minutes timeframe_minutes = timeframe_to_minutes(timeframe) + if not trades: + raise ValueError('Trade-list empty.') df = pd.DataFrame(trades, columns=DEFAULT_TRADES_COLUMNS) df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True,) diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py index 00e41673d..d116637e7 100644 --- a/freqtrade/data/history/hdf5datahandler.py +++ b/freqtrade/data/history/hdf5datahandler.py @@ -9,9 +9,9 @@ import pandas as pd from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, - ListPairsWithTimeframes) + ListPairsWithTimeframes, TradeList) -from .idatahandler import IDataHandler, TradeList +from .idatahandler import IDataHandler logger = logging.getLogger(__name__) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index a170a9dc5..070d9039d 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -13,16 +13,13 @@ from typing import List, Optional, Type from pandas import DataFrame from freqtrade.configuration import TimeRange -from freqtrade.constants import ListPairsWithTimeframes +from freqtrade.constants import ListPairsWithTimeframes, TradeList from freqtrade.data.converter import clean_ohlcv_dataframe, trades_remove_duplicates, trim_dataframe from freqtrade.exchange import timeframe_to_seconds logger = logging.getLogger(__name__) -# Type for trades list -TradeList = List[List] - class IDataHandler(ABC): diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py index 6436aa13d..9122170d5 100644 --- a/freqtrade/data/history/jsondatahandler.py +++ b/freqtrade/data/history/jsondatahandler.py @@ -8,10 +8,10 @@ from pandas import DataFrame, read_json, to_datetime from freqtrade import misc from freqtrade.configuration import TimeRange -from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes +from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes, TradeList from freqtrade.data.converter import trades_dict_to_list -from .idatahandler import IDataHandler, TradeList +from .idatahandler import IDataHandler logger = logging.getLogger(__name__) diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index fdba7900f..4fdcce4d2 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -1,10 +1,13 @@ # pragma pylint: disable=missing-docstring, C0103 import logging +import pytest + from freqtrade.configuration.timerange import TimeRange from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format, ohlcv_fill_up_missing_data, ohlcv_to_dataframe, - trades_dict_to_list, trades_remove_duplicates, trim_dataframe) + trades_dict_to_list, trades_remove_duplicates, + trades_to_ohlcv, trim_dataframe) from freqtrade.data.history import (get_timerange, load_data, load_pair_history, validate_backtest_data) from tests.conftest import log_has @@ -26,6 +29,28 @@ def test_ohlcv_to_dataframe(ohlcv_history_list, caplog): assert log_has('Converting candle (OHLCV) data to dataframe for pair UNITTEST/BTC.', caplog) +def test_trades_to_ohlcv(ohlcv_history_list, caplog): + + caplog.set_level(logging.DEBUG) + with pytest.raises(ValueError, match="Trade-list empty."): + trades_to_ohlcv([], '1m') + + trades = [ + [1570752011620, "13519807", None, "sell", 0.00141342, 23.0, 0.03250866], + [1570752011620, "13519808", None, "sell", 0.00141266, 54.0, 0.07628364], + [1570752017964, "13519809", None, "sell", 0.00141266, 8.0, 0.01130128]] + + df = trades_to_ohlcv(trades, '1m') + assert not df.empty + assert len(df) == 1 + assert 'open' in df.columns + assert 'high' in df.columns + assert 'low' in df.columns + assert 'close' in df.columns + assert df.loc[:, 'high'][0] == 0.00141342 + assert df.loc[:, 'low'][0] == 0.00141266 + + def test_ohlcv_fill_up_missing_data(testdatadir, caplog): data = load_pair_history(datadir=testdatadir, timeframe='1m', From e8e3ca0c3c114637a460fdddeea5fca155bdf534 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 10:57:19 +0100 Subject: [PATCH 53/92] Catch ValueError from trade_conversion closes #3967 --- freqtrade/data/converter.py | 2 +- freqtrade/data/history/history_utils.py | 9 ++++++--- tests/data/test_history.py | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 09930950a..d4053abaa 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -183,7 +183,7 @@ def trades_to_ohlcv(trades: TradeList, timeframe: str) -> DataFrame: :param trades: List of trades, as returned by ccxt.fetch_trades. :param timeframe: Timeframe to resample data to :return: OHLCV Dataframe. - :raises: Valueerror if no trades are provided + :raises: ValueError if no trades are provided """ from freqtrade.exchange import timeframe_to_minutes timeframe_minutes = timeframe_to_minutes(timeframe) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index a420b9dcc..17b510b92 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -356,9 +356,12 @@ def convert_trades_to_ohlcv(pairs: List[str], timeframes: List[str], if erase: if data_handler_ohlcv.ohlcv_purge(pair, timeframe): logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.') - ohlcv = trades_to_ohlcv(trades, timeframe) - # Store ohlcv - data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv) + try: + ohlcv = trades_to_ohlcv(trades, timeframe) + # Store ohlcv + data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv) + except ValueError: + logger.exception(f'Could not convert {pair} to OHLCV.') def get_timerange(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 538a0840f..905798041 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -620,6 +620,12 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog): _clean_test_file(file1) _clean_test_file(file5) + assert not log_has('Could not convert NoDatapair to OHLCV.', caplog) + + convert_trades_to_ohlcv(['NoDatapair'], timeframes=['1m', '5m'], + datadir=testdatadir, timerange=tr, erase=True) + assert log_has('Could not convert NoDatapair to OHLCV.', caplog) + def test_datahandler_ohlcv_get_pairs(testdatadir): pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m') From 89ea8dbef2a73c050649d45db6185de8fb8cf789 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 11:13:44 +0100 Subject: [PATCH 54/92] Update slack invite --- CONTRIBUTING.md | 3 +-- README.md | 2 +- docs/developer.md | 2 +- docs/faq.md | 2 +- docs/index.md | 2 +- docs/strategy-customization.md | 2 -- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 399588f88..6b4e8adaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,8 +12,7 @@ Few pointers for contributions: - New features need to contain unit tests, must conform to PEP8 (max-line-length = 100) and should be documented with the introduction PR. - PR's can be declared as `[WIP]` - which signify Work in Progress Pull Requests (which are not finished). -If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) -or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR. +If you are unsure, discuss the feature on our [discord server](https://discord.gg/MA9v74M), on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR. ## Getting started diff --git a/README.md b/README.md index c9f4d0a52..4daa1854a 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ to understand the requirements before sending your pull-requests. Coding is not a necessity to contribute - maybe start with improving our documentation? Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase. -**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it. +**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/MA9v74M) or [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it. **Important:** Always create your PR against the `develop` branch, not `stable`. diff --git a/docs/developer.md b/docs/developer.md index 8ef816d5d..c253f4460 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -2,7 +2,7 @@ This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running. -All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) where you can ask questions. +All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel on [discord](https://discord.gg/MA9v74M) or [slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) where you can ask questions. ## Documentation diff --git a/docs/faq.md b/docs/faq.md index a775060de..aa33218fb 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -145,7 +145,7 @@ freqtrade hyperopt --hyperop SampleHyperopt --hyperopt-loss SharpeHyperOptLossDa ### Why does it take a long time to run hyperopt? -* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you. +* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you. * If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers: diff --git a/docs/index.md b/docs/index.md index 5608587db..c6697d165 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,7 +63,7 @@ Alternatively For any questions not covered by the documentation or for further information about the bot, we encourage you to join our passionate Slack community. -Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) to join the Freqtrade Slack channel. +Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) to join the Freqtrade Slack channel. Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M). diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 6c7d78864..db007985f 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -770,8 +770,6 @@ To get additional Ideas for strategies, head over to our [strategy repository](h Feel free to use any of them as inspiration for your own strategies. We're happy to accept Pull Requests containing new Strategies to that repo. -We also got a *strategy-sharing* channel in our [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) which is a great place to get and/or share ideas. - ## Next step Now you have a perfect strategy you probably want to backtest it. From 4d60a4cf4e9a850a1c03ac2923e163b6969564fd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 11:32:46 +0100 Subject: [PATCH 55/92] Add warning to StochRSI in sample strategy closes #2961 --- freqtrade/templates/sample_strategy.py | 2 ++ freqtrade/templates/subtemplates/indicators_full.j2 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 44590dbbe..b3f9fef07 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -184,6 +184,8 @@ class SampleStrategy(IStrategy): dataframe['fastk'] = stoch_fast['fastk'] # # Stochastic RSI + # Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this. + # STOCHRSI is NOT aligned with tradingview, which may result in non-expected results. # stoch_rsi = ta.STOCHRSI(dataframe) # dataframe['fastd_rsi'] = stoch_rsi['fastd'] # dataframe['fastk_rsi'] = stoch_rsi['fastk'] diff --git a/freqtrade/templates/subtemplates/indicators_full.j2 b/freqtrade/templates/subtemplates/indicators_full.j2 index 60a358bec..57d2ca665 100644 --- a/freqtrade/templates/subtemplates/indicators_full.j2 +++ b/freqtrade/templates/subtemplates/indicators_full.j2 @@ -62,6 +62,8 @@ dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] # # Stochastic RSI +# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this. +# STOCHRSI is NOT aligned with tradingview, which may result in non-expected results. # stoch_rsi = ta.STOCHRSI(dataframe) # dataframe['fastd_rsi'] = stoch_rsi['fastd'] # dataframe['fastk_rsi'] = stoch_rsi['fastk'] From 73f0e6e704fc85c57f0b7d982a1ff0df0fae7fa5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 11:40:28 +0100 Subject: [PATCH 56/92] Improve wording for discord server fix link to correct docker install guide --- README.md | 10 ++++------ docs/index.md | 13 +++++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4daa1854a..8526b5c91 100644 --- a/README.md +++ b/README.md @@ -132,15 +132,13 @@ The project is currently setup in two main branches: ## Support -### Help / Slack / Discord +### Help / Discord / Slack -For any questions not covered by the documentation or for further information about the bot, we encourage you to join our slack channel. +For any questions not covered by the documentation or for further information about the bot, or to simply engage with like-minded individuals, we encourage you to join our slack channel. -- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE). +Please check out our [discord server](https://discord.gg/MA9v74M). -Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M). - -*Note*: Since the discord server is relatively new, answers to questions might be slightly delayed as currently the user base quite small. +You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). ### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue) diff --git a/docs/index.md b/docs/index.md index c6697d165..f63aeb6b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -59,17 +59,14 @@ Alternatively ## Support -### Help / Slack / Discord +### Help / Discord / Slack -For any questions not covered by the documentation or for further information about the bot, we encourage you to join our passionate Slack community. +For any questions not covered by the documentation or for further information about the bot, or to simply engage with like-minded individuals, we encourage you to join our slack channel. -Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) to join the Freqtrade Slack channel. +Please check out our [discord server](https://discord.gg/MA9v74M). -Alternatively, check out the newly created [discord server](https://discord.gg/MA9v74M). - -!!! Note - Since the discord server is relatively new, answers to questions might be slightly delayed as currently the user base quite small. +You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). ## Ready to try? -Begin by reading our installation guide [for docker](docker.md) (recommended), or for [installation without docker](installation.md). +Begin by reading our installation guide [for docker](docker_quickstart.md) (recommended), or for [installation without docker](installation.md). From fb86d8f8ff43646ded784c1ebace16cc1e8fd616 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:28:50 +0100 Subject: [PATCH 57/92] Add get_historic_ohlcv_as_df to support VolatilityFilter --- freqtrade/exchange/exchange.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2bbdb0d59..2f52c512f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -679,12 +679,25 @@ class Exchange: :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from - :returns List with candle (OHLCV) data + :return: List with candle (OHLCV) data """ return asyncio.get_event_loop().run_until_complete( self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, since_ms=since_ms)) + def get_historic_ohlcv_as_df(self, pair: str, timeframe: str, + since_ms: int) -> DataFrame: + """ + Minimal wrapper around get_historic_ohlcv - converting the result into a dataframe + :param pair: Pair to download + :param timeframe: Timeframe to get data for + :param since_ms: Timestamp in milliseconds to get history from + :return: OHLCV DataFrame + """ + ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms) + return ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True, + drop_incomplete=self._ohlcv_partial_candle) + async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int) -> List: From 109824c9a80cb78c7c4ec9d6f90cb1c8c3afa640 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:29:11 +0100 Subject: [PATCH 58/92] Add VolatilityFilter --- freqtrade/constants.py | 2 +- freqtrade/pairlist/AgeFilter.py | 2 +- freqtrade/pairlist/volatilityfilter.py | 89 ++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 freqtrade/pairlist/volatilityfilter.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 3271dda39..55d802587 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', - 'ShuffleFilter', 'SpreadFilter'] + 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py index 19cf1c090..352fff082 100644 --- a/freqtrade/pairlist/AgeFilter.py +++ b/freqtrade/pairlist/AgeFilter.py @@ -49,7 +49,7 @@ class AgeFilter(IPairList): return (f"{self.name} - Filtering pairs with age less than " f"{self._min_days_listed} {plural(self._min_days_listed, 'day')}.") - def _validate_pair(self, ticker: dict) -> bool: + def _validate_pair(self, ticker: Dict) -> bool: """ Validate age for the ticker :param ticker: ticker dict as returned from ccxt.load_markets() diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py new file mode 100644 index 000000000..6039f6f69 --- /dev/null +++ b/freqtrade/pairlist/volatilityfilter.py @@ -0,0 +1,89 @@ +""" +Minimum age (days listed) pair list filter +""" +import logging +from typing import Any, Dict + +import arrow +from cachetools.ttl import TTLCache + +from freqtrade.exceptions import OperationalException +from freqtrade.misc import plural +from freqtrade.pairlist.IPairList import IPairList + + +logger = logging.getLogger(__name__) + + +class VolatilityFilter(IPairList): + + def __init__(self, exchange, pairlistmanager, + config: Dict[str, Any], pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + self._days = pairlistconfig.get('volatility_over_days', 10) + self._min_volatility = pairlistconfig.get('min_volatility', 0.01) + self._refresh_period = pairlistconfig.get('refresh_period', 1440) + + self._pair_cache: TTLCache = TTLCache(maxsize=100, ttl=self._refresh_period) + + if self._days < 1: + raise OperationalException("VolatilityFilter requires min_days_listed to be >= 1") + if self._days > exchange.ohlcv_candle_limit: + raise OperationalException("VolatilityFilter requires min_days_listed to not exceed " + "exchange max request size " + f"({exchange.ohlcv_candle_limit})") + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return True + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return (f"{self.name} - Filtering pairs with volatility below {self._min_volatility} " + f"over the last {plural(self._days, 'day')}.") + + def _validate_pair(self, ticker: Dict) -> bool: + """ + Validate volatility + :param ticker: ticker dict as returned from ccxt.load_markets() + :return: True if the pair can stay, False if it should be removed + """ + pair = ticker['symbol'] + # Check symbol in cache + if pair in self._pair_cache: + return self._pair_cache[pair] + + since_ms = int(arrow.utcnow() + .floor('day') + .shift(days=-self._days) + .float_timestamp) * 1000 + + daily_candles = self._exchange.get_historic_ohlcv_as_df(pair=pair, + timeframe='1d', + since_ms=since_ms) + result = False + if daily_candles is not None: + highest_high = daily_candles['high'].max() + lowest_low = daily_candles['low'].min() + pct_change = (highest_high - lowest_low) / lowest_low + if pct_change >= self._min_volatility: + result = True + else: + self.log_on_refresh(logger.info, + f"Removed {pair} from whitelist, " + f"because volatility over {plural(self._days, 'day')} is " + f"{pct_change:.3f}, which is below the " + f"threshold of {self._min_volatility}.") + result = False + self._pair_cache[pair] = result + + return result From 191616e4e5cbb558f48ec39e67bf5399fbf87da5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:39:04 +0100 Subject: [PATCH 59/92] Add first tests for volatilityFilter --- tests/pairlist/test_pairlist.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 1f05bef1e..3e1cca30c 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -340,6 +340,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): ([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.02}], "USDT", ['ETH/USDT', 'NANO/USDT']), + ([{"method": "StaticPairList"}, + {"method": "VolatilityFilter", "volatility_over_days": 10, + "min_volatility": 0.01, "refresh_period": 1440}], + "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history_list, pairlists, base_currency, @@ -617,6 +621,11 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his None, "PriceFilter requires max_price to be >= 0" ), # OperationalException expected + ({"method": "VolatilityFilter", "volatility_over_days": 10, "min_volatility": 0.01}, + "[{'VolatilityFilter': 'VolatilityFilter - Filtering pairs with volatility below 0.01 " + "over the last days.'}]", + None + ), ]) def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, desc_expected, exception_expected): From 6b672cd0b95f8a35fe83dab95e6b931e6b85c51d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:43:29 +0100 Subject: [PATCH 60/92] Document volatilityFilter --- docs/includes/pairlists.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index e6a9fc1a8..301a5453d 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -19,6 +19,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac * [`PriceFilter`](#pricefilter) * [`ShuffleFilter`](#shufflefilter) * [`SpreadFilter`](#spreadfilter) +* [`VolatilityFilter`](#volatilityfilter) !!! Tip "Testing pairlists" Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility sub-command to test your configuration quickly. @@ -118,6 +119,27 @@ Example: If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005` and this pair will be filtered out. +#### VolatilityFilter + +Removes pairs where the difference between lowest low and highest high over `volatility_over_days` days is below `min_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. + +In the below example: +If volatility over the last 10 days is <1%, remove the pair from the whitelist. + +```json +"pairlists": [ + { + "method": "VolatilityFilter", + "volatility_over_days": 10, + "min_volatility": 0.01, + "refresh_period": 1440 + } +] +``` + +!!! Tip + This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely hard to trade with profit. + ### Full example of Pairlist Handlers The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies both [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#price-filter), filtering all assets where 1 price unit is > 1%. Then the `SpreadFilter` is applied and pairs are finally shuffled with the random seed set to some predefined value. @@ -137,6 +159,12 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, + { + "method": "VolatilityFilter", + "volatility_over_days": 10, + "min_volatility": 0.01, + "refresh_period": 1440 + }, {"method": "ShuffleFilter", "seed": 42} ], ``` From f8fab5c4f8d120b7838cac24c6a0c7d30df85fc2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 15:51:39 +0100 Subject: [PATCH 61/92] Add tests for failure cases --- freqtrade/pairlist/volatilityfilter.py | 4 ++-- tests/pairlist/test_pairlist.py | 33 ++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py index 6039f6f69..e9ca61794 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/volatilityfilter.py @@ -29,9 +29,9 @@ class VolatilityFilter(IPairList): self._pair_cache: TTLCache = TTLCache(maxsize=100, ttl=self._refresh_period) if self._days < 1: - raise OperationalException("VolatilityFilter requires min_days_listed to be >= 1") + raise OperationalException("VolatilityFilter requires volatility_over_days to be >= 1") if self._days > exchange.ohlcv_candle_limit: - raise OperationalException("VolatilityFilter requires min_days_listed to not exceed " + raise OperationalException("VolatilityFilter requires volatility_over_days to not exceed " "exchange max request size " f"({exchange.ohlcv_candle_limit})") diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 3e1cca30c..5bbb233b4 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -58,7 +58,7 @@ def whitelist_conf_2(default_conf): @pytest.fixture(scope="function") -def whitelist_conf_3(default_conf): +def whitelist_conf_agefilter(default_conf): default_conf['stake_currency'] = 'BTC' default_conf['exchange']['pair_whitelist'] = [ 'ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC', @@ -532,7 +532,7 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf -def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers, caplog): +def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': -1}] @@ -547,7 +547,7 @@ def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tick get_patched_freqtradebot(mocker, default_conf) -def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers, caplog): +def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'AgeFilter', 'min_days_listed': 99999}] @@ -563,7 +563,7 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick get_patched_freqtradebot(mocker, default_conf) -def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_history_list): +def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history_list): mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -575,7 +575,7 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), ) - freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_3) + freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter) assert freqtrade.exchange.get_historic_ohlcv.call_count == 0 freqtrade.pairlists.refresh_pairlist() assert freqtrade.exchange.get_historic_ohlcv.call_count > 0 @@ -586,6 +586,29 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count +def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): + default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, + {'method': 'VolatilityFilter', 'volatility_over_days': 99999}] + + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) + + with pytest.raises(OperationalException, + match=r'VolatilityFilter requires volatility_over_days to not exceed ' + r'exchange max request size \([0-9]+\)'): + get_patched_freqtradebot(mocker, default_conf) + + default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, + {'method': 'VolatilityFilter', 'volatility_over_days': 0}] + + with pytest.raises(OperationalException, + match='VolatilityFilter requires volatility_over_days to be >= 1'): + get_patched_freqtradebot(mocker, default_conf) + + @pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [ ({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010, "max_price": 1.0}, From 2e1551a2ebce9cd9d288ba03a019778ff758b7ad Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Nov 2020 16:01:52 +0100 Subject: [PATCH 62/92] Improve tests of volatilityfilter --- docs/includes/pairlists.md | 2 +- freqtrade/pairlist/volatilityfilter.py | 4 ++-- tests/pairlist/test_pairlist.py | 33 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 301a5453d..7cd2369b1 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -138,7 +138,7 @@ If volatility over the last 10 days is <1%, remove the pair from the whitelist. ``` !!! Tip - This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely hard to trade with profit. + This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely difficult to trade with profit. ### Full example of Pairlist Handlers diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py index e9ca61794..415b6e89e 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/volatilityfilter.py @@ -31,8 +31,8 @@ class VolatilityFilter(IPairList): if self._days < 1: raise OperationalException("VolatilityFilter requires volatility_over_days to be >= 1") if self._days > exchange.ohlcv_candle_limit: - raise OperationalException("VolatilityFilter requires volatility_over_days to not exceed " - "exchange max request size " + raise OperationalException("VolatilityFilter requires volatility_over_days to not " + "exceed exchange max request size " f"({exchange.ohlcv_candle_limit})") @property diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 5bbb233b4..e9df5d3f4 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -609,6 +609,39 @@ def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): get_patched_freqtradebot(mocker, default_conf) +@pytest.mark.parametrize('min_volatility,expected_length', [ + (0.01, 5), + (0.05, 0), # Setting volatility to 5% removes all pairs from the whitelist. +]) +def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, + min_volatility, expected_length): + default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, + {'method': 'VolatilityFilter', 'volatility_over_days': 2, + 'min_volatility': min_volatility}] + + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list), + ) + + freqtrade = get_patched_freqtradebot(mocker, default_conf) + assert freqtrade.exchange.get_historic_ohlcv.call_count == 0 + freqtrade.pairlists.refresh_pairlist() + assert len(freqtrade.pairlists.whitelist) == expected_length + assert freqtrade.exchange.get_historic_ohlcv.call_count > 0 + + previous_call_count = freqtrade.exchange.get_historic_ohlcv.call_count + freqtrade.pairlists.refresh_pairlist() + assert len(freqtrade.pairlists.whitelist) == expected_length + # Should not have increased since first call. + assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count + + @pytest.mark.parametrize("pairlistconfig,desc_expected,exception_expected", [ ({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010, "max_price": 1.0}, From f12a8afd4151d6a2f069f5375291dc57e6b862b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 10:56:19 +0100 Subject: [PATCH 63/92] Add test for ohlcv_as_df --- tests/exchange/test_exchange.py | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index e4452a83c..42681b367 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1307,6 +1307,57 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): assert log_has_re(r"Async code raised an exception: .*", caplog) +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + ohlcv = [ + [ + arrow.utcnow().int_timestamp * 1000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ], + [ + arrow.utcnow().shift(minutes=5).int_timestamp * 1000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ], + [ + arrow.utcnow().shift(minutes=10).int_timestamp * 1000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ] + ] + pair = 'ETH/BTC' + + async def mock_candle_hist(pair, timeframe, since_ms): + return pair, timeframe, ohlcv + + exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) + # one_call calculation * 1.8 should do 2 calls + + since = 5 * 60 * exchange._ft_has['ohlcv_candle_limit'] * 1.8 + ret = exchange.get_historic_ohlcv_as_df(pair, "5m", int(( + arrow.utcnow().int_timestamp - since) * 1000)) + + assert exchange._async_get_candle_history.call_count == 2 + # Returns twice the above OHLCV data + assert len(ret) == 2 + assert isinstance(ret, DataFrame) + assert 'date' in ret.columns + assert 'open' in ret.columns + assert 'close' in ret.columns + assert 'high' in ret.columns + + def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: ohlcv = [ [ From 7e4fe23bf94128fa1df7477011d65d6d3ff2afd8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 11:08:01 +0100 Subject: [PATCH 64/92] Add VolatilityFilter to full config --- config_full.json.example | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config_full.json.example b/config_full.json.example index 45c5c695c..0d82b9a2b 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -67,7 +67,13 @@ {"method": "AgeFilter", "min_days_listed": 10}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.01, "min_price": 0.00000010}, - {"method": "SpreadFilter", "max_spread_ratio": 0.005} + {"method": "SpreadFilter", "max_spread_ratio": 0.005}, + { + "method": "VolatilityFilter", + "volatility_over_days": 10, + "min_volatility": 0.01, + "refresh_period": 1440 + } ], "exchange": { "name": "bittrex", From 29c6a9263de13b3a480662d1c59b203512df8bd3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 15:50:44 +0100 Subject: [PATCH 65/92] Protect against 0 values --- freqtrade/pairlist/volatilityfilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/volatilityfilter.py index 415b6e89e..14ac0c617 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/volatilityfilter.py @@ -74,7 +74,7 @@ class VolatilityFilter(IPairList): if daily_candles is not None: highest_high = daily_candles['high'].max() lowest_low = daily_candles['low'].min() - pct_change = (highest_high - lowest_low) / lowest_low + pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 if pct_change >= self._min_volatility: result = True else: From ec330112552afc3902bef12b6ea3884a39a16193 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:43 +0000 Subject: [PATCH 66/92] Bump ccxt from 1.37.69 to 1.38.13 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.37.69 to 1.38.13. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.37.69...1.38.13) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67b69a5dd..7f2ac98b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.19.4 pandas==1.1.4 -ccxt==1.37.69 +ccxt==1.38.13 aiohttp==3.7.2 SQLAlchemy==1.3.20 python-telegram-bot==13.0 From 83b4cd7b39f1ea2eba4d820043f47b7a42c96fd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:51 +0000 Subject: [PATCH 67/92] Bump mkdocs-material from 6.1.5 to 6.1.6 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.5 to 6.1.6. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.5...6.1.6) Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index cd294bef6..87bc6dfdd 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,3 +1,3 @@ -mkdocs-material==6.1.5 +mkdocs-material==6.1.6 mdx_truly_sane_lists==1.2 pymdown-extensions==8.0.1 From be4807d85c24c719f87a3ee43d13012d5988de41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:52 +0000 Subject: [PATCH 68/92] Bump questionary from 1.8.0 to 1.8.1 Bumps [questionary](https://github.com/tmbo/questionary) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/tmbo/questionary/releases) - [Commits](https://github.com/tmbo/questionary/compare/1.8.0...1.8.1) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67b69a5dd..66fa04824 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,5 +35,5 @@ flask-cors==3.0.9 # Support for colorized terminal output colorama==0.4.4 # Building config files interactively -questionary==1.8.0 +questionary==1.8.1 prompt-toolkit==3.0.8 From 7c7a8190abf5548ba54cc54141929668492ac56b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:50:54 +0000 Subject: [PATCH 69/92] Bump python-rapidjson from 0.9.3 to 0.9.4 Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 0.9.3 to 0.9.4. - [Release notes](https://github.com/python-rapidjson/python-rapidjson/releases) - [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst) - [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v0.9.3...v0.9.4) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67b69a5dd..487b19159 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ blosc==1.9.2 py_find_1st==1.1.4 # Load ticker files 30% faster -python-rapidjson==0.9.3 +python-rapidjson==0.9.4 # Notify systemd sdnotify==0.3.2 From 56629d882e1d5fc39c00de14f5995af9ce16ce6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 05:51:03 +0000 Subject: [PATCH 70/92] Bump coveralls from 2.1.2 to 2.2.0 Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 2.1.2 to 2.2.0. - [Release notes](https://github.com/coveralls-clients/coveralls-python/releases) - [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md) - [Commits](https://github.com/coveralls-clients/coveralls-python/compare/2.1.2...2.2.0) Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1c96a880a..e681274c8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ -r requirements-plot.txt -r requirements-hyperopt.txt -coveralls==2.1.2 +coveralls==2.2.0 flake8==3.8.4 flake8-type-annotations==0.1.0 flake8-tidy-imports==4.1.0 From 1ec99e6b76010c0f0471d009b64e7f3bef0c433d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 08:05:45 +0000 Subject: [PATCH 71/92] Bump aiohttp from 3.7.2 to 3.7.3 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.7.2 to 3.7.3. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.7.2...v3.7.3) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fa3024e30..7490688d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.19.4 pandas==1.1.4 ccxt==1.38.13 -aiohttp==3.7.2 +aiohttp==3.7.3 SQLAlchemy==1.3.20 python-telegram-bot==13.0 arrow==0.17.0 From 730c9ce4719ee257b62a149d2c807c5da43e07d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 06:57:11 +0100 Subject: [PATCH 72/92] Add Max_open_trades to summary metrics --- docs/backtesting.md | 9 +++++++-- freqtrade/optimize/optimize_reports.py | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 84911568b..277b11083 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -162,6 +162,8 @@ A backtesting result will look like that: |-----------------------+---------------------| | Backtesting from | 2019-01-01 00:00:00 | | Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | | Total trades | 429 | | First trade | 2019-01-01 18:30:00 | | First trade Pair | EOS/USDT | @@ -233,6 +235,8 @@ It contains some useful key metrics about performance of your strategy on backte |-----------------------+---------------------| | Backtesting from | 2019-01-01 00:00:00 | | Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | | Total trades | 429 | | First trade | 2019-01-01 18:30:00 | | First trade Pair | EOS/USDT | @@ -251,16 +255,17 @@ It contains some useful key metrics about performance of your strategy on backte ``` +- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). +- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - to clearly see settings for this. - `Total trades`: Identical to the total trades of the backtest output table. - `First trade`: First trade entered. - `First trade pair`: Which pair was part of the first trade. -- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). - `Total Profit %`: Total profit per stake amount. Aligned to the TOTAL column of the first table. - `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy). - `Best day` / `Worst day`: Best and worst day based on daily profit. - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). -- `Drawdown Start` / `Drawdown End`: Start and end datetimes for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). +- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. ### Assumptions made by backtesting diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index c977a991b..fc04cbd93 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -396,6 +396,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: metrics = [ ('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)), ('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)), + ('Max open trades', strat_results['max_open_trades']), + ('', ''), # Empty line to improve readability ('Total trades', strat_results['total_trades']), ('First trade', min_trade['open_date'].strftime(DATETIME_PRINT_FORMAT)), ('First trade Pair', min_trade['pair']), From 006436a18d2d2c821ca4a51ff1e604074ddacf9e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 07:47:35 +0100 Subject: [PATCH 73/92] Require use_sell_signal to be true for edge Otherwise edge will have strange results, as edge runs with sell signal, while the bot runs without sell signal, causing results to be invalid closes #3900 --- freqtrade/configuration/config_validation.py | 4 ++++ tests/test_configuration.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index d4612d8e0..ab21bc686 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -137,6 +137,10 @@ def _validate_edge(conf: Dict[str, Any]) -> None: "Edge and VolumePairList are incompatible, " "Edge will override whatever pairs VolumePairlist selects." ) + if not conf.get('ask_strategy', {}).get('use_sell_signal', True): + raise OperationalException( + "Edge requires `use_sell_signal` to be True, otherwise no sells will happen." + ) def _validate_whitelist(conf: Dict[str, Any]) -> None: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7d6c81f74..9594b6413 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -812,6 +812,21 @@ def test_validate_edge(edge_conf): validate_config_consistency(edge_conf) +def test_validate_edge2(edge_conf): + edge_conf.update({"ask_strategy": { + "use_sell_signal": True, + }}) + # Passes test + validate_config_consistency(edge_conf) + + edge_conf.update({"ask_strategy": { + "use_sell_signal": False, + }}) + with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, " + "otherwise no sells will happen."): + validate_config_consistency(edge_conf) + + def test_validate_whitelist(default_conf): default_conf['runmode'] = RunMode.DRY_RUN # Test regular case - has whitelist and uses StaticPairlist From bd98ff6332f9bd3d4ea73d1ee18446b48f0e187e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 20:24:51 +0100 Subject: [PATCH 74/92] Update docstring in all pairlists --- freqtrade/pairlist/AgeFilter.py | 2 +- freqtrade/pairlist/IPairList.py | 2 +- freqtrade/pairlist/PrecisionFilter.py | 2 +- freqtrade/pairlist/PriceFilter.py | 2 +- freqtrade/pairlist/ShuffleFilter.py | 2 +- freqtrade/pairlist/SpreadFilter.py | 2 +- freqtrade/pairlist/StaticPairList.py | 2 +- freqtrade/pairlist/VolumePairList.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/pairlist/AgeFilter.py b/freqtrade/pairlist/AgeFilter.py index 19cf1c090..20635a9ed 100644 --- a/freqtrade/pairlist/AgeFilter.py +++ b/freqtrade/pairlist/AgeFilter.py @@ -37,7 +37,7 @@ class AgeFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 6b5bd11e7..c869e499b 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -68,7 +68,7 @@ class IPairList(ABC): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index cf853397b..29e32fd44 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -32,7 +32,7 @@ class PrecisionFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/PriceFilter.py b/freqtrade/pairlist/PriceFilter.py index 8cd57ee1d..bef1c0a15 100644 --- a/freqtrade/pairlist/PriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -35,7 +35,7 @@ class PriceFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/ShuffleFilter.py b/freqtrade/pairlist/ShuffleFilter.py index eb4f6dcc3..28778db7b 100644 --- a/freqtrade/pairlist/ShuffleFilter.py +++ b/freqtrade/pairlist/ShuffleFilter.py @@ -25,7 +25,7 @@ class ShuffleFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return False diff --git a/freqtrade/pairlist/SpreadFilter.py b/freqtrade/pairlist/SpreadFilter.py index 2527a3131..a636b90bd 100644 --- a/freqtrade/pairlist/SpreadFilter.py +++ b/freqtrade/pairlist/SpreadFilter.py @@ -24,7 +24,7 @@ class SpreadFilter(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 3b6440763..2879cb364 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -30,7 +30,7 @@ class StaticPairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return False diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 44e5c52d7..7d3c2c653 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -49,7 +49,7 @@ class VolumePairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True From ceb50a78071c7295b486de887161875126bb3f72 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 07:57:23 +0100 Subject: [PATCH 75/92] use exception handler when downloading data closes #3992 --- freqtrade/data/history/history_utils.py | 12 +++++------- tests/data/test_history.py | 5 +---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 17b510b92..3b8b5a2f0 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -214,10 +214,9 @@ def _download_pair_history(datadir: Path, data_handler.ohlcv_store(pair, timeframe, data=data) return True - except Exception as e: - logger.error( - f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}. ' - f'Error: {e}' + except Exception: + logger.exception( + f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}.' ) return False @@ -304,10 +303,9 @@ def _download_trades_history(exchange: Exchange, logger.info(f"New Amount of trades: {len(trades)}") return True - except Exception as e: - logger.error( + except Exception: + logger.exception( f'Failed to download historic trades for pair: "{pair}". ' - f'Error: {e}' ) return False diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 905798041..99b22adda 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -312,10 +312,7 @@ def test_download_backtesting_data_exception(ohlcv_history, mocker, caplog, # clean files freshly downloaded _clean_test_file(file1_1) _clean_test_file(file1_5) - assert log_has( - 'Failed to download history data for pair: "MEME/BTC", timeframe: 1m. ' - 'Error: File Error', caplog - ) + assert log_has('Failed to download history data for pair: "MEME/BTC", timeframe: 1m.', caplog) def test_load_partial_missing(testdatadir, caplog) -> None: From 99b67348b206a75556d35f3994ec293b3d543dac Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 14:30:58 +0100 Subject: [PATCH 76/92] Add test for double-logging --- tests/test_configuration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 9594b6413..47d393860 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -678,6 +678,9 @@ def test_set_loggers_syslog(mocker): assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler] assert [x for x in logger.handlers if type(x) == logging.StreamHandler] assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler] + # setting up logging again should NOT cause the loggers to be added a second time. + setup_logging(config) + assert len(logger.handlers) == 3 # reset handlers to not break pytest logger.handlers = orig_handlers From 0104c9fde68601e694969006029cd1b17e8c015c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 14:31:34 +0100 Subject: [PATCH 77/92] Fix double logging --- freqtrade/loggers.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index 169cd2610..fbb05d879 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -37,6 +37,13 @@ def _set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None: ) +def get_existing_handlers(handlertype): + """ + Returns Existing handler or None (if the handler has not yet been added to the root handlers). + """ + return next((h for h in logging.root.handlers if isinstance(h, handlertype)), None) + + def setup_logging_pre() -> None: """ Early setup for logging. @@ -71,18 +78,24 @@ def setup_logging(config: Dict[str, Any]) -> None: # config['logfilename']), which defaults to '/dev/log', applicable for most # of the systems. address = (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else '/dev/log' - handler = SysLogHandler(address=address) + handler_sl = get_existing_handlers(SysLogHandler) + if handler_sl: + logging.root.removeHandler(handler_sl) + handler_sl = SysLogHandler(address=address) # No datetime field for logging into syslog, to allow syslog # to perform reduction of repeating messages if this is set in the # syslog config. The messages should be equal for this. - handler.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) - logging.root.addHandler(handler) + handler_sl.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) + logging.root.addHandler(handler_sl) elif s[0] == 'journald': try: from systemd.journal import JournaldLogHandler except ImportError: raise OperationalException("You need the systemd python package be installed in " "order to use logging to journald.") + handler_jd = get_existing_handlers(JournaldLogHandler) + if handler_jd: + logging.root.removeHandler(handler_jd) handler_jd = JournaldLogHandler() # No datetime field for logging into journald, to allow syslog # to perform reduction of repeating messages if this is set in the @@ -90,6 +103,9 @@ def setup_logging(config: Dict[str, Any]) -> None: handler_jd.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s')) logging.root.addHandler(handler_jd) else: + handler_rf = get_existing_handlers(RotatingFileHandler) + if handler_rf: + logging.root.removeHandler(handler_rf) handler_rf = RotatingFileHandler(logfile, maxBytes=1024 * 1024 * 10, # 10Mb backupCount=10) From b9980330a5469aa99160c8e40815a0c8707e0482 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 14:53:25 +0100 Subject: [PATCH 78/92] Add explicit test for FileHandler --- tests/test_configuration.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 47d393860..3501f1f3d 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -663,7 +663,7 @@ def test_set_loggers() -> None: @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") -def test_set_loggers_syslog(mocker): +def test_set_loggers_syslog(): logger = logging.getLogger() orig_handlers = logger.handlers logger.handlers = [] @@ -685,6 +685,30 @@ def test_set_loggers_syslog(mocker): logger.handlers = orig_handlers +def test_set_loggers_Filehandler(tmpdir): + logger = logging.getLogger() + orig_handlers = logger.handlers + logger.handlers = [] + logfile = Path(tmpdir) / 'ft_logfile.log' + config = {'verbosity': 2, + 'logfile': str(logfile), + } + + setup_logging_pre() + setup_logging(config) + assert len(logger.handlers) == 3 + assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler] + assert [x for x in logger.handlers if type(x) == logging.StreamHandler] + assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler] + # setting up logging again should NOT cause the loggers to be added a second time. + setup_logging(config) + assert len(logger.handlers) == 3 + # reset handlers to not break pytest + if logfile.exists: + logfile.unlink() + logger.handlers = orig_handlers + + @pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.") def test_set_loggers_journald(mocker): logger = logging.getLogger() From 46389e343bc0314427823023604c55bf212686e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 15:10:17 +0100 Subject: [PATCH 79/92] Skip filehandler test on windows - as that causes a permission-error --- tests/test_configuration.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 3501f1f3d..e6c91a96e 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -685,6 +685,7 @@ def test_set_loggers_syslog(): logger.handlers = orig_handlers +@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") def test_set_loggers_Filehandler(tmpdir): logger = logging.getLogger() orig_handlers = logger.handlers From 8f1d2ff0701bcf34609cec0c52e25697e3a8c65f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 19:47:27 +0100 Subject: [PATCH 80/92] Renamd volatilityFilter to RangeStabilityFilter --- config_full.json.example | 4 +-- docs/includes/pairlists.md | 22 ++++++------- freqtrade/constants.py | 2 +- ...ilityfilter.py => rangestabilityfilter.py} | 22 ++++++------- tests/pairlist/test_pairlist.py | 32 +++++++++---------- 5 files changed, 41 insertions(+), 41 deletions(-) rename freqtrade/pairlist/{volatilityfilter.py => rangestabilityfilter.py} (77%) diff --git a/config_full.json.example b/config_full.json.example index 0d82b9a2b..365b6180b 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -69,8 +69,8 @@ {"method": "PriceFilter", "low_price_ratio": 0.01, "min_price": 0.00000010}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, { - "method": "VolatilityFilter", - "volatility_over_days": 10, + "method": "RangeStabilityFilter", + "lookback_days": 10, "min_volatility": 0.01, "refresh_period": 1440 } diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 7cd2369b1..149e784bd 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -19,7 +19,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac * [`PriceFilter`](#pricefilter) * [`ShuffleFilter`](#shufflefilter) * [`SpreadFilter`](#spreadfilter) -* [`VolatilityFilter`](#volatilityfilter) +* [`RangeStabilityFilter`](#rangestabilityfilter) !!! Tip "Testing pairlists" Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility sub-command to test your configuration quickly. @@ -119,26 +119,26 @@ Example: If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005` and this pair will be filtered out. -#### VolatilityFilter +#### RangeStabilityFilter -Removes pairs where the difference between lowest low and highest high over `volatility_over_days` days is below `min_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. +Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. In the below example: -If volatility over the last 10 days is <1%, remove the pair from the whitelist. +If the trading range over the last 10 days is <1%, remove the pair from the whitelist. ```json "pairlists": [ { - "method": "VolatilityFilter", - "volatility_over_days": 10, - "min_volatility": 0.01, + "method": "RangeStabilityFilter", + "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440 } ] ``` !!! Tip - This Filter can be used to automatically remove stable coin pairs, which have a very low volatility, and are therefore extremely difficult to trade with profit. + This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit. ### Full example of Pairlist Handlers @@ -160,9 +160,9 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, {"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "SpreadFilter", "max_spread_ratio": 0.005}, { - "method": "VolatilityFilter", - "volatility_over_days": 10, - "min_volatility": 0.01, + "method": "RangeStabilityFilter", + "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440 }, {"method": "ShuffleFilter", "seed": 42} diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 55d802587..2022556d2 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -25,7 +25,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'PrecisionFilter', 'PriceFilter', - 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] + 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/freqtrade/pairlist/volatilityfilter.py b/freqtrade/pairlist/rangestabilityfilter.py similarity index 77% rename from freqtrade/pairlist/volatilityfilter.py rename to freqtrade/pairlist/rangestabilityfilter.py index 14ac0c617..f428bb113 100644 --- a/freqtrade/pairlist/volatilityfilter.py +++ b/freqtrade/pairlist/rangestabilityfilter.py @@ -15,23 +15,23 @@ from freqtrade.pairlist.IPairList import IPairList logger = logging.getLogger(__name__) -class VolatilityFilter(IPairList): +class RangeStabilityFilter(IPairList): def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: Dict[str, Any], pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - self._days = pairlistconfig.get('volatility_over_days', 10) - self._min_volatility = pairlistconfig.get('min_volatility', 0.01) + self._days = pairlistconfig.get('lookback_days', 10) + self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01) self._refresh_period = pairlistconfig.get('refresh_period', 1440) self._pair_cache: TTLCache = TTLCache(maxsize=100, ttl=self._refresh_period) if self._days < 1: - raise OperationalException("VolatilityFilter requires volatility_over_days to be >= 1") + raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1") if self._days > exchange.ohlcv_candle_limit: - raise OperationalException("VolatilityFilter requires volatility_over_days to not " + raise OperationalException("RangeStabilityFilter requires lookback_days to not " "exceed exchange max request size " f"({exchange.ohlcv_candle_limit})") @@ -48,12 +48,12 @@ class VolatilityFilter(IPairList): """ Short whitelist method description - used for startup-messages """ - return (f"{self.name} - Filtering pairs with volatility below {self._min_volatility} " - f"over the last {plural(self._days, 'day')}.") + return (f"{self.name} - Filtering pairs with rate of change below " + f"{self._min_rate_of_change} over the last {plural(self._days, 'day')}.") def _validate_pair(self, ticker: Dict) -> bool: """ - Validate volatility + Validate trading range :param ticker: ticker dict as returned from ccxt.load_markets() :return: True if the pair can stay, False if it should be removed """ @@ -75,14 +75,14 @@ class VolatilityFilter(IPairList): highest_high = daily_candles['high'].max() lowest_low = daily_candles['low'].min() pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 - if pct_change >= self._min_volatility: + if pct_change >= self._min_rate_of_change: result = True else: self.log_on_refresh(logger.info, f"Removed {pair} from whitelist, " - f"because volatility over {plural(self._days, 'day')} is " + f"because rate of change over {plural(self._days, 'day')} is " f"{pct_change:.3f}, which is below the " - f"threshold of {self._min_volatility}.") + f"threshold of {self._min_rate_of_change}.") result = False self._pair_cache[pair] = result diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index e9df5d3f4..d696e6d02 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -341,8 +341,8 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): {"method": "PriceFilter", "low_price_ratio": 0.02}], "USDT", ['ETH/USDT', 'NANO/USDT']), ([{"method": "StaticPairList"}, - {"method": "VolatilityFilter", "volatility_over_days": 10, - "min_volatility": 0.01, "refresh_period": 1440}], + {"method": "RangeStabilityFilter", "lookback_days": 10, + "min_rate_of_change": 0.01, "refresh_period": 1440}], "BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, @@ -586,9 +586,9 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, o assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count -def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): +def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 99999}] + {'method': 'RangeStabilityFilter', 'lookback_days': 99999}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -597,27 +597,27 @@ def test_volatilityfilter_checks(mocker, default_conf, markets, tickers): ) with pytest.raises(OperationalException, - match=r'VolatilityFilter requires volatility_over_days to not exceed ' + match=r'RangeStabilityFilter requires lookback_days to not exceed ' r'exchange max request size \([0-9]+\)'): get_patched_freqtradebot(mocker, default_conf) default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 0}] + {'method': 'RangeStabilityFilter', 'lookback_days': 0}] with pytest.raises(OperationalException, - match='VolatilityFilter requires volatility_over_days to be >= 1'): + match='RangeStabilityFilter requires lookback_days to be >= 1'): get_patched_freqtradebot(mocker, default_conf) -@pytest.mark.parametrize('min_volatility,expected_length', [ +@pytest.mark.parametrize('min_rate_of_change,expected_length', [ (0.01, 5), - (0.05, 0), # Setting volatility to 5% removes all pairs from the whitelist. + (0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist. ]) -def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, - min_volatility, expected_length): +def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history_list, + min_rate_of_change, expected_length): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, - {'method': 'VolatilityFilter', 'volatility_over_days': 2, - 'min_volatility': min_volatility}] + {'method': 'RangeStabilityFilter', 'lookback_days': 2, + 'min_rate_of_change': min_rate_of_change}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), @@ -677,9 +677,9 @@ def test_volatilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_ None, "PriceFilter requires max_price to be >= 0" ), # OperationalException expected - ({"method": "VolatilityFilter", "volatility_over_days": 10, "min_volatility": 0.01}, - "[{'VolatilityFilter': 'VolatilityFilter - Filtering pairs with volatility below 0.01 " - "over the last days.'}]", + ({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01}, + "[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below " + "0.01 over the last days.'}]", None ), ]) From 0d349cb3550bfbbfa6af7915a53789074a76d6a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Nov 2020 19:59:18 +0100 Subject: [PATCH 81/92] Small finetuning --- config_full.json.example | 2 +- freqtrade/exchange/exchange.py | 2 +- freqtrade/pairlist/rangestabilityfilter.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 365b6180b..5ee2a1faf 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -71,7 +71,7 @@ { "method": "RangeStabilityFilter", "lookback_days": 10, - "min_volatility": 0.01, + "min_rate_of_change": 0.01, "refresh_period": 1440 } ], diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2f52c512f..18f4fbff5 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -689,7 +689,7 @@ class Exchange: since_ms: int) -> DataFrame: """ Minimal wrapper around get_historic_ohlcv - converting the result into a dataframe - :param pair: Pair to download + :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from :return: OHLCV DataFrame diff --git a/freqtrade/pairlist/rangestabilityfilter.py b/freqtrade/pairlist/rangestabilityfilter.py index f428bb113..798d192bd 100644 --- a/freqtrade/pairlist/rangestabilityfilter.py +++ b/freqtrade/pairlist/rangestabilityfilter.py @@ -1,5 +1,5 @@ """ -Minimum age (days listed) pair list filter +Rate of change pairlist filter """ import logging from typing import Any, Dict From 8ae604d473df16f769c9cf43d4b37f3eec9f26cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 20:05:06 +0100 Subject: [PATCH 82/92] Ensure we're not running off of empty dataframes --- freqtrade/pairlist/rangestabilityfilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/rangestabilityfilter.py b/freqtrade/pairlist/rangestabilityfilter.py index 798d192bd..b460ff477 100644 --- a/freqtrade/pairlist/rangestabilityfilter.py +++ b/freqtrade/pairlist/rangestabilityfilter.py @@ -71,7 +71,7 @@ class RangeStabilityFilter(IPairList): timeframe='1d', since_ms=since_ms) result = False - if daily_candles is not None: + if daily_candles is not None and not daily_candles.empty: highest_high = daily_candles['high'].max() lowest_low = daily_candles['low'].min() pct_change = ((highest_high - lowest_low) / lowest_low) if lowest_low > 0 else 0 From 6810192992df1e9f7943728c65c6c02e675c2d92 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 24 Nov 2020 20:25:18 +0100 Subject: [PATCH 83/92] Update docstring for new filter --- freqtrade/pairlist/VolumePairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 44e5c52d7..7d3c2c653 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -49,7 +49,7 @@ class VolumePairList(IPairList): def needstickers(self) -> bool: """ Boolean property defining if tickers are necessary. - If no Pairlist requires tickers, an empty List is passed + If no Pairlist requires tickers, an empty Dict is passed as tickers argument to filter_pairlist """ return True From c14c0f60a1b8a2fd52c501b5355713b92e3ba100 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 16:27:27 +0100 Subject: [PATCH 84/92] Add Support for kraken stoploss-limit --- docs/exchanges.md | 8 ++++---- docs/stoploss.md | 12 +++++++++--- freqtrade/exchange/kraken.py | 11 ++++++++--- tests/exchange/test_kraken.py | 26 +++++++++++++++----------- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 5d7505795..d877e6da2 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -23,7 +23,8 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f ## Kraken !!! Tip "Stoploss on Exchange" - Kraken supports `stoploss_on_exchange`. It provides great advantages, so we recommend to benefit from it. + Kraken supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. + You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use. ### Historic Kraken data @@ -75,8 +76,7 @@ print(res) !!! Tip "Stoploss on Exchange" FTX supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. - You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide. - + You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used. ### Using subaccounts @@ -99,10 +99,10 @@ To use subaccounts with FTX, you need to edit the configuration and add the foll Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys. - ## Random notes for other exchanges * The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed: + ```shell $ pip3 install web3 ``` diff --git a/docs/stoploss.md b/docs/stoploss.md index fa888cd47..7993da401 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -23,11 +23,12 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market) and FTX (stop limit and stop-market) as of now. - Do not set too low stoploss value if using stop loss on exchange! - If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work + Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market, stop-loss-limit) and FTX (stop limit and stop-market) as of now. + Do not set too low/tight stoploss value if using stop loss on exchange! + If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. ### stoploss_on_exchange and stoploss_on_exchange_limit_ratio + Enable or Disable stop loss on exchange. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfully. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. @@ -40,13 +41,18 @@ Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the limit order For example, assuming the stoploss is on exchange, and trailing stoploss is enabled, and the market is going up, then the bot automatically cancels the previous stoploss order and puts a new one with a stop value higher than the previous stoploss order. +!!! Note + If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order. + ### stoploss_on_exchange_interval + In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. The bot cannot do these every 5 seconds (at each iteration), otherwise it would get banned by the exchange. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). This same logic will reapply a stoploss order on the exchange should you cancel it accidentally. ### emergencysell + `emergencysell` is an optional value, which defaults to `market` and is used when creating stop loss on exchange orders fails. The below is the default which is used if not changed in strategy or configuration file. diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 5b7aa5c5b..d66793845 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -77,8 +77,15 @@ class Kraken(Exchange): Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. """ + params = self._params.copy() - ordertype = "stop-loss" + if order_types.get('stoploss', 'market') == 'limit': + ordertype = "stop-loss-limit" + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + limit_rate = stop_price * limit_price_pct + params['price2'] = self.price_to_precision(pair, limit_rate) + else: + ordertype = "stop-loss" stop_price = self.price_to_precision(pair, stop_price) @@ -88,8 +95,6 @@ class Kraken(Exchange): return dry_order try: - params = self._params.copy() - amount = self.amount_to_precision(pair, amount) order = self._api.create_order(symbol=pair, type=ordertype, side='sell', diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 31b79a202..3803658eb 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -10,6 +10,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers STOPLOSS_ORDERTYPE = 'stop-loss' +STOPLOSS_LIMIT_ORDERTYPE = 'stop-loss-limit' def test_buy_kraken_trading_agreement(default_conf, mocker): @@ -156,7 +157,8 @@ def test_get_balances_prod(default_conf, mocker): "get_balances", "fetch_balance") -def test_stoploss_order_kraken(default_conf, mocker): +@pytest.mark.parametrize('ordertype', ['market', 'limit']) +def test_stoploss_order_kraken(default_conf, mocker, ordertype): api_mock = MagicMock() order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) @@ -173,24 +175,26 @@ def test_stoploss_order_kraken(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') - # stoploss_on_exchange_limit_ratio is irrelevant for kraken market orders - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, - order_types={'stoploss_on_exchange_limit_ratio': 1.05}) - assert api_mock.create_order.call_count == 1 - - api_mock.create_order.reset_mock() - - order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, + order_types={'stoploss': ordertype, + 'stoploss_on_exchange_limit_ratio': 0.99 + }) assert 'id' in order assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' - assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE + if ordertype == 'limit': + assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE + assert api_mock.create_order.call_args_list[0][1]['params'] == { + 'trading_agreement': 'agree', 'price2': 217.8} + else: + assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE + assert api_mock.create_order.call_args_list[0][1]['params'] == { + 'trading_agreement': 'agree'} assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 assert api_mock.create_order.call_args_list[0][1]['price'] == 220 - assert api_mock.create_order.call_args_list[0][1]['params'] == {'trading_agreement': 'agree'} # test exception handling with pytest.raises(DependencyException): From d0d9921b42d5e4e10e57d3307d976c1f987c7e6e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 16:27:41 +0100 Subject: [PATCH 85/92] Reorder mkdocs sequence --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 8d1ce1cfe..2cc0c9fcb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,13 +20,13 @@ nav: - Hyperopt: hyperopt.md - Edge Positioning: edge.md - Utility Subcommands: utils.md - - Exchange-specific Notes: exchanges.md - FAQ: faq.md - Data Analysis: - Jupyter Notebooks: data-analysis.md - Strategy analysis: strategy_analysis_example.md - Plotting: plotting.md - SQL Cheatsheet: sql_cheatsheet.md + - Exchange-specific Notes: exchanges.md - Advanced Post-installation Tasks: advanced-setup.md - Advanced Strategy: strategy-advanced.md - Advanced Hyperopt: advanced-hyperopt.md From 1d56c87a34850453e88500ee6028ec5e222b3d3f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 21:39:12 +0100 Subject: [PATCH 86/92] Fully support kraken limit stoploss --- freqtrade/exchange/exchange.py | 2 +- freqtrade/persistence/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 18f4fbff5..611ce4abd 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -524,7 +524,7 @@ class Exchange: 'rate': self.get_fee(pair) } }) - if closed_order["type"] in ["stop_loss_limit"]: + if closed_order["type"] in ["stop_loss_limit", "stop-loss-limit"]: closed_order["info"].update({"stopPrice": closed_order["price"]}) self._dry_run_open_orders[closed_order["id"]] = closed_order diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 8160ffbbf..6027908da 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -397,7 +397,7 @@ class Trade(_DECL_BASE): if self.is_open: logger.info(f'{order_type.upper()}_SELL has been fulfilled for {self}.') self.close(safe_value_fallback(order, 'average', 'price')) - elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'): + elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): self.stoploss_order_id = None self.close_rate_requested = self.stop_loss if self.is_open: From 0b68402c1094c89ead68bd4908eec47383005835 Mon Sep 17 00:00:00 2001 From: hoeckxer Date: Thu, 26 Nov 2020 10:24:48 +0100 Subject: [PATCH 87/92] Fixed a small typo in the pairlist documentation Signed-off-by: hoeckxer --- docs/includes/pairlists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 149e784bd..5bb02470d 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -60,7 +60,7 @@ The `refresh_period` setting allows to define the period (in seconds), at which "method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume", - "refresh_period": 1800, + "refresh_period": 1800 }], ``` From dddbc799f9b1c8d686a33de95a6f620322a56ec7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Nov 2020 19:40:08 +0100 Subject: [PATCH 88/92] have kraken stoploss-limit support trailing stop --- freqtrade/exchange/kraken.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index d66793845..4e4713052 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -69,7 +69,8 @@ class Kraken(Exchange): Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - return order['type'] == 'stop-loss' and stop_loss > float(order['price']) + return (order['type'] in ('stop-loss', 'stop-loss-limit') + and stop_loss > float(order['price'])) @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: From 98118f5e956d02c3f646ccb8feff1992e118cc22 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Thu, 26 Nov 2020 18:46:36 -0600 Subject: [PATCH 89/92] Fix parameter name Correct which parameter name was referred to within the 2nd Note under "Amend last stake amount" --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 56ba13414..2e8f6555f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -177,7 +177,7 @@ In the example above this would mean: This option only applies with [Static stake amount](#static-stake-amount) - since [Dynamic stake amount](#dynamic-stake-amount) divides the balances evenly. !!! Note - The minimum last stake amount can be configured using `amend_last_stake_amount` - which defaults to 0.5 (50%). This means that the minimum stake amount that's ever used is `stake_amount * 0.5`. This avoids very low stake amounts, that are close to the minimum tradable amount for the pair and can be refused by the exchange. + The minimum last stake amount can be configured using `last_stake_amount_min_ratio` - which defaults to 0.5 (50%). This means that the minimum stake amount that's ever used is `stake_amount * 0.5`. This avoids very low stake amounts, that are close to the minimum tradable amount for the pair and can be refused by the exchange. #### Static stake amount From fce31447edf2c0466cef8de13e0f8d0a26d71e61 Mon Sep 17 00:00:00 2001 From: Leif Segen Date: Thu, 26 Nov 2020 19:38:20 -0600 Subject: [PATCH 90/92] Prevent unintended LaTeX rendering --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 7993da401..14b04c7e0 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -140,7 +140,7 @@ For example, simplified math: * the stop loss would get triggered once the asset drops below 90$ * assuming the asset now increases to 102$ * the stop loss will now be -2% of 102$ = 99.96$ (99.96$ stop loss will be locked in and will follow asset price increasements with -2%) -* now the asset drops in value to 101$, the stop loss will still be 99.96$ and would trigger at 99.96$ +* now the asset drops in value to 101$, the stop loss will still be 99.96$ and would trigger at 99.96$ The 0.02 would translate to a -2% stop loss. Before this, `stoploss` is used for the trailing stoploss. From 31449987c08780afc384d524fd2bf9098995ae5b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 07:35:12 +0100 Subject: [PATCH 91/92] Fix mkdocs rendering --- docs/stoploss.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 14b04c7e0..1e21fc50d 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -36,8 +36,8 @@ If `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the st `stoploss` defines the stop-price where the limit order is placed - and limit should be slightly below this. If an exchange supports both limit and market stoploss orders, then the value of `stoploss` will be used to determine the stoploss type. -Calculation example: we bought the asset at 100$. -Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the limit order fill can happen between 95$ and 94.05$. +Calculation example: we bought the asset at 100\$. +Stop-price is 95\$, then limit would be `95 * 0.99 = 94.05$` - so the limit order fill can happen between 95$ and 94.05$. For example, assuming the stoploss is on exchange, and trailing stoploss is enabled, and the market is going up, then the bot automatically cancels the previous stoploss order and puts a new one with a stop value higher than the previous stoploss order. @@ -90,6 +90,7 @@ Example of stop loss: ``` For example, simplified math: + * the bot buys an asset at a price of 100$ * the stop loss is defined at -10% * the stop loss would get triggered once the asset drops below 90$ @@ -113,7 +114,7 @@ For example, simplified math: * the stop loss would get triggered once the asset drops below 90$ * assuming the asset now increases to 102$ * the stop loss will now be -10% of 102$ = 91.8$ -* now the asset drops in value to 101$, the stop loss will still be 91.8$ and would trigger at 91.8$. +* now the asset drops in value to 101\$, the stop loss will still be 91.8$ and would trigger at 91.8$. In summary: The stoploss will be adjusted to be always be -10% of the highest observed price. @@ -139,8 +140,8 @@ For example, simplified math: * the stop loss is defined at -10% * the stop loss would get triggered once the asset drops below 90$ * assuming the asset now increases to 102$ -* the stop loss will now be -2% of 102$ = 99.96$ (99.96$ stop loss will be locked in and will follow asset price increasements with -2%) -* now the asset drops in value to 101$, the stop loss will still be 99.96$ and would trigger at 99.96$ +* the stop loss will now be -2% of 102$ = 99.96$ (99.96$ stop loss will be locked in and will follow asset price increments with -2%) +* now the asset drops in value to 101\$, the stop loss will still be 99.96$ and would trigger at 99.96$ The 0.02 would translate to a -2% stop loss. Before this, `stoploss` is used for the trailing stoploss. @@ -157,7 +158,7 @@ This option can be used with or without `trailing_stop_positive`, but uses `trai trailing_only_offset_is_reached = True ``` -Configuration (offset is buyprice + 3%): +Configuration (offset is buy-price + 3%): ``` python stoploss = -0.10 @@ -175,7 +176,7 @@ For example, simplified math: * stoploss will remain at 90$ unless asset increases to or above our configured offset * assuming the asset now increases to 103$ (where we have the offset configured) * the stop loss will now be -2% of 103$ = 100.94$ -* now the asset drops in value to 101$, the stop loss will still be 100.94$ and would trigger at 100.94$ +* now the asset drops in value to 101\$, the stop loss will still be 100.94$ and would trigger at 100.94$ !!! Tip Make sure to have this value (`trailing_stop_positive_offset`) lower than minimal ROI, otherwise minimal ROI will apply first and sell the trade. From 1353c59f188d49a3f317bac853bd2ae4e3a08b34 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Nov 2020 11:19:01 +0100 Subject: [PATCH 92/92] Version bump to 2020.11 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 125dca1ef..3054bc4a1 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2020.10' +__version__ = '2020.11' if __version__ == 'develop':