From 309186911590630cb01bdd503da840c9eca8518c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Dec 2019 14:30:14 +0100 Subject: [PATCH 01/15] refactor get_close_rate out of get_sell_trade-entry --- freqtrade/optimize/backtesting.py | 44 +++++++++++++++++-------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index d9fb1f2d1..c7d25cb7b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -261,6 +261,29 @@ class Backtesting: ticker[pair] = [x for x in ticker_data.itertuples()] return ticker + def _get_close_rate(self, sell_row, trade: Trade, sell, trade_dur) -> float: + # Special handling if high or low hit STOP_LOSS or ROI + if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): + # Set close_rate to stoploss + closerate = trade.stop_loss + elif sell.sell_type == (SellType.ROI): + roi = self.strategy.min_roi_reached_entry(trade_dur) + if roi is not None: + # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) + closerate = - (trade.open_rate * roi + trade.open_rate * + (1 + trade.fee_open)) / (trade.fee_close - 1) + + # Use the maximum between closerate and low as we + # cannot sell outside of a candle. + # Applies when using {"xx": -1} as roi to force sells after xx minutes + closerate = max(closerate, sell_row.low) + else: + # This should not be reached... + closerate = sell_row.open + else: + closerate = sell_row.open + return closerate + def _get_sell_trade_entry( self, pair: str, buy_row: DataFrame, partial_ticker: List, trade_count_lock: Dict, @@ -287,26 +310,7 @@ class Backtesting: sell_row.sell, low=sell_row.low, high=sell_row.high) if sell.sell_flag: trade_dur = int((sell_row.date - buy_row.date).total_seconds() // 60) - # Special handling if high or low hit STOP_LOSS or ROI - if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): - # Set close_rate to stoploss - closerate = trade.stop_loss - elif sell.sell_type == (SellType.ROI): - roi = self.strategy.min_roi_reached_entry(trade_dur) - if roi is not None: - # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) - closerate = - (trade.open_rate * roi + trade.open_rate * - (1 + trade.fee_open)) / (trade.fee_close - 1) - - # Use the maximum between closerate and low as we - # cannot sell outside of a candle. - # Applies when using {"xx": -1} as roi to force sells after xx minutes - closerate = max(closerate, sell_row.low) - else: - # This should not be reached... - closerate = sell_row.open - else: - closerate = sell_row.open + closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) return BacktestResult(pair=pair, profit_percent=trade.calc_profit_percent(rate=closerate), From 1e6f9f9fe20cab92711b06f929e5641991c8ea60 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Dec 2019 15:13:49 +0100 Subject: [PATCH 02/15] Add testcase for negative ROI sell using open --- tests/optimize/test_backtest_detail.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 3f6cc8c9a..e269009b1 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -265,6 +265,22 @@ tc16 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) +# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1 +# Causes negative profit even though sell-reason is ROI. +# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) +# Uses open as sell-rate (special case) - since the roi-time is a multiple of the ticker interval. +tc17 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5050, 6172, 0, 0], + [3, 4980, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] +) + TESTS = [ tc0, tc1, @@ -283,6 +299,7 @@ TESTS = [ tc14, tc15, tc16, + tc17, ] From 3163cbdf8a56f2eafefa5736945087327752ed10 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Dec 2019 15:18:12 +0100 Subject: [PATCH 03/15] Apply special case for negative ROI --- freqtrade/optimize/backtesting.py | 10 ++++++++-- freqtrade/strategy/interface.py | 8 ++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c7d25cb7b..6e7d36368 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -267,7 +267,7 @@ class Backtesting: # Set close_rate to stoploss closerate = trade.stop_loss elif sell.sell_type == (SellType.ROI): - roi = self.strategy.min_roi_reached_entry(trade_dur) + roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur) if roi is not None: # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) closerate = - (trade.open_rate * roi + trade.open_rate * @@ -275,8 +275,14 @@ class Backtesting: # Use the maximum between closerate and low as we # cannot sell outside of a candle. - # Applies when using {"xx": -1} as roi to force sells after xx minutes + # Applies when a new ROI setting comes in place and the whole candle is above that. closerate = max(closerate, sell_row.low) + if roi == -1 and roi_entry % self.timeframe_mins == 0: + # When forceselling with ROI=-1, the roi-entry will always be "on the entry". + # If that entry is a multiple of the timeframe (so on open) + # - we'll use open instead of close + closerate = max(closerate, sell_row.open) + else: # This should not be reached... closerate = sell_row.open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index e208138e7..2b3a6194f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -394,7 +394,7 @@ class IStrategy(ABC): return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) - def min_roi_reached_entry(self, trade_dur: int) -> Optional[float]: + def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: """ Based on trade duration defines the ROI entry that may have been reached. :param trade_dur: trade duration in minutes @@ -403,9 +403,9 @@ class IStrategy(ABC): # Get highest entry in ROI dict where key <= trade-duration roi_list = list(filter(lambda x: x <= trade_dur, self.minimal_roi.keys())) if not roi_list: - return None + return None, None roi_entry = max(roi_list) - return self.minimal_roi[roi_entry] + return roi_entry, self.minimal_roi[roi_entry] def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: """ @@ -415,7 +415,7 @@ class IStrategy(ABC): """ # Check if time matches and current rate is above threshold trade_dur = int((current_time.timestamp() - trade.open_date.timestamp()) // 60) - roi = self.min_roi_reached_entry(trade_dur) + _, roi = self.min_roi_reached_entry(trade_dur) if roi is None: return False else: From 189835b9635e54ce10c88ae02ba0ce4936c7749d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Dec 2019 15:26:10 +0100 Subject: [PATCH 04/15] Add documentation for ROI-1 case --- docs/backtesting.md | 5 ++++- docs/configuration.md | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 68782bb9c..017289905 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -194,7 +194,10 @@ Since backtesting lacks some detailed information about what happens within a ca - Buys happen at open-price - Sell signal sells happen at open-price of the following candle - Low happens before high for stoploss, protecting capital first. -- ROI sells are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the sell will be at 2%) +- ROI + - sells are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the sell will be at 2%) + - sells are never "below the candle", so a ROI of 2% may result in a sell at 2.4% if low was at 2.4% profit + - Forcesells caused by `=-1` ROI entries use low as sell value, unless N falls on the candle open (e.g. `120: -1` for 1h candles) - Stoploss sells happen exactly at stoploss price, even if low was lower - Trailing stoploss - High happens first - adjusting stoploss diff --git a/docs/configuration.md b/docs/configuration.md index 5ad1a886e..5bebbcfcd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -169,6 +169,9 @@ This parameter can be set in either Strategy or Configuration file. If you use i `minimal_roi` value from the strategy file. If it is not set in either Strategy or Configuration, a default of 1000% `{"0": 10}` is used, and minimal roi is disabled unless your trade generates 1000% profit. +!!! Note "Special case to forcesell after a specific time" + A special case presents using `"": -1` as ROI. This forces the bot to sell a trade after N Minutes, no matter if it's positive or negative, so represents a time-limited force-sell. + ### Understand stoploss Go to the [stoploss documentation](stoploss.md) for more details. From 45d12dbc83fd6a24f1d7b28bcf6bf5022053d454 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 7 Dec 2019 15:28:56 +0100 Subject: [PATCH 05/15] Avoid a few calculations during backtesting --- freqtrade/optimize/backtesting.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6e7d36368..7b5bd34b4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -262,13 +262,22 @@ class Backtesting: return ticker def _get_close_rate(self, sell_row, trade: Trade, sell, trade_dur) -> float: + """ + Get close rate for backtesting result + """ # Special handling if high or low hit STOP_LOSS or ROI if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): # Set close_rate to stoploss - closerate = trade.stop_loss + return trade.stop_loss elif sell.sell_type == (SellType.ROI): roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur) if roi is not None: + if roi == -1 and roi_entry % self.timeframe_mins == 0: + # When forceselling with ROI=-1, the roi-entry will always be "on the entry". + # If that entry is a multiple of the timeframe (so on open) + # - we'll use open instead of close + return sell_row.open + # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) closerate = - (trade.open_rate * roi + trade.open_rate * (1 + trade.fee_open)) / (trade.fee_close - 1) @@ -276,19 +285,13 @@ class Backtesting: # Use the maximum between closerate and low as we # cannot sell outside of a candle. # Applies when a new ROI setting comes in place and the whole candle is above that. - closerate = max(closerate, sell_row.low) - if roi == -1 and roi_entry % self.timeframe_mins == 0: - # When forceselling with ROI=-1, the roi-entry will always be "on the entry". - # If that entry is a multiple of the timeframe (so on open) - # - we'll use open instead of close - closerate = max(closerate, sell_row.open) + return max(closerate, sell_row.low) else: # This should not be reached... - closerate = sell_row.open + return sell_row.open else: - closerate = sell_row.open - return closerate + return sell_row.open def _get_sell_trade_entry( self, pair: str, buy_row: DataFrame, From de33ec425086e1aacc093b6cea0694ca549db5cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 9 Dec 2019 16:52:12 +0100 Subject: [PATCH 06/15] use sell_row.open also when the active ROI value just changed --- freqtrade/optimize/backtesting.py | 19 ++++++---- tests/optimize/test_backtest_detail.py | 50 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7b5bd34b4..4e86acdc6 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -273,19 +273,26 @@ class Backtesting: roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur) if roi is not None: if roi == -1 and roi_entry % self.timeframe_mins == 0: - # When forceselling with ROI=-1, the roi-entry will always be "on the entry". - # If that entry is a multiple of the timeframe (so on open) + # When forceselling with ROI=-1, the roi time will always be equal to trade_dur. + # If that entry is a multiple of the timeframe (so on candle open) # - we'll use open instead of close return sell_row.open # - (Expected abs profit + open_rate + open_fee) / (fee_close -1) - closerate = - (trade.open_rate * roi + trade.open_rate * - (1 + trade.fee_open)) / (trade.fee_close - 1) + close_rate = - (trade.open_rate * roi + trade.open_rate * + (1 + trade.fee_open)) / (trade.fee_close - 1) - # Use the maximum between closerate and low as we + if (trade_dur > 0 and trade_dur == roi_entry + and roi_entry % self.timeframe_mins == 0 + and sell_row.open > close_rate): + # new ROI entry came into effect. + # use Open rate if open_rate > calculated sell rate + return sell_row.open + + # Use the maximum between close_rate and low as we # cannot sell outside of a candle. # Applies when a new ROI setting comes in place and the whole candle is above that. - return max(closerate, sell_row.low) + return max(close_rate, sell_row.low) else: # This should not be reached... diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index e269009b1..eff236a5e 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -281,6 +281,53 @@ tc17 = BTContainer(data=[ trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) + +# Test 18: Buy, hold for 120 mins, then drop ROI to 1%, causing a sell in candle 3. +# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) +# uses open_rate as sell-price +tc18 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0], + [3, 5200, 5220, 4940, 4962, 6172, 0, 0], # Sell on ROI (sells on open) + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] +) + +# Test 19: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3. +# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) +# uses calculated ROI (1%) as sell rate, otherwise identical to tc18 +tc19 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0], + [3, 5000, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] +) + +# Test 20: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3. +# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) +# uses calculated ROI (1%) as sell rate, otherwise identical to tc18 +tc20 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5025, 4975, 4987, 6172, 1, 0], + [1, 5000, 5025, 4975, 4987, 6172, 0, 0], + [2, 4987, 5300, 4950, 5200, 6172, 0, 0], + [3, 5200, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI + [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] +) + + TESTS = [ tc0, tc1, @@ -300,6 +347,9 @@ TESTS = [ tc15, tc16, tc17, + tc18, + tc19, + tc20, ] From 6e778ad710746c22c4ef96888b18946382e92010 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 12 Dec 2019 03:12:28 +0300 Subject: [PATCH 07/15] Seed hyperopt random_state if not passed --- freqtrade/optimize/hyperopt.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index ceed704c2..b4eecce2a 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -6,6 +6,7 @@ This module contains the hyperopt logic import locale import logging +import random import sys import warnings from collections import OrderedDict @@ -436,7 +437,7 @@ class Hyperopt: acq_optimizer="auto", n_initial_points=INITIAL_POINTS, acq_optimizer_kwargs={'n_jobs': cpu_count}, - random_state=self.config.get('hyperopt_random_state', None), + random_state=self.random_state, ) def fix_optimizer_models_list(self): @@ -475,7 +476,13 @@ class Hyperopt: logger.info(f"Loaded {len(trials)} previous evaluations from disk.") return trials + def _set_random_state(self, random_state: Optional[int]) -> int: + return random_state or random.randint(1, 2**32 - 1) + def start(self) -> None: + self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) + logger.info(f"Using optimizer random state: {self.random_state}") + data, timerange = self.backtesting.load_bt_data() preprocessed = self.backtesting.strategy.tickerdata_to_dataframe(data) From 3bd873f3c6e6b954711cc63d95bb2dfe70a808dc Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 13 Dec 2019 13:59:18 +0300 Subject: [PATCH 08/15] Add notes on random-state to the docs --- docs/hyperopt.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 9c9e9fdef..16bd4130b 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -6,8 +6,12 @@ algorithms included in the `scikit-optimize` package to accomplish this. The search will burn all your CPU cores, make your laptop sound like a fighter jet and still take a long time. +In general, the search for best parameters starts with a few random combinations and then uses Bayesian search with a +ML regressor algorithm (currently ExtraTreesRegressor) to quickly find a combination of parameters in the search hyperspace +that minimizes the value of the [loss function](#loss-functions). + Hyperopt requires historic data to be available, just as backtesting does. -To learn how to get data for the pairs and exchange you're interrested in, head over to the [Data Downloading](data-download.md) section of the documentation. +To learn how to get data for the pairs and exchange you're interested in, head over to the [Data Downloading](data-download.md) section of the documentation. !!! Bug Hyperopt can crash when used with only 1 CPU Core as found out in [Issue #1133](https://github.com/freqtrade/freqtrade/issues/1133) @@ -170,10 +174,6 @@ with different value combinations. It will then use the given historical data an buys based on the buy signals generated with the above function and based on the results it will end with telling you which paramter combination produced the best profits. -The search for best parameters starts with a few random combinations and then uses a -regressor algorithm (currently ExtraTreesRegressor) to quickly find a parameter combination -that minimizes the value of the [loss function](#loss-functions). - The above setup expects to find ADX, RSI and Bollinger Bands in the populated indicators. When you want to test an indicator that isn't used by the bot currently, remember to add it to the `populate_indicators()` method in your custom hyperopt file. @@ -284,6 +284,16 @@ number). You can also enable position stacking in the configuration file by explicitly setting `"position_stacking"=true`. +### Reproducible results + +The search for optimal parameters starts with a few (currently 30) random combinations in the hyperspace of parameters, random Hyperopt epochs. These random epochs are marked with a leading asterisk sign at the Hyperop output. + +The initial state for generation of these random values (random state) is controlled by the value of the `--random-state` command line option. You can set it to some arbitrary value of your choice to obtain reproducible results. + +If you have not set this value explicitly in the command line options, Hyperopt seeds the random state with some random value for you. The random state value for each Hyperopt run is shown in the log, so you can copy and paste it into the `--random-state` command line option to repeat the set of the initial random epochs used. + +If you have not changed anything in the command line options, configuration, timerange, Strategy and Hyperopt classes, historical data and the Loss Function -- you should obtain same hyperoptimization results with same random state value used. + ## Understand the Hyperopt Result Once Hyperopt is completed you can use the result to create a new strategy. From 014c18ead2d7a2847a74fa1880a5ba9fac405c40 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Dec 2019 20:27:06 +0100 Subject: [PATCH 09/15] Improve output from show_config when trailing_stop is active --- freqtrade/rpc/telegram.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 51736968b..2e736f11a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -587,12 +587,23 @@ class Telegram(RPC): :return: None """ val = self._rpc_show_config() + if val['trailing_stop']: + sl_info = ( + f"*Initial Stoploss:* `{val['stoploss']}`\n" + f"*Trailing stop positive:* `{val['trailing_stop_positive']}`\n" + f"*Trailing stop offset:* `{val['trailing_stop_positive_offset']}`\n" + f"*Only trail above offset:* `{val['trailing_only_offset_is_reached']}`\n" + ) + + else: + sl_info = f"*Stoploss:* `{val['stoploss']}`\n" + self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Exchange:* `{val['exchange']}`\n" f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n" f"*Minimum ROI:* `{val['minimal_roi']}`\n" - f"*{'Trailing ' if val['trailing_stop'] else ''}Stoploss:* `{val['stoploss']}`\n" + f"{sl_info}" f"*Ticker Interval:* `{val['ticker_interval']}`\n" f"*Strategy:* `{val['strategy']}`'" ) From e4cc5c479fe73888a01b77fd4da310b36482ca2b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Dec 2019 20:27:39 +0100 Subject: [PATCH 10/15] Test new show_config branch --- tests/rpc/test_rpc_telegram.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 7c4a8f0d6..2ba1ccf4b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1177,6 +1177,16 @@ def test_show_config_handle(default_conf, update, mocker) -> None: assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0] assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0] + assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] + + msg_mock.reset_mock() + freqtradebot.config['trailing_stop'] = True + telegram._show_config(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] + assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0] + assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0] + assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] def test_send_msg_buy_notification(default_conf, mocker) -> None: From a48c0ad8680a591f48ddb582a740fa3d3acfdcb8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Dec 2019 12:52:33 +0100 Subject: [PATCH 11/15] Use first pair of pairlist to get fee Use this instead of hardcoded ETH/BTC - so backtesting works with exchanges without ETH/BTC pair --- freqtrade/edge/__init__.py | 2 +- freqtrade/optimize/backtesting.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index afd20cf61..4bc3023a4 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -80,7 +80,7 @@ class Edge: if config.get('fee'): self.fee = config['fee'] else: - self.fee = self.exchange.get_fee() + self.fee = self.exchange.get_fee(symbol=self.config['exchange']['pair_whitelist'][0]) def calculate(self) -> bool: pairs = self.config['exchange']['pair_whitelist'] diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 637115dfd..7ef38331e 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -65,7 +65,7 @@ class Backtesting: if config.get('fee'): self.fee = config['fee'] else: - self.fee = self.exchange.get_fee() + self.fee = self.exchange.get_fee(symbol=self.config['exchange']['pair_whitelist'][0]) if self.config.get('runmode') != RunMode.HYPEROPT: self.dataprovider = DataProvider(self.config, self.exchange) From 82ff878e3868c8f08666a8fa08b4b12474184718 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sat, 14 Dec 2019 15:15:20 +0300 Subject: [PATCH 12/15] Fix typo in the docs --- docs/hyperopt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 16bd4130b..f399fe816 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -286,7 +286,7 @@ You can also enable position stacking in the configuration file by explicitly se ### Reproducible results -The search for optimal parameters starts with a few (currently 30) random combinations in the hyperspace of parameters, random Hyperopt epochs. These random epochs are marked with a leading asterisk sign at the Hyperop output. +The search for optimal parameters starts with a few (currently 30) random combinations in the hyperspace of parameters, random Hyperopt epochs. These random epochs are marked with a leading asterisk sign at the Hyperopt output. The initial state for generation of these random values (random state) is controlled by the value of the `--random-state` command line option. You can set it to some arbitrary value of your choice to obtain reproducible results. From f2266ea9f42184e39597c63d3a64b3cd3b0a97f2 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Sat, 14 Dec 2019 15:17:45 +0300 Subject: [PATCH 13/15] Use shorter range for seeded random-state --- 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 b4eecce2a..1d524acf8 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -477,7 +477,7 @@ class Hyperopt: return trials def _set_random_state(self, random_state: Optional[int]) -> int: - return random_state or random.randint(1, 2**32 - 1) + return random_state or random.randint(1, 2**16 - 1) def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) From 2275a1539ee989c9b5e2b2666e805472a5c5e8bc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Dec 2019 13:22:42 +0100 Subject: [PATCH 14/15] Remove default symbol from get_fee() --- freqtrade/exchange/exchange.py | 2 +- tests/edge/test_edge.py | 2 +- tests/exchange/test_exchange.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index fbe7cd29a..860f59fba 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -921,7 +921,7 @@ class Exchange: raise OperationalException(e) from e @retrier - def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1, + def get_fee(self, symbol, type='', side='', amount=1, price=1, taker_or_maker='maker') -> float: try: # validate that markets are loaded before trying to get fee diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index 001dc9591..bdb986d6d 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -334,7 +334,7 @@ def test_process_expectancy(mocker, edge_conf): edge_conf['edge']['min_trade_number'] = 2 freqtrade = get_patched_freqtradebot(mocker, edge_conf) - def get_fee(): + def get_fee(*args, **kwargs): return 0.001 freqtrade.exchange.get_fee = get_fee diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a21a5f3ac..82b62d5b8 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1646,10 +1646,10 @@ def test_get_fee(default_conf, mocker, exchange_name): }) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - assert exchange.get_fee() == 0.025 + assert exchange.get_fee('ETH/BTC') == 0.025 ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, - 'get_fee', 'calculate_fee') + 'get_fee', 'calculate_fee', symbol="ETH/BTC") def test_stoploss_limit_order_unsupported_exchange(default_conf, mocker): From f81c49ce6d9734852b3354964543e5793c4adb10 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Dec 2019 19:53:20 +0100 Subject: [PATCH 15/15] Fix typo causing a trailing "tic" in /show_config output --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2e736f11a..a3f88003a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -605,7 +605,7 @@ class Telegram(RPC): f"*Minimum ROI:* `{val['minimal_roi']}`\n" f"{sl_info}" f"*Ticker Interval:* `{val['ticker_interval']}`\n" - f"*Strategy:* `{val['strategy']}`'" + f"*Strategy:* `{val['strategy']}`" ) def _send_msg(self, msg: str, parse_mode: ParseMode = ParseMode.MARKDOWN) -> None: