Initial backtesting support. This does make it rather slow.

This commit is contained in:
Reigo Reinmets 2021-12-09 23:21:35 +02:00
parent 00366c5c88
commit b2c2852f86
2 changed files with 76 additions and 5 deletions

View File

@ -24,7 +24,7 @@ from freqtrade.mixins import LoggingMixin
from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.bt_progress import BTProgress
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results,
store_backtest_stats) store_backtest_stats)
from freqtrade.persistence import LocalTrade, PairLocks, Trade from freqtrade.persistence import LocalTrade, PairLocks, Trade, Order
from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
@ -350,8 +350,64 @@ class Backtesting:
else: else:
return sell_row[OPEN_IDX] return sell_row[OPEN_IDX]
def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:
current_rate = sell_row[OPEN_IDX]
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
current_profit = trade.calc_profit_ratio(current_rate)
amount_to_adjust = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)(
pair=trade.pair, trade=trade, current_time=sell_candle_time,
current_rate=current_rate, current_profit=current_profit)
# Check if we should increase our position
if amount_to_adjust is not None and amount_to_adjust > 0.0:
return self._execute_trade_position_change(trade, sell_row, amount_to_adjust)
return trade
def _execute_trade_position_change(self, trade: LocalTrade, row: Tuple,
amount_to_adjust: float) -> Optional[LocalTrade]:
current_price = row[OPEN_IDX]
stake_amount = current_price * amount_to_adjust
propose_rate = min(max(current_price, row[LOW_IDX]), row[HIGH_IDX])
available_amount = self.wallets.get_available_stake_amount()
try:
min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, propose_rate, -0.05) or 0
stake_amount = self.wallets.validate_stake_amount(trade.pair, stake_amount, min_stake_amount)
stake_amount = self.wallets._check_available_stake_amount(stake_amount, available_amount)
except DependencyException:
logger.debug(f"{trade.pair} adjustment failed, wallet is smaller than asked stake {stake_amount}")
return trade
amount = stake_amount / current_price
if amount <= 0:
logger.debug(f"{trade.pair} adjustment failed, amount ended up being zero {amount}")
return trade
order = Order(
ft_is_open=False,
ft_pair=trade.pair,
symbol=trade.pair,
ft_order_side="buy",
side="buy",
order_type="market",
status="closed",
price=propose_rate,
average=propose_rate,
amount=amount,
cost=stake_amount
)
trade.orders.append(order)
trade.recalc_trade_from_orders()
self.wallets.update();
return trade
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]:
trade = self._get_adjust_trade_entry_for_candle(trade, sell_row)
sell_candle_time = sell_row[DATE_IDX].to_pydatetime() sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
sell_candle_time, sell_row[BUY_IDX], sell_candle_time, sell_row[BUY_IDX],
@ -476,6 +532,21 @@ class Backtesting:
buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None, buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None,
exchange='backtesting', exchange='backtesting',
) )
order = Order(
ft_is_open=False,
ft_pair=trade.pair,
symbol=trade.pair,
ft_order_side="buy",
side="buy",
order_type="market",
status="closed",
price=trade.open_rate,
average=trade.open_rate,
amount=trade.amount,
cost=trade.stake_amount + trade.fee_open
)
trade.orders = []
trade.orders.append(order)
return trade return trade
return None return None

View File

@ -1475,8 +1475,8 @@ def test_recalc_trade_from_orders(fee):
assert trade.amount == o1_amount + o2_amount + o3_amount assert trade.amount == o1_amount + o2_amount + o3_amount
assert trade.stake_amount == o1_cost + o2_cost + o3_cost assert trade.stake_amount == o1_cost + o2_cost + o3_cost
assert trade.open_rate == avg_price assert trade.open_rate == avg_price
assert round(trade.fee_open_cost, 8) == round(o1_fee_cost + o2_fee_cost + o3_fee_cost, 8) assert pytest.approx(trade.fee_open_cost) == o1_fee_cost + o2_fee_cost + o3_fee_cost
assert round(trade.open_trade_value, 8) == round(o1_trade_val + o2_trade_val + o3_trade_val, 8) assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val
# Just to make sure sell orders are ignored, let's calculate one more time. # Just to make sure sell orders are ignored, let's calculate one more time.
sell1 = Order( sell1 = Order(
@ -1503,8 +1503,8 @@ def test_recalc_trade_from_orders(fee):
assert trade.amount == o1_amount + o2_amount + o3_amount assert trade.amount == o1_amount + o2_amount + o3_amount
assert trade.stake_amount == o1_cost + o2_cost + o3_cost assert trade.stake_amount == o1_cost + o2_cost + o3_cost
assert trade.open_rate == avg_price assert trade.open_rate == avg_price
assert round(trade.fee_open_cost, 8) == round(o1_fee_cost + o2_fee_cost + o3_fee_cost, 8) assert pytest.approx(trade.fee_open_cost) == o1_fee_cost + o2_fee_cost + o3_fee_cost
assert round(trade.open_trade_value, 8) == round(o1_trade_val + o2_trade_val + o3_trade_val, 8) assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val
def test_recalc_trade_from_orders_ignores_bad_orders(fee): def test_recalc_trade_from_orders_ignores_bad_orders(fee):