2021-12-10 22:28:12 +00:00
|
|
|
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
|
|
|
|
2022-01-02 21:25:40 +00:00
|
|
|
from copy import deepcopy
|
2022-07-31 12:19:04 +00:00
|
|
|
from unittest.mock import MagicMock
|
2022-01-02 21:25:40 +00:00
|
|
|
|
2021-12-10 22:28:12 +00:00
|
|
|
import pandas as pd
|
2022-07-31 12:19:04 +00:00
|
|
|
import pytest
|
2021-12-10 22:28:12 +00:00
|
|
|
|
|
|
|
from freqtrade.configuration import TimeRange
|
|
|
|
from freqtrade.data import history
|
|
|
|
from freqtrade.data.history import get_timerange
|
2023-09-27 04:19:51 +00:00
|
|
|
from freqtrade.enums import ExitType
|
2021-12-10 22:28:12 +00:00
|
|
|
from freqtrade.optimize.backtesting import Backtesting
|
2023-05-14 15:46:56 +00:00
|
|
|
from freqtrade.util.datetime_helpers import dt_utc
|
2023-02-25 19:26:04 +00:00
|
|
|
from tests.conftest import EXMS, patch_exchange
|
2021-12-18 09:00:25 +00:00
|
|
|
|
2021-12-10 22:28:12 +00:00
|
|
|
|
|
|
|
def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None:
|
2024-05-12 13:29:14 +00:00
|
|
|
default_conf["use_exit_signal"] = False
|
|
|
|
default_conf["max_open_trades"] = 10
|
|
|
|
mocker.patch(f"{EXMS}.get_fee", fee)
|
|
|
|
mocker.patch(
|
|
|
|
"freqtrade.optimize.backtesting.amount_to_contract_precision",
|
|
|
|
lambda x, *args, **kwargs: round(x, 8),
|
|
|
|
)
|
2023-02-25 19:26:04 +00:00
|
|
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
|
2024-05-12 13:29:14 +00:00
|
|
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
|
2021-12-10 22:28:12 +00:00
|
|
|
patch_exchange(mocker)
|
2024-05-12 13:29:14 +00:00
|
|
|
default_conf.update(
|
|
|
|
{"stake_amount": 100.0, "dry_run_wallet": 1000.0, "strategy": "StrategyTestV3"}
|
|
|
|
)
|
2021-12-10 22:28:12 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
|
|
|
backtesting._set_strategy(backtesting.strategylist[0])
|
2024-05-12 13:29:14 +00:00
|
|
|
pair = "UNITTEST/BTC"
|
|
|
|
timerange = TimeRange("date", None, 1517227800, 0)
|
|
|
|
data = history.load_data(
|
|
|
|
datadir=testdatadir, timeframe="5m", pairs=["UNITTEST/BTC"], timerange=timerange
|
|
|
|
)
|
2021-12-24 10:38:43 +00:00
|
|
|
backtesting.strategy.position_adjustment_enable = True
|
2021-12-10 22:28:12 +00:00
|
|
|
processed = backtesting.strategy.advise_all_indicators(data)
|
|
|
|
min_date, max_date = get_timerange(processed)
|
|
|
|
result = backtesting.backtest(
|
2022-01-02 21:25:40 +00:00
|
|
|
processed=deepcopy(processed),
|
2021-12-10 22:28:12 +00:00
|
|
|
start_date=min_date,
|
|
|
|
end_date=max_date,
|
|
|
|
)
|
2024-05-12 13:29:14 +00:00
|
|
|
results = result["results"]
|
2021-12-10 22:28:12 +00:00
|
|
|
assert not results.empty
|
|
|
|
assert len(results) == 2
|
|
|
|
|
|
|
|
expected = pd.DataFrame(
|
2024-05-12 13:29:14 +00:00
|
|
|
{
|
|
|
|
"pair": [pair, pair],
|
|
|
|
"stake_amount": [500.0, 100.0],
|
|
|
|
"max_stake_amount": [500.0, 100],
|
|
|
|
"amount": [4806.87657523, 970.63960782],
|
|
|
|
"open_date": pd.to_datetime(
|
|
|
|
[dt_utc(2018, 1, 29, 18, 40, 0), dt_utc(2018, 1, 30, 3, 30, 0)], utc=True
|
|
|
|
),
|
|
|
|
"close_date": pd.to_datetime(
|
|
|
|
[dt_utc(2018, 1, 29, 22, 00, 0), dt_utc(2018, 1, 30, 4, 10, 0)], utc=True
|
|
|
|
),
|
|
|
|
"open_rate": [0.10401764891917063, 0.10302485],
|
|
|
|
"close_rate": [0.10453904064307624, 0.10354126528822055],
|
|
|
|
"fee_open": [0.0025, 0.0025],
|
|
|
|
"fee_close": [0.0025, 0.0025],
|
|
|
|
"trade_duration": [200, 40],
|
|
|
|
"profit_ratio": [0.0, 0.0],
|
|
|
|
"profit_abs": [0.0, 0.0],
|
|
|
|
"exit_reason": [ExitType.ROI.value, ExitType.ROI.value],
|
|
|
|
"initial_stop_loss_abs": [0.0940005, 0.092722365],
|
|
|
|
"initial_stop_loss_ratio": [-0.1, -0.1],
|
|
|
|
"stop_loss_abs": [0.0940005, 0.092722365],
|
|
|
|
"stop_loss_ratio": [-0.1, -0.1],
|
|
|
|
"min_rate": [0.10370188, 0.10300000000000001],
|
|
|
|
"max_rate": [0.10481985, 0.10388887000000001],
|
|
|
|
"is_open": [False, False],
|
|
|
|
"enter_tag": ["", ""],
|
|
|
|
"leverage": [1.0, 1.0],
|
|
|
|
"is_short": [False, False],
|
|
|
|
"open_timestamp": [1517251200000, 1517283000000],
|
|
|
|
"close_timestamp": [1517263200000, 1517285400000],
|
|
|
|
}
|
|
|
|
)
|
|
|
|
results_no = results.drop(columns=["orders"])
|
2024-02-26 18:17:50 +00:00
|
|
|
pd.testing.assert_frame_equal(results_no, expected, check_exact=True)
|
|
|
|
|
2021-12-10 22:28:12 +00:00
|
|
|
data_pair = processed[pair]
|
2024-05-12 13:29:14 +00:00
|
|
|
assert len(results.iloc[0]["orders"]) == 6
|
|
|
|
assert len(results.iloc[1]["orders"]) == 2
|
2022-05-26 05:17:22 +00:00
|
|
|
|
2021-12-10 22:28:12 +00:00
|
|
|
for _, t in results.iterrows():
|
|
|
|
ln = data_pair.loc[data_pair["date"] == t["open_date"]]
|
2024-04-18 20:51:25 +00:00
|
|
|
# Check open trade rate aligns to open rate
|
2021-12-10 22:28:12 +00:00
|
|
|
assert ln is not None
|
2024-04-18 20:51:25 +00:00
|
|
|
# check close trade rate aligns to close rate or is between high and low
|
2021-12-10 22:28:12 +00:00
|
|
|
ln = data_pair.loc[data_pair["date"] == t["close_date"]]
|
2024-05-12 13:29:14 +00:00
|
|
|
assert round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or round(
|
|
|
|
ln.iloc[0]["low"], 6
|
|
|
|
) < round(t["close_rate"], 6) < round(ln.iloc[0]["high"], 6)
|
2022-07-31 12:19:04 +00:00
|
|
|
|
|
|
|
|
2024-05-12 13:29:14 +00:00
|
|
|
@pytest.mark.parametrize("leverage", [1, 2])
|
2022-09-27 17:47:49 +00:00
|
|
|
def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, leverage) -> None:
|
2024-05-12 13:29:14 +00:00
|
|
|
default_conf["use_exit_signal"] = False
|
|
|
|
mocker.patch(f"{EXMS}.get_fee", fee)
|
2023-02-25 19:26:04 +00:00
|
|
|
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=10)
|
2024-05-12 13:29:14 +00:00
|
|
|
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf"))
|
2023-02-25 19:26:04 +00:00
|
|
|
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
|
2023-09-27 04:19:51 +00:00
|
|
|
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", return_value=(0.1, 0.1))
|
2024-05-12 13:29:14 +00:00
|
|
|
mocker.patch("freqtrade.optimize.backtesting.Backtesting._run_funding_fees")
|
2022-09-27 17:47:49 +00:00
|
|
|
|
2022-07-31 12:19:04 +00:00
|
|
|
patch_exchange(mocker)
|
2024-05-12 13:29:14 +00:00
|
|
|
default_conf.update(
|
|
|
|
{
|
|
|
|
"stake_amount": 100.0,
|
|
|
|
"dry_run_wallet": 1000.0,
|
|
|
|
"strategy": "StrategyTestV3",
|
|
|
|
"trading_mode": "futures",
|
|
|
|
"margin_mode": "isolated",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
default_conf["pairlists"] = [{"method": "StaticPairList", "allow_inactive": True}]
|
2022-07-31 12:19:04 +00:00
|
|
|
backtesting = Backtesting(default_conf)
|
2022-09-27 17:47:49 +00:00
|
|
|
backtesting._can_short = True
|
2022-07-31 12:19:04 +00:00
|
|
|
backtesting._set_strategy(backtesting.strategylist[0])
|
2024-05-12 13:29:14 +00:00
|
|
|
pair = "XRP/USDT:USDT"
|
2024-04-11 18:54:16 +00:00
|
|
|
row_enter = [
|
2024-05-12 13:29:14 +00:00
|
|
|
pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=0),
|
|
|
|
2.1, # Open
|
|
|
|
2.2, # High
|
|
|
|
1.9, # Low
|
|
|
|
2.1, # Close
|
|
|
|
1, # enter_long
|
|
|
|
0, # exit_long
|
|
|
|
0, # enter_short
|
|
|
|
0, # exit_short
|
|
|
|
"", # enter_tag
|
|
|
|
"", # exit_tag
|
|
|
|
]
|
2024-04-11 18:54:16 +00:00
|
|
|
# Exit row - with slightly different values
|
|
|
|
row_exit = [
|
2024-05-12 13:29:14 +00:00
|
|
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
|
|
|
|
2.2, # Open
|
|
|
|
2.3, # High
|
|
|
|
2.0, # Low
|
|
|
|
2.2, # Close
|
|
|
|
1, # enter_long
|
|
|
|
0, # exit_long
|
|
|
|
0, # enter_short
|
|
|
|
0, # exit_short
|
|
|
|
"", # enter_tag
|
|
|
|
"", # exit_tag
|
|
|
|
]
|
2022-09-27 17:47:49 +00:00
|
|
|
backtesting.strategy.leverage = MagicMock(return_value=leverage)
|
2024-05-12 13:29:14 +00:00
|
|
|
trade = backtesting._enter_trade(pair, row=row_enter, direction="long")
|
2024-04-11 18:54:16 +00:00
|
|
|
current_time = row_enter[0].to_pydatetime()
|
2022-07-31 12:19:04 +00:00
|
|
|
assert trade
|
|
|
|
assert pytest.approx(trade.stake_amount) == 100.0
|
2022-09-27 17:47:49 +00:00
|
|
|
assert pytest.approx(trade.amount) == 47.61904762 * leverage
|
2022-07-31 12:19:04 +00:00
|
|
|
assert len(trade.orders) == 1
|
|
|
|
backtesting.strategy.adjust_trade_position = MagicMock(return_value=None)
|
2023-09-27 04:29:03 +00:00
|
|
|
assert pytest.approx(trade.liquidation_price) == (0.10278333 if leverage == 1 else 1.2122249)
|
2022-07-31 12:19:04 +00:00
|
|
|
|
2024-04-11 18:54:16 +00:00
|
|
|
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_enter, current_time)
|
2022-07-31 12:19:04 +00:00
|
|
|
assert trade
|
|
|
|
assert pytest.approx(trade.stake_amount) == 100.0
|
2022-09-27 17:47:49 +00:00
|
|
|
assert pytest.approx(trade.amount) == 47.61904762 * leverage
|
2022-07-31 12:19:04 +00:00
|
|
|
assert len(trade.orders) == 1
|
|
|
|
# Increase position by 100
|
2024-05-12 13:29:14 +00:00
|
|
|
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(100, "PartIncrease"))
|
2022-07-31 12:19:04 +00:00
|
|
|
|
2024-04-11 18:54:16 +00:00
|
|
|
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_enter, current_time)
|
2022-07-31 12:19:04 +00:00
|
|
|
|
2024-04-11 18:53:58 +00:00
|
|
|
liq_price = 0.1038916 if leverage == 1 else 1.2127791
|
2022-07-31 12:19:04 +00:00
|
|
|
assert trade
|
|
|
|
assert pytest.approx(trade.stake_amount) == 200.0
|
2022-09-27 17:47:49 +00:00
|
|
|
assert pytest.approx(trade.amount) == 95.23809524 * leverage
|
2022-07-31 12:19:04 +00:00
|
|
|
assert len(trade.orders) == 2
|
2024-05-12 13:29:14 +00:00
|
|
|
assert trade.orders[-1].ft_order_tag == "PartIncrease"
|
2024-04-11 18:53:58 +00:00
|
|
|
assert pytest.approx(trade.liquidation_price) == liq_price
|
2022-07-31 12:19:04 +00:00
|
|
|
|
|
|
|
# Reduce by more than amount - no change to trade.
|
|
|
|
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-500)
|
2024-04-11 18:54:16 +00:00
|
|
|
current_time = row_exit[0].to_pydatetime()
|
2022-07-31 12:19:04 +00:00
|
|
|
|
2024-04-11 18:54:16 +00:00
|
|
|
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_exit, current_time)
|
2022-07-31 12:19:04 +00:00
|
|
|
|
|
|
|
assert trade
|
|
|
|
assert pytest.approx(trade.stake_amount) == 200.0
|
2022-09-27 17:47:49 +00:00
|
|
|
assert pytest.approx(trade.amount) == 95.23809524 * leverage
|
2022-07-31 12:19:04 +00:00
|
|
|
assert len(trade.orders) == 2
|
|
|
|
assert trade.nr_of_successful_entries == 2
|
2024-04-11 18:53:58 +00:00
|
|
|
assert pytest.approx(trade.liquidation_price) == liq_price
|
2022-07-31 12:19:04 +00:00
|
|
|
|
|
|
|
# Reduce position by 50
|
2024-05-12 13:29:14 +00:00
|
|
|
backtesting.strategy.adjust_trade_position = MagicMock(return_value=(-100, "partDecrease"))
|
2024-04-11 18:54:16 +00:00
|
|
|
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_exit, current_time)
|
2022-07-31 12:19:04 +00:00
|
|
|
|
|
|
|
assert trade
|
|
|
|
assert pytest.approx(trade.stake_amount) == 100.0
|
2022-09-27 17:47:49 +00:00
|
|
|
assert pytest.approx(trade.amount) == 47.61904762 * leverage
|
2022-07-31 12:19:04 +00:00
|
|
|
assert len(trade.orders) == 3
|
2024-05-12 13:29:14 +00:00
|
|
|
assert trade.orders[-1].ft_order_tag == "partDecrease"
|
2022-07-31 12:19:04 +00:00
|
|
|
assert trade.nr_of_successful_entries == 2
|
|
|
|
assert trade.nr_of_successful_exits == 1
|
2024-04-11 18:53:58 +00:00
|
|
|
assert pytest.approx(trade.liquidation_price) == liq_price
|
2022-07-31 12:19:04 +00:00
|
|
|
|
|
|
|
# Adjust below minimum
|
|
|
|
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-99)
|
2024-04-11 18:54:16 +00:00
|
|
|
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_exit, current_time)
|
2022-07-31 12:19:04 +00:00
|
|
|
|
|
|
|
assert trade
|
|
|
|
assert pytest.approx(trade.stake_amount) == 100.0
|
2022-09-27 17:47:49 +00:00
|
|
|
assert pytest.approx(trade.amount) == 47.61904762 * leverage
|
2022-07-31 12:19:04 +00:00
|
|
|
assert len(trade.orders) == 3
|
|
|
|
assert trade.nr_of_successful_entries == 2
|
|
|
|
assert trade.nr_of_successful_exits == 1
|
2024-04-11 18:53:58 +00:00
|
|
|
assert pytest.approx(trade.liquidation_price) == liq_price
|
2024-05-04 07:15:15 +00:00
|
|
|
|
|
|
|
# Adjust to close trade
|
|
|
|
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-trade.stake_amount)
|
|
|
|
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row_exit, current_time)
|
|
|
|
assert trade.is_open is False
|