From 00406ea7d5af15e994b2468f73419c9d261e7975 Mon Sep 17 00:00:00 2001 From: GluTbl <30377623+GluTbl@users.noreply.github.com> Date: Tue, 19 Oct 2021 17:15:45 +0530 Subject: [PATCH 1/5] Update backtesting.py Support for custom entry-prices and exit-prices during backtesting. --- freqtrade/optimize/backtesting.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 8328d61d3..59bfd4dd0 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -360,6 +360,13 @@ class Backtesting: trade.sell_reason = sell.sell_reason trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) + # call the custom exit price,with default value as previous closerate + current_profit = trade.calc_profit_ratio(closerate) + closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, + default_retval=closerate)( + pair=trade.pair, trade=trade, + current_time=sell_row[DATE_IDX], + proposed_rate=closerate, current_profit=current_profit) # Confirm trade exit: time_in_force = self.strategy.order_time_in_force['sell'] @@ -407,13 +414,18 @@ class Backtesting: stake_amount = self.wallets.get_trade_stake_amount(pair, None) except DependencyException: return None + # let's call the custom entry price, using the open price as default price + propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price, + default_retval=row[OPEN_IDX])( + pair=pair, current_time=row[DATE_IDX].to_pydatetime(), + proposed_rate=row[OPEN_IDX]) # default value is the open rate - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, row[OPEN_IDX], -0.05) or 0 + min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 max_stake_amount = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( - pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], + pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=propose_rate, proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount) stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount) @@ -424,7 +436,7 @@ class Backtesting: time_in_force = self.strategy.order_time_in_force['sell'] # Confirm trade entry: if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( - pair=pair, order_type=order_type, amount=stake_amount, rate=row[OPEN_IDX], + pair=pair, order_type=order_type, amount=stake_amount, rate=propose_rate, time_in_force=time_in_force, current_time=row[DATE_IDX].to_pydatetime()): return None @@ -433,10 +445,10 @@ class Backtesting: has_buy_tag = len(row) >= BUY_TAG_IDX + 1 trade = LocalTrade( pair=pair, - open_rate=row[OPEN_IDX], + open_rate=propose_rate, open_date=row[DATE_IDX].to_pydatetime(), stake_amount=stake_amount, - amount=round(stake_amount / row[OPEN_IDX], 8), + amount=round(stake_amount / propose_rate, 8), fee_open=self.fee, fee_close=self.fee, is_open=True, From 86910b58dcae064dfb717fa27f058d71b0f83df4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 3 Dec 2021 17:44:53 +0100 Subject: [PATCH 2/5] Bracket entry/exit prices to low/high of the candle --- docs/bot-basics.md | 4 ++++ docs/strategy-callbacks.md | 5 +++-- freqtrade/optimize/backtesting.py | 11 +++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 80443a0bf..67cc5cd1b 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -56,7 +56,11 @@ This loop will be repeated again and again until the bot is stopped. * Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair). * Loops per candle simulating entry and exit points. * Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy). + * Call `custom_entry_price()` (if implemented in the strategy) to determine entry price (Prices are moved to be within the opening candle). + * Determine stake size by calling the `custom_stake_amount()` callback. * Call `custom_stoploss()` and `custom_sell()` to find custom exit points. + * Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). + * Generate backtest report output !!! Note diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 7a7756652..869a985ae 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -387,8 +387,9 @@ class AwesomeStrategy(IStrategy): **Example**: If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98, which is 2% below the current (proposed) rate. -!!! Warning "No backtesting support" - Custom entry-prices are currently not supported during backtesting. +!!! Warning "Backtesting" + While Custom prices are supported in backtesting (starting with 2021.12), prices will be moved to within the candle's high/low prices. + This behavior is currently in testing, and might be changed at a later point. ## Custom order timeout rules diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 1d28a91f5..a561502fa 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -342,10 +342,7 @@ class Backtesting: # use Open rate if open_rate > calculated sell rate return sell_row[OPEN_IDX] - # 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 min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) + return close_rate else: # This should not be reached... @@ -373,6 +370,9 @@ class Backtesting: pair=trade.pair, trade=trade, current_time=sell_row[DATE_IDX], proposed_rate=closerate, current_profit=current_profit) + # 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. + closerate = min(max(closerate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) # Confirm trade exit: time_in_force = self.strategy.order_time_in_force['sell'] @@ -437,6 +437,9 @@ class Backtesting: pair=pair, current_time=row[DATE_IDX].to_pydatetime(), proposed_rate=row[OPEN_IDX]) # default value is the open rate + # Move rate to within the candle's low/high rate + propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX]) + min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 max_stake_amount = self.wallets.get_available_stake_amount() From 84ad17628724ea38be94705461d50f57bda85289 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Dec 2021 13:33:38 +0100 Subject: [PATCH 3/5] Improve documentation wording --- docs/strategy-callbacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 869a985ae..895e50425 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -389,7 +389,7 @@ class AwesomeStrategy(IStrategy): !!! Warning "Backtesting" While Custom prices are supported in backtesting (starting with 2021.12), prices will be moved to within the candle's high/low prices. - This behavior is currently in testing, and might be changed at a later point. + This behavior is currently being tested, and might be changed at a later point. ## Custom order timeout rules From 68ac8008ecf2eae00a01d6ee2e21f8703fb9606a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Dec 2021 14:14:22 +0100 Subject: [PATCH 4/5] Call custom_exit_price only for sell_signal and custom_sell --- docs/bot-basics.md | 2 +- docs/strategy-callbacks.md | 1 + freqtrade/optimize/backtesting.py | 12 +++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 67cc5cd1b..0b9f7b67c 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -59,7 +59,7 @@ This loop will be repeated again and again until the bot is stopped. * Call `custom_entry_price()` (if implemented in the strategy) to determine entry price (Prices are moved to be within the opening candle). * Determine stake size by calling the `custom_stake_amount()` callback. * Call `custom_stoploss()` and `custom_sell()` to find custom exit points. - * Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). + * For sells based on sell-signal and custom-sell: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). * Generate backtest report output diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 895e50425..11032433d 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -390,6 +390,7 @@ class AwesomeStrategy(IStrategy): !!! Warning "Backtesting" While Custom prices are supported in backtesting (starting with 2021.12), prices will be moved to within the candle's high/low prices. This behavior is currently being tested, and might be changed at a later point. + `custom_exit_price()` is only called for sells of type Sell_signal and Custom sell. All other sell-types will use regular backtesting prices. ## Custom order timeout rules diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a561502fa..55461b3e1 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -365,11 +365,13 @@ class Backtesting: closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) # call the custom exit price,with default value as previous closerate current_profit = trade.calc_profit_ratio(closerate) - closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, - default_retval=closerate)( - pair=trade.pair, trade=trade, - current_time=sell_row[DATE_IDX], - proposed_rate=closerate, current_profit=current_profit) + if sell.sell_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL): + # Custom exit pricing only for sell-signals + closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, + default_retval=closerate)( + pair=trade.pair, trade=trade, + current_time=sell_row[DATE_IDX], + proposed_rate=closerate, current_profit=current_profit) # 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. closerate = min(max(closerate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) From c981cc335d0c69a654628a490a8a02e299700b6a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Dec 2021 14:51:55 +0100 Subject: [PATCH 5/5] Remove wrong comment --- freqtrade/optimize/backtesting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 55461b3e1..d4b51d04d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -373,7 +373,6 @@ class Backtesting: current_time=sell_row[DATE_IDX], proposed_rate=closerate, current_profit=current_profit) # 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. closerate = min(max(closerate, sell_row[LOW_IDX]), sell_row[HIGH_IDX]) # Confirm trade exit: