From fceb41115448b4e7c776412094491ffce32926ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 1 Aug 2019 20:08:30 +0200 Subject: [PATCH 01/24] Create detailed section about strategy problem analysis --- docs/data-analysis.md | 119 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 1940fa3e6..76fedf475 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -6,6 +6,125 @@ A good way for this is using Jupyter (notebook or lab) - which provides an inter The following helpers will help you loading the data into Pandas DataFrames, and may also give you some starting points in analyzing the results. +## Strategy development problem analysis + +Debugging a strategy (are there no buy signals, ...) can be very time-consuming. +FreqTrade tries to help you by exposing a few helper-functions, which can be very handy. + +I recommend using Juptyer Notebooks for this analysis, since it offers a dynamic way to rerun certain parts. + +The following is a full code-snippet, which will be explained by both comments, and step by step below. + +```python +# Some necessary imports +from pathlib import Path + +from freqtrade.data.history import load_pair_history +from freqtrade.resolvers import StrategyResolver +# Define some constants +ticker_interval = "5m" + +# Name of the strategy class +strategyname = 'Awesomestrategy' +# Location of the strategy +strategy_location = '../xmatt/strategies' +# Location of the data +data_location = '../freqtrade/user_data/data/binance/' +# Only use one pair here +pair = "XRP_ETH" + +# Load data +bt_data = load_pair_history(datadir=Path(data_location), + ticker_interval = ticker_interval, + pair=pair) +print(len(bt_data)) + + +# Load strategy - best done in a new cell +# Needs to be ran each time the strategy-file is changed. +strategy = StrategyResolver({'strategy': strategyname, + 'user_data_dir': Path.cwd(), + 'strategy_path': location}).strategy + +# Run strategy (just like in backtesting) +df = strategy.analyze_ticker(bt_data, {'pair': pair}) +print(f"Generated {df['buy'].sum()} buy signals") + +# Reindex data to be "nicer" and show data +data = df.set_index('date', drop=True) +data.tail() + +``` + +### Explanation + +#### Imports and constant definition + +``` python +# Some necessary imports +from pathlib import Path + +from freqtrade.data.history import load_pair_history +from freqtrade.resolvers import StrategyResolver +# Define some constants +ticker_interval = "5m" + +# Name of the strategy class +strategyname = 'Awesomestrategy' +# Location of the strategy +strategy_location = 'user_data/strategies' +# Location of the data +data_location = 'user_data/data/binance' +# Only use one pair here +pair = "XRP_ETH" +``` + +This first section imports necessary modules, and defines some constants you'll probably need differently + +#### Load candles + +``` python +# Load data +bt_data = load_pair_history(datadir=Path(data_location), + ticker_interval = ticker_interval, + pair=pair) +print(len(bt_data)) +``` + +This second section loads the historic data and prints the amount of candles in the data. + +#### Run strategy and analyze results + +Now, it's time to load and run your strategy. +For this, I recommend using a new cell in your notebook, since you'll want to repeat this until you're satisfied with your strategy. + +``` python +# Load strategy - best done in a new cell +# Needs to be ran each time the strategy-file is changed. +strategy = StrategyResolver({'strategy': strategyname, + 'user_data_dir': Path.cwd(), + 'strategy_path': location}).strategy + +# Run strategy (just like in backtesting) +df = strategy.analyze_ticker(bt_data, {'pair': pair}) +print(f"Generated {df['buy'].sum()} buy signals") + +# Reindex data to be "nicer" and show data +data = df.set_index('date', drop=True) +data.tail() +``` + +The code snippet loads and analyzes the strategy, prints the number of buy signals. + +The last 2 lines serve to analyze the dataframe in detail. +This can be important if your strategy did not generate any buy signals. +Note that using `data.head()` would also work, however this is misleading since most indicators have some "startup" time at the start of a backtested dataframe. + +There can be many things wrong, some signs to look for are: + +* Columns with NaN values at the end of the dataframe +* Columns used in `crossed*()` functions with completely different units + ## Backtesting To analyze your backtest results, you can [export the trades](#exporting-trades-to-file). From 01cd30984b1e166ab59b446521350b74fa83eb12 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2019 06:47:03 +0200 Subject: [PATCH 02/24] Improve wording --- docs/data-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 76fedf475..2c5cc8842 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -79,7 +79,7 @@ data_location = 'user_data/data/binance' pair = "XRP_ETH" ``` -This first section imports necessary modules, and defines some constants you'll probably need differently +This first section imports necessary modules, and defines some constants you'll probably need to adjust for your case. #### Load candles From 0b9b5f39936967ad1e014d06032df14c18d6d31f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2019 19:50:12 +0200 Subject: [PATCH 03/24] Improve document wording --- docs/data-analysis.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 2c5cc8842..68e085ff3 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -11,7 +11,7 @@ The following helpers will help you loading the data into Pandas DataFrames, and Debugging a strategy (are there no buy signals, ...) can be very time-consuming. FreqTrade tries to help you by exposing a few helper-functions, which can be very handy. -I recommend using Juptyer Notebooks for this analysis, since it offers a dynamic way to rerun certain parts. +It's recommendet using Juptyer Notebooks for analysis, since it offers a dynamic way to rerun certain parts of the code. The following is a full code-snippet, which will be explained by both comments, and step by step below. @@ -33,6 +33,8 @@ data_location = '../freqtrade/user_data/data/binance/' # Only use one pair here pair = "XRP_ETH" +### End constants + # Load data bt_data = load_pair_history(datadir=Path(data_location), ticker_interval = ticker_interval, @@ -91,7 +93,8 @@ bt_data = load_pair_history(datadir=Path(data_location), print(len(bt_data)) ``` -This second section loads the historic data and prints the amount of candles in the data. +This second section loads the historic data and prints the amount of candles in the DataFrame. +You can also inspect this dataframe by using `bt_data.head()` or `bt_data.tail()`. #### Run strategy and analyze results @@ -114,7 +117,7 @@ data = df.set_index('date', drop=True) data.tail() ``` -The code snippet loads and analyzes the strategy, prints the number of buy signals. +The code snippet loads and analyzes the strategy, calculates and prints the number of buy signals. The last 2 lines serve to analyze the dataframe in detail. This can be important if your strategy did not generate any buy signals. From 32605fa10aea008c5332c7369d10df77e568db64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2019 19:52:56 +0200 Subject: [PATCH 04/24] small improvements --- docs/data-analysis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 68e085ff3..5099e1013 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -41,9 +41,9 @@ bt_data = load_pair_history(datadir=Path(data_location), pair=pair) print(len(bt_data)) - +### Start strategy reload # Load strategy - best done in a new cell -# Needs to be ran each time the strategy-file is changed. +# Rerun each time the strategy-file is changed. strategy = StrategyResolver({'strategy': strategyname, 'user_data_dir': Path.cwd(), 'strategy_path': location}).strategy From 3eb571f34c1ae12a33dd2dfc6f2f7a85f398898a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Aug 2019 20:04:18 +0200 Subject: [PATCH 05/24] recommended ... --- docs/data-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 5099e1013..2b6d6ed58 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -11,7 +11,7 @@ The following helpers will help you loading the data into Pandas DataFrames, and Debugging a strategy (are there no buy signals, ...) can be very time-consuming. FreqTrade tries to help you by exposing a few helper-functions, which can be very handy. -It's recommendet using Juptyer Notebooks for analysis, since it offers a dynamic way to rerun certain parts of the code. +It's recommended using Juptyer Notebooks for analysis, since it offers a dynamic way to rerun certain parts of the code. The following is a full code-snippet, which will be explained by both comments, and step by step below. From aa8f44f68c428962485231628a38dc9820009280 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 2 Aug 2019 22:22:58 +0300 Subject: [PATCH 06/24] improvements to hyperopt output --- freqtrade/optimize/hyperopt.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 479f8c70a..b68b1ac4f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -11,7 +11,7 @@ import sys from operator import itemgetter from pathlib import Path from pprint import pprint -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count from pandas import DataFrame @@ -70,7 +70,7 @@ class Hyperopt(Backtesting): if hasattr(self.custom_hyperopt, 'populate_sell_trend'): self.advise_sell = self.custom_hyperopt.populate_sell_trend # type: ignore - # Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set + # Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set if self.config.get('use_max_market_positions', True): self.max_open_trades = self.config['max_open_trades'] else: @@ -134,10 +134,19 @@ class Hyperopt(Backtesting): log_str = self.format_results_logstring(best_result) print(f"\nBest result:\n{log_str}\nwith values:") - pprint(params, indent=4) + if self.has_space('buy'): + print('Buy hyperspace params:') + pprint({p.name: params.get(p.name) for p in self.hyperopt_space('buy')}, + indent=4) + if self.has_space('sell'): + print('Sell hyperspace params:') + pprint({p.name: params.get(p.name) for p in self.hyperopt_space('sell')}, + indent=4) if self.has_space('roi'): print("ROI table:") pprint(self.custom_hyperopt.generate_roi_table(params), indent=4) + if self.has_space('stoploss'): + print(f"Stoploss: {params.get('stoploss')}") def log_results(self, results) -> None: """ @@ -171,21 +180,24 @@ class Hyperopt(Backtesting): """ return any(s in self.config['spaces'] for s in [space, 'all']) - def hyperopt_space(self) -> List[Dimension]: + def hyperopt_space(self, space: Optional[str] = None) -> List[Dimension]: """ - Return the space to use during Hyperopt + Return the dimentions in the hyperoptimization space. + :param space: Defines hyperspace to return dimentions for. + If None, then the self.has_space() will be used to return dimentions + for all hyperspaces used. """ spaces: List[Dimension] = [] - if self.has_space('buy'): + if space == 'buy' or (space is None and self.has_space('buy')): logger.debug("Hyperopt has 'buy' space") spaces += self.custom_hyperopt.indicator_space() - if self.has_space('sell'): + if space == 'sell' or (space is None and self.has_space('sell')): logger.debug("Hyperopt has 'sell' space") spaces += self.custom_hyperopt.sell_indicator_space() - if self.has_space('roi'): + if space == 'roi' or (space is None and self.has_space('roi')): logger.debug("Hyperopt has 'roi' space") spaces += self.custom_hyperopt.roi_space() - if self.has_space('stoploss'): + if space == 'stoploss' or (space is None and self.has_space('stoploss')): logger.debug("Hyperopt has 'stoploss' space") spaces += self.custom_hyperopt.stoploss_space() return spaces From b152d1a7abd09774a6b3367b28a89bfeae4e4a47 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 2 Aug 2019 22:23:48 +0300 Subject: [PATCH 07/24] docs agjusted, plus minor fixes --- docs/hyperopt.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 2755cae2d..fbd00bcbd 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -303,8 +303,9 @@ Given the following result from hyperopt: ``` Best result: - 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. + 44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367 with values: +Buy hyperspace params: { 'adx-value': 44, 'rsi-value': 29, 'adx-enabled': False, @@ -347,21 +348,14 @@ If you are optimizing ROI, you're result will look as follows and include a ROI ``` Best result: - 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. + 44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367 with values: +Buy hyperspace params: { 'adx-value': 44, 'rsi-value': 29, - 'adx-enabled': false, + 'adx-enabled': False, 'rsi-enabled': True, - 'trigger': 'bb_lower', - 'roi_t1': 40, - 'roi_t2': 57, - 'roi_t3': 21, - 'roi_p1': 0.03634636907306948, - 'roi_p2': 0.055237357937802885, - 'roi_p3': 0.015163796015548354, - 'stoploss': -0.37996664668703606 -} + 'trigger': 'bb_lower'} ROI table: { 0: 0.10674752302642071, 21: 0.09158372701087236, @@ -372,9 +366,9 @@ ROI table: This would translate to the following ROI table: ``` python - minimal_roi = { +minimal_roi = { "118": 0, - "78": 0.0363463, + "78": 0.0363, "21": 0.0915, "0": 0.106 } From cad7d9135a3c2a926993d43710dfb5fe2585858f Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 3 Aug 2019 09:24:27 +0300 Subject: [PATCH 08/24] tests: hide deprecation warning due to use of --live --- freqtrade/tests/optimize/test_backtesting.py | 3 +++ freqtrade/tests/test_configuration.py | 1 + 2 files changed, 4 insertions(+) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 9304871a8..37757743e 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -202,6 +202,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> assert config['runmode'] == RunMode.BACKTEST +@pytest.mark.filterwarnings("ignore:DEPRECATED") def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) mocker.patch( @@ -812,6 +813,7 @@ def test_backtest_record(default_conf, fee, mocker): assert dur > 0 +@pytest.mark.filterwarnings("ignore:DEPRECATED") def test_backtest_start_live(default_conf, mocker, caplog): default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] @@ -858,6 +860,7 @@ def test_backtest_start_live(default_conf, mocker, caplog): assert log_has(line, caplog.record_tuples) +@pytest.mark.filterwarnings("ignore:DEPRECATED") def test_backtest_start_multi_strat(default_conf, mocker, caplog): default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 56ff79625..1e76297a6 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -325,6 +325,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> assert 'export' not in config +@pytest.mark.filterwarnings("ignore:DEPRECATED") def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) mocker.patch( From 3b65c986eea10e68da391997831a4d1a769aff2c Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 3 Aug 2019 10:20:20 +0300 Subject: [PATCH 09/24] wordings fixed --- freqtrade/optimize/hyperopt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index b68b1ac4f..8767ed9a7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -182,9 +182,9 @@ class Hyperopt(Backtesting): def hyperopt_space(self, space: Optional[str] = None) -> List[Dimension]: """ - Return the dimentions in the hyperoptimization space. - :param space: Defines hyperspace to return dimentions for. - If None, then the self.has_space() will be used to return dimentions + Return the dimensions in the hyperoptimization space. + :param space: Defines hyperspace to return dimensions for. + If None, then the self.has_space() will be used to return dimensions for all hyperspaces used. """ spaces: List[Dimension] = [] From 13620df717bf73e38e3ac974dfca8d31cdd53178 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 3 Aug 2019 11:05:05 +0300 Subject: [PATCH 10/24] 'with values:' line removed --- docs/hyperopt.md | 6 ++++-- freqtrade/optimize/hyperopt.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index fbd00bcbd..0b5d1a50e 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -303,8 +303,9 @@ Given the following result from hyperopt: ``` Best result: + 44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367 -with values: + Buy hyperspace params: { 'adx-value': 44, 'rsi-value': 29, @@ -348,8 +349,9 @@ If you are optimizing ROI, you're result will look as follows and include a ROI ``` Best result: + 44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367 -with values: + Buy hyperspace params: { 'adx-value': 44, 'rsi-value': 29, diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 8767ed9a7..427b17cb8 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -133,7 +133,7 @@ class Hyperopt(Backtesting): params = best_result['params'] log_str = self.format_results_logstring(best_result) - print(f"\nBest result:\n{log_str}\nwith values:") + print(f"\nBest result:\n\n{log_str}\n") if self.has_space('buy'): print('Buy hyperspace params:') pprint({p.name: params.get(p.name) for p in self.hyperopt_space('buy')}, From e8b2ae0b85c1e311a87c037b3852fadb60d60dff Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 3 Aug 2019 11:19:36 +0300 Subject: [PATCH 11/24] tests adjusted --- freqtrade/tests/optimize/test_hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index e114dceaf..e3b049c06 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -463,7 +463,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: parallel.assert_called_once() out, err = capsys.readouterr() - assert 'Best result:\n* 1/1: foo result Objective: 1.00000\nwith values:\n' in out + assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert dumper.called # Should be called twice, once for tickerdata, once to save evaluations assert dumper.call_count == 2 From bbd58e772eaa003aa18ce28c5fe2a15818f5d76f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2019 13:11:51 +0200 Subject: [PATCH 12/24] Warn when using restricted pairs As noted in https://github.com/ccxt/ccxt/issues/5624, there is currently no way to detect if a user is impacted by this or not prior to creating a order. --- freqtrade/exchange/exchange.py | 8 +++++++- freqtrade/tests/conftest.py | 18 +++++++++--------- freqtrade/tests/exchange/test_exchange.py | 21 +++++++++++++++++++-- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a7c76e635..37bbb778e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -260,7 +260,7 @@ class Exchange(object): if not self.markets: logger.warning('Unable to validate pairs (assuming they are correct).') - # return + return for pair in pairs: # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs @@ -269,6 +269,12 @@ class Exchange(object): raise OperationalException( f'Pair {pair} is not available on {self.name}. ' f'Please remove {pair} from your whitelist.') + elif self.markets[pair].get('info', {}).get('IsRestricted', False): + # Warn users about restricted pairs in whitelist. + # We cannot determine reliably if Users are affected. + logger.warning(f"Pair {pair} is restricted for some users on this exchange." + f"Please check if you are impacted by this restriction " + f"on the exchange and eventually remove {pair} from your whitelist.") def get_valid_pair_combination(self, curr_1, curr_2) -> str: """ diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 5862a2e89..71ed23901 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -304,7 +304,7 @@ def markets(): 'max': 500000, }, }, - 'info': '', + 'info': {}, }, 'TKN/BTC': { 'id': 'tknbtc', @@ -329,7 +329,7 @@ def markets(): 'max': 500000, }, }, - 'info': '', + 'info': {}, }, 'BLK/BTC': { 'id': 'blkbtc', @@ -354,7 +354,7 @@ def markets(): 'max': 500000, }, }, - 'info': '', + 'info': {}, }, 'LTC/BTC': { 'id': 'ltcbtc', @@ -379,7 +379,7 @@ def markets(): 'max': 500000, }, }, - 'info': '', + 'info': {}, }, 'XRP/BTC': { 'id': 'xrpbtc', @@ -404,7 +404,7 @@ def markets(): 'max': 500000, }, }, - 'info': '', + 'info': {}, }, 'NEO/BTC': { 'id': 'neobtc', @@ -429,7 +429,7 @@ def markets(): 'max': 500000, }, }, - 'info': '', + 'info': {}, }, 'BTT/BTC': { 'id': 'BTTBTC', @@ -457,7 +457,7 @@ def markets(): 'max': None } }, - 'info': "", + 'info': {}, }, 'ETH/USDT': { 'id': 'USDT-ETH', @@ -479,7 +479,7 @@ def markets(): } }, 'active': True, - 'info': "" + 'info': {}, }, 'LTC/USDT': { 'id': 'USDT-LTC', @@ -501,7 +501,7 @@ def markets(): 'max': None } }, - 'info': "" + 'info': {}, } } diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index a5cdf0a82..67a4bbeeb 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -318,7 +318,7 @@ def test__reload_markets_exception(default_conf, mocker, caplog): def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ - 'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': '' + 'ETH/BTC': {}, 'LTC/BTC': {}, 'XRP/BTC': {}, 'NEO/BTC': {} }) id_mock = PropertyMock(return_value='test_exchange') type(api_mock).id = id_mock @@ -332,7 +332,7 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d def test_validate_pairs_not_available(default_conf, mocker): api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ - 'XRP/BTC': 'inactive' + 'XRP/BTC': {'inactive'} }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) @@ -361,6 +361,23 @@ def test_validate_pairs_exception(default_conf, mocker, caplog): caplog.record_tuples) +def test_validate_pairs_restricted(default_conf, mocker, caplog): + api_mock = MagicMock() + type(api_mock).markets = PropertyMock(return_value={ + 'ETH/BTC': {}, 'LTC/BTC': {}, 'NEO/BTC': {}, + 'XRP/BTC': {'info': {'IsRestricted': True}} + }) + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) + + Exchange(default_conf) + assert log_has(f"Pair XRP/BTC is restricted for some users on this exchange." + f"Please check if you are impacted by this restriction " + f"on the exchange and eventually remove XRP/BTC from your whitelist.", + caplog.record_tuples) + + def test_validate_timeframes(default_conf, mocker): default_conf["ticker_interval"] = "5m" api_mock = MagicMock() From ad55faafa899fefc828bddd2e08a7f4a4f52749d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2019 13:18:37 +0200 Subject: [PATCH 13/24] Fix odd test --- freqtrade/tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 67a4bbeeb..2f9e525dd 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -332,7 +332,7 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d def test_validate_pairs_not_available(default_conf, mocker): api_mock = MagicMock() type(api_mock).markets = PropertyMock(return_value={ - 'XRP/BTC': {'inactive'} + 'XRP/BTC': {'inactive': True} }) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) From 8ab07e0451d1205eb4ebee2f2043f9df56cc2e13 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2019 13:22:44 +0200 Subject: [PATCH 14/24] Add FAQ section about restricted markets --- docs/faq.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 83576af4d..fe6f66130 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -45,6 +45,16 @@ the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-c You can use the `/forcesell all` command from Telegram. +### I get the message "RESTRICTED_MARKET" + +Currently known to happen for US Bittrex users. +Bittrex split it's exchange into an US and an International verssion. +The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. + +If you have restricted pairs in your whitelist, you'll get a Warning-message on startup for each restricted pair. +If you're an "International" Customer on the Bittrex exchange, then this warning will not impact you. +If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist. + ## Hyperopt module ### How many epoch do I need to get a good Hyperopt result? From d59608f7649ba5fc352c7a12d89b9466cbd11b2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2019 17:19:37 +0200 Subject: [PATCH 15/24] adjust some documentation wordings --- docs/faq.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index fe6f66130..e8367608f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -48,11 +48,11 @@ You can use the `/forcesell all` command from Telegram. ### I get the message "RESTRICTED_MARKET" Currently known to happen for US Bittrex users. -Bittrex split it's exchange into an US and an International verssion. +Bittrex split it's exchange into US and International versions. The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. -If you have restricted pairs in your whitelist, you'll get a Warning-message on startup for each restricted pair. -If you're an "International" Customer on the Bittrex exchange, then this warning will not impact you. +If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair. +If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you. If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist. ## Hyperopt module From c4e30862eeda84a1aa7f4e587c6851bf05905b4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2019 19:55:54 +0200 Subject: [PATCH 16/24] load_trades_db should give as many columns as possible --- freqtrade/data/btanalysis.py | 21 ++++++++++++++++----- freqtrade/tests/data/test_btanalysis.py | 5 +++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 5865d56a7..6568fe31e 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -81,19 +81,30 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: """ trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS) persistence.init(db_url, clean_open_orders=False) - columns = ["pair", "profit", "open_time", "close_time", - "open_rate", "close_rate", "duration", "sell_reason", - "max_rate", "min_rate"] - trades = pd.DataFrame([(t.pair, t.calc_profit(), + columns = ["pair", "open_time", "close_time", "profit", "profitperc", + "open_rate", "close_rate", "amount", "duration", "sell_reason", + "fee_open", "fee_close", "open_rate_requested", "close_rate_requested", + "stake_amount", "max_rate", "min_rate", "id", "exchange", + "stop_loss", "initial_stop_loss", "strategy", "ticker_interval"] + + trades = pd.DataFrame([(t.pair, t.open_date.replace(tzinfo=pytz.UTC), t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None, - t.open_rate, t.close_rate, + t.calc_profit(), t.calc_profit_percent(), + t.open_rate, t.close_rate, t.amount, t.close_date.timestamp() - t.open_date.timestamp() if t.close_date else None, t.sell_reason, + t.fee_open, t.fee_close, + t.open_rate_requested, + t.close_rate_requested, + t.stake_amount, t.max_rate, t.min_rate, + t.id, t.exchange, + t.stop_loss, t.initial_stop_loss, + t.strategy, t.ticker_interval ) for t in Trade.query.all()], columns=columns) diff --git a/freqtrade/tests/data/test_btanalysis.py b/freqtrade/tests/data/test_btanalysis.py index e80840009..bad8db66f 100644 --- a/freqtrade/tests/data/test_btanalysis.py +++ b/freqtrade/tests/data/test_btanalysis.py @@ -45,6 +45,11 @@ def test_load_trades_db(default_conf, fee, mocker): assert isinstance(trades, DataFrame) assert "pair" in trades.columns assert "open_time" in trades.columns + assert "profitperc" in trades.columns + + for col in BT_DATA_COLUMNS: + if col not in ['index', 'open_at_end']: + assert col in trades.columns def test_extract_trades_of_period(): From d51fd1a5d0bec672fcadd92fcc38cf15add751ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2019 19:56:41 +0200 Subject: [PATCH 17/24] fix typo --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index e8367608f..a441ffacd 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -48,7 +48,7 @@ You can use the `/forcesell all` command from Telegram. ### I get the message "RESTRICTED_MARKET" Currently known to happen for US Bittrex users. -Bittrex split it's exchange into US and International versions. +Bittrex split its exchange into US and International versions. The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair. From c6bd14378502aeccf4ee2b6af120ca90922e61be Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Aug 2019 20:04:49 +0200 Subject: [PATCH 18/24] add Operating system to issue template --- .github/ISSUE_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 94d998310..ae5375f43 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,6 +5,7 @@ If it hasn't been reported, please create a new issue. ## Step 2: Describe your environment + * Operating system: ____ * Python Version: _____ (`python -V`) * CCXT version: _____ (`pip freeze | grep ccxt`) * Branch: Master | Develop From 52d92cba90cd3e9baa1c48da8e47c8ddb68a4fb3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2019 10:20:31 +0200 Subject: [PATCH 19/24] Split analyze_ticker and _analyze_ticker_int --- freqtrade/strategy/interface.py | 22 +++++++++++++++++----- freqtrade/tests/strategy/test_interface.py | 20 ++++++++++---------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 2a28bcd22..5bd183fa9 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -158,6 +158,21 @@ class IStrategy(ABC): """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it + :param dataframe: Dataframe containing ticker data + :param metadata: Metadata dictionary with additional data (e.g. 'pair') + :return: DataFrame with ticker data and indicator data + """ + logger.debug("TA Analysis Launched") + dataframe = self.advise_indicators(dataframe, metadata) + dataframe = self.advise_buy(dataframe, metadata) + dataframe = self.advise_sell(dataframe, metadata) + return dataframe + + def _analyze_ticker_int(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Parses the given ticker history and returns a populated DataFrame + add several TA indicators and buy signal to it + Used internally, may skip analysis if `process_only_new_candles` is set. :return: DataFrame with ticker data and indicator data """ @@ -168,10 +183,7 @@ class IStrategy(ABC): if (not self.process_only_new_candles or self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']): # Defs that only make change on new candle data. - logger.debug("TA Analysis Launched") - dataframe = self.advise_indicators(dataframe, metadata) - dataframe = self.advise_buy(dataframe, metadata) - dataframe = self.advise_sell(dataframe, metadata) + dataframe = self.analyze_ticker(dataframe, metadata) self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date'] else: logger.debug("Skipping TA Analysis for already analyzed candle") @@ -198,7 +210,7 @@ class IStrategy(ABC): return False, False try: - dataframe = self.analyze_ticker(dataframe, {'pair': pair}) + dataframe = self._analyze_ticker_int(dataframe, {'pair': pair}) except ValueError as error: logger.warning( 'Unable to analyze ticker for pair %s: %s', diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index ee8c8ddd4..6bf3770c5 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -19,13 +19,13 @@ _STRATEGY = DefaultStrategy(config={}) def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_int', return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_int', return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) @@ -33,14 +33,14 @@ def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): def test_returns_latest_sell_signal(mocker, default_conf, ticker_history): mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_int', return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_int', return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) @@ -60,7 +60,7 @@ def test_get_signal_empty(default_conf, mocker, caplog): def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_history): caplog.set_level(logging.INFO) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_int', side_effect=ValueError('xyz') ) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], @@ -71,7 +71,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_hi def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history): caplog.set_level(logging.INFO) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_int', return_value=DataFrame([]) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], @@ -86,7 +86,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history): oldtime = arrow.utcnow().shift(minutes=-16) ticks = DataFrame([{'buy': 1, 'date': oldtime}]) mocker.patch.object( - _STRATEGY, 'analyze_ticker', + _STRATEGY, '_analyze_ticker_int', return_value=DataFrame(ticks) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], @@ -252,7 +252,7 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: caplog.record_tuples) -def test_analyze_ticker_skip_analyze(ticker_history, mocker, caplog) -> None: +def test__analyze_ticker_int_skip_analyze(ticker_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) buy_mock = MagicMock(side_effect=lambda x, meta: x) @@ -267,7 +267,7 @@ def test_analyze_ticker_skip_analyze(ticker_history, mocker, caplog) -> None: strategy = DefaultStrategy({}) strategy.process_only_new_candles = True - ret = strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_int(ticker_history, {'pair': 'ETH/BTC'}) assert 'high' in ret.columns assert 'low' in ret.columns assert 'close' in ret.columns @@ -280,7 +280,7 @@ def test_analyze_ticker_skip_analyze(ticker_history, mocker, caplog) -> None: caplog.record_tuples) caplog.clear() - ret = strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_int(ticker_history, {'pair': 'ETH/BTC'}) # No analysis happens as process_only_new_candles is true assert ind_mock.call_count == 1 assert buy_mock.call_count == 1 From 62262d0bb50a398c09e4d9f31f5f6ab9176f972c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2019 10:21:22 +0200 Subject: [PATCH 20/24] improve docstring of _analyze_ticker_int --- freqtrade/strategy/interface.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 5bd183fa9..48a4f96a2 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -172,7 +172,9 @@ class IStrategy(ABC): """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it - Used internally, may skip analysis if `process_only_new_candles` is set. + WARNING: Used internally only, may skip analysis if `process_only_new_candles` is set. + :param dataframe: Dataframe containing ticker data + :param metadata: Metadata dictionary with additional data (e.g. 'pair') :return: DataFrame with ticker data and indicator data """ From e4380b533bc4515cd6fc227690d7efb77b945d12 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2019 10:25:46 +0200 Subject: [PATCH 21/24] Print plot filename so it can be easily opened --- freqtrade/plot/plotting.py | 5 +++-- freqtrade/tests/test_plotting.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 5c9c6e457..d03d3ae53 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -318,6 +318,7 @@ def store_plot_file(fig, filename: str, auto_open: bool = False) -> None: """ Path("user_data/plots").mkdir(parents=True, exist_ok=True) - - plot(fig, filename=str(Path('user_data/plots').joinpath(filename)), + _filename = Path('user_data/plots').joinpath(filename) + plot(fig, filename=str(_filename), auto_open=auto_open) + logger.info(f"Stored plot as {_filename}") diff --git a/freqtrade/tests/test_plotting.py b/freqtrade/tests/test_plotting.py index 509bf7880..f9da773fe 100644 --- a/freqtrade/tests/test_plotting.py +++ b/freqtrade/tests/test_plotting.py @@ -215,6 +215,8 @@ def test_generate_plot_file(mocker, caplog): assert plot_mock.call_args[0][0] == fig assert (plot_mock.call_args_list[0][1]['filename'] == "user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html") + assert log_has("Stored plot as user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html", + caplog.record_tuples) def test_add_profit(): From c5ccf44750d70cc670671ea50af19d710468b97e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2019 10:26:04 +0200 Subject: [PATCH 22/24] Remove generate_dataframe from plot_dataframe script --- scripts/plot_dataframe.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 034a6f448..6412c45a4 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -16,8 +16,6 @@ import logging import sys from typing import Any, Dict, List -import pandas as pd - from freqtrade.configuration import Arguments from freqtrade.configuration.arguments import ARGS_PLOT_DATAFRAME from freqtrade.data.btanalysis import extract_trades_of_period @@ -30,20 +28,6 @@ from freqtrade.state import RunMode logger = logging.getLogger(__name__) -def generate_dataframe(strategy, tickers, pair) -> pd.DataFrame: - """ - Get tickers then Populate strategy indicators and signals, then return the full dataframe - :return: the DataFrame of a pair - """ - - dataframes = strategy.tickerdata_to_dataframe(tickers) - dataframe = dataframes[pair] - dataframe = strategy.advise_buy(dataframe, {'pair': pair}) - dataframe = strategy.advise_sell(dataframe, {'pair': pair}) - - return dataframe - - def analyse_and_plot_pairs(config: Dict[str, Any]): """ From arguments provided in cli: @@ -57,6 +41,7 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): """ plot_elements = init_plotscript(config) trades = plot_elements['trades'] + strategy = plot_elements["strategy"] pair_counter = 0 for pair, data in plot_elements["tickers"].items(): @@ -64,7 +49,8 @@ def analyse_and_plot_pairs(config: Dict[str, Any]): logger.info("analyse pair %s", pair) tickers = {} tickers[pair] = data - dataframe = generate_dataframe(plot_elements["strategy"], tickers, pair) + + dataframe = strategy.analyze_ticker(tickers[pair], {'pair': pair}) trades_pair = trades.loc[trades['pair'] == pair] trades_pair = extract_trades_of_period(dataframe, trades_pair) From 4d1ce8178c8abc55488932f07b20055c08497312 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2019 10:38:37 +0200 Subject: [PATCH 23/24] intend if to be clearer --- freqtrade/data/btanalysis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 6568fe31e..36d8aedbb 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -93,8 +93,8 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None, t.calc_profit(), t.calc_profit_percent(), t.open_rate, t.close_rate, t.amount, - t.close_date.timestamp() - t.open_date.timestamp() - if t.close_date else None, + (t.close_date.timestamp() - t.open_date.timestamp() + if t.close_date else None), t.sell_reason, t.fee_open, t.fee_close, t.open_rate_requested, From 2af663dccbd4fe395984125778729d5c968df087 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Aug 2019 12:55:03 +0200 Subject: [PATCH 24/24] rename _analyze_ticker_int to _analyze_ticker_internal --- freqtrade/strategy/interface.py | 4 ++-- freqtrade/tests/strategy/test_interface.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 48a4f96a2..37aa97bb1 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -168,7 +168,7 @@ class IStrategy(ABC): dataframe = self.advise_sell(dataframe, metadata) return dataframe - def _analyze_ticker_int(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it @@ -212,7 +212,7 @@ class IStrategy(ABC): return False, False try: - dataframe = self._analyze_ticker_int(dataframe, {'pair': pair}) + dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair}) except ValueError as error: logger.warning( 'Unable to analyze ticker for pair %s: %s', diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index 6bf3770c5..9f5ab70e3 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -19,13 +19,13 @@ _STRATEGY = DefaultStrategy(config={}) def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): mocker.patch.object( - _STRATEGY, '_analyze_ticker_int', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) mocker.patch.object( - _STRATEGY, '_analyze_ticker_int', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) @@ -33,14 +33,14 @@ def test_returns_latest_buy_signal(mocker, default_conf, ticker_history): def test_returns_latest_sell_signal(mocker, default_conf, ticker_history): mocker.patch.object( - _STRATEGY, '_analyze_ticker_int', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True) mocker.patch.object( - _STRATEGY, '_analyze_ticker_int', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False) @@ -60,7 +60,7 @@ def test_get_signal_empty(default_conf, mocker, caplog): def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_history): caplog.set_level(logging.INFO) mocker.patch.object( - _STRATEGY, '_analyze_ticker_int', + _STRATEGY, '_analyze_ticker_internal', side_effect=ValueError('xyz') ) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], @@ -71,7 +71,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_hi def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history): caplog.set_level(logging.INFO) mocker.patch.object( - _STRATEGY, '_analyze_ticker_int', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame([]) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], @@ -86,7 +86,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history): oldtime = arrow.utcnow().shift(minutes=-16) ticks = DataFrame([{'buy': 1, 'date': oldtime}]) mocker.patch.object( - _STRATEGY, '_analyze_ticker_int', + _STRATEGY, '_analyze_ticker_internal', return_value=DataFrame(ticks) ) assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], @@ -252,7 +252,7 @@ def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None: caplog.record_tuples) -def test__analyze_ticker_int_skip_analyze(ticker_history, mocker, caplog) -> None: +def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) buy_mock = MagicMock(side_effect=lambda x, meta: x) @@ -267,7 +267,7 @@ def test__analyze_ticker_int_skip_analyze(ticker_history, mocker, caplog) -> Non strategy = DefaultStrategy({}) strategy.process_only_new_candles = True - ret = strategy._analyze_ticker_int(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_internal(ticker_history, {'pair': 'ETH/BTC'}) assert 'high' in ret.columns assert 'low' in ret.columns assert 'close' in ret.columns @@ -280,7 +280,7 @@ def test__analyze_ticker_int_skip_analyze(ticker_history, mocker, caplog) -> Non caplog.record_tuples) caplog.clear() - ret = strategy._analyze_ticker_int(ticker_history, {'pair': 'ETH/BTC'}) + ret = strategy._analyze_ticker_internal(ticker_history, {'pair': 'ETH/BTC'}) # No analysis happens as process_only_new_candles is true assert ind_mock.call_count == 1 assert buy_mock.call_count == 1