Merge pull request #5756 from GluTbl/patch-1
add custom entry/exit price support to backtesting
This commit is contained in:
commit
210202a797
@ -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).
|
* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair).
|
||||||
* Loops per candle simulating entry and exit points.
|
* 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).
|
* 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_stoploss()` and `custom_sell()` to find custom exit points.
|
||||||
|
* 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
|
* Generate backtest report output
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
|
@ -387,8 +387,10 @@ class AwesomeStrategy(IStrategy):
|
|||||||
**Example**:
|
**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.
|
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"
|
!!! Warning "Backtesting"
|
||||||
Custom entry-prices are currently not supported during 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
|
## Custom order timeout rules
|
||||||
|
|
||||||
|
@ -342,10 +342,7 @@ class Backtesting:
|
|||||||
# use Open rate if open_rate > calculated sell rate
|
# use Open rate if open_rate > calculated sell rate
|
||||||
return sell_row[OPEN_IDX]
|
return sell_row[OPEN_IDX]
|
||||||
|
|
||||||
# Use the maximum between close_rate and low as we
|
return close_rate
|
||||||
# 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])
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# This should not be reached...
|
# This should not be reached...
|
||||||
@ -366,6 +363,17 @@ class Backtesting:
|
|||||||
|
|
||||||
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
|
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)
|
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)
|
||||||
|
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.
|
||||||
|
closerate = min(max(closerate, sell_row[LOW_IDX]), sell_row[HIGH_IDX])
|
||||||
|
|
||||||
# Confirm trade exit:
|
# Confirm trade exit:
|
||||||
time_in_force = self.strategy.order_time_in_force['sell']
|
time_in_force = self.strategy.order_time_in_force['sell']
|
||||||
@ -424,13 +432,21 @@ class Backtesting:
|
|||||||
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
|
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
|
||||||
except DependencyException:
|
except DependencyException:
|
||||||
return None
|
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
|
# 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()
|
max_stake_amount = self.wallets.get_available_stake_amount()
|
||||||
|
|
||||||
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
|
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
|
||||||
default_retval=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)
|
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)
|
stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount)
|
||||||
|
|
||||||
@ -441,7 +457,7 @@ class Backtesting:
|
|||||||
time_in_force = self.strategy.order_time_in_force['sell']
|
time_in_force = self.strategy.order_time_in_force['sell']
|
||||||
# Confirm trade entry:
|
# Confirm trade entry:
|
||||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
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()):
|
time_in_force=time_in_force, current_time=row[DATE_IDX].to_pydatetime()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -450,10 +466,10 @@ class Backtesting:
|
|||||||
has_buy_tag = len(row) >= BUY_TAG_IDX + 1
|
has_buy_tag = len(row) >= BUY_TAG_IDX + 1
|
||||||
trade = LocalTrade(
|
trade = LocalTrade(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
open_rate=row[OPEN_IDX],
|
open_rate=propose_rate,
|
||||||
open_date=row[DATE_IDX].to_pydatetime(),
|
open_date=row[DATE_IDX].to_pydatetime(),
|
||||||
stake_amount=stake_amount,
|
stake_amount=stake_amount,
|
||||||
amount=round(stake_amount / row[OPEN_IDX], 8),
|
amount=round(stake_amount / propose_rate, 8),
|
||||||
fee_open=self.fee,
|
fee_open=self.fee,
|
||||||
fee_close=self.fee,
|
fee_close=self.fee,
|
||||||
is_open=True,
|
is_open=True,
|
||||||
|
Loading…
Reference in New Issue
Block a user