mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-14 20:23:57 +00:00
Merge pull request #10782 from freqtrade/feat/bt_reverse
Some checks are pending
Build Documentation / Deploy Docs through mike (push) Waiting to run
Some checks are pending
Build Documentation / Deploy Docs through mike (push) Waiting to run
Enable future positions to reverse their position
This commit is contained in:
commit
67f26fa1ac
|
@ -558,6 +558,7 @@ Since backtesting lacks some detailed information about what happens within a ca
|
|||
- Stoploss
|
||||
- ROI
|
||||
- Trailing stoploss
|
||||
- Position reversals (futures only) happen if an entry signal in the other direction than the closing trade triggers at the candle the existing trade closes.
|
||||
|
||||
Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode.
|
||||
Also, keep in mind that past results don't guarantee future success.
|
||||
|
|
|
@ -2284,7 +2284,7 @@ class FreqtradeBot(LoggingMixin):
|
|||
|
||||
def handle_protections(self, pair: str, side: LongShort) -> None:
|
||||
# Lock pair for one candle to prevent immediate re-entries
|
||||
self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason="Auto lock")
|
||||
self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason="Auto lock", side=side)
|
||||
prot_trig = self.protections.stop_per_pair(pair, side=side)
|
||||
if prot_trig:
|
||||
msg: RPCProtectionMsg = {
|
||||
|
|
|
@ -1335,11 +1335,38 @@ class Backtesting:
|
|||
trade_dir: Optional[LongShort],
|
||||
can_enter: bool,
|
||||
) -> None:
|
||||
"""
|
||||
Conditionally call backtest_loop_inner a 2nd time if shorting is enabled,
|
||||
a position closed and a new signal in the other direction is available.
|
||||
"""
|
||||
if not self._can_short or trade_dir is None:
|
||||
# No need to reverse position if shorting is disabled or there's no new signal
|
||||
self.backtest_loop_inner(row, pair, current_time, trade_dir, can_enter)
|
||||
else:
|
||||
for _ in (0, 1):
|
||||
a = self.backtest_loop_inner(row, pair, current_time, trade_dir, can_enter)
|
||||
if not a or a == trade_dir:
|
||||
# the trade didn't close or position change is in the same direction
|
||||
break
|
||||
|
||||
def backtest_loop_inner(
|
||||
self,
|
||||
row: tuple,
|
||||
pair: str,
|
||||
current_time: datetime,
|
||||
trade_dir: Optional[LongShort],
|
||||
can_enter: bool,
|
||||
) -> Optional[LongShort]:
|
||||
"""
|
||||
NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized.
|
||||
|
||||
Backtesting processing for one candle/pair.
|
||||
"""
|
||||
exiting_dir: Optional[LongShort] = None
|
||||
if not self._position_stacking and len(LocalTrade.bt_trades_open_pp[pair]) > 0:
|
||||
# position_stacking not supported for now.
|
||||
exiting_dir = "short" if LocalTrade.bt_trades_open_pp[pair][0].is_short else "long"
|
||||
|
||||
for t in list(LocalTrade.bt_trades_open_pp[pair]):
|
||||
# 1. Manage currently open orders of active trades
|
||||
if self.manage_open_orders(t, current_time, row):
|
||||
|
@ -1380,6 +1407,10 @@ class Backtesting:
|
|||
if order:
|
||||
self._process_exit_order(order, trade, current_time, row, pair)
|
||||
|
||||
if exiting_dir and len(LocalTrade.bt_trades_open_pp[pair]) == 0:
|
||||
return exiting_dir
|
||||
return None
|
||||
|
||||
def time_pair_generator(
|
||||
self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: list[str]
|
||||
):
|
||||
|
|
|
@ -3483,16 +3483,17 @@ def test_locked_pairs(
|
|||
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS),
|
||||
)
|
||||
trade.close(ticker_usdt_sell_down()["bid"])
|
||||
assert freqtrade.strategy.is_pair_locked(trade.pair, side="*")
|
||||
assert not freqtrade.strategy.is_pair_locked(trade.pair, side="*")
|
||||
# Both sides are locked
|
||||
assert freqtrade.strategy.is_pair_locked(trade.pair, side="long")
|
||||
assert freqtrade.strategy.is_pair_locked(trade.pair, side="short")
|
||||
assert freqtrade.strategy.is_pair_locked(trade.pair, side="long") != is_short
|
||||
assert freqtrade.strategy.is_pair_locked(trade.pair, side="short") == is_short
|
||||
|
||||
# reinit - should buy other pair.
|
||||
caplog.clear()
|
||||
freqtrade.enter_positions()
|
||||
direction = "short" if is_short else "long"
|
||||
|
||||
assert log_has_re(rf"Pair {trade.pair} \* is locked.*", caplog)
|
||||
assert log_has_re(rf"Pair {trade.pair} {direction} is locked.*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
|
|
|
@ -1121,6 +1121,70 @@ tc53 = BTContainer(
|
|||
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2, is_short=True)],
|
||||
)
|
||||
|
||||
# Test 54: Switch position from long to short
|
||||
tc54 = BTContainer(
|
||||
data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0],
|
||||
[1, 5000, 5000, 4951, 5000, 6172, 0, 0, 0, 0],
|
||||
[2, 4910, 5150, 4910, 5100, 6172, 0, 0, 1, 0], # Enter short signal being ignored
|
||||
[3, 5100, 5100, 4950, 4950, 6172, 0, 1, 1, 0], # exit - re-enter short
|
||||
[4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 1],
|
||||
[5, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
|
||||
],
|
||||
stop_loss=-0.10,
|
||||
roi={"0": 0.10},
|
||||
profit_perc=0.00,
|
||||
use_exit_signal=True,
|
||||
trades=[
|
||||
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=False),
|
||||
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=4, close_tick=5, is_short=True),
|
||||
],
|
||||
)
|
||||
|
||||
# Test 55: Switch position from short to long
|
||||
tc55 = BTContainer(
|
||||
data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
|
||||
[1, 5000, 5000, 4951, 5000, 6172, 1, 0, 0, 0], # Enter long signal being ignored
|
||||
[2, 4910, 5150, 4910, 5100, 6172, 1, 0, 0, 1], # Exit - reenter long
|
||||
[3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
|
||||
[4, 5000, 5100, 4950, 4950, 6172, 0, 1, 0, 0],
|
||||
[5, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
|
||||
],
|
||||
stop_loss=-0.10,
|
||||
roi={"0": 0.10},
|
||||
profit_perc=-0.04,
|
||||
use_exit_signal=True,
|
||||
trades=[
|
||||
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=3, is_short=True),
|
||||
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=3, close_tick=5, is_short=False),
|
||||
],
|
||||
)
|
||||
|
||||
# Test 56: Switch position from long to short
|
||||
tc56 = BTContainer(
|
||||
data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0],
|
||||
[1, 5000, 5000, 4951, 5000, 6172, 0, 0, 0, 0],
|
||||
[2, 4910, 5150, 4910, 5100, 6172, 0, 0, 1, 0], # exit on stoploss - re-enter short
|
||||
[3, 5100, 5100, 4888, 4950, 6172, 0, 0, 0, 0],
|
||||
[4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 1],
|
||||
[5, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
|
||||
],
|
||||
stop_loss=-0.02,
|
||||
roi={"0": 0.10},
|
||||
profit_perc=-0.0,
|
||||
use_exit_signal=True,
|
||||
trades=[
|
||||
BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3, is_short=False),
|
||||
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=3, close_tick=5, is_short=True),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
tc1,
|
||||
|
@ -1176,6 +1240,9 @@ TESTS = [
|
|||
tc51,
|
||||
tc52,
|
||||
tc53,
|
||||
tc54,
|
||||
tc55,
|
||||
tc56,
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1899,7 +1899,7 @@ def test_backtest_multi_pair_long_short_switch(
|
|||
|
||||
if use_detail:
|
||||
# Backtest loop is called once per candle per pair
|
||||
assert bl_spy.call_count == 1071
|
||||
assert bl_spy.call_count == 1523
|
||||
else:
|
||||
assert bl_spy.call_count == 479
|
||||
|
||||
|
@ -1909,7 +1909,7 @@ def test_backtest_multi_pair_long_short_switch(
|
|||
assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0
|
||||
|
||||
# Expect 26 results initially
|
||||
assert len(results["results"]) == 30
|
||||
assert len(results["results"]) == 53
|
||||
|
||||
|
||||
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
|
|
Loading…
Reference in New Issue
Block a user