Support funding-fees while running backtest
This commit is contained in:
		| @@ -154,6 +154,7 @@ class Backtesting: | |||||||
|         else: |         else: | ||||||
|             self.timeframe_detail_min = 0 |             self.timeframe_detail_min = 0 | ||||||
|         self.detail_data: Dict[str, DataFrame] = {} |         self.detail_data: Dict[str, DataFrame] = {} | ||||||
|  |         self.futures_data: Dict[CandleType, Dict[str, DataFrame]] = {} | ||||||
|  |  | ||||||
|     def init_backtest(self): |     def init_backtest(self): | ||||||
|  |  | ||||||
| @@ -233,6 +234,33 @@ class Backtesting: | |||||||
|             ) |             ) | ||||||
|         else: |         else: | ||||||
|             self.detail_data = {} |             self.detail_data = {} | ||||||
|  |         if self.trading_mode == TradingMode.FUTURES: | ||||||
|  |             # Load additional futures data. | ||||||
|  |             self.futures_data[CandleType.FUNDING_RATE] = history.load_data( | ||||||
|  |                 datadir=self.config['datadir'], | ||||||
|  |                 pairs=self.pairlists.whitelist, | ||||||
|  |                 timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'], | ||||||
|  |                 timerange=self.timerange, | ||||||
|  |                 startup_candles=0, | ||||||
|  |                 fail_without_data=True, | ||||||
|  |                 data_format=self.config.get('dataformat_ohlcv', 'json'), | ||||||
|  |                 candle_type=CandleType.FUNDING_RATE | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             # For simplicity, assign to CandleType.Mark (might contian index candles!) | ||||||
|  |             self.futures_data[CandleType.MARK] = history.load_data( | ||||||
|  |                 datadir=self.config['datadir'], | ||||||
|  |                 pairs=self.pairlists.whitelist, | ||||||
|  |                 timeframe=self.exchange._ft_has['mark_ohlcv_timeframe'], | ||||||
|  |                 timerange=self.timerange, | ||||||
|  |                 startup_candles=0, | ||||||
|  |                 fail_without_data=True, | ||||||
|  |                 data_format=self.config.get('dataformat_ohlcv', 'json'), | ||||||
|  |                 candle_type=CandleType.from_string(self.exchange._ft_has["mark_ohlcv_price"]) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             self.futures_data = {} | ||||||
|  |  | ||||||
|     def prepare_backtest(self, enable_protections): |     def prepare_backtest(self, enable_protections): | ||||||
|         """ |         """ | ||||||
| @@ -399,15 +427,12 @@ class Backtesting: | |||||||
|  |  | ||||||
|     def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, |     def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, | ||||||
|                                          sell_row: Tuple) -> Optional[LocalTrade]: |                                          sell_row: Tuple) -> Optional[LocalTrade]: | ||||||
|         # TODO-lev: add interest / funding fees to trade object -> |  | ||||||
|         # Must be done either here, or one level higher -> |  | ||||||
|         # (if we don't want to do it at "detail" level) |  | ||||||
|  |  | ||||||
|         # Check if we need to adjust our current positions |         # Check if we need to adjust our current positions | ||||||
|         if self.strategy.position_adjustment_enable: |         if self.strategy.position_adjustment_enable: | ||||||
|             trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) |             trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) | ||||||
|  |  | ||||||
|         sell_candle_time = sell_row[DATE_IDX].to_pydatetime() |         sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime() | ||||||
|         enter = sell_row[SHORT_IDX] if trade.is_short else sell_row[LONG_IDX] |         enter = sell_row[SHORT_IDX] if trade.is_short else sell_row[LONG_IDX] | ||||||
|         exit_ = sell_row[ESHORT_IDX] if trade.is_short else sell_row[ELONG_IDX] |         exit_ = sell_row[ESHORT_IDX] if trade.is_short else sell_row[ELONG_IDX] | ||||||
|         sell = self.strategy.should_exit( |         sell = self.strategy.should_exit( | ||||||
| @@ -460,8 +485,18 @@ class Backtesting: | |||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: |     def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: | ||||||
|  |         sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime() | ||||||
|  |  | ||||||
|  |         if self.trading_mode == TradingMode.FUTURES: | ||||||
|  |             trade.funding_fees = self.exchange._calculate_funding_fees( | ||||||
|  |                 funding_rates=self.futures_data[CandleType.FUNDING_RATE][trade.pair], | ||||||
|  |                 mark_rates=self.futures_data[CandleType.MARK][trade.pair], | ||||||
|  |                 amount=trade.amount, | ||||||
|  |                 open_date=trade.open_date_utc, | ||||||
|  |                 close_date=sell_candle_time, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|         if self.timeframe_detail and trade.pair in self.detail_data: |         if self.timeframe_detail and trade.pair in self.detail_data: | ||||||
|             sell_candle_time = sell_row[DATE_IDX].to_pydatetime() |  | ||||||
|             sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min) |             sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min) | ||||||
|  |  | ||||||
|             detail_data = self.detail_data[trade.pair] |             detail_data = self.detail_data[trade.pair] | ||||||
| @@ -549,7 +584,7 @@ class Backtesting: | |||||||
|                 return None |                 return None | ||||||
|  |  | ||||||
|         if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): |         if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): | ||||||
|             amount = round(stake_amount / propose_rate, 8) |             amount = round((stake_amount / propose_rate) * leverage, 8) | ||||||
|             if trade is None: |             if trade is None: | ||||||
|                 # Enter trade |                 # Enter trade | ||||||
|                 has_buy_tag = len(row) >= ENTER_TAG_IDX + 1 |                 has_buy_tag = len(row) >= ENTER_TAG_IDX + 1 | ||||||
| @@ -558,13 +593,14 @@ class Backtesting: | |||||||
|                     open_rate=propose_rate, |                     open_rate=propose_rate, | ||||||
|                     open_date=current_time, |                     open_date=current_time, | ||||||
|                     stake_amount=stake_amount, |                     stake_amount=stake_amount, | ||||||
|                     amount=round((stake_amount / propose_rate) * leverage, 8), |                     amount=amount, | ||||||
|                     fee_open=self.fee, |                     fee_open=self.fee, | ||||||
|                     fee_close=self.fee, |                     fee_close=self.fee, | ||||||
|                     is_open=True, |                     is_open=True, | ||||||
|                     enter_tag=row[ENTER_TAG_IDX] if has_buy_tag else None, |                     enter_tag=row[ENTER_TAG_IDX] if has_buy_tag else None, | ||||||
|                     exchange=self._exchange_name, |                     exchange=self._exchange_name, | ||||||
|                     is_short=(direction == 'short'), |                     is_short=(direction == 'short'), | ||||||
|  |                     trading_mode=self.trading_mode, | ||||||
|                     leverage=leverage, |                     leverage=leverage, | ||||||
|                     orders=[] |                     orders=[] | ||||||
|                 ) |                 ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user