From c0a7725c1fdcf8dadae42ba8a7af4c2dda657f8f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Jul 2018 21:23:35 +0200 Subject: [PATCH 01/56] Add stoploss offset --- freqtrade/constants.py | 1 + freqtrade/strategy/interface.py | 10 +++-- freqtrade/tests/test_freqtradebot.py | 66 +++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 385dac1d1..a2b69b2d7 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -63,6 +63,7 @@ CONF_SCHEMA = { 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'trailing_stop': {'type': 'boolean'}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, + 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'unfilledtimeout': { 'type': 'object', 'properties': { diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index fb8bcd31d..7cf990cf5 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -174,6 +174,7 @@ class IStrategy(ABC): """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not + :param current_profit: current profit in percent """ trailing_stop = self.config.get('trailing_stop', False) @@ -199,13 +200,16 @@ class IStrategy(ABC): # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = self.stoploss - if 'trailing_stop_positive' in self.config and current_profit > 0: + stop_loss_value = self.strategy.stoploss + sl_offset = self.config.get('trailing_stop_positive_offset', 0.0) + + if 'trailing_stop_positive' in self.config and current_profit > sl_offset: # Ignore mypy error check in configuration that this is a float stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore logger.debug(f"using positive stop loss mode: {stop_loss_value} " - f"since we have profit {current_profit}") + f"with offset {sl_offset:.4g} " + f"since we have profit {current_profit:.4f}%") trade.adjust_stop_loss(current_rate, stop_loss_value) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index b7ae96048..61907a321 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1805,7 +1805,71 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.01 since we have profit 0.26662643', + assert log_has(f'using positive stop loss mode: 0.01 with offset 0 ' + f'since we have profit 0.2666%', + caplog.record_tuples) + assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.0000138501 + + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.000002, + 'ask': buy_price + 0.000002, + 'last': buy_price + 0.000002 + })) + # Lower price again (but still positive) + assert freqtrade.handle_trade(trade) is True + assert log_has( + f'HIT STOP: current price at {buy_price + 0.000002:.6f}, ' + f'stop loss is {trade.stop_loss:.6f}, ' + f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + + +def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, mocker) -> None: + """ + Test sell_profit_only feature when enabled and we have a loss + """ + buy_price = limit_buy_order['price'] + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': buy_price - 0.000001, + 'ask': buy_price - 0.000001, + 'last': buy_price - 0.000001 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + ) + + conf = deepcopy(default_conf) + conf['trailing_stop'] = True + conf['trailing_stop_positive'] = 0.01 + conf['trailing_stop_positive_offset'] = 0.011 + freqtrade = FreqtradeBot(conf) + freqtrade.create_trade() + + trade = Trade.query.first() + trade.update(limit_buy_order) + caplog.set_level(logging.DEBUG) + # stop-loss not reached + assert freqtrade.handle_trade(trade) is False + + # Raise ticker above buy price + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.000003, + 'ask': buy_price + 0.000003, + 'last': buy_price + 0.000003 + })) + # stop-loss not reached, adjusted stoploss + assert freqtrade.handle_trade(trade) is False + assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 ' + f'since we have profit 0.2666%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000138501 From 6a3c8e3933a911ba7df6d9694be235a78579a78a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Jul 2018 21:23:45 +0200 Subject: [PATCH 02/56] update docs for trailing stoploss offset --- docs/configuration.md | 1 + docs/stoploss.md | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index ddfa9834e..d4ccedf84 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -27,6 +27,7 @@ The table below will list all configuration parameters. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. | `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached. +| `trailing_stoploss_positve_offset` | 0 | No | Offset on when to apply `trailing_stoploss_positive`. Percentage value which should be positive. | `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. diff --git a/docs/stoploss.md b/docs/stoploss.md index db4433a02..9740672cd 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -35,14 +35,17 @@ basically what this means is that your stop loss will be adjusted to be always b ### Custom positive loss -Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your buy turns positive, -the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the -black, it will be changed to be only a 1% stop loss +Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your profit surpasses a certain percentage, +the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you have 1.1% profit, +it will be changed to be only a 1% stop loss, which trails the green candles until it goes below them. -This can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true. +Both values can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true. ``` json "trailing_stop_positive": 0.01, + "trailing_stop_positive_offset": 0.011, ``` -The 0.01 would translate to a 1% stop loss, once you hit profit. +The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit. + +You should also make sure to have this value higher than your minimal ROI, otherwise minimal ROI will apply first and sell your trade. From 365ba98131c57a90280f8bf615e18d79c67eee2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Jul 2018 21:30:50 +0200 Subject: [PATCH 03/56] add option to full_json example --- config_full.json.example | 1 + 1 file changed, 1 insertion(+) diff --git a/config_full.json.example b/config_full.json.example index 4003b1c5c..b0714535f 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -7,6 +7,7 @@ "ticker_interval": "5m", "trailing_stop": false, "trailing_stop_positive": 0.005, + "trailing_stop_positive_offset": 0.0051, "minimal_roi": { "40": 0.0, "30": 0.01, From 060469fefc774b0dd0110bfbd76106b354c6fce2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Jul 2018 20:12:20 +0200 Subject: [PATCH 04/56] Add stuff after rebase --- freqtrade/strategy/interface.py | 2 +- freqtrade/tests/test_freqtradebot.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7cf990cf5..61ad41891 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -200,7 +200,7 @@ class IStrategy(ABC): # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = self.strategy.stoploss + stop_loss_value = self.stoploss sl_offset = self.config.get('trailing_stop_positive_offset', 0.0) if 'trailing_stop_positive' in self.config and current_profit > sl_offset: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 61907a321..cf1f35e3d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -45,7 +45,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: :param value: which value IStrategy.get_signal() must return :return: None """ - freqtrade.strategy.get_signal = lambda e, s, t: value + freqtrade.get_signal = lambda e, s, t: value def patch_RPCManager(mocker) -> MagicMock: @@ -1833,7 +1833,6 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), @@ -1851,6 +1850,8 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m conf['trailing_stop_positive'] = 0.01 conf['trailing_stop_positive_offset'] = 0.011 freqtrade = FreqtradeBot(conf) + freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True + freqtrade.create_trade() trade = Trade.query.first() From 1b2bfad34827298dd3f9b3bf46bd97dab8c4b83a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Jul 2018 20:36:49 +0200 Subject: [PATCH 05/56] Fix wrong test --- freqtrade/tests/test_freqtradebot.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index cf1f35e3d..9448f8c68 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -45,7 +45,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: :param value: which value IStrategy.get_signal() must return :return: None """ - freqtrade.get_signal = lambda e, s, t: value + freqtrade.strategy.get_signal = lambda e, s, t: value def patch_RPCManager(mocker) -> MagicMock: @@ -1830,7 +1830,6 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m Test sell_profit_only feature when enabled and we have a loss """ buy_price = limit_buy_order['price'] - patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( @@ -1850,8 +1849,8 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m conf['trailing_stop_positive'] = 0.01 conf['trailing_stop_positive_offset'] = 0.011 freqtrade = FreqtradeBot(conf) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True - + patch_get_signal(freqtrade) + freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False freqtrade.create_trade() trade = Trade.query.first() From 484103b957974e2091c619626b1359074902b258 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Jul 2018 18:22:23 +0100 Subject: [PATCH 06/56] extract get_history_data from get_signal --- freqtrade/freqtradebot.py | 11 ++++++++--- freqtrade/strategy/interface.py | 3 +-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3527fba81..08eda2843 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -327,13 +327,18 @@ class FreqtradeBot(object): if not whitelist: raise DependencyException('No currency pairs in whitelist') + th = {} + for _pair in whitelist: + th[_pair] = self.exchange.get_ticker_history(_pair, interval) # Pick pair based on buy signals + bought_at_least_one = False for _pair in whitelist: - (buy, sell) = self.strategy.get_signal(self.exchange, _pair, interval) + (buy, sell) = self.strategy.get_signal(_pair, interval, th[_pair]) + if buy and not sell: - return self.execute_buy(_pair, stake_amount) - return False + bought_at_least_one |= self.execute_buy(_pair, stake_amount) + return bought_at_least_one def execute_buy(self, pair: str, stake_amount: float) -> bool: """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 9120d3e04..23b0563a2 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -107,14 +107,13 @@ class IStrategy(ABC): dataframe = self.populate_sell_trend(dataframe) return dataframe - def get_signal(self, exchange: Exchange, pair: str, interval: str) -> Tuple[bool, bool]: + def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]: """ Calculates current signal based several technical analysis indicators :param pair: pair in format ANT/BTC :param interval: Interval to use (in min) :return: (Buy, Sell) A bool-tuple indicating buy/sell signal """ - ticker_hist = exchange.get_ticker_history(pair, interval) if not ticker_hist: logger.warning('Empty ticker history for pair %s', pair) return False, False From 3324cdfcbe78a110e28ae0024b72c8c1cf46a537 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Jul 2018 18:58:49 +0100 Subject: [PATCH 07/56] add mock for get_history in patch_get_signal --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/tests/test_freqtradebot.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 08eda2843..824607a2d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -337,8 +337,8 @@ class FreqtradeBot(object): (buy, sell) = self.strategy.get_signal(_pair, interval, th[_pair]) if buy and not sell: - bought_at_least_one |= self.execute_buy(_pair, stake_amount) - return bought_at_least_one + return self.execute_buy(_pair, stake_amount) + return False def execute_buy(self, pair: str, stake_amount: float) -> bool: """ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 72f11abf9..fb6687a2c 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -47,6 +47,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: :return: None """ freqtrade.strategy.get_signal = lambda e, s, t: value + freqtrade.exchange.get_ticker_history = lambda p, i: None def patch_RPCManager(mocker) -> MagicMock: From f2a9be36847e9eb98300da6d1992383cb35902cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Jul 2018 19:06:25 +0100 Subject: [PATCH 08/56] Adjust tests and remove legacy variable --- freqtrade/freqtradebot.py | 1 - freqtrade/tests/strategy/test_interface.py | 30 +++++++--------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 824607a2d..3317a7b68 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -332,7 +332,6 @@ class FreqtradeBot(object): th[_pair] = self.exchange.get_ticker_history(_pair, interval) # Pick pair based on buy signals - bought_at_least_one = False for _pair in whitelist: (buy, sell) = self.strategy.get_signal(_pair, interval, th[_pair]) diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index a016b7f96..de11a9d2c 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -20,74 +20,62 @@ _STRATEGY = DefaultStrategy(config={}) def test_returns_latest_buy_signal(mocker, default_conf): - mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock()) - exchange = get_patched_exchange(mocker, default_conf) mocker.patch.object( _STRATEGY, 'analyze_ticker', return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (True, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (True, False) mocker.patch.object( _STRATEGY, 'analyze_ticker', return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (False, True) + assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (False, True) def test_returns_latest_sell_signal(mocker, default_conf): - mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock()) - exchange = get_patched_exchange(mocker, default_conf) mocker.patch.object( _STRATEGY, 'analyze_ticker', return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (False, True) + assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (False, True) mocker.patch.object( _STRATEGY, 'analyze_ticker', return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) ) - assert _STRATEGY.get_signal(exchange, 'ETH/BTC', '5m') == (True, False) + assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (True, False) def test_get_signal_empty(default_conf, mocker, caplog): - caplog.set_level(logging.INFO) - mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=None) - exchange = get_patched_exchange(mocker, default_conf) - assert (False, False) == _STRATEGY.get_signal(exchange, 'foo', default_conf['ticker_interval']) + assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], + None) assert log_has('Empty ticker history for pair foo', caplog.record_tuples) def test_get_signal_exception_valueerror(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1) - exchange = get_patched_exchange(mocker, default_conf) mocker.patch.object( _STRATEGY, 'analyze_ticker', side_effect=ValueError('xyz') ) - assert (False, False) == _STRATEGY.get_signal(exchange, 'foo', default_conf['ticker_interval']) + assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], 1) assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples) def test_get_signal_empty_dataframe(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1) - exchange = get_patched_exchange(mocker, default_conf) mocker.patch.object( _STRATEGY, 'analyze_ticker', return_value=DataFrame([]) ) - assert (False, False) == _STRATEGY.get_signal(exchange, 'xyz', default_conf['ticker_interval']) + assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 1) assert log_has('Empty dataframe for pair xyz', caplog.record_tuples) def test_get_signal_old_dataframe(default_conf, mocker, caplog): caplog.set_level(logging.INFO) - mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1) - exchange = get_patched_exchange(mocker, default_conf) # default_conf defines a 5m interval. we check interval * 2 + 5m # this is necessary as the last candle is removed (partial candles) by default oldtime = arrow.utcnow().shift(minutes=-16) @@ -96,7 +84,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog): _STRATEGY, 'analyze_ticker', return_value=DataFrame(ticks) ) - assert (False, False) == _STRATEGY.get_signal(exchange, 'xyz', default_conf['ticker_interval']) + assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 1) assert log_has( 'Outdated history for pair xyz. Last tick is 16 minutes old', caplog.record_tuples From df3e76a65df49448315786f23af73d637a76889d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 26 Jul 2018 19:11:29 +0100 Subject: [PATCH 09/56] Remove legacy code, fix missed call --- freqtrade/freqtradebot.py | 11 ++++++----- freqtrade/strategy/interface.py | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3317a7b68..d7c5657c3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -327,13 +327,13 @@ class FreqtradeBot(object): if not whitelist: raise DependencyException('No currency pairs in whitelist') - th = {} + thistory = {} for _pair in whitelist: - th[_pair] = self.exchange.get_ticker_history(_pair, interval) + thistory[_pair] = self.exchange.get_ticker_history(_pair, interval) # Pick pair based on buy signals for _pair in whitelist: - (buy, sell) = self.strategy.get_signal(_pair, interval, th[_pair]) + (buy, sell) = self.strategy.get_signal(_pair, interval, thistory[_pair]) if buy and not sell: return self.execute_buy(_pair, stake_amount) @@ -499,8 +499,9 @@ class FreqtradeBot(object): (buy, sell) = (False, False) experimental = self.config.get('experimental', {}) if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'): - (buy, sell) = self.strategy.get_signal(self.exchange, - trade.pair, self.strategy.ticker_interval) + ticker = self.exchange.get_ticker_history(trade.pair, self.strategy.ticker_interval) + (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval, + ticker) should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell) if should_sell.sell_flag: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 23b0563a2..a5145aabb 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -13,7 +13,6 @@ from pandas import DataFrame from freqtrade import constants from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe -from freqtrade.exchange import Exchange from freqtrade.persistence import Trade logger = logging.getLogger(__name__) From 48cd468b6ca10438d367644ae970de35bc4ce16b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Jul 2018 07:40:27 +0100 Subject: [PATCH 10/56] Don't do all network calls at once without async --- freqtrade/freqtradebot.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d7c5657c3..46fbb3a38 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -327,13 +327,11 @@ class FreqtradeBot(object): if not whitelist: raise DependencyException('No currency pairs in whitelist') - thistory = {} - for _pair in whitelist: - thistory[_pair] = self.exchange.get_ticker_history(_pair, interval) # Pick pair based on buy signals for _pair in whitelist: - (buy, sell) = self.strategy.get_signal(_pair, interval, thistory[_pair]) + thistory = self.exchange.get_ticker_history(_pair, interval) + (buy, sell) = self.strategy.get_signal(_pair, interval, thistory) if buy and not sell: return self.execute_buy(_pair, stake_amount) From c47253133a895a3d256ecd9df0208fe7c7c187aa Mon Sep 17 00:00:00 2001 From: creslinux Date: Fri, 27 Jul 2018 12:07:07 +0000 Subject: [PATCH 11/56] have to begin before we can stop --- freqtrade/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c27cd875c..1611903ab 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -126,6 +126,7 @@ CONF_SCHEMA = { 'name': {'type': 'string'}, 'key': {'type': 'string'}, 'secret': {'type': 'string'}, + 'password': {'type': 'string'}, 'pair_whitelist': { 'type': 'array', 'items': { From 40ae250193009997e9e94d26c9c7f037684ec1b3 Mon Sep 17 00:00:00 2001 From: creslin <34645187+creslinux@users.noreply.github.com> Date: Fri, 27 Jul 2018 12:19:01 +0000 Subject: [PATCH 12/56] Update constants.py Adding UID also, as itll get ran into in future on an exchange that needs it. --- freqtrade/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1611903ab..24de36f3d 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -127,6 +127,7 @@ CONF_SCHEMA = { 'key': {'type': 'string'}, 'secret': {'type': 'string'}, 'password': {'type': 'string'}, + 'uid': {'type': 'string'}, 'pair_whitelist': { 'type': 'array', 'items': { From 4547ae930aa75f34a8f9ac7c5f8a3ea53b049df0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 27 Jul 2018 14:24:06 +0200 Subject: [PATCH 13/56] Update ccxt from 1.17.20 to 1.17.29 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6a3c8d1ba..4255e2c38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.20 +ccxt==1.17.29 SQLAlchemy==1.2.10 python-telegram-bot==10.1.0 arrow==0.12.1 From ca0d658f15960a3664fea2a3af817948328d47e0 Mon Sep 17 00:00:00 2001 From: Sandoche ADITTANE Date: Fri, 27 Jul 2018 15:28:06 +0200 Subject: [PATCH 14/56] Error fixed in the quickstart documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1705ff41..b5715f743 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ Freqtrade provides a Linux/macOS script to install all dependencies and help you ```bash git clone git@github.com:freqtrade/freqtrade.git -git checkout develop cd freqtrade +git checkout develop ./setup.sh --install ``` From 243b63e39ca5ec3523d03dd0067ef301bd85e0c3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Jul 2018 21:12:48 +0100 Subject: [PATCH 15/56] fix rpc test going to network (unsuitable for flights...) --- freqtrade/tests/rpc/test_rpc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e3530a149..2b4b71e05 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -165,6 +165,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, 'freqtrade.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) + patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -315,6 +316,7 @@ def test_rpc_balance_handle(default_conf, mocker): 'freqtrade.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) + patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( From 099e7020c890a4cc08bc1b320f6a297b8a9cf2ed Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 28 Jul 2018 14:24:06 +0200 Subject: [PATCH 16/56] Update ccxt from 1.17.29 to 1.17.39 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4255e2c38..7407f5184 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.29 +ccxt==1.17.39 SQLAlchemy==1.2.10 python-telegram-bot==10.1.0 arrow==0.12.1 From cdd8cc551c8dbdc031ca45d85240eea836e6ebf6 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sat, 28 Jul 2018 21:56:11 +0300 Subject: [PATCH 17/56] backtesting: try to load data with ujson if it exists --- freqtrade/optimize/__init__.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index e806ff2b8..5aa47725d 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -1,7 +1,11 @@ # pragma pylint: disable=missing-docstring import gzip -import json +try: + import ujson as json +except ImportError: + import json +import inspect import logging import os from typing import Optional, List, Dict, Tuple, Any @@ -14,6 +18,14 @@ from freqtrade.arguments import TimeRange logger = logging.getLogger(__name__) +def json_load(data): + """Try to load data with ujson""" + if inspect.getfullargspec(json.load)[5].get('precise_float'): + return json.load(data, precise_float=True) + else: + return json.load(data) + + def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]: if not tickerlist: return tickerlist @@ -63,11 +75,11 @@ def load_tickerdata_file( if os.path.isfile(gzipfile): logger.debug('Loading ticker data from file %s', gzipfile) with gzip.open(gzipfile) as tickerdata: - pairdata = json.load(tickerdata) + pairdata = json_load(tickerdata) elif os.path.isfile(file): logger.debug('Loading ticker data from file %s', file) with open(file) as tickerdata: - pairdata = json.load(tickerdata) + pairdata = json_load(tickerdata) else: return None @@ -163,7 +175,7 @@ def load_cached_data_for_updating(filename: str, # read the cached file if os.path.isfile(filename): with open(filename, "rt") as file: - data = json.load(file) + data = json_load(file) # remove the last item, because we are not sure if it is correct # it could be fetched when the candle was incompleted if data: From cb2fff89095d1f3102d4517239483837f6c15792 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sat, 28 Jul 2018 22:06:26 +0300 Subject: [PATCH 18/56] mypy doesn't handle common idiomacy so disable the line (see the open issue more details) --- freqtrade/optimize/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 5aa47725d..5c1bd06ab 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -4,7 +4,8 @@ import gzip try: import ujson as json except ImportError: - import json + # see mypy/issues/1153 + import json # type: ignore import inspect import logging import os From 7f27beff4baca0f6a05c09a788133a240858e7cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Jul 2018 13:23:11 +0200 Subject: [PATCH 19/56] Revert "backtesting: try to load data with ujson if it exists" --- freqtrade/optimize/__init__.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 5c1bd06ab..e806ff2b8 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -1,12 +1,7 @@ # pragma pylint: disable=missing-docstring import gzip -try: - import ujson as json -except ImportError: - # see mypy/issues/1153 - import json # type: ignore -import inspect +import json import logging import os from typing import Optional, List, Dict, Tuple, Any @@ -19,14 +14,6 @@ from freqtrade.arguments import TimeRange logger = logging.getLogger(__name__) -def json_load(data): - """Try to load data with ujson""" - if inspect.getfullargspec(json.load)[5].get('precise_float'): - return json.load(data, precise_float=True) - else: - return json.load(data) - - def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]: if not tickerlist: return tickerlist @@ -76,11 +63,11 @@ def load_tickerdata_file( if os.path.isfile(gzipfile): logger.debug('Loading ticker data from file %s', gzipfile) with gzip.open(gzipfile) as tickerdata: - pairdata = json_load(tickerdata) + pairdata = json.load(tickerdata) elif os.path.isfile(file): logger.debug('Loading ticker data from file %s', file) with open(file) as tickerdata: - pairdata = json_load(tickerdata) + pairdata = json.load(tickerdata) else: return None @@ -176,7 +163,7 @@ def load_cached_data_for_updating(filename: str, # read the cached file if os.path.isfile(filename): with open(filename, "rt") as file: - data = json_load(file) + data = json.load(file) # remove the last item, because we are not sure if it is correct # it could be fetched when the candle was incompleted if data: From 9c7f53d90dceb25f26f056e0f7c66d361134bc67 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 29 Jul 2018 14:24:06 +0200 Subject: [PATCH 20/56] Update ccxt from 1.17.39 to 1.17.45 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7407f5184..21dfe2948 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.39 +ccxt==1.17.45 SQLAlchemy==1.2.10 python-telegram-bot==10.1.0 arrow==0.12.1 From 2ef35400c9620885b07832ba3539a3788635bbd0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 29 Jul 2018 14:24:08 +0200 Subject: [PATCH 21/56] Update pytest from 3.6.3 to 3.6.4 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 21dfe2948..3a00111ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ scipy==1.1.0 jsonschema==2.6.0 numpy==1.15.0 TA-Lib==0.4.17 -pytest==3.6.3 +pytest==3.6.4 pytest-mock==1.10.0 pytest-cov==2.5.1 tabulate==0.8.2 From 1bbb86c621be1892a57c223c367ab900f6a54446 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 29 Jul 2018 16:23:17 +0300 Subject: [PATCH 22/56] remove nonsense asserts --- freqtrade/tests/rpc/test_rpc_manager.py | 6 ------ freqtrade/tests/strategy/test_strategy.py | 12 ----------- freqtrade/tests/test_arguments.py | 12 ----------- freqtrade/tests/test_configuration.py | 13 ------------ freqtrade/tests/test_constants.py | 25 ----------------------- freqtrade/tests/test_freqtradebot.py | 16 --------------- freqtrade/tests/test_state.py | 14 ------------- 7 files changed, 98 deletions(-) delete mode 100644 freqtrade/tests/test_constants.py delete mode 100644 freqtrade/tests/test_state.py diff --git a/freqtrade/tests/rpc/test_rpc_manager.py b/freqtrade/tests/rpc/test_rpc_manager.py index 4686cf5ca..0280f9cc3 100644 --- a/freqtrade/tests/rpc/test_rpc_manager.py +++ b/freqtrade/tests/rpc/test_rpc_manager.py @@ -10,12 +10,6 @@ from freqtrade.rpc import RPCMessageType, RPCManager from freqtrade.tests.conftest import log_has, get_patched_freqtradebot -def test_rpc_manager_object() -> None: - """ Test the Arguments object has the mandatory methods """ - assert hasattr(RPCManager, 'send_msg') - assert hasattr(RPCManager, 'cleanup') - - def test__init__(mocker, default_conf) -> None: """ Test __init__() method """ conf = deepcopy(default_conf) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 2f9221467..e25738775 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -57,7 +57,6 @@ def test_search_strategy(): def test_load_strategy(result): resolver = StrategyResolver({'strategy': 'TestStrategy'}) - assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result) @@ -71,8 +70,6 @@ def test_load_strategy_invalid_directory(result, caplog): logging.WARNING, 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples - - assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result) @@ -89,26 +86,20 @@ def test_strategy(result): resolver = StrategyResolver(config) - assert hasattr(resolver.strategy, 'minimal_roi') assert resolver.strategy.minimal_roi[0] == 0.04 assert config["minimal_roi"]['0'] == 0.04 - assert hasattr(resolver.strategy, 'stoploss') assert resolver.strategy.stoploss == -0.10 assert config['stoploss'] == -0.10 - assert hasattr(resolver.strategy, 'ticker_interval') assert resolver.strategy.ticker_interval == '5m' assert config['ticker_interval'] == '5m' - assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result) - assert hasattr(resolver.strategy, 'populate_buy_trend') dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result)) assert 'buy' in dataframe.columns - assert hasattr(resolver.strategy, 'populate_sell_trend') dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result)) assert 'sell' in dataframe.columns @@ -123,7 +114,6 @@ def test_strategy_override_minimal_roi(caplog): } resolver = StrategyResolver(config) - assert hasattr(resolver.strategy, 'minimal_roi') assert resolver.strategy.minimal_roi[0] == 0.5 assert ('freqtrade.strategy.resolver', logging.INFO, @@ -139,7 +129,6 @@ def test_strategy_override_stoploss(caplog): } resolver = StrategyResolver(config) - assert hasattr(resolver.strategy, 'stoploss') assert resolver.strategy.stoploss == -0.5 assert ('freqtrade.strategy.resolver', logging.INFO, @@ -156,7 +145,6 @@ def test_strategy_override_ticker_interval(caplog): } resolver = StrategyResolver(config) - assert hasattr(resolver.strategy, 'ticker_interval') assert resolver.strategy.ticker_interval == 60 assert ('freqtrade.strategy.resolver', logging.INFO, diff --git a/freqtrade/tests/test_arguments.py b/freqtrade/tests/test_arguments.py index 07018c79e..c7740ce47 100644 --- a/freqtrade/tests/test_arguments.py +++ b/freqtrade/tests/test_arguments.py @@ -11,23 +11,11 @@ import pytest from freqtrade.arguments import Arguments, TimeRange -def test_arguments_object() -> None: - """ - Test the Arguments object has the mandatory methods - :return: None - """ - assert hasattr(Arguments, 'get_parsed_arg') - assert hasattr(Arguments, 'parse_args') - assert hasattr(Arguments, 'parse_timerange') - assert hasattr(Arguments, 'scripts_options') - - # Parse common command-line-arguments. Used for all tools def test_parse_args_none() -> None: arguments = Arguments([], '') assert isinstance(arguments, Arguments) assert isinstance(arguments.parser, argparse.ArgumentParser) - assert isinstance(arguments.parser, argparse.ArgumentParser) def test_parse_args_defaults() -> None: diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index a8a2c5fce..46b08a3d4 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -19,19 +19,6 @@ from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL from freqtrade.tests.conftest import log_has -def test_configuration_object() -> None: - """ - Test the Constants object has the mandatory Constants - """ - assert hasattr(Configuration, 'load_config') - assert hasattr(Configuration, '_load_config_file') - assert hasattr(Configuration, '_validate_config') - assert hasattr(Configuration, '_load_common_config') - assert hasattr(Configuration, '_load_backtesting_config') - assert hasattr(Configuration, '_load_hyperopt_config') - assert hasattr(Configuration, 'get_config') - - def test_load_config_invalid_pair(default_conf) -> None: """ Test the configuration validator with an invalid PAIR format diff --git a/freqtrade/tests/test_constants.py b/freqtrade/tests/test_constants.py deleted file mode 100644 index 541c6e533..000000000 --- a/freqtrade/tests/test_constants.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Unit test file for constants.py -""" - -from freqtrade import constants - - -def test_constant_object() -> None: - """ - Test the Constants object has the mandatory Constants - """ - assert hasattr(constants, 'CONF_SCHEMA') - assert hasattr(constants, 'DYNAMIC_WHITELIST') - assert hasattr(constants, 'PROCESS_THROTTLE_SECS') - assert hasattr(constants, 'TICKER_INTERVAL') - assert hasattr(constants, 'HYPEROPT_EPOCH') - assert hasattr(constants, 'RETRY_TIMEOUT') - assert hasattr(constants, 'DEFAULT_STRATEGY') - - -def test_conf_schema() -> None: - """ - Test the CONF_SCHEMA is from the right type - """ - assert isinstance(constants.CONF_SCHEMA, dict) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index a1683ae6a..b1e08383b 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -62,22 +62,6 @@ def patch_RPCManager(mocker) -> MagicMock: # Unit tests -def test_freqtradebot_object() -> None: - """ - Test the FreqtradeBot object has the mandatory public methods - """ - assert hasattr(FreqtradeBot, 'worker') - assert hasattr(FreqtradeBot, 'cleanup') - assert hasattr(FreqtradeBot, 'create_trade') - assert hasattr(FreqtradeBot, 'get_target_bid') - assert hasattr(FreqtradeBot, 'process_maybe_execute_buy') - assert hasattr(FreqtradeBot, 'process_maybe_execute_sell') - assert hasattr(FreqtradeBot, 'handle_trade') - assert hasattr(FreqtradeBot, 'check_handle_timedout') - assert hasattr(FreqtradeBot, 'handle_timedout_limit_buy') - assert hasattr(FreqtradeBot, 'handle_timedout_limit_sell') - assert hasattr(FreqtradeBot, 'execute_sell') - def test_freqtradebot(mocker, default_conf) -> None: """ diff --git a/freqtrade/tests/test_state.py b/freqtrade/tests/test_state.py deleted file mode 100644 index 51fa06cc2..000000000 --- a/freqtrade/tests/test_state.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Unit test file for constants.py -""" - -from freqtrade.state import State - - -def test_state_object() -> None: - """ - Test the State object has the mandatory states - :return: None - """ - assert hasattr(State, 'RUNNING') - assert hasattr(State, 'STOPPED') From f832edf5bcad168c2da4eccb62de91ed4e9dab0c Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 29 Jul 2018 17:09:44 +0300 Subject: [PATCH 23/56] remove useless docstrings from tests --- freqtrade/tests/exchange/test_exchange.py | 2 +- .../tests/exchange/test_exchange_helpers.py | 4 - freqtrade/tests/optimize/test_hyperopt.py | 19 ---- freqtrade/tests/optimize/test_optimize.py | 14 --- freqtrade/tests/rpc/test_rpc.py | 35 +------ freqtrade/tests/rpc/test_rpc_manager.py | 36 +------ freqtrade/tests/rpc/test_rpc_telegram.py | 97 +------------------ freqtrade/tests/rpc/test_rpc_webhook.py | 17 +--- freqtrade/tests/strategy/test_interface.py | 4 - freqtrade/tests/test_acl_pair.py | 2 - freqtrade/tests/test_configuration.py | 66 +------------ freqtrade/tests/test_indicator_helpers.py | 2 + freqtrade/tests/test_main.py | 24 +---- freqtrade/tests/test_misc.py | 24 ----- 14 files changed, 16 insertions(+), 330 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 282d8ef01..814f56acc 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -173,7 +173,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): Exchange(default_conf) -def test_exchangehas(default_conf, mocker): +def test_exchange_has(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) assert not exchange.exchange_has('ASDFASDF') api_mock = MagicMock() diff --git a/freqtrade/tests/exchange/test_exchange_helpers.py b/freqtrade/tests/exchange/test_exchange_helpers.py index 6a3bc9eb6..82525e805 100644 --- a/freqtrade/tests/exchange/test_exchange_helpers.py +++ b/freqtrade/tests/exchange/test_exchange_helpers.py @@ -1,9 +1,5 @@ # pragma pylint: disable=missing-docstring, C0103 -""" -Unit test file for exchange_helpers.py -""" - from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 72a102c22..dd7bf7da0 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -77,9 +77,6 @@ def test_start(mocker, default_conf, caplog) -> None: def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None: - """ - Test Hyperopt.calculate_loss() - """ hyperopt = _HYPEROPT StrategyResolver({'strategy': 'DefaultStrategy'}) @@ -91,9 +88,6 @@ def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None: def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None: - """ - Test Hyperopt.calculate_loss() - """ hyperopt = _HYPEROPT shorter = hyperopt.calculate_loss(1, 100, 20) @@ -240,9 +234,6 @@ def test_format_results(init_hyperopt): def test_has_space(init_hyperopt): - """ - Test Hyperopt.has_space() method - """ _HYPEROPT.config.update({'spaces': ['buy', 'roi']}) assert _HYPEROPT.has_space('roi') assert _HYPEROPT.has_space('buy') @@ -253,9 +244,6 @@ def test_has_space(init_hyperopt): def test_populate_indicators(init_hyperopt) -> None: - """ - Test Hyperopt.populate_indicators() - """ tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) @@ -268,9 +256,6 @@ def test_populate_indicators(init_hyperopt) -> None: def test_buy_strategy_generator(init_hyperopt) -> None: - """ - Test Hyperopt.buy_strategy_generator() - """ tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) @@ -296,9 +281,6 @@ def test_buy_strategy_generator(init_hyperopt) -> None: def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None: - """ - Test Hyperopt.generate_optimizer() function - """ conf = deepcopy(default_conf) conf.update({'config': 'config.json.example'}) conf.update({'timerange': None}) @@ -335,7 +317,6 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None: 'roi_p3': 0.1, 'stoploss': -0.4, } - response_expected = { 'loss': 1.9840569076926293, 'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' diff --git a/freqtrade/tests/optimize/test_optimize.py b/freqtrade/tests/optimize/test_optimize.py index 4ab32343a..eef79bef3 100644 --- a/freqtrade/tests/optimize/test_optimize.py +++ b/freqtrade/tests/optimize/test_optimize.py @@ -53,9 +53,6 @@ def _clean_test_file(file: str) -> None: def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None: - """ - Test load_data() with 30 min ticker - """ mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json') _backup_file(file, copy_file=True) @@ -66,9 +63,6 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None: - """ - Test load_data() with 5 min ticker - """ mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json') @@ -80,11 +74,7 @@ def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None: - """ - Test load_data() with 1 min ticker - """ mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history) - file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json') _backup_file(file, copy_file=True) optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC']) @@ -421,10 +411,6 @@ def test_trim_tickerlist() -> None: def test_file_dump_json() -> None: - """ - Test file_dump_json() - :return: None - """ file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'test_{id}.json'.format(id=str(uuid.uuid4()))) data = {'bar': 'foo'} diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 2b4b71e05..63624db85 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -1,9 +1,6 @@ +# pragma pylint: disable=missing-docstring, C0103 # pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments -""" -Unit test file for rpc/rpc.py -""" - from datetime import datetime from unittest.mock import MagicMock, ANY @@ -28,9 +25,6 @@ def prec_satoshi(a, b) -> float: # Unit tests def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: - """ - Test rpc_trade_status() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -72,9 +66,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: - """ - Test rpc_status_table() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -106,9 +97,6 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: def test_rpc_daily_profit(default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: - """ - Test rpc_daily_profit() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -158,9 +146,6 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: - """ - Test rpc_trade_statistics() method - """ mocker.patch.multiple( 'freqtrade.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), @@ -237,9 +222,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, # trade.open_rate (it is set to None) def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets, ticker_sell_up, limit_buy_order, limit_sell_order): - """ - Test rpc_trade_statistics() method - """ mocker.patch.multiple( 'freqtrade.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), @@ -344,9 +326,6 @@ def test_rpc_balance_handle(default_conf, mocker): def test_rpc_start(mocker, default_conf) -> None: - """ - Test rpc_start() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -370,9 +349,6 @@ def test_rpc_start(mocker, default_conf) -> None: def test_rpc_stop(mocker, default_conf) -> None: - """ - Test rpc_stop() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -397,9 +373,6 @@ def test_rpc_stop(mocker, default_conf) -> None: def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: - """ - Test rpc_forcesell() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -501,9 +474,6 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None: def test_performance_handle(default_conf, ticker, limit_buy_order, fee, limit_sell_order, markets, mocker) -> None: - """ - Test rpc_performance() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( @@ -540,9 +510,6 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None: - """ - Test rpc_count() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( diff --git a/freqtrade/tests/rpc/test_rpc_manager.py b/freqtrade/tests/rpc/test_rpc_manager.py index 0280f9cc3..c4f27787b 100644 --- a/freqtrade/tests/rpc/test_rpc_manager.py +++ b/freqtrade/tests/rpc/test_rpc_manager.py @@ -1,6 +1,4 @@ -""" -Unit test file for rpc/rpc_manager.py -""" +# pragma pylint: disable=missing-docstring, C0103 import logging from copy import deepcopy @@ -11,7 +9,6 @@ from freqtrade.tests.conftest import log_has, get_patched_freqtradebot def test__init__(mocker, default_conf) -> None: - """ Test __init__() method """ conf = deepcopy(default_conf) conf['telegram']['enabled'] = False @@ -20,12 +17,9 @@ def test__init__(mocker, default_conf) -> None: def test_init_telegram_disabled(mocker, default_conf, caplog) -> None: - """ Test _init() method with Telegram disabled """ caplog.set_level(logging.DEBUG) - conf = deepcopy(default_conf) conf['telegram']['enabled'] = False - rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf)) assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples) @@ -33,12 +27,8 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None: def test_init_telegram_enabled(mocker, default_conf, caplog) -> None: - """ - Test _init() method with Telegram enabled - """ caplog.set_level(logging.DEBUG) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) - rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) assert log_has('Enabling rpc.telegram ...', caplog.record_tuples) @@ -48,12 +38,8 @@ def test_init_telegram_enabled(mocker, default_conf, caplog) -> None: def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None: - """ - Test cleanup() method with Telegram disabled - """ caplog.set_level(logging.DEBUG) telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock()) - conf = deepcopy(default_conf) conf['telegram']['enabled'] = False @@ -66,9 +52,6 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None: def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None: - """ - Test cleanup() method with Telegram enabled - """ caplog.set_level(logging.DEBUG) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock()) @@ -86,11 +69,7 @@ def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None: def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None: - """ - Test send_msg() method with Telegram disabled - """ telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) - conf = deepcopy(default_conf) conf['telegram']['enabled'] = False @@ -106,9 +85,6 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None: def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: - """ - Test send_msg() method with Telegram disabled - """ telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) @@ -124,13 +100,10 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: def test_init_webhook_disabled(mocker, default_conf, caplog) -> None: - """ Test _init() method with Webhook disabled """ caplog.set_level(logging.DEBUG) - conf = deepcopy(default_conf) conf['telegram']['enabled'] = False conf['webhook'] = {'enabled': False} - rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf)) assert not log_has('Enabling rpc.webhook ...', caplog.record_tuples) @@ -138,16 +111,11 @@ def test_init_webhook_disabled(mocker, default_conf, caplog) -> None: def test_init_webhook_enabled(mocker, default_conf, caplog) -> None: - """ - Test _init() method with Webhook enabled - """ caplog.set_level(logging.DEBUG) default_conf['telegram']['enabled'] = False default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} - rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) assert log_has('Enabling rpc.webhook ...', caplog.record_tuples) - len_modules = len(rpc_manager.registered_modules) - assert len_modules == 1 + assert len(rpc_manager.registered_modules) == 1 assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules] diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index b2cab6d37..ceb8a7808 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -1,10 +1,7 @@ +# pragma pylint: disable=missing-docstring, C0103 # pragma pylint: disable=protected-access, unused-argument, invalid-name # pragma pylint: disable=too-many-lines, too-many-arguments -""" -Unit test file for rpc/telegram.py -""" - import re from copy import deepcopy from datetime import datetime @@ -55,9 +52,6 @@ class DummyCls(Telegram): def test__init__(default_conf, mocker) -> None: - """ - Test __init__() method - """ mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) @@ -67,7 +61,6 @@ def test__init__(default_conf, mocker) -> None: def test_init(default_conf, mocker, caplog) -> None: - """ Test _init() method """ start_polling = MagicMock() mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling)) @@ -86,9 +79,6 @@ def test_init(default_conf, mocker, caplog) -> None: def test_cleanup(default_conf, mocker) -> None: - """ - Test cleanup() method - """ updater_mock = MagicMock() updater_mock.stop = MagicMock() mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock) @@ -99,9 +89,6 @@ def test_cleanup(default_conf, mocker) -> None: def test_authorized_only(default_conf, mocker, caplog) -> None: - """ - Test authorized_only() method when we are authorized - """ patch_coinmarketcap(mocker) patch_exchange(mocker, None) @@ -131,9 +118,6 @@ def test_authorized_only(default_conf, mocker, caplog) -> None: def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: - """ - Test authorized_only() method when we are unauthorized - """ patch_coinmarketcap(mocker) patch_exchange(mocker, None) chat = Chat(0xdeadbeef, 0) @@ -162,9 +146,6 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: def test_authorized_only_exception(default_conf, mocker, caplog) -> None: - """ - Test authorized_only() method when an exception is thrown - """ patch_coinmarketcap(mocker) patch_exchange(mocker) @@ -195,9 +176,6 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None: def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: - """ - Test _status() method - """ update.message.chat.id = 123 conf = deepcopy(default_conf) conf['telegram']['enabled'] = False @@ -254,9 +232,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - """ - Test _status() method - """ patch_coinmarketcap(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -302,9 +277,6 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - """ - Test _status_table() method - """ patch_coinmarketcap(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -357,9 +329,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, limit_sell_order, markets, mocker) -> None: - """ - Test _daily() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch( 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', @@ -431,9 +400,6 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: - """ - Test _daily() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -470,9 +436,6 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: - """ - Test _profit() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( @@ -531,10 +494,6 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, def test_telegram_balance_handle(default_conf, update, mocker) -> None: - """ - Test _balance() method - """ - mock_balance = { 'BTC': { 'total': 12.0, @@ -559,9 +518,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: } def mock_ticker(symbol, refresh): - """ - Mock Bittrex.get_ticker() response - """ if symbol == 'BTC/USDT': return { 'bid': 10000.00, @@ -602,10 +558,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: assert 'BTC: 14.00000000' in result -def test_zero_balance_handle(default_conf, update, mocker) -> None: - """ - Test _balance() method when the Exchange platform returns nothing - """ +def test_balance_handle_empty_response(default_conf, update, mocker) -> None: mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={}) msg_mock = MagicMock() @@ -627,9 +580,6 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None: def test_start_handle(default_conf, update, mocker) -> None: - """ - Test _start() method - """ msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -648,9 +598,6 @@ def test_start_handle(default_conf, update, mocker) -> None: def test_start_handle_already_running(default_conf, update, mocker) -> None: - """ - Test _start() method - """ msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', @@ -670,9 +617,6 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None: def test_stop_handle(default_conf, update, mocker) -> None: - """ - Test _stop() method - """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -693,9 +637,6 @@ def test_stop_handle(default_conf, update, mocker) -> None: def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: - """ - Test _stop() method - """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -716,7 +657,6 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: def test_reload_conf_handle(default_conf, update, mocker) -> None: - """ Test _reload_conf() method """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -738,9 +678,6 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None: def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, markets, mocker) -> None: - """ - Test _forcesell() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -790,9 +727,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee, def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_down, markets, mocker) -> None: - """ - Test _forcesell() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -846,9 +780,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - """ - Test _forcesell() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -894,9 +825,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: - """ - Test _forcesell() method - """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) msg_mock = MagicMock() @@ -937,9 +865,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: def test_performance_handle(default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: - """ - Test _performance() method - """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -979,9 +904,6 @@ def test_performance_handle(default_conf, update, ticker, fee, def test_performance_handle_invalid(default_conf, update, mocker) -> None: - """ - Test _performance() method - """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -1002,9 +924,6 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None: def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None: - """ - Test _count() method - """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -1046,9 +965,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non def test_help_handle(default_conf, update, mocker) -> None: - """ - Test _help() method - """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -1066,9 +982,6 @@ def test_help_handle(default_conf, update, mocker) -> None: def test_version_handle(default_conf, update, mocker) -> None: - """ - Test _version() method - """ patch_coinmarketcap(mocker) msg_mock = MagicMock() mocker.patch.multiple( @@ -1266,9 +1179,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: def test__send_msg(default_conf, mocker) -> None: - """ - Test send_msg() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) conf = deepcopy(default_conf) @@ -1282,9 +1192,6 @@ def test__send_msg(default_conf, mocker) -> None: def test__send_msg_network_error(default_conf, mocker, caplog) -> None: - """ - Test send_msg() method - """ patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) conf = deepcopy(default_conf) diff --git a/freqtrade/tests/rpc/test_rpc_webhook.py b/freqtrade/tests/rpc/test_rpc_webhook.py index b9c005d73..bfe0416b0 100644 --- a/freqtrade/tests/rpc/test_rpc_webhook.py +++ b/freqtrade/tests/rpc/test_rpc_webhook.py @@ -1,9 +1,10 @@ +# pragma pylint: disable=missing-docstring, C0103, protected-access + from unittest.mock import MagicMock import pytest from requests import RequestException - from freqtrade.rpc import RPCMessageType from freqtrade.rpc.webhook import Webhook from freqtrade.tests.conftest import get_patched_freqtradebot, log_has @@ -32,23 +33,12 @@ def get_webhook_dict() -> dict: def test__init__(mocker, default_conf): - """ - Test __init__() method - """ default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) assert webhook._config == default_conf -def test_cleanup(default_conf, mocker) -> None: - """ - Test cleanup() method - not needed for webhook - """ - pass - - def test_send_msg(default_conf, mocker): - """ Test send_msg for Webhook rpc class""" default_conf["webhook"] = get_webhook_dict() msg_mock = MagicMock() mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) @@ -118,7 +108,6 @@ def test_send_msg(default_conf, mocker): def test_exception_send_msg(default_conf, mocker, caplog): - """Test misconfigured notification""" default_conf["webhook"] = get_webhook_dict() default_conf["webhook"]["webhookbuy"] = None @@ -158,8 +147,6 @@ def test_exception_send_msg(default_conf, mocker, caplog): def test__send_msg(default_conf, mocker, caplog): - """Test internal method - calling the actual api""" - default_conf["webhook"] = get_webhook_dict() webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) msg = {'value1': 'DEADBEEF', diff --git a/freqtrade/tests/strategy/test_interface.py b/freqtrade/tests/strategy/test_interface.py index de11a9d2c..1099f4b5f 100644 --- a/freqtrade/tests/strategy/test_interface.py +++ b/freqtrade/tests/strategy/test_interface.py @@ -1,9 +1,5 @@ # pragma pylint: disable=missing-docstring, C0103 -""" -Unit test file for analyse.py -""" - import logging from unittest.mock import MagicMock diff --git a/freqtrade/tests/test_acl_pair.py b/freqtrade/tests/test_acl_pair.py index 094c166b8..535684b22 100644 --- a/freqtrade/tests/test_acl_pair.py +++ b/freqtrade/tests/test_acl_pair.py @@ -11,7 +11,6 @@ import freqtrade.tests.conftest as tt # test tools def whitelist_conf(): config = tt.default_conf() - config['stake_currency'] = 'BTC' config['exchange']['pair_whitelist'] = [ 'ETH/BTC', @@ -20,7 +19,6 @@ def whitelist_conf(): 'SWT/BTC', 'BCC/BTC' ] - config['exchange']['pair_blacklist'] = [ 'BLK/BTC' ] diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 46b08a3d4..d4f9f46e1 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -1,8 +1,5 @@ -# pragma pylint: disable=protected-access, invalid-name +# pragma pylint: disable=missing-docstring, protected-access, invalid-name -""" -Unit test file for configuration.py -""" import json from argparse import Namespace from copy import deepcopy @@ -20,9 +17,6 @@ from freqtrade.tests.conftest import log_has def test_load_config_invalid_pair(default_conf) -> None: - """ - Test the configuration validator with an invalid PAIR format - """ conf = deepcopy(default_conf) conf['exchange']['pair_whitelist'].append('ETH-BTC') @@ -32,9 +26,6 @@ def test_load_config_invalid_pair(default_conf) -> None: def test_load_config_missing_attributes(default_conf) -> None: - """ - Test the configuration validator with a missing attribute - """ conf = deepcopy(default_conf) conf.pop('exchange') @@ -44,9 +35,6 @@ def test_load_config_missing_attributes(default_conf) -> None: def test_load_config_incorrect_stake_amount(default_conf) -> None: - """ - Test the configuration validator with a missing attribute - """ conf = deepcopy(default_conf) conf['stake_amount'] = 'fake' @@ -56,9 +44,6 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None: def test_load_config_file(default_conf, mocker, caplog) -> None: - """ - Test Configuration._load_config_file() method - """ file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) @@ -72,9 +57,6 @@ def test_load_config_file(default_conf, mocker, caplog) -> None: def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: - """ - Test Configuration._load_config_file() method - """ conf = deepcopy(default_conf) conf['max_open_trades'] = 0 file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open( @@ -87,9 +69,6 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: def test_load_config_file_exception(mocker) -> None: - """ - Test Configuration._load_config_file() method - """ mocker.patch( 'freqtrade.configuration.open', MagicMock(side_effect=FileNotFoundError('File not found')) @@ -101,9 +80,6 @@ def test_load_config_file_exception(mocker) -> None: def test_load_config(default_conf, mocker) -> None: - """ - Test Configuration.load_config() without any cli params - """ mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) @@ -118,13 +94,9 @@ def test_load_config(default_conf, mocker) -> None: def test_load_config_with_params(default_conf, mocker) -> None: - """ - Test Configuration.load_config() with cli params used - """ mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) - arglist = [ '--dynamic-whitelist', '10', '--strategy', 'TestStrategy', @@ -132,7 +104,6 @@ def test_load_config_with_params(default_conf, mocker) -> None: '--db-url', 'sqlite:///someurl', ] args = Arguments(arglist, '').get_parsed_arg() - configuration = Configuration(args) validated_conf = configuration.load_config() @@ -149,10 +120,10 @@ def test_load_config_with_params(default_conf, mocker) -> None: )) arglist = [ - '--dynamic-whitelist', '10', - '--strategy', 'TestStrategy', - '--strategy-path', '/some/path' - ] + '--dynamic-whitelist', '10', + '--strategy', 'TestStrategy', + '--strategy-path', '/some/path' + ] args = Arguments(arglist, '').get_parsed_arg() configuration = Configuration(args) @@ -180,9 +151,6 @@ def test_load_config_with_params(default_conf, mocker) -> None: def test_load_custom_strategy(default_conf, mocker) -> None: - """ - Test Configuration.load_config() without any cli params - """ custom_conf = deepcopy(default_conf) custom_conf.update({ 'strategy': 'CustomStrategy', @@ -201,13 +169,9 @@ def test_load_custom_strategy(default_conf, mocker) -> None: def test_show_info(default_conf, mocker, caplog) -> None: - """ - Test Configuration.show_info() - """ mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) - arglist = [ '--dynamic-whitelist', '10', '--strategy', 'TestStrategy', @@ -224,19 +188,14 @@ def test_show_info(default_conf, mocker, caplog) -> None: '(not applicable with Backtesting and Hyperopt)', caplog.record_tuples ) - assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples) assert log_has('Dry run is enabled', caplog.record_tuples) def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: - """ - Test setup_configuration() function - """ mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) - arglist = [ '--config', 'config.json', '--strategy', 'DefaultStrategy', @@ -274,9 +233,6 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None: - """ - Test setup_configuration() function - """ mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) @@ -342,19 +298,14 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None: - """ - Test setup_configuration() function - """ mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf) )) - arglist = [ 'hyperopt', '--epochs', '10', '--spaces', 'all', ] - args = Arguments(arglist, '').get_parsed_arg() configuration = Configuration(args) @@ -397,10 +348,6 @@ def test_check_exchange(default_conf) -> None: def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: - """ - Test Configuration.load_config() with cli params used - """ - mocker.patch('freqtrade.configuration.open', mocker.mock_open( read_data=json.dumps(default_conf))) # Prevent setting loggers @@ -416,9 +363,6 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: def test_set_loggers() -> None: - """ - Test set_loggers() update the logger level for third-party libraries - """ # Reset Logging to Debug, otherwise this fails randomly as it's set globally logging.getLogger('requests').setLevel(logging.DEBUG) logging.getLogger("urllib3").setLevel(logging.DEBUG) diff --git a/freqtrade/tests/test_indicator_helpers.py b/freqtrade/tests/test_indicator_helpers.py index f3d34ec0b..99d6cd79c 100644 --- a/freqtrade/tests/test_indicator_helpers.py +++ b/freqtrade/tests/test_indicator_helpers.py @@ -1,3 +1,5 @@ +# pragma pylint: disable=missing-docstring + import pandas as pd from freqtrade.indicator_helpers import went_down, went_up diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 80f5367b8..7aae98ebe 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -1,6 +1,4 @@ -""" -Unit test file for main.py -""" +# pragma pylint: disable=missing-docstring from copy import deepcopy from unittest.mock import MagicMock @@ -33,9 +31,6 @@ def test_parse_args_backtesting(mocker) -> None: def test_main_start_hyperopt(mocker) -> None: - """ - Test that main() can start hyperopt - """ hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock()) main(['hyperopt']) assert hyperopt_mock.call_count == 1 @@ -47,10 +42,6 @@ def test_main_start_hyperopt(mocker) -> None: def test_main_fatal_exception(mocker, default_conf, caplog) -> None: - """ - Test main() function - In this test we are skipping the while True loop by throwing an exception. - """ patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', @@ -74,10 +65,6 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None: def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: - """ - Test main() function - In this test we are skipping the while True loop by throwing an exception. - """ patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', @@ -101,10 +88,6 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: def test_main_operational_exception(mocker, default_conf, caplog) -> None: - """ - Test main() function - In this test we are skipping the while True loop by throwing an exception. - """ patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', @@ -128,10 +111,6 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: def test_main_reload_conf(mocker, default_conf, caplog) -> None: - """ - Test main() function - In this test we are skipping the while True loop by throwing an exception. - """ patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', @@ -158,7 +137,6 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: def test_reconfigure(mocker, default_conf) -> None: - """ Test recreate() function """ patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', diff --git a/freqtrade/tests/test_misc.py b/freqtrade/tests/test_misc.py index 76290c6ca..26e0c5ee6 100644 --- a/freqtrade/tests/test_misc.py +++ b/freqtrade/tests/test_misc.py @@ -1,9 +1,5 @@ # pragma pylint: disable=missing-docstring,C0103 -""" -Unit test file for misc.py -""" - import datetime from unittest.mock import MagicMock @@ -15,20 +11,12 @@ from freqtrade.strategy.default_strategy import DefaultStrategy def test_shorten_date() -> None: - """ - Test shorten_date() function - :return: None - """ str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago' str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago' assert shorten_date(str_data) == str_shorten_data def test_datesarray_to_datetimearray(ticker_history): - """ - Test datesarray_to_datetimearray() function - :return: None - """ dataframes = parse_ticker_dataframe(ticker_history) dates = datesarray_to_datetimearray(dataframes['date']) @@ -44,10 +32,6 @@ def test_datesarray_to_datetimearray(ticker_history): def test_common_datearray(default_conf) -> None: - """ - Test common_datearray() - :return: None - """ strategy = DefaultStrategy(default_conf) tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} @@ -61,10 +45,6 @@ def test_common_datearray(default_conf) -> None: def test_file_dump_json(mocker) -> None: - """ - Test file_dump_json() - :return: None - """ file_open = mocker.patch('freqtrade.misc.open', MagicMock()) json_dump = mocker.patch('json.dump', MagicMock()) file_dump_json('somefile', [1, 2, 3]) @@ -78,10 +58,6 @@ def test_file_dump_json(mocker) -> None: def test_format_ms_time() -> None: - """ - test format_ms_time() - :return: None - """ # Date 2018-04-10 18:02:01 date_in_epoch_ms = 1523383321000 date = format_ms_time(date_in_epoch_ms) From 296d3d8bbe10f562b9e4d14d8379ca04bb864674 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Thu, 14 Jun 2018 20:27:41 -0700 Subject: [PATCH 24/56] working on refacturing of the strategy class --- freqtrade/strategy/interface.py | 54 +++++++++++++++++++++++++++++++-- freqtrade/strategy/resolver.py | 2 ++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index dbe6435b7..e6987d114 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -10,6 +10,7 @@ from typing import Dict, List, NamedTuple, Tuple import arrow from pandas import DataFrame +import warnings from freqtrade import constants from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe @@ -57,36 +58,52 @@ class IStrategy(ABC): ticker_interval -> str: value of the ticker interval to use for the strategy """ + # associated minimal roi minimal_roi: Dict + + # associated stoploss stoploss: float + + # associated ticker interval ticker_interval: str def __init__(self, config: dict) -> None: self.config = config - @abstractmethod def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :return: a Dataframe with all mandatory indicators for the strategies """ + warnings.warn("deprecated - please replace this method with advise_indicators!", DeprecationWarning) + return dataframe - @abstractmethod def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame :return: DataFrame with buy column """ + warnings.warn("deprecated - please replace this method with advise_buy!", DeprecationWarning) + dataframe.loc[ + ( + ), + 'buy'] = 0 + return dataframe - @abstractmethod def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame :return: DataFrame with sell column """ + warnings.warn("deprecated - please replace this method with advise_sell!", DeprecationWarning) + dataframe.loc[ + ( + ), + 'sell'] = 0 + return dataframe def get_strategy_name(self) -> str: """ @@ -265,3 +282,34 @@ class IStrategy(ABC): """ return {pair: self.populate_indicators(parse_ticker_dataframe(pair_data)) for pair, pair_data in tickerdata.items()} + + def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + """ + + This wraps around the internal method + + Populate indicators that will be used in the Buy and Sell strategy + :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param pair: The currently traded pair + :return: a Dataframe with all mandatory indicators for the strategies + """ + return self.populate_indicators(dataframe) + + def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame + :param pair: The currently traded pair + :return: DataFrame with buy column + """ + + return self.populate_buy_trend(dataframe) + + def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame + :param pair: The currently traded pair + :return: DataFrame with sell column + """ + return self.populate_sell_trend(dataframe) diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 3360cd44a..882501f65 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -37,6 +37,8 @@ class StrategyResolver(object): config=config, extra_dir=config.get('strategy_path')) + self.strategy.config = config + # Set attributes # Check if we need to override configuration if 'minimal_roi' in config: From 57f683697db206348dfa4a0485af3dded77bde9a Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Fri, 15 Jun 2018 09:59:34 -0700 Subject: [PATCH 25/56] revised code --- freqtrade/strategy/interface.py | 12 +++--------- freqtrade/strategy/resolver.py | 1 - 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index e6987d114..23055cf88 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -7,10 +7,10 @@ from abc import ABC, abstractmethod from datetime import datetime from enum import Enum from typing import Dict, List, NamedTuple, Tuple +import warnings import arrow from pandas import DataFrame -import warnings from freqtrade import constants from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe @@ -86,10 +86,7 @@ class IStrategy(ABC): :return: DataFrame with buy column """ warnings.warn("deprecated - please replace this method with advise_buy!", DeprecationWarning) - dataframe.loc[ - ( - ), - 'buy'] = 0 + dataframe.loc[(), 'buy'] = 0 return dataframe def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: @@ -99,10 +96,7 @@ class IStrategy(ABC): :return: DataFrame with sell column """ warnings.warn("deprecated - please replace this method with advise_sell!", DeprecationWarning) - dataframe.loc[ - ( - ), - 'sell'] = 0 + dataframe.loc[(), 'sell'] = 0 return dataframe def get_strategy_name(self) -> str: diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 882501f65..0361bd91f 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -37,7 +37,6 @@ class StrategyResolver(object): config=config, extra_dir=config.get('strategy_path')) - self.strategy.config = config # Set attributes # Check if we need to override configuration From 19b996641771a2bfa530985472bd8b7de7f3ce47 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Tue, 19 Jun 2018 10:00:41 -0700 Subject: [PATCH 26/56] satisfied flake8 again --- freqtrade/strategy/interface.py | 9 ++++++--- freqtrade/strategy/resolver.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 23055cf88..94098a63a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -76,7 +76,8 @@ class IStrategy(ABC): :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :return: a Dataframe with all mandatory indicators for the strategies """ - warnings.warn("deprecated - please replace this method with advise_indicators!", DeprecationWarning) + warnings.warn("deprecated - please replace this method with advise_indicators!", + DeprecationWarning) return dataframe def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: @@ -85,7 +86,8 @@ class IStrategy(ABC): :param dataframe: DataFrame :return: DataFrame with buy column """ - warnings.warn("deprecated - please replace this method with advise_buy!", DeprecationWarning) + warnings.warn("deprecated - please replace this method with advise_buy!", + DeprecationWarning) dataframe.loc[(), 'buy'] = 0 return dataframe @@ -95,7 +97,8 @@ class IStrategy(ABC): :param dataframe: DataFrame :return: DataFrame with sell column """ - warnings.warn("deprecated - please replace this method with advise_sell!", DeprecationWarning) + warnings.warn("deprecated - please replace this method with advise_sell!", + DeprecationWarning) dataframe.loc[(), 'sell'] = 0 return dataframe diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 0361bd91f..3360cd44a 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -37,7 +37,6 @@ class StrategyResolver(object): config=config, extra_dir=config.get('strategy_path')) - # Set attributes # Check if we need to override configuration if 'minimal_roi' in config: From 2e6e5029ba91f81daa323bf4846e33590e96c23d Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 26 Jun 2018 07:08:09 +0200 Subject: [PATCH 27/56] fix mypy and tests --- freqtrade/optimize/backtesting.py | 2 +- freqtrade/tests/optimize/test_backtesting.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 78cbe6d33..852759c12 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -230,7 +230,7 @@ class Backtesting(object): pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run ticker_data = self.populate_sell_trend( - self.populate_buy_trend(pair_data))[headers].copy() + self.populate_buy_trend(pair_data, pair), pair)[headers].copy() # to avoid using data from future, we buy/sell with signal from previous candle ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 836c7c302..6f578d079 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -146,7 +146,7 @@ def _trend(signals, buy_value, sell_value): return signals -def _trend_alternate(dataframe=None): +def _trend_alternate(dataframe=None, pair=None): signals = dataframe low = signals['low'] n = len(low) @@ -623,7 +623,7 @@ def test_backtest_ticks(default_conf, fee, mocker): def test_backtest_clash_buy_sell(mocker, default_conf): # Override the default buy trend function in our default_strategy - def fun(dataframe=None): + def fun(dataframe=None, pair=None): buy_value = 1 sell_value = 1 return _trend(dataframe, buy_value, sell_value) @@ -638,7 +638,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf): def test_backtest_only_sell(mocker, default_conf): # Override the default buy trend function in our default_strategy - def fun(dataframe=None): + def fun(dataframe=None, pair=None): buy_value = 0 sell_value = 1 return _trend(dataframe, buy_value, sell_value) From 58714888587a3c9ec44cf55da8f07f62973e4609 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Mon, 16 Jul 2018 20:40:17 -0700 Subject: [PATCH 28/56] fixed errors and making flake pass --- freqtrade/strategy/interface.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 94098a63a..213546497 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -70,6 +70,7 @@ class IStrategy(ABC): def __init__(self, config: dict) -> None: self.config = config + @abstractmethod def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy @@ -80,6 +81,7 @@ class IStrategy(ABC): DeprecationWarning) return dataframe + @abstractmethod def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe @@ -91,6 +93,7 @@ class IStrategy(ABC): dataframe.loc[(), 'buy'] = 0 return dataframe + @abstractmethod def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe From abc55a6e6b727005d5c4196f58453d3e238cca83 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Mon, 16 Jul 2018 21:03:41 -0700 Subject: [PATCH 29/56] fixing? hyperopt --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 59cc0f296..a4b3a6af7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -75,7 +75,7 @@ class Hyperopt(Backtesting): return arg_dict @staticmethod - def populate_indicators(dataframe: DataFrame) -> DataFrame: + def populate_indicators(dataframe: DataFrame, pair:str) -> DataFrame: dataframe['adx'] = ta.ADX(dataframe) macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] From 3dd7d209e90144fca52291a88ce4e27097a03059 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Mon, 16 Jul 2018 21:22:59 -0700 Subject: [PATCH 30/56] more test fixes --- freqtrade/optimize/hyperopt.py | 6 ++++-- freqtrade/tests/optimize/test_hyperopt.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index a4b3a6af7..1cbfa592a 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -40,6 +40,7 @@ class Hyperopt(Backtesting): hyperopt = Hyperopt(config) hyperopt.start() """ + def __init__(self, config: Dict[str, Any]) -> None: super().__init__(config) # set TARGET_TRADES to suit your number concurrent trades so its realistic @@ -75,7 +76,7 @@ class Hyperopt(Backtesting): return arg_dict @staticmethod - def populate_indicators(dataframe: DataFrame, pair:str) -> DataFrame: + def populate_indicators(dataframe: DataFrame, pair: str) -> DataFrame: dataframe['adx'] = ta.ADX(dataframe) macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] @@ -228,6 +229,7 @@ class Hyperopt(Backtesting): """ Define the buy strategy parameters to be used by hyperopt """ + def populate_buy_trend(dataframe: DataFrame) -> DataFrame: """ Buy strategy Hyperopt will build and use @@ -360,7 +362,7 @@ class Hyperopt(Backtesting): logger.info(f'Found {cpus} CPU cores. Let\'s make them scream!') opt = self.get_optimizer(cpus) - EVALS = max(self.total_tries//cpus, 1) + EVALS = max(self.total_tries // cpus, 1) try: with Parallel(n_jobs=cpus) as parallel: for i in range(EVALS): diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index dd7bf7da0..6326814d7 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -117,7 +117,7 @@ def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None: } ) out, err = capsys.readouterr() - assert ' 1/2: foo. Loss 1.00000'in out + assert ' 1/2: foo. Loss 1.00000' in out def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None: @@ -247,7 +247,7 @@ def test_populate_indicators(init_hyperopt) -> None: tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) - dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC']) + dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], 'UNITTEST/BTC') # Check if some indicators are generated. We will not test all of them assert 'adx' in dataframe @@ -259,7 +259,7 @@ def test_buy_strategy_generator(init_hyperopt) -> None: tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) - dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC']) + dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], 'UNITTEST/BTC') populate_buy_trend = _HYPEROPT.buy_strategy_generator( { From 0dcaa82c3b9d512ae91318903acafe00b2e1b3d4 Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Mon, 16 Jul 2018 21:33:49 -0700 Subject: [PATCH 31/56] fixed test? --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 1cbfa592a..2df38a5ef 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -230,7 +230,7 @@ class Hyperopt(Backtesting): Define the buy strategy parameters to be used by hyperopt """ - def populate_buy_trend(dataframe: DataFrame) -> DataFrame: + def populate_buy_trend(dataframe: DataFrame, pair: str) -> DataFrame: """ Buy strategy Hyperopt will build and use """ From 921f645623c21ca4c681d1172f7664cd0e703b2a Mon Sep 17 00:00:00 2001 From: Gert Wohlgemuth Date: Mon, 16 Jul 2018 21:41:42 -0700 Subject: [PATCH 32/56] fixing tests... --- 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 6326814d7..9b7d301cc 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -274,7 +274,7 @@ def test_buy_strategy_generator(init_hyperopt) -> None: 'trigger': 'bb_lower' } ) - result = populate_buy_trend(dataframe) + result = populate_buy_trend(dataframe, 'UNITTEST/BTC') # Check if some indicators are generated. We will not test all of them assert 'buy' in result assert 1 in result['buy'] From 7300c0a0feaebaddc9ba697399dcffc4181a9b70 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Jul 2018 21:31:19 +0200 Subject: [PATCH 33/56] remove @abstractmethod as this method may not be present in new strategies --- freqtrade/strategy/interface.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 213546497..94098a63a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -70,7 +70,6 @@ class IStrategy(ABC): def __init__(self, config: dict) -> None: self.config = config - @abstractmethod def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy @@ -81,7 +80,6 @@ class IStrategy(ABC): DeprecationWarning) return dataframe - @abstractmethod def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe @@ -93,7 +91,6 @@ class IStrategy(ABC): dataframe.loc[(), 'buy'] = 0 return dataframe - @abstractmethod def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe From 2f905cb6968423ffb927492304714dace60a4ef6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Jul 2018 21:34:04 +0200 Subject: [PATCH 34/56] Update test-strategy with new methods --- user_data/strategies/test_strategy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index c04f4935f..26aa46a76 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -44,7 +44,7 @@ class TestStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' - def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Adds several different TA indicators to the given DataFrame @@ -211,7 +211,7 @@ class TestStrategy(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame @@ -227,7 +227,7 @@ class TestStrategy(IStrategy): return dataframe - def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame From c9a97bccb7e62244d1326016156cf9ea491e2bb3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Jul 2018 21:45:04 +0200 Subject: [PATCH 35/56] Add tests for deprecation --- freqtrade/tests/strategy/test_strategy.py | 41 ++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index e25738775..76a1d2b35 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 import logging import os +import warnings import pytest @@ -8,6 +9,7 @@ from freqtrade.strategy import import_strategy from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.tests.conftest import log_has def test_import_strategy(caplog): @@ -57,7 +59,8 @@ def test_search_strategy(): def test_load_strategy(result): resolver = StrategyResolver({'strategy': 'TestStrategy'}) - assert 'adx' in resolver.strategy.populate_indicators(result) + pair = 'ETH/BTC' + assert 'adx' in resolver.strategy.advise_indicators(result, pair=pair) def test_load_strategy_invalid_directory(result, caplog): @@ -150,3 +153,39 @@ def test_strategy_override_ticker_interval(caplog): logging.INFO, 'Override strategy \'ticker_interval\' with value in config file: 60.' ) in caplog.record_tuples + + +def test_deprecate_populate_indicators(result): + resolver = StrategyResolver({'strategy': 'TestStrategy'}) + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + resolver.strategy.populate_indicators(result) + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated - please replace this method with advise_indicators!" in str( + w[-1].message) + + +def test_deprecate_populate_buy_trend(result): + resolver = StrategyResolver({'strategy': 'TestStrategy'}) + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + resolver.strategy.populate_buy_trend(result) + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated - please replace this method with advise_buy!" in str( + w[-1].message) + + +def test_deprecate_populate_sell_trend(result): + resolver = StrategyResolver({'strategy': 'TestStrategy'}) + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + resolver.strategy.populate_sell_trend(result) + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated - please replace this method with advise_sell!" in str( + w[-1].message) From fa48b8a535bb2e5fcf58ffcdb33ac481ff20c08a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Jul 2018 21:52:40 +0200 Subject: [PATCH 36/56] Update documentation with advise-* methods --- docs/bot-optimization.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index d7b628fd4..62ab24070 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -61,22 +61,23 @@ file as reference.** ### Buy strategy -Edit the method `populate_buy_trend()` into your strategy file to +Edit the method `advise_buy()` into your strategy file to update your buy strategy. Sample from `user_data/strategies/test_strategy.py`: ```python -def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: +def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame + :param dataframe: DataFrame populated with indicators + :param pair: Pair currently analyzed :return: DataFrame with buy column """ dataframe.loc[ ( (dataframe['adx'] > 30) & - (dataframe['tema'] <= dataframe['blower']) & + (dataframe['tema'] <= dataframe['bb_middleband']) & (dataframe['tema'] > dataframe['tema'].shift(1)) ), 'buy'] = 1 @@ -86,21 +87,23 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: ### Sell strategy -Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy. +Edit the method `advise_sell()` into your strategy file to update your sell strategy. +Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration. Sample from `user_data/strategies/test_strategy.py`: ```python -def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: +def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame + :param dataframe: DataFrame populated with indicators + :param pair: Pair currently analyzed :return: DataFrame with buy column """ dataframe.loc[ ( (dataframe['adx'] > 70) & - (dataframe['tema'] > dataframe['blower']) & + (dataframe['tema'] > dataframe['bb_middleband']) & (dataframe['tema'] < dataframe['tema'].shift(1)) ), 'sell'] = 1 @@ -109,14 +112,14 @@ def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: ## Add more Indicator -As you have seen, buy and sell strategies need indicators. You can add -more indicators by extending the list contained in -the method `populate_indicators()` from your strategy file. +As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `advise_indicators()` from your strategy file. + +You should only add the indicators used in either `advise_buy()`, `advise_sell()`, or to populate another indicator, otherwise performance may suffer. Sample: ```python -def populate_indicators(dataframe: DataFrame) -> DataFrame: +def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Adds several different TA indicators to the given DataFrame """ From 4ebd706cb8fc050353c21cf6f086e9303b74a34e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Jul 2018 21:53:03 +0200 Subject: [PATCH 37/56] improve comments --- user_data/strategies/test_strategy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 26aa46a76..56dc1b6a8 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -214,7 +214,8 @@ class TestStrategy(IStrategy): def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame + :param dataframe: DataFrame populated with indicators + :param pair: Pair currently analyzed :return: DataFrame with buy column """ dataframe.loc[ @@ -230,7 +231,8 @@ class TestStrategy(IStrategy): def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame + :param dataframe: DataFrame populated with indicators + :param pair: Pair currently analyzed :return: DataFrame with buy column """ dataframe.loc[ From aa772c28adbc973f803139b16328eb2d0bffe649 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Jul 2018 22:04:34 +0200 Subject: [PATCH 38/56] Add tests for advise_indicator methods --- freqtrade/strategy/default_strategy.py | 8 +++++--- freqtrade/strategy/interface.py | 2 +- freqtrade/tests/strategy/test_strategy.py | 13 +++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 22689f17a..60dabd431 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' - def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Adds several different TA indicators to the given DataFrame @@ -196,10 +196,11 @@ class DefaultStrategy(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame + :param pair: Pair currently analyzed :return: DataFrame with buy column """ dataframe.loc[ @@ -217,10 +218,11 @@ class DefaultStrategy(IStrategy): return dataframe - def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame + :param pair: Pair currently analyzed :return: DataFrame with buy column """ dataframe.loc[ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 94098a63a..4d1e135fd 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -3,7 +3,7 @@ IStrategy interface This module defines the interface to apply for strategies """ import logging -from abc import ABC, abstractmethod +from abc import ABC from datetime import datetime from enum import Enum from typing import Dict, List, NamedTuple, Tuple diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 76a1d2b35..03ab884d0 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -9,7 +9,6 @@ from freqtrade.strategy import import_strategy from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.resolver import StrategyResolver -from freqtrade.tests.conftest import log_has def test_import_strategy(caplog): @@ -73,7 +72,8 @@ def test_load_strategy_invalid_directory(result, caplog): logging.WARNING, 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples - assert 'adx' in resolver.strategy.populate_indicators(result) + + assert 'adx' in resolver.strategy.advise_indicators(result, 'ETH/BTC') def test_load_not_found_strategy(): @@ -88,7 +88,7 @@ def test_strategy(result): config = {'strategy': 'DefaultStrategy'} resolver = StrategyResolver(config) - + pair = 'ETH/BTC' assert resolver.strategy.minimal_roi[0] == 0.04 assert config["minimal_roi"]['0'] == 0.04 @@ -98,12 +98,13 @@ def test_strategy(result): assert resolver.strategy.ticker_interval == '5m' assert config['ticker_interval'] == '5m' - assert 'adx' in resolver.strategy.populate_indicators(result) + df_indicators = resolver.strategy.advise_indicators(result, pair=pair) + assert 'adx' in df_indicators - dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result)) + dataframe = resolver.strategy.advise_buy(df_indicators, pair=pair) assert 'buy' in dataframe.columns - dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result)) + dataframe = resolver.strategy.advise_sell(df_indicators, pair='ETH/BTC') assert 'sell' in dataframe.columns From 0eff6719c2178f65c6c1c23cad2551c7766986dc Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 18 Jul 2018 23:23:30 +0200 Subject: [PATCH 39/56] improve tests for legacy-strategy loading --- freqtrade/tests/strategy/legacy_strategy.py | 242 ++++++++++++++++++++ freqtrade/tests/strategy/test_strategy.py | 34 ++- 2 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 freqtrade/tests/strategy/legacy_strategy.py diff --git a/freqtrade/tests/strategy/legacy_strategy.py b/freqtrade/tests/strategy/legacy_strategy.py new file mode 100644 index 000000000..cb97bd63b --- /dev/null +++ b/freqtrade/tests/strategy/legacy_strategy.py @@ -0,0 +1,242 @@ + +# --- Do not remove these libs --- +from freqtrade.strategy.interface import IStrategy +from pandas import DataFrame +# -------------------------------- + +# Add your lib to import here +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +import numpy # noqa + + +# This class is a sample. Feel free to customize it. +class TestStrategyLegacy(IStrategy): + """ + This is a test strategy to inspire you. + More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md + + You can: + - Rename the class name (Do not forget to update class_name) + - Add any methods you want to build your strategy + - Add any lib you need to build your strategy + + You must keep: + - the lib in the section "Do not remove these libs" + - the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend, + populate_sell_trend, hyperopt_space, buy_strategy_generator + """ + + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + } + + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.10 + + # Optimal ticker interval for the strategy + ticker_interval = '5m' + + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + """ + + # Momentum Indicator + # ------------------------------------ + + # ADX + dataframe['adx'] = ta.ADX(dataframe) + + """ + # Awesome oscillator + dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) + + # Commodity Channel Index: values Oversold:<-100, Overbought:>100 + dataframe['cci'] = ta.CCI(dataframe) + + # MACD + macd = ta.MACD(dataframe) + dataframe['macd'] = macd['macd'] + dataframe['macdsignal'] = macd['macdsignal'] + dataframe['macdhist'] = macd['macdhist'] + + # MFI + dataframe['mfi'] = ta.MFI(dataframe) + + # Minus Directional Indicator / Movement + dataframe['minus_dm'] = ta.MINUS_DM(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # Plus Directional Indicator / Movement + dataframe['plus_dm'] = ta.PLUS_DM(dataframe) + dataframe['plus_di'] = ta.PLUS_DI(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # ROC + dataframe['roc'] = ta.ROC(dataframe) + + # RSI + dataframe['rsi'] = ta.RSI(dataframe) + + # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) + rsi = 0.1 * (dataframe['rsi'] - 50) + dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) + + # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) + dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + + # Stoch + stoch = ta.STOCH(dataframe) + dataframe['slowd'] = stoch['slowd'] + dataframe['slowk'] = stoch['slowk'] + + # Stoch fast + stoch_fast = ta.STOCHF(dataframe) + dataframe['fastd'] = stoch_fast['fastd'] + dataframe['fastk'] = stoch_fast['fastk'] + + # Stoch RSI + stoch_rsi = ta.STOCHRSI(dataframe) + dataframe['fastd_rsi'] = stoch_rsi['fastd'] + dataframe['fastk_rsi'] = stoch_rsi['fastk'] + """ + + # Overlap Studies + # ------------------------------------ + + # Bollinger bands + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + dataframe['bb_middleband'] = bollinger['mid'] + dataframe['bb_upperband'] = bollinger['upper'] + + """ + # EMA - Exponential Moving Average + dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) + dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) + dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) + dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + + # SAR Parabol + dataframe['sar'] = ta.SAR(dataframe) + + # SMA - Simple Moving Average + dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) + """ + + # TEMA - Triple Exponential Moving Average + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + # Cycle Indicator + # ------------------------------------ + # Hilbert Transform Indicator - SineWave + hilbert = ta.HT_SINE(dataframe) + dataframe['htsine'] = hilbert['sine'] + dataframe['htleadsine'] = hilbert['leadsine'] + + # Pattern Recognition - Bullish candlestick patterns + # ------------------------------------ + """ + # Hammer: values [0, 100] + dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) + # Inverted Hammer: values [0, 100] + dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) + # Dragonfly Doji: values [0, 100] + dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) + # Piercing Line: values [0, 100] + dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] + # Morningstar: values [0, 100] + dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] + # Three White Soldiers: values [0, 100] + dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] + """ + + # Pattern Recognition - Bearish candlestick patterns + # ------------------------------------ + """ + # Hanging Man: values [0, 100] + dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) + # Shooting Star: values [0, 100] + dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) + # Gravestone Doji: values [0, 100] + dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) + # Dark Cloud Cover: values [0, 100] + dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) + # Evening Doji Star: values [0, 100] + dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) + # Evening Star: values [0, 100] + dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) + """ + + # Pattern Recognition - Bullish/Bearish candlestick patterns + # ------------------------------------ + """ + # Three Line Strike: values [0, -100, 100] + dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) + # Spinning Top: values [0, -100, 100] + dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] + # Engulfing: values [0, -100, 100] + dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] + # Harami: values [0, -100, 100] + dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] + # Three Outside Up/Down: values [0, -100, 100] + dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] + # Three Inside Up/Down: values [0, -100, 100] + dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] + """ + + # Chart type + # ------------------------------------ + """ + # Heikinashi stategy + heikinashi = qtpylib.heikinashi(dataframe) + dataframe['ha_open'] = heikinashi['open'] + dataframe['ha_close'] = heikinashi['close'] + dataframe['ha_high'] = heikinashi['high'] + dataframe['ha_low'] = heikinashi['low'] + """ + + return dataframe + + def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (dataframe['adx'] > 30) & + (dataframe['tema'] <= dataframe['bb_middleband']) & + (dataframe['tema'] > dataframe['tema'].shift(1)) + ), + 'buy'] = 1 + + return dataframe + + def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (dataframe['adx'] > 70) & + (dataframe['tema'] > dataframe['bb_middleband']) & + (dataframe['tema'] < dataframe['tema'].shift(1)) + ), + 'sell'] = 1 + return dataframe diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 03ab884d0..6c11f0092 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 import logging -import os +from os import path +from unittest.mock import MagicMock import warnings import pytest @@ -37,9 +38,8 @@ def test_import_strategy(caplog): def test_search_strategy(): - default_config = {} - default_location = os.path.join(os.path.dirname( - os.path.realpath(__file__)), '..', '..', 'strategy' + default_location = path.join(path.dirname( + path.realpath(__file__)), '..', '..', 'strategy' ) assert isinstance( StrategyResolver._search_strategy( @@ -64,8 +64,8 @@ def test_load_strategy(result): def test_load_strategy_invalid_directory(result, caplog): resolver = StrategyResolver() - extra_dir = os.path.join('some', 'path') - resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) + extra_dir = path.join('some', 'path') + resolver._load_strategy('TestStrategy', extra_dir) assert ( 'freqtrade.strategy.resolver', @@ -190,3 +190,25 @@ def test_deprecate_populate_sell_trend(result): assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated - please replace this method with advise_sell!" in str( w[-1].message) + + +def test_call_deprecated_function(result, monkeypatch): + default_location = path.join(path.dirname(path.realpath(__file__))) + resolver = StrategyResolver({'strategy': 'TestStrategyLegacy', + 'strategy_path': default_location}) + pair = 'ETH/BTC' + indicators_mock = MagicMock() + buy_trend_mock = MagicMock() + sell_trend_mock = MagicMock() + + monkeypatch.setattr(resolver.strategy, 'populate_indicators', indicators_mock) + resolver.strategy.advise_indicators(result, pair=pair) + assert indicators_mock.call_count == 1 + + monkeypatch.setattr(resolver.strategy, 'populate_buy_trend', buy_trend_mock) + resolver.strategy.advise_buy(result, pair=pair) + assert buy_trend_mock.call_count == 1 + + monkeypatch.setattr(resolver.strategy, 'populate_sell_trend', sell_trend_mock) + resolver.strategy.advise_sell(result, pair=pair) + assert sell_trend_mock.call_count == 1 From df8700ead086470aabba675c7d0eb658a283c71d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jul 2018 20:56:44 +0200 Subject: [PATCH 40/56] Adapt after merge from develop --- freqtrade/optimize/backtesting.py | 8 ++++---- freqtrade/strategy/interface.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 852759c12..e3c3974be 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -57,8 +57,8 @@ class Backtesting(object): self.strategy: IStrategy = StrategyResolver(self.config).strategy self.ticker_interval = self.strategy.ticker_interval self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe - self.populate_buy_trend = self.strategy.populate_buy_trend - self.populate_sell_trend = self.strategy.populate_sell_trend + self.advise_buy = self.strategy.advise_buy + self.advise_sell = self.strategy.advise_sell # Reset keys for backtesting self.config['exchange']['key'] = '' @@ -229,8 +229,8 @@ class Backtesting(object): for pair, pair_data in processed.items(): pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run - ticker_data = self.populate_sell_trend( - self.populate_buy_trend(pair_data, pair), pair)[headers].copy() + ticker_data = self.advise_sell( + self.advise_buy(pair_data, pair), pair)[headers].copy() # to avoid using data from future, we buy/sell with signal from previous candle ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 4d1e135fd..45a131c5e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -277,7 +277,7 @@ class IStrategy(ABC): """ Creates a dataframe and populates indicators for given ticker data """ - return {pair: self.populate_indicators(parse_ticker_dataframe(pair_data)) + return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), pair) for pair, pair_data in tickerdata.items()} def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: From f12167f0dcecdc28f3caece83e8d434b16f17164 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jul 2018 20:59:56 +0200 Subject: [PATCH 41/56] Fix backtesting test --- freqtrade/tests/optimize/test_backtesting.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 6f578d079..91ea2eee1 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -332,8 +332,8 @@ def test_backtesting_init(mocker, default_conf) -> None: assert backtesting.config == default_conf assert backtesting.ticker_interval == '5m' assert callable(backtesting.tickerdata_to_dataframe) - assert callable(backtesting.populate_buy_trend) - assert callable(backtesting.populate_sell_trend) + assert callable(backtesting.advise_buy) + assert callable(backtesting.advise_sell) get_fee.assert_called() assert backtesting.fee == 0.5 @@ -611,12 +611,12 @@ def test_backtest_ticks(default_conf, fee, mocker): mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) patch_exchange(mocker) ticks = [1, 5] - fun = Backtesting(default_conf).populate_buy_trend + fun = Backtesting(default_conf).advise_buy for _ in ticks: backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtesting = Backtesting(default_conf) - backtesting.populate_buy_trend = fun # Override - backtesting.populate_sell_trend = fun # Override + backtesting.advise_buy = fun # Override + backtesting.advise_sell = fun # Override results = backtesting.backtest(backtest_conf) assert not results.empty @@ -630,8 +630,8 @@ def test_backtest_clash_buy_sell(mocker, default_conf): backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtesting = Backtesting(default_conf) - backtesting.populate_buy_trend = fun # Override - backtesting.populate_sell_trend = fun # Override + backtesting.advise_buy = fun # Override + backtesting.advise_sell = fun # Override results = backtesting.backtest(backtest_conf) assert results.empty @@ -645,8 +645,8 @@ def test_backtest_only_sell(mocker, default_conf): backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtesting = Backtesting(default_conf) - backtesting.populate_buy_trend = fun # Override - backtesting.populate_sell_trend = fun # Override + backtesting.advise_buy = fun # Override + backtesting.advise_sell = fun # Override results = backtesting.backtest(backtest_conf) assert results.empty @@ -655,8 +655,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker): mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC') backtesting = Backtesting(default_conf) - backtesting.populate_buy_trend = _trend_alternate # Override - backtesting.populate_sell_trend = _trend_alternate # Override + backtesting.advise_buy = _trend_alternate # Override + backtesting.advise_sell = _trend_alternate # Override results = backtesting.backtest(backtest_conf) backtesting._store_backtest_result("test_.json", results) assert len(results) == 4 From 18b8f20f1c533512a6e9212c019de87e6928f6a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jul 2018 21:48:59 +0200 Subject: [PATCH 42/56] fix small test bug --- freqtrade/tests/strategy/test_strategy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 6c11f0092..9a62c8c73 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -38,6 +38,7 @@ def test_import_strategy(caplog): def test_search_strategy(): + default_config = {} default_location = path.join(path.dirname( path.realpath(__file__)), '..', '..', 'strategy' ) @@ -65,7 +66,7 @@ def test_load_strategy(result): def test_load_strategy_invalid_directory(result, caplog): resolver = StrategyResolver() extra_dir = path.join('some', 'path') - resolver._load_strategy('TestStrategy', extra_dir) + resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) assert ( 'freqtrade.strategy.resolver', From 8a9c54ed61c5a2ea2d84b2ee21d6c36df4232d7c Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jul 2018 22:02:17 +0200 Subject: [PATCH 43/56] use new methods --- freqtrade/strategy/interface.py | 10 +++++----- freqtrade/tests/test_dataframe.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 45a131c5e..40db21cc2 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -108,16 +108,16 @@ class IStrategy(ABC): """ return self.__class__.__name__ - def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame: + def analyze_ticker(self, ticker_history: List[Dict], pair: str) -> DataFrame: """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it :return DataFrame with ticker data and indicator data """ dataframe = parse_ticker_dataframe(ticker_history) - dataframe = self.populate_indicators(dataframe) - dataframe = self.populate_buy_trend(dataframe) - dataframe = self.populate_sell_trend(dataframe) + dataframe = self.advise_indicators(dataframe, pair) + dataframe = self.advise_buy(dataframe, pair) + dataframe = self.advise_sell(dataframe, pair) return dataframe def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]: @@ -132,7 +132,7 @@ class IStrategy(ABC): return False, False try: - dataframe = self.analyze_ticker(ticker_hist) + dataframe = self.analyze_ticker(ticker_hist, pair) except ValueError as error: logger.warning( 'Unable to analyze ticker for pair %s: %s', diff --git a/freqtrade/tests/test_dataframe.py b/freqtrade/tests/test_dataframe.py index 019587af1..ce144e118 100644 --- a/freqtrade/tests/test_dataframe.py +++ b/freqtrade/tests/test_dataframe.py @@ -14,7 +14,7 @@ def load_dataframe_pair(pairs, strategy): assert isinstance(pairs[0], str) dataframe = ld[pairs[0]] - dataframe = strategy.analyze_ticker(dataframe) + dataframe = strategy.analyze_ticker(dataframe, pairs[0]) return dataframe From 791c5ff0710dba50eadf3e708bfc0e9857f69b20 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jul 2018 22:06:15 +0200 Subject: [PATCH 44/56] update comments to explain what advise methods do --- freqtrade/strategy/interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 40db21cc2..5051e9398 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -282,10 +282,8 @@ class IStrategy(ABC): def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ - - This wraps around the internal method - Populate indicators that will be used in the Buy and Sell strategy + If not overridden, calls the legacy method `populate_indicators to keep strategies working :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :param pair: The currently traded pair :return: a Dataframe with all mandatory indicators for the strategies @@ -295,6 +293,7 @@ class IStrategy(ABC): def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe + If not overridden, calls the legacy method `populate_buy_trend to keep strategies working :param dataframe: DataFrame :param pair: The currently traded pair :return: DataFrame with buy column @@ -305,6 +304,7 @@ class IStrategy(ABC): def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe + If not overridden, calls the legacy method `populate_sell_trend to keep strategies working :param dataframe: DataFrame :param pair: The currently traded pair :return: DataFrame with sell column From cf83416d6985666a7ead9216aca55126ae5b968a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jul 2018 22:07:18 +0200 Subject: [PATCH 45/56] update script to use new method --- scripts/plot_dataframe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 11f1f85d5..06e1cd1d8 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -159,8 +159,8 @@ def plot_analyzed_dataframe(args: Namespace) -> None: dataframes = strategy.tickerdata_to_dataframe(tickers) dataframe = dataframes[pair] - dataframe = strategy.populate_buy_trend(dataframe) - dataframe = strategy.populate_sell_trend(dataframe) + dataframe = strategy.advise_buy(dataframe, pair) + dataframe = strategy.advise_sell(dataframe, pair) if len(dataframe.index) > args.plot_limit: logger.warning('Ticker contained more than %s candles as defined ' From 98665dcef4afc1a6f69583dedebcd2c8d4c394a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jul 2018 22:15:32 +0200 Subject: [PATCH 46/56] revert inadvertent wihtespace changes --- freqtrade/optimize/hyperopt.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 2df38a5ef..af41d799f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -40,7 +40,6 @@ class Hyperopt(Backtesting): hyperopt = Hyperopt(config) hyperopt.start() """ - def __init__(self, config: Dict[str, Any]) -> None: super().__init__(config) # set TARGET_TRADES to suit your number concurrent trades so its realistic @@ -229,7 +228,6 @@ class Hyperopt(Backtesting): """ Define the buy strategy parameters to be used by hyperopt """ - def populate_buy_trend(dataframe: DataFrame, pair: str) -> DataFrame: """ Buy strategy Hyperopt will build and use From f286ba6b8786eb7670aa9c5b98839ddc81a88b16 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Jul 2018 17:39:35 +0200 Subject: [PATCH 47/56] overload populate_indicators to work with and without pair argumen all while not breaking users strategies --- freqtrade/strategy/default_strategy.py | 6 +- freqtrade/strategy/interface.py | 53 +++++++------ .../tests/strategy/test_default_strategy.py | 7 +- freqtrade/tests/strategy/test_strategy.py | 75 ++++++++++--------- user_data/strategies/test_strategy.py | 6 +- 5 files changed, 82 insertions(+), 65 deletions(-) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 60dabd431..6285a483e 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' - def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Adds several different TA indicators to the given DataFrame @@ -196,7 +196,7 @@ class DefaultStrategy(IStrategy): return dataframe - def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame @@ -218,7 +218,7 @@ class DefaultStrategy(IStrategy): return dataframe - def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 5051e9398..e80ee0e0e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -3,7 +3,7 @@ IStrategy interface This module defines the interface to apply for strategies """ import logging -from abc import ABC +from abc import ABC, abstractmethod from datetime import datetime from enum import Enum from typing import Dict, List, NamedTuple, Tuple @@ -70,37 +70,32 @@ class IStrategy(ABC): def __init__(self, config: dict) -> None: self.config = config - def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + @abstractmethod + def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param pair: Pair currently analyzed :return: a Dataframe with all mandatory indicators for the strategies """ - warnings.warn("deprecated - please replace this method with advise_indicators!", - DeprecationWarning) - return dataframe - def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + @abstractmethod + def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame + :param pair: Pair currently analyzed :return: DataFrame with buy column """ - warnings.warn("deprecated - please replace this method with advise_buy!", - DeprecationWarning) - dataframe.loc[(), 'buy'] = 0 - return dataframe - def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + @abstractmethod + def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame + :param pair: Pair currently analyzed :return: DataFrame with sell column """ - warnings.warn("deprecated - please replace this method with advise_sell!", - DeprecationWarning) - dataframe.loc[(), 'sell'] = 0 - return dataframe def get_strategy_name(self) -> str: """ @@ -283,30 +278,44 @@ class IStrategy(ABC): def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy - If not overridden, calls the legacy method `populate_indicators to keep strategies working + This method should not be overridden. :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() :param pair: The currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ - return self.populate_indicators(dataframe) + if len(self.populate_indicators.__annotations__) == 2: + warnings.warn("deprecated - check out the Sample strategy to see " + "the current function headers!", DeprecationWarning) + return self.populate_indicators(dataframe) # type: ignore + else: + return self.populate_indicators(dataframe, pair) def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe - If not overridden, calls the legacy method `populate_buy_trend to keep strategies working + This method should not be overridden. :param dataframe: DataFrame :param pair: The currently traded pair :return: DataFrame with buy column """ - - return self.populate_buy_trend(dataframe) + if len(self.populate_buy_trend.__annotations__) == 2: + warnings.warn("deprecated - check out the Sample strategy to see " + "the current function headers!", DeprecationWarning) + return self.populate_buy_trend(dataframe) # type: ignore + else: + return self.populate_buy_trend(dataframe, pair) def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe - If not overridden, calls the legacy method `populate_sell_trend to keep strategies working + This method should not be overridden. :param dataframe: DataFrame :param pair: The currently traded pair :return: DataFrame with sell column """ - return self.populate_sell_trend(dataframe) + if len(self.populate_sell_trend.__annotations__) == 2: + warnings.warn("deprecated - check out the Sample strategy to see " + "the current function headers!", DeprecationWarning) + return self.populate_sell_trend(dataframe) # type: ignore + else: + return self.populate_sell_trend(dataframe, pair) diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py index 37df1748f..2b10e9023 100644 --- a/freqtrade/tests/strategy/test_default_strategy.py +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -25,10 +25,11 @@ def test_default_strategy_structure(): def test_default_strategy(result): strategy = DefaultStrategy({}) + pair = 'ETH/BTC' assert type(strategy.minimal_roi) is dict assert type(strategy.stoploss) is float assert type(strategy.ticker_interval) is str - indicators = strategy.populate_indicators(result) + indicators = strategy.populate_indicators(result, pair) assert type(indicators) is DataFrame - assert type(strategy.populate_buy_trend(indicators)) is DataFrame - assert type(strategy.populate_sell_trend(indicators)) is DataFrame + assert type(strategy.populate_buy_trend(indicators, pair)) is DataFrame + assert type(strategy.populate_sell_trend(indicators, pair)) is DataFrame diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 9a62c8c73..c90271506 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -1,10 +1,10 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 import logging from os import path -from unittest.mock import MagicMock import warnings import pytest +from pandas import DataFrame from freqtrade.strategy import import_strategy from freqtrade.strategy.default_strategy import DefaultStrategy @@ -60,6 +60,9 @@ def test_search_strategy(): def test_load_strategy(result): resolver = StrategyResolver({'strategy': 'TestStrategy'}) pair = 'ETH/BTC' + assert len(resolver.strategy.populate_indicators.__annotations__) == 3 + assert 'dataframe' in resolver.strategy.populate_indicators.__annotations__ + assert 'pair' in resolver.strategy.populate_indicators.__annotations__ assert 'adx' in resolver.strategy.advise_indicators(result, pair=pair) @@ -158,39 +161,35 @@ def test_strategy_override_ticker_interval(caplog): def test_deprecate_populate_indicators(result): - resolver = StrategyResolver({'strategy': 'TestStrategy'}) + default_location = path.join(path.dirname(path.realpath(__file__))) + resolver = StrategyResolver({'strategy': 'TestStrategyLegacy', + 'strategy_path': default_location}) with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") - resolver.strategy.populate_indicators(result) + indicators = resolver.strategy.advise_indicators(result, 'ETH/BTC') assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) - assert "deprecated - please replace this method with advise_indicators!" in str( - w[-1].message) + assert "deprecated - check out the Sample strategy to see the current function headers!" \ + in str(w[-1].message) + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + resolver.strategy.advise_buy(indicators, 'ETH/BTC') + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated - check out the Sample strategy to see the current function headers!" \ + in str(w[-1].message) -def test_deprecate_populate_buy_trend(result): - resolver = StrategyResolver({'strategy': 'TestStrategy'}) with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") - resolver.strategy.populate_buy_trend(result) + resolver.strategy.advise_sell(indicators, 'ETH_BTC') assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) - assert "deprecated - please replace this method with advise_buy!" in str( - w[-1].message) - - -def test_deprecate_populate_sell_trend(result): - resolver = StrategyResolver({'strategy': 'TestStrategy'}) - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - resolver.strategy.populate_sell_trend(result) - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) - assert "deprecated - please replace this method with advise_sell!" in str( - w[-1].message) + assert "deprecated - check out the Sample strategy to see the current function headers!" \ + in str(w[-1].message) def test_call_deprecated_function(result, monkeypatch): @@ -198,18 +197,26 @@ def test_call_deprecated_function(result, monkeypatch): resolver = StrategyResolver({'strategy': 'TestStrategyLegacy', 'strategy_path': default_location}) pair = 'ETH/BTC' - indicators_mock = MagicMock() - buy_trend_mock = MagicMock() - sell_trend_mock = MagicMock() - monkeypatch.setattr(resolver.strategy, 'populate_indicators', indicators_mock) - resolver.strategy.advise_indicators(result, pair=pair) - assert indicators_mock.call_count == 1 + # Make sure we are using a legacy function + assert len(resolver.strategy.populate_indicators.__annotations__) == 2 + assert 'dataframe' in resolver.strategy.populate_indicators.__annotations__ + assert 'pair' not in resolver.strategy.populate_indicators.__annotations__ + assert len(resolver.strategy.populate_buy_trend.__annotations__) == 2 + assert 'dataframe' in resolver.strategy.populate_buy_trend.__annotations__ + assert 'pair' not in resolver.strategy.populate_buy_trend.__annotations__ + assert len(resolver.strategy.populate_sell_trend.__annotations__) == 2 + assert 'dataframe' in resolver.strategy.populate_sell_trend.__annotations__ + assert 'pair' not in resolver.strategy.populate_sell_trend.__annotations__ - monkeypatch.setattr(resolver.strategy, 'populate_buy_trend', buy_trend_mock) - resolver.strategy.advise_buy(result, pair=pair) - assert buy_trend_mock.call_count == 1 + indicator_df = resolver.strategy.advise_indicators(result, pair=pair) + assert type(indicator_df) is DataFrame + assert 'adx' in indicator_df.columns - monkeypatch.setattr(resolver.strategy, 'populate_sell_trend', sell_trend_mock) - resolver.strategy.advise_sell(result, pair=pair) - assert sell_trend_mock.call_count == 1 + buydf = resolver.strategy.advise_buy(result, pair=pair) + assert type(buydf) is DataFrame + assert 'buy' in buydf.columns + + selldf = resolver.strategy.advise_sell(result, pair=pair) + assert type(selldf) is DataFrame + assert 'sell' in selldf diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 56dc1b6a8..96d1e0bfd 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -44,7 +44,7 @@ class TestStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' - def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Adds several different TA indicators to the given DataFrame @@ -211,7 +211,7 @@ class TestStrategy(IStrategy): return dataframe - def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame populated with indicators @@ -228,7 +228,7 @@ class TestStrategy(IStrategy): return dataframe - def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame populated with indicators From 39cf0deccebf2a9081116b5ef05e57e2e49b1215 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 23 Jul 2018 17:38:21 +0100 Subject: [PATCH 48/56] don't use __annotate__ it is only present when typehints are used which cannot be guaranteed for userdefined classes --- freqtrade/strategy/interface.py | 9 ++++++--- freqtrade/strategy/resolver.py | 7 +++++++ freqtrade/tests/strategy/test_strategy.py | 12 +++--------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index e80ee0e0e..887c1d583 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -58,6 +58,9 @@ class IStrategy(ABC): ticker_interval -> str: value of the ticker interval to use for the strategy """ + _populate_fun_len: int = 0 + _buy_fun_len: int = 0 + _sell_fun_len: int = 0 # associated minimal roi minimal_roi: Dict @@ -283,7 +286,7 @@ class IStrategy(ABC): :param pair: The currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ - if len(self.populate_indicators.__annotations__) == 2: + if self._populate_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) return self.populate_indicators(dataframe) # type: ignore @@ -298,7 +301,7 @@ class IStrategy(ABC): :param pair: The currently traded pair :return: DataFrame with buy column """ - if len(self.populate_buy_trend.__annotations__) == 2: + if self._buy_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) return self.populate_buy_trend(dataframe) # type: ignore @@ -313,7 +316,7 @@ class IStrategy(ABC): :param pair: The currently traded pair :return: DataFrame with sell column """ - if len(self.populate_sell_trend.__annotations__) == 2: + if self._sell_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) return self.populate_sell_trend(dataframe) # type: ignore diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 3360cd44a..ea887e43e 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -92,6 +92,13 @@ class StrategyResolver(object): strategy = self._search_strategy(path, strategy_name=strategy_name, config=config) if strategy: logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) + strategy._populate_fun_len = len( + inspect.getfullargspec(strategy.populate_indicators).args) + strategy._buy_fun_len = len( + inspect.getfullargspec(strategy.populate_buy_trend).args) + strategy._sell_fun_len = len( + inspect.getfullargspec(strategy.populate_sell_trend).args) + return import_strategy(strategy, config=config) except FileNotFoundError: logger.warning('Path "%s" does not exist', path) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index c90271506..02eea312f 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -199,15 +199,9 @@ def test_call_deprecated_function(result, monkeypatch): pair = 'ETH/BTC' # Make sure we are using a legacy function - assert len(resolver.strategy.populate_indicators.__annotations__) == 2 - assert 'dataframe' in resolver.strategy.populate_indicators.__annotations__ - assert 'pair' not in resolver.strategy.populate_indicators.__annotations__ - assert len(resolver.strategy.populate_buy_trend.__annotations__) == 2 - assert 'dataframe' in resolver.strategy.populate_buy_trend.__annotations__ - assert 'pair' not in resolver.strategy.populate_buy_trend.__annotations__ - assert len(resolver.strategy.populate_sell_trend.__annotations__) == 2 - assert 'dataframe' in resolver.strategy.populate_sell_trend.__annotations__ - assert 'pair' not in resolver.strategy.populate_sell_trend.__annotations__ + assert resolver.strategy._populate_fun_len == 2 + assert resolver.strategy._buy_fun_len == 2 + assert resolver.strategy._sell_fun_len == 2 indicator_df = resolver.strategy.advise_indicators(result, pair=pair) assert type(indicator_df) is DataFrame From 5fbce13830cb193878c64da6cb140ad39b94832b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 23 Jul 2018 23:29:54 +0100 Subject: [PATCH 49/56] update hyperopt to use new methods --- freqtrade/optimize/hyperopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index af41d799f..f0d81e3ff 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -270,7 +270,7 @@ class Hyperopt(Backtesting): self.strategy.minimal_roi = self.generate_roi_table(params) if self.has_space('buy'): - self.populate_buy_trend = self.buy_strategy_generator(params) + self.advise_buy = self.buy_strategy_generator(params) if self.has_space('stoploss'): self.strategy.stoploss = params['stoploss'] @@ -351,7 +351,7 @@ class Hyperopt(Backtesting): ) if self.has_space('buy'): - self.strategy.populate_indicators = Hyperopt.populate_indicators # type: ignore + self.strategy.advise_indicators = Hyperopt.populate_indicators # type: ignore dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE) self.exchange = None # type: ignore self.load_previous_results() From 82680ac6aa80b0aea35e4c2df2f15323eb9d5282 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Jul 2018 07:54:01 +0100 Subject: [PATCH 50/56] improve docstrings for strategy --- freqtrade/strategy/default_strategy.py | 3 +++ user_data/strategies/test_strategy.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 6285a483e..9eaf093ae 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -35,6 +35,9 @@ class DefaultStrategy(IStrategy): Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param pair: Pair currently analyzed + :return: a Dataframe with all mandatory indicators for the strategies """ # Momentum Indicator diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 96d1e0bfd..65e06558c 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -18,6 +18,7 @@ class TestStrategy(IStrategy): More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md You can: + :return: a Dataframe with all mandatory indicators for the strategies - Rename the class name (Do not forget to update class_name) - Add any methods you want to build your strategy - Add any lib you need to build your strategy @@ -51,6 +52,9 @@ class TestStrategy(IStrategy): Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param pair: Pair currently analyzed + :return: a Dataframe with all mandatory indicators for the strategies """ # Momentum Indicator From 941879dc190ac5d73ab543148cb36f3d799aed07 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Jul 2018 07:54:17 +0100 Subject: [PATCH 51/56] revert docs to use populate_* functions --- docs/bot-optimization.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 62ab24070..ebd8cbe8c 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -61,13 +61,13 @@ file as reference.** ### Buy strategy -Edit the method `advise_buy()` into your strategy file to +Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy. Sample from `user_data/strategies/test_strategy.py`: ```python -def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: +def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame populated with indicators @@ -87,13 +87,13 @@ def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: ### Sell strategy -Edit the method `advise_sell()` into your strategy file to update your sell strategy. +Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy. Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration. Sample from `user_data/strategies/test_strategy.py`: ```python -def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: +def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame populated with indicators @@ -112,14 +112,14 @@ def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: ## Add more Indicator -As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `advise_indicators()` from your strategy file. +As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file. -You should only add the indicators used in either `advise_buy()`, `advise_sell()`, or to populate another indicator, otherwise performance may suffer. +You should only add the indicators used in either `populate_buy_trend()`, `populate_sell_trend()`, or to populate another indicator, otherwise performance may suffer. Sample: ```python -def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: +def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Adds several different TA indicators to the given DataFrame """ From 787d6042de49e2e71ad4c27704460401560ac02e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Jul 2018 20:36:03 +0200 Subject: [PATCH 52/56] Switch from pair(str) to metadata(dict) --- docs/bot-optimization.md | 28 ++++++++++----- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/hyperopt.py | 4 +-- freqtrade/strategy/default_strategy.py | 12 +++---- freqtrade/strategy/interface.py | 42 +++++++++++------------ freqtrade/tests/strategy/test_strategy.py | 21 +++++------- user_data/strategies/test_strategy.py | 12 +++---- 7 files changed, 64 insertions(+), 57 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index ebd8cbe8c..0214ce5c5 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -39,7 +39,6 @@ A strategy file contains all the information needed to build a good strategy: - Sell strategy rules - Minimal ROI recommended - Stoploss recommended -- Hyperopt parameter The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`. You can test it with the parameter: `--strategy TestStrategy` @@ -61,17 +60,16 @@ file as reference.** ### Buy strategy -Edit the method `populate_buy_trend()` into your strategy file to -update your buy strategy. +Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy. Sample from `user_data/strategies/test_strategy.py`: ```python -def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: +def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame populated with indicators - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ @@ -93,11 +91,11 @@ Please note that the sell-signal is only used if `use_sell_signal` is set to tru Sample from `user_data/strategies/test_strategy.py`: ```python -def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: +def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame populated with indicators - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ @@ -110,7 +108,7 @@ def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: return dataframe ``` -## Add more Indicator +## Add more Indicators As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file. @@ -119,9 +117,16 @@ You should only add the indicators used in either `populate_buy_trend()`, `popul Sample: ```python -def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: +def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param metadata: Additional information, like the currently traded pair + :return: a Dataframe with all mandatory indicators for the strategies """ dataframe['sar'] = ta.SAR(dataframe) dataframe['adx'] = ta.ADX(dataframe) @@ -152,6 +157,11 @@ def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: return dataframe ``` +### Metadata dict + +The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information. +Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`. + ### Want more indicator examples Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py). diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e3c3974be..593af619c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -230,7 +230,7 @@ class Backtesting(object): pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run ticker_data = self.advise_sell( - self.advise_buy(pair_data, pair), pair)[headers].copy() + self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() # to avoid using data from future, we buy/sell with signal from previous candle ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index f0d81e3ff..086cad5aa 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -75,7 +75,7 @@ class Hyperopt(Backtesting): return arg_dict @staticmethod - def populate_indicators(dataframe: DataFrame, pair: str) -> DataFrame: + def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['adx'] = ta.ADX(dataframe) macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] @@ -228,7 +228,7 @@ class Hyperopt(Backtesting): """ Define the buy strategy parameters to be used by hyperopt """ - def populate_buy_trend(dataframe: DataFrame, pair: str) -> DataFrame: + def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: """ Buy strategy Hyperopt will build and use """ diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 9eaf093ae..f1646779b 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' - def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame @@ -36,7 +36,7 @@ class DefaultStrategy(IStrategy): you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ @@ -199,11 +199,11 @@ class DefaultStrategy(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ @@ -221,11 +221,11 @@ class DefaultStrategy(IStrategy): return dataframe - def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 887c1d583..dfd624393 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -74,29 +74,29 @@ class IStrategy(ABC): self.config = config @abstractmethod - def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ @abstractmethod - def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ @abstractmethod - def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with sell column """ @@ -106,16 +106,16 @@ class IStrategy(ABC): """ return self.__class__.__name__ - def analyze_ticker(self, ticker_history: List[Dict], pair: str) -> DataFrame: + def analyze_ticker(self, ticker_history: List[Dict], metadata: dict) -> DataFrame: """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it :return DataFrame with ticker data and indicator data """ dataframe = parse_ticker_dataframe(ticker_history) - dataframe = self.advise_indicators(dataframe, pair) - dataframe = self.advise_buy(dataframe, pair) - dataframe = self.advise_sell(dataframe, pair) + dataframe = self.advise_indicators(dataframe, metadata) + dataframe = self.advise_buy(dataframe, metadata) + dataframe = self.advise_sell(dataframe, metadata) return dataframe def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]: @@ -130,7 +130,7 @@ class IStrategy(ABC): return False, False try: - dataframe = self.analyze_ticker(ticker_hist, pair) + dataframe = self.analyze_ticker(ticker_hist, {'pair': pair}) except ValueError as error: logger.warning( 'Unable to analyze ticker for pair %s: %s', @@ -275,15 +275,15 @@ class IStrategy(ABC): """ Creates a dataframe and populates indicators for given ticker data """ - return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), pair) + return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), {'pair': pair}) for pair, pair_data in tickerdata.items()} - def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy This method should not be overridden. :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() - :param pair: The currently traded pair + :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ if self._populate_fun_len == 2: @@ -291,14 +291,14 @@ class IStrategy(ABC): "the current function headers!", DeprecationWarning) return self.populate_indicators(dataframe) # type: ignore else: - return self.populate_indicators(dataframe, pair) + return self.populate_indicators(dataframe, metadata) - def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: + def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe This method should not be overridden. :param dataframe: DataFrame - :param pair: The currently traded pair + :param pair: Additional information, like the currently traded pair :return: DataFrame with buy column """ if self._buy_fun_len == 2: @@ -306,14 +306,14 @@ class IStrategy(ABC): "the current function headers!", DeprecationWarning) return self.populate_buy_trend(dataframe) # type: ignore else: - return self.populate_buy_trend(dataframe, pair) + return self.populate_buy_trend(dataframe, metadata) - def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: + def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe This method should not be overridden. :param dataframe: DataFrame - :param pair: The currently traded pair + :param pair: Additional information, like the currently traded pair :return: DataFrame with sell column """ if self._sell_fun_len == 2: @@ -321,4 +321,4 @@ class IStrategy(ABC): "the current function headers!", DeprecationWarning) return self.populate_sell_trend(dataframe) # type: ignore else: - return self.populate_sell_trend(dataframe, pair) + return self.populate_sell_trend(dataframe, metadata) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 02eea312f..1c8f80ca1 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -60,10 +60,7 @@ def test_search_strategy(): def test_load_strategy(result): resolver = StrategyResolver({'strategy': 'TestStrategy'}) pair = 'ETH/BTC' - assert len(resolver.strategy.populate_indicators.__annotations__) == 3 - assert 'dataframe' in resolver.strategy.populate_indicators.__annotations__ - assert 'pair' in resolver.strategy.populate_indicators.__annotations__ - assert 'adx' in resolver.strategy.advise_indicators(result, pair=pair) + assert 'adx' in resolver.strategy.advise_indicators(result, metadata=pair) def test_load_strategy_invalid_directory(result, caplog): @@ -92,7 +89,7 @@ def test_strategy(result): config = {'strategy': 'DefaultStrategy'} resolver = StrategyResolver(config) - pair = 'ETH/BTC' + metadata = {'pair': 'ETH/BTC'} assert resolver.strategy.minimal_roi[0] == 0.04 assert config["minimal_roi"]['0'] == 0.04 @@ -102,13 +99,13 @@ def test_strategy(result): assert resolver.strategy.ticker_interval == '5m' assert config['ticker_interval'] == '5m' - df_indicators = resolver.strategy.advise_indicators(result, pair=pair) + df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata) assert 'adx' in df_indicators - dataframe = resolver.strategy.advise_buy(df_indicators, pair=pair) + dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata) assert 'buy' in dataframe.columns - dataframe = resolver.strategy.advise_sell(df_indicators, pair='ETH/BTC') + dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata) assert 'sell' in dataframe.columns @@ -196,21 +193,21 @@ def test_call_deprecated_function(result, monkeypatch): default_location = path.join(path.dirname(path.realpath(__file__))) resolver = StrategyResolver({'strategy': 'TestStrategyLegacy', 'strategy_path': default_location}) - pair = 'ETH/BTC' + metadata = {'pair': 'ETH/BTC'} # Make sure we are using a legacy function assert resolver.strategy._populate_fun_len == 2 assert resolver.strategy._buy_fun_len == 2 assert resolver.strategy._sell_fun_len == 2 - indicator_df = resolver.strategy.advise_indicators(result, pair=pair) + indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata) assert type(indicator_df) is DataFrame assert 'adx' in indicator_df.columns - buydf = resolver.strategy.advise_buy(result, pair=pair) + buydf = resolver.strategy.advise_buy(result, metadata=metadata) assert type(buydf) is DataFrame assert 'buy' in buydf.columns - selldf = resolver.strategy.advise_sell(result, pair=pair) + selldf = resolver.strategy.advise_sell(result, metadata=metadata) assert type(selldf) is DataFrame assert 'sell' in selldf diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 65e06558c..80c238d92 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -45,7 +45,7 @@ class TestStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' - def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame @@ -53,7 +53,7 @@ class TestStrategy(IStrategy): you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ @@ -215,11 +215,11 @@ class TestStrategy(IStrategy): return dataframe - def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame populated with indicators - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ @@ -232,11 +232,11 @@ class TestStrategy(IStrategy): return dataframe - def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame populated with indicators - :param pair: Pair currently analyzed + :param metadata: Additional information, like the currently traded pair :return: DataFrame with buy column """ dataframe.loc[ From 2401fa15d2103af375b0095da82623cac59ae435 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Jul 2018 21:07:21 +0200 Subject: [PATCH 53/56] Change missed calls to advise_* functions --- freqtrade/tests/optimize/test_backtesting.py | 2 +- freqtrade/tests/optimize/test_hyperopt.py | 6 +++--- freqtrade/tests/strategy/legacy_strategy.py | 15 ++++----------- freqtrade/tests/strategy/test_default_strategy.py | 8 ++++---- freqtrade/tests/strategy/test_strategy.py | 6 +++--- scripts/plot_dataframe.py | 4 ++-- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 91ea2eee1..e4177eab5 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -146,7 +146,7 @@ def _trend(signals, buy_value, sell_value): return signals -def _trend_alternate(dataframe=None, pair=None): +def _trend_alternate(dataframe=None, metadata=None): signals = dataframe low = signals['low'] n = len(low) diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 9b7d301cc..f1e7ad1d7 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -247,7 +247,7 @@ def test_populate_indicators(init_hyperopt) -> None: tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) - dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], 'UNITTEST/BTC') + dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'}) # Check if some indicators are generated. We will not test all of them assert 'adx' in dataframe @@ -259,7 +259,7 @@ def test_buy_strategy_generator(init_hyperopt) -> None: tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m') tickerlist = {'UNITTEST/BTC': tick} dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist) - dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], 'UNITTEST/BTC') + dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'}) populate_buy_trend = _HYPEROPT.buy_strategy_generator( { @@ -274,7 +274,7 @@ def test_buy_strategy_generator(init_hyperopt) -> None: 'trigger': 'bb_lower' } ) - result = populate_buy_trend(dataframe, 'UNITTEST/BTC') + result = populate_buy_trend(dataframe, {'pair': 'UNITTEST/BTC'}) # Check if some indicators are generated. We will not test all of them assert 'buy' in result assert 1 in result['buy'] diff --git a/freqtrade/tests/strategy/legacy_strategy.py b/freqtrade/tests/strategy/legacy_strategy.py index cb97bd63b..2cd13b791 100644 --- a/freqtrade/tests/strategy/legacy_strategy.py +++ b/freqtrade/tests/strategy/legacy_strategy.py @@ -13,18 +13,11 @@ import numpy # noqa # This class is a sample. Feel free to customize it. class TestStrategyLegacy(IStrategy): """ - This is a test strategy to inspire you. - More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md + This is a test strategy using the legacy function headers, which will be + removed in a future update. + Please do not use this as a template, but refer to user_data/strategy/TestStrategy.py + for a uptodate version of this template. - You can: - - Rename the class name (Do not forget to update class_name) - - Add any methods you want to build your strategy - - Add any lib you need to build your strategy - - You must keep: - - the lib in the section "Do not remove these libs" - - the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend, - populate_sell_trend, hyperopt_space, buy_strategy_generator """ # Minimal ROI designed for the strategy. diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py index 2b10e9023..6acfc439f 100644 --- a/freqtrade/tests/strategy/test_default_strategy.py +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -25,11 +25,11 @@ def test_default_strategy_structure(): def test_default_strategy(result): strategy = DefaultStrategy({}) - pair = 'ETH/BTC' + metadata = {'pair': 'ETH/BTC'} assert type(strategy.minimal_roi) is dict assert type(strategy.stoploss) is float assert type(strategy.ticker_interval) is str - indicators = strategy.populate_indicators(result, pair) + indicators = strategy.populate_indicators(result, metadata) assert type(indicators) is DataFrame - assert type(strategy.populate_buy_trend(indicators, pair)) is DataFrame - assert type(strategy.populate_sell_trend(indicators, pair)) is DataFrame + assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame + assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 1c8f80ca1..6bb17fc28 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -59,8 +59,8 @@ def test_search_strategy(): def test_load_strategy(result): resolver = StrategyResolver({'strategy': 'TestStrategy'}) - pair = 'ETH/BTC' - assert 'adx' in resolver.strategy.advise_indicators(result, metadata=pair) + metadata = {'pair': 'ETH/BTC'} + assert 'adx' in resolver.strategy.advise_indicators(result, metadata=metadata) def test_load_strategy_invalid_directory(result, caplog): @@ -74,7 +74,7 @@ def test_load_strategy_invalid_directory(result, caplog): 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples - assert 'adx' in resolver.strategy.advise_indicators(result, 'ETH/BTC') + assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) def test_load_not_found_strategy(): diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 06e1cd1d8..fbb385a3c 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -159,8 +159,8 @@ def plot_analyzed_dataframe(args: Namespace) -> None: dataframes = strategy.tickerdata_to_dataframe(tickers) dataframe = dataframes[pair] - dataframe = strategy.advise_buy(dataframe, pair) - dataframe = strategy.advise_sell(dataframe, pair) + dataframe = strategy.advise_buy(dataframe, {'pair': pair}) + dataframe = strategy.advise_sell(dataframe, {'pair': pair}) if len(dataframe.index) > args.plot_limit: logger.warning('Ticker contained more than %s candles as defined ' From 3ecc502d863eb9798a5111258657a8ef04e4ee64 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 30 Jul 2018 14:24:06 +0200 Subject: [PATCH 54/56] Update ccxt from 1.17.45 to 1.17.49 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3a00111ac..964da51e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.45 +ccxt==1.17.49 SQLAlchemy==1.2.10 python-telegram-bot==10.1.0 arrow==0.12.1 From 012fe94333d3b2187ab06fb85e912b6f0b1064a2 Mon Sep 17 00:00:00 2001 From: creslinux Date: Mon, 30 Jul 2018 16:49:58 +0000 Subject: [PATCH 55/56] Recommitted as new branch with unit tests - GIT screwd me on the last PR --- freqtrade/exchange/__init__.py | 31 +++++++++++++++ freqtrade/tests/exchange/test_exchange.py | 48 ++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 972ff49ca..0f89eb660 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -4,6 +4,7 @@ import logging from random import randint from typing import List, Dict, Any, Optional from datetime import datetime +from math import floor, ceil import ccxt import arrow @@ -150,6 +151,28 @@ class Exchange(object): """ return endpoint in self._api.has and self._api.has[endpoint] + def symbol_amount_prec(self, pair, amount: float): + ''' + Returns the amount to buy or sell to a precision the Exchange accepts + Rounded down + ''' + if self._api.markets[pair]['precision']['amount']: + symbol_prec = self._api.markets[pair]['precision']['amount'] + big_amount = amount * pow(10, symbol_prec) + amount = floor(big_amount) / pow(10, symbol_prec) + return amount + + def symbol_price_prec(self, pair, price: float): + ''' + Returns the price buying or selling with to the precision the Exchange accepts + Rounds up + ''' + if self._api.markets[pair]['precision']['price']: + symbol_prec = self._api.markets[pair]['precision']['price'] + big_price = price * pow(10, symbol_prec) + price = ceil(big_price) / pow(10, symbol_prec) + return price + def buy(self, pair: str, rate: float, amount: float) -> Dict: if self._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' @@ -167,6 +190,10 @@ class Exchange(object): return {'id': order_id} try: + # Set the precision for amount and price(rate) as accepted by the exchange + amount = self.symbol_amount_prec(pair, amount) + rate = self.symbol_price_prec(pair, rate) + return self._api.create_limit_buy_order(pair, amount, rate) except ccxt.InsufficientFunds as e: raise DependencyException( @@ -200,6 +227,10 @@ class Exchange(object): return {'id': order_id} try: + # Set the precision for amount and price(rate) as accepted by the exchange + amount = self.symbol_amount_prec(pair, amount) + rate = self.symbol_price_prec(pair, rate) + return self._api.create_limit_sell_order(pair, amount, rate) except ccxt.InsufficientFunds as e: raise DependencyException( diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 814f56acc..7bfbb68ce 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -52,6 +52,52 @@ def test_init_exception(default_conf, mocker): Exchange(default_conf) +def test_symbol_amount_prec(default_conf, mocker): + ''' + Test rounds down to 4 Decimal places + ''' + api_mock = MagicMock() + api_mock.load_markets = MagicMock(return_value={ + 'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': '' + }) + mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='binance')) + + markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 4}}}) + type(api_mock).markets = markets + + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + exchange = Exchange(default_conf) + + amount = 2.34559 + pair = 'ETH/BTC' + amount = exchange.symbol_amount_prec(pair, amount) + assert amount == 2.3455 + + +def test_symbol_price_prec(default_conf, mocker): + ''' + Test rounds up to 4 decimal places + ''' + api_mock = MagicMock() + api_mock.load_markets = MagicMock(return_value={ + 'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': '' + }) + mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='binance')) + + markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 4}}}) + type(api_mock).markets = markets + + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + exchange = Exchange(default_conf) + + price = 2.34559 + pair = 'ETH/BTC' + price = exchange.symbol_price_prec(pair, price) + assert price == 2.3456 + + def test_validate_pairs(default_conf, mocker): api_mock = MagicMock() api_mock.load_markets = MagicMock(return_value={ @@ -173,7 +219,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): Exchange(default_conf) -def test_exchange_has(default_conf, mocker): +def test_exchangehas(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) assert not exchange.exchange_has('ASDFASDF') api_mock = MagicMock() From fe27ca63b4ba7a9f1695d95aba77f2afcdc4e131 Mon Sep 17 00:00:00 2001 From: creslin <34645187+creslinux@users.noreply.github.com> Date: Mon, 30 Jul 2018 17:08:33 +0000 Subject: [PATCH 56/56] Update test_exchange.py --- 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 7bfbb68ce..4acd5c6b2 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -219,7 +219,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker): Exchange(default_conf) -def test_exchangehas(default_conf, mocker): +def test_exchange_has(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) assert not exchange.exchange_has('ASDFASDF') api_mock = MagicMock()