Initial backtesting support. This does make it rather slow.
This commit is contained in:
parent
00366c5c88
commit
b2c2852f86
@ -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
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user