Merge remote-tracking branch 'origin/develop' into feature/proceed-exit-while-open-order
This commit is contained in:
@@ -90,13 +90,6 @@ def test_historic_trades(mocker, default_conf, trades_history_df):
|
||||
assert isinstance(data, DataFrame)
|
||||
assert len(data) == len(trades_history_df)
|
||||
|
||||
# Random other runmode
|
||||
default_conf["runmode"] = RunMode.UTIL_EXCHANGE
|
||||
dp = DataProvider(default_conf, None)
|
||||
data = dp.trades("UNITTEST/BTC", "5m")
|
||||
assert isinstance(data, DataFrame)
|
||||
assert len(data) == 0
|
||||
|
||||
|
||||
def test_historic_ohlcv_dataformat(mocker, default_conf, ohlcv_history):
|
||||
hdf5loadmock = MagicMock(return_value=ohlcv_history)
|
||||
|
||||
@@ -7,6 +7,7 @@ import pytest
|
||||
from freqtrade.commands.analyze_commands import start_analysis_entries_exits
|
||||
from freqtrade.commands.optimize_commands import start_backtesting
|
||||
from freqtrade.enums import ExitType
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from tests.conftest import get_args, patch_exchange, patched_configuration_load_config_file
|
||||
|
||||
@@ -18,7 +19,9 @@ def entryexitanalysis_cleanup() -> None:
|
||||
Backtesting.cleanup()
|
||||
|
||||
|
||||
def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, user_dir, capsys):
|
||||
def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
|
||||
default_conf, mocker, caplog, testdatadir, user_dir, capsys
|
||||
):
|
||||
caplog.set_level(logging.INFO)
|
||||
(user_dir / "backtest_results").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -158,6 +161,15 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, use
|
||||
assert "34.049" in captured.out
|
||||
assert "0.104" in captured.out
|
||||
assert "52.829" in captured.out
|
||||
# assert indicator list
|
||||
assert "close (entry)" in captured.out
|
||||
assert "0.016" in captured.out
|
||||
assert "rsi (entry)" in captured.out
|
||||
assert "54.320" in captured.out
|
||||
assert "close (exit)" in captured.out
|
||||
assert "rsi (exit)" in captured.out
|
||||
assert "52.829" in captured.out
|
||||
assert "profit_abs" in captured.out
|
||||
|
||||
# test group 1
|
||||
args = get_args(base_args + ["--analysis-groups", "1"])
|
||||
@@ -245,3 +257,306 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, use
|
||||
start_analysis_entries_exits(args)
|
||||
captured = capsys.readouterr()
|
||||
assert "no rejected signals" in captured.out
|
||||
|
||||
|
||||
def test_backtest_analysis_with_invalid_config(
|
||||
default_conf, mocker, caplog, testdatadir, user_dir, capsys
|
||||
):
|
||||
caplog.set_level(logging.INFO)
|
||||
(user_dir / "backtest_results").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
default_conf.update(
|
||||
{
|
||||
"use_exit_signal": True,
|
||||
"exit_profit_only": False,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": False,
|
||||
}
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
result1 = pd.DataFrame(
|
||||
{
|
||||
"pair": ["ETH/BTC", "LTC/BTC", "ETH/BTC", "LTC/BTC"],
|
||||
"profit_ratio": [0.025, 0.05, -0.1, -0.05],
|
||||
"profit_abs": [0.5, 2.0, -4.0, -2.0],
|
||||
"open_date": pd.to_datetime(
|
||||
[
|
||||
"2018-01-29 18:40:00",
|
||||
"2018-01-30 03:30:00",
|
||||
"2018-01-30 08:10:00",
|
||||
"2018-01-31 13:30:00",
|
||||
],
|
||||
utc=True,
|
||||
),
|
||||
"close_date": pd.to_datetime(
|
||||
[
|
||||
"2018-01-29 20:45:00",
|
||||
"2018-01-30 05:35:00",
|
||||
"2018-01-30 09:10:00",
|
||||
"2018-01-31 15:00:00",
|
||||
],
|
||||
utc=True,
|
||||
),
|
||||
"trade_duration": [235, 40, 60, 90],
|
||||
"is_open": [False, False, False, False],
|
||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||
"open_rate": [0.104445, 0.10302485, 0.10302485, 0.10302485],
|
||||
"close_rate": [0.104969, 0.103541, 0.102041, 0.102541],
|
||||
"is_short": [False, False, False, False],
|
||||
"enter_tag": [
|
||||
"enter_tag_long_a",
|
||||
"enter_tag_long_b",
|
||||
"enter_tag_long_a",
|
||||
"enter_tag_long_b",
|
||||
],
|
||||
"exit_reason": [
|
||||
ExitType.ROI.value,
|
||||
ExitType.EXIT_SIGNAL.value,
|
||||
ExitType.STOP_LOSS.value,
|
||||
ExitType.TRAILING_STOP_LOSS.value,
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
backtestmock = MagicMock(
|
||||
side_effect=[
|
||||
{
|
||||
"results": result1,
|
||||
"config": default_conf,
|
||||
"locks": [],
|
||||
"rejected_signals": 20,
|
||||
"timedout_entry_orders": 0,
|
||||
"timedout_exit_orders": 0,
|
||||
"canceled_trade_entries": 0,
|
||||
"canceled_entry_orders": 0,
|
||||
"replaced_entry_orders": 0,
|
||||
"final_balance": 1000,
|
||||
}
|
||||
]
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlistmanager.PairListManager.whitelist",
|
||||
PropertyMock(return_value=["ETH/BTC", "LTC/BTC", "DASH/BTC"]),
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock)
|
||||
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
args = [
|
||||
"backtesting",
|
||||
"--config",
|
||||
"config.json",
|
||||
"--datadir",
|
||||
str(testdatadir),
|
||||
"--user-data-dir",
|
||||
str(user_dir),
|
||||
"--timeframe",
|
||||
"5m",
|
||||
"--timerange",
|
||||
"1515560100-1517287800",
|
||||
"--export",
|
||||
"signals",
|
||||
"--cache",
|
||||
"none",
|
||||
]
|
||||
args = get_args(args)
|
||||
start_backtesting(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "BACKTESTING REPORT" in captured.out
|
||||
assert "EXIT REASON STATS" in captured.out
|
||||
assert "LEFT OPEN TRADES REPORT" in captured.out
|
||||
|
||||
base_args = [
|
||||
"backtesting-analysis",
|
||||
"--config",
|
||||
"config.json",
|
||||
"--datadir",
|
||||
str(testdatadir),
|
||||
"--user-data-dir",
|
||||
str(user_dir),
|
||||
]
|
||||
|
||||
# test with both entry and exit only arguments
|
||||
args = get_args(
|
||||
base_args
|
||||
+ [
|
||||
"--analysis-groups",
|
||||
"0",
|
||||
"--indicator-list",
|
||||
"close",
|
||||
"rsi",
|
||||
"profit_abs",
|
||||
"--entry-only",
|
||||
"--exit-only",
|
||||
]
|
||||
)
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r"Cannot use --entry-only and --exit-only at the same time. Please choose one.",
|
||||
):
|
||||
start_analysis_entries_exits(args)
|
||||
|
||||
|
||||
def test_backtest_analysis_on_entry_and_rejected_signals_only_entry_signals(
|
||||
default_conf, mocker, caplog, testdatadir, user_dir, capsys
|
||||
):
|
||||
caplog.set_level(logging.INFO)
|
||||
(user_dir / "backtest_results").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
default_conf.update(
|
||||
{
|
||||
"use_exit_signal": True,
|
||||
"exit_profit_only": False,
|
||||
"exit_profit_offset": 0.0,
|
||||
"ignore_roi_if_entry_signal": False,
|
||||
}
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
result1 = pd.DataFrame(
|
||||
{
|
||||
"pair": ["ETH/BTC", "LTC/BTC", "ETH/BTC", "LTC/BTC"],
|
||||
"profit_ratio": [0.025, 0.05, -0.1, -0.05],
|
||||
"profit_abs": [0.5, 2.0, -4.0, -2.0],
|
||||
"open_date": pd.to_datetime(
|
||||
[
|
||||
"2018-01-29 18:40:00",
|
||||
"2018-01-30 03:30:00",
|
||||
"2018-01-30 08:10:00",
|
||||
"2018-01-31 13:30:00",
|
||||
],
|
||||
utc=True,
|
||||
),
|
||||
"close_date": pd.to_datetime(
|
||||
[
|
||||
"2018-01-29 20:45:00",
|
||||
"2018-01-30 05:35:00",
|
||||
"2018-01-30 09:10:00",
|
||||
"2018-01-31 15:00:00",
|
||||
],
|
||||
utc=True,
|
||||
),
|
||||
"trade_duration": [235, 40, 60, 90],
|
||||
"is_open": [False, False, False, False],
|
||||
"stake_amount": [0.01, 0.01, 0.01, 0.01],
|
||||
"open_rate": [0.104445, 0.10302485, 0.10302485, 0.10302485],
|
||||
"close_rate": [0.104969, 0.103541, 0.102041, 0.102541],
|
||||
"is_short": [False, False, False, False],
|
||||
"enter_tag": [
|
||||
"enter_tag_long_a",
|
||||
"enter_tag_long_b",
|
||||
"enter_tag_long_a",
|
||||
"enter_tag_long_b",
|
||||
],
|
||||
"exit_reason": [
|
||||
ExitType.ROI.value,
|
||||
ExitType.EXIT_SIGNAL.value,
|
||||
ExitType.STOP_LOSS.value,
|
||||
ExitType.TRAILING_STOP_LOSS.value,
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
backtestmock = MagicMock(
|
||||
side_effect=[
|
||||
{
|
||||
"results": result1,
|
||||
"config": default_conf,
|
||||
"locks": [],
|
||||
"rejected_signals": 20,
|
||||
"timedout_entry_orders": 0,
|
||||
"timedout_exit_orders": 0,
|
||||
"canceled_trade_entries": 0,
|
||||
"canceled_entry_orders": 0,
|
||||
"replaced_entry_orders": 0,
|
||||
"final_balance": 1000,
|
||||
}
|
||||
]
|
||||
)
|
||||
mocker.patch(
|
||||
"freqtrade.plugins.pairlistmanager.PairListManager.whitelist",
|
||||
PropertyMock(return_value=["ETH/BTC", "LTC/BTC", "DASH/BTC"]),
|
||||
)
|
||||
mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock)
|
||||
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
args = [
|
||||
"backtesting",
|
||||
"--config",
|
||||
"config.json",
|
||||
"--datadir",
|
||||
str(testdatadir),
|
||||
"--user-data-dir",
|
||||
str(user_dir),
|
||||
"--timeframe",
|
||||
"5m",
|
||||
"--timerange",
|
||||
"1515560100-1517287800",
|
||||
"--export",
|
||||
"signals",
|
||||
"--cache",
|
||||
"none",
|
||||
]
|
||||
args = get_args(args)
|
||||
start_backtesting(args)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "BACKTESTING REPORT" in captured.out
|
||||
assert "EXIT REASON STATS" in captured.out
|
||||
assert "LEFT OPEN TRADES REPORT" in captured.out
|
||||
|
||||
base_args = [
|
||||
"backtesting-analysis",
|
||||
"--config",
|
||||
"config.json",
|
||||
"--datadir",
|
||||
str(testdatadir),
|
||||
"--user-data-dir",
|
||||
str(user_dir),
|
||||
]
|
||||
|
||||
# test group 0 and indicator list
|
||||
args = get_args(
|
||||
base_args
|
||||
+ [
|
||||
"--analysis-groups",
|
||||
"0",
|
||||
"--indicator-list",
|
||||
"close",
|
||||
"rsi",
|
||||
"profit_abs",
|
||||
"--entry-only",
|
||||
]
|
||||
)
|
||||
start_analysis_entries_exits(args)
|
||||
captured = capsys.readouterr()
|
||||
assert "LTC/BTC" in captured.out
|
||||
assert "ETH/BTC" in captured.out
|
||||
assert "enter_tag_long_a" in captured.out
|
||||
assert "enter_tag_long_b" in captured.out
|
||||
assert "exit_signal" in captured.out
|
||||
assert "roi" in captured.out
|
||||
assert "stop_loss" in captured.out
|
||||
assert "trailing_stop_loss" in captured.out
|
||||
assert "0.5" in captured.out
|
||||
assert "-4" in captured.out
|
||||
assert "-2" in captured.out
|
||||
assert "-3.5" in captured.out
|
||||
assert "50" in captured.out
|
||||
assert "0" in captured.out
|
||||
assert "0.016" in captured.out
|
||||
assert "34.049" in captured.out
|
||||
assert "0.104" in captured.out
|
||||
assert "52.829" in captured.out
|
||||
# assert indicator list
|
||||
assert "close" in captured.out
|
||||
assert "close (entry)" not in captured.out
|
||||
assert "0.016" in captured.out
|
||||
assert "rsi (entry)" not in captured.out
|
||||
assert "rsi" in captured.out
|
||||
assert "54.320" in captured.out
|
||||
assert "close (exit)" not in captured.out
|
||||
assert "rsi (exit)" not in captured.out
|
||||
assert "52.829" in captured.out
|
||||
assert "profit_abs" in captured.out
|
||||
|
||||
@@ -123,12 +123,12 @@ def test_load_data_startup_candles(mocker, testdatadir) -> None:
|
||||
|
||||
@pytest.mark.parametrize("candle_type", ["mark", ""])
|
||||
def test_load_data_with_new_pair_1min(
|
||||
ohlcv_history_list, mocker, caplog, default_conf, tmp_path, candle_type
|
||||
ohlcv_history, mocker, caplog, default_conf, tmp_path, candle_type
|
||||
) -> None:
|
||||
"""
|
||||
Test load_pair_history() with 1 min timeframe
|
||||
"""
|
||||
mocker.patch(f"{EXMS}.get_historic_ohlcv", return_value=ohlcv_history_list)
|
||||
mocker.patch(f"{EXMS}.get_historic_ohlcv", return_value=ohlcv_history)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file = tmp_path / "MEME_BTC-1m.feather"
|
||||
|
||||
@@ -303,9 +303,9 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
|
||||
],
|
||||
)
|
||||
def test_download_pair_history(
|
||||
ohlcv_history_list, mocker, default_conf, tmp_path, candle_type, subdir, file_tail
|
||||
ohlcv_history, mocker, default_conf, tmp_path, candle_type, subdir, file_tail
|
||||
) -> None:
|
||||
mocker.patch(f"{EXMS}.get_historic_ohlcv", return_value=ohlcv_history_list)
|
||||
mocker.patch(f"{EXMS}.get_historic_ohlcv", return_value=ohlcv_history)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file1_1 = tmp_path / f"{subdir}MEME_BTC-1m{file_tail}.feather"
|
||||
file1_5 = tmp_path / f"{subdir}MEME_BTC-5m{file_tail}.feather"
|
||||
@@ -351,16 +351,12 @@ def test_download_pair_history(
|
||||
assert file2_5.is_file()
|
||||
|
||||
|
||||
def test_download_pair_history2(mocker, default_conf, testdatadir) -> None:
|
||||
tick = [
|
||||
[1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839],
|
||||
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199],
|
||||
]
|
||||
def test_download_pair_history2(mocker, default_conf, testdatadir, ohlcv_history) -> None:
|
||||
json_dump_mock = mocker.patch(
|
||||
"freqtrade.data.history.datahandlers.featherdatahandler.FeatherDataHandler.ohlcv_store",
|
||||
return_value=None,
|
||||
)
|
||||
mocker.patch(f"{EXMS}.get_historic_ohlcv", return_value=tick)
|
||||
mocker.patch(f"{EXMS}.get_historic_ohlcv", return_value=ohlcv_history)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
_download_pair_history(
|
||||
datadir=testdatadir,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums.marginmode import MarginMode
|
||||
from freqtrade.enums.tradingmode import TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
@@ -27,13 +24,11 @@ def test_additional_exchange_init_bybit(default_conf, mocker, caplog):
|
||||
|
||||
api_mock.set_position_mode.reset_mock()
|
||||
api_mock.is_unified_enabled = MagicMock(return_value=[False, True])
|
||||
with pytest.raises(OperationalException, match=r"Bybit: Unified account is not supported.*"):
|
||||
get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock)
|
||||
assert log_has("Bybit: Unified account.", caplog)
|
||||
# exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock)
|
||||
# assert api_mock.set_position_mode.call_count == 1
|
||||
# assert api_mock.is_unified_enabled.call_count == 1
|
||||
# assert exchange.unified_account is True
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock)
|
||||
assert log_has("Bybit: Unified account. Assuming dedicated subaccount for this bot.", caplog)
|
||||
assert api_mock.set_position_mode.call_count == 1
|
||||
assert api_mock.is_unified_enabled.call_count == 1
|
||||
assert exchange.unified_account is True
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker, default_conf, api_mock, "bybit", "additional_exchange_init", "set_position_mode"
|
||||
|
||||
+42
-173
@@ -255,7 +255,6 @@ def test_init_exception(default_conf, mocker):
|
||||
def test_exchange_resolver(default_conf, mocker, caplog):
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=MagicMock()))
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -555,7 +554,6 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
|
||||
|
||||
def test__load_async_markets(default_conf, mocker, caplog):
|
||||
mocker.patch(f"{EXMS}._init_ccxt")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
@@ -567,7 +565,15 @@ def test__load_async_markets(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
exchange._api_async.load_markets = get_mock_coro(side_effect=ccxt.BaseError("deadbeef"))
|
||||
with pytest.raises(ccxt.BaseError, match="deadbeef"):
|
||||
with pytest.raises(TemporaryError, match="deadbeef"):
|
||||
exchange._load_async_markets()
|
||||
|
||||
exchange._api_async.load_markets = get_mock_coro(side_effect=ccxt.DDoSProtection("deadbeef"))
|
||||
with pytest.raises(DDosProtection, match="deadbeef"):
|
||||
exchange._load_async_markets()
|
||||
|
||||
exchange._api_async.load_markets = get_mock_coro(side_effect=ccxt.OperationFailed("deadbeef"))
|
||||
with pytest.raises(TemporaryError, match="deadbeef"):
|
||||
exchange._load_async_markets()
|
||||
|
||||
|
||||
@@ -576,7 +582,6 @@ def test__load_markets(default_conf, mocker, caplog):
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = get_mock_coro(side_effect=ccxt.BaseError("SomeError"))
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -630,6 +635,21 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine):
|
||||
exchange.reload_markets()
|
||||
assert lam_spy.call_count == 0
|
||||
|
||||
# Another reload should happen but it fails.
|
||||
time_machine.move_to(start_dt + timedelta(minutes=51), tick=False)
|
||||
api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("LoadError"))
|
||||
|
||||
exchange.reload_markets(force=False)
|
||||
assert exchange.markets == updated_markets
|
||||
assert lam_spy.call_count == 1
|
||||
# Tried once, failed
|
||||
|
||||
lam_spy.reset_mock()
|
||||
# When forceing (bot startup), it should retry 3 times.
|
||||
exchange.reload_markets(force=True)
|
||||
assert lam_spy.call_count == 4
|
||||
assert exchange.markets == updated_markets
|
||||
|
||||
|
||||
def test_reload_markets_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
@@ -661,7 +681,6 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
Exchange(default_conf)
|
||||
@@ -679,7 +698,6 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
with pytest.raises(
|
||||
ConfigurationError,
|
||||
@@ -732,147 +750,6 @@ def test_get_pair_base_currency(default_conf, mocker, pair, expected):
|
||||
assert ex.get_pair_base_currency(pair) == expected
|
||||
|
||||
|
||||
def test_validate_pairs(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
id_mock = PropertyMock(return_value="test_exchange")
|
||||
type(api_mock).id = id_mock
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(
|
||||
f"{EXMS}._load_async_markets",
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
"XRP/BTC": {"quote": "BTC"},
|
||||
"NEO/BTC": {"quote": "BTC"},
|
||||
},
|
||||
)
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
# test exchange.validate_pairs directly
|
||||
# No assert - but this should not fail (!)
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_pairs_not_available(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).markets = PropertyMock(
|
||||
return_value={"XRP/BTC": {"inactive": True, "base": "XRP", "quote": "BTC"}}
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
|
||||
with pytest.raises(OperationalException, match=r"not available"):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
api_mock = MagicMock()
|
||||
mocker.patch(f"{EXMS}.name", PropertyMock(return_value="Binance"))
|
||||
|
||||
type(api_mock).markets = PropertyMock(return_value={})
|
||||
mocker.patch(f"{EXMS}._init_ccxt", api_mock)
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
|
||||
with pytest.raises(OperationalException, match=r"Pair ETH/BTC is not available on Binance"):
|
||||
Exchange(default_conf)
|
||||
|
||||
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value={}))
|
||||
Exchange(default_conf)
|
||||
assert log_has("Unable to validate pairs (assuming they are correct).", caplog)
|
||||
|
||||
|
||||
def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
"XRP/BTC": {"quote": "BTC", "info": {"prohibitedIn": ["US"]}},
|
||||
"NEO/BTC": {"quote": "BTC", "info": "TestString"}, # info can also be a string ...
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
|
||||
Exchange(default_conf)
|
||||
assert log_has(
|
||||
"Pair XRP/BTC is restricted for some users on this exchange."
|
||||
"Please check if you are impacted by this restriction "
|
||||
"on the exchange and eventually remove XRP/BTC from your whitelist.",
|
||||
caplog,
|
||||
)
|
||||
|
||||
|
||||
def test_validate_pairs_stakecompatibility(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
"XRP/BTC": {"quote": "BTC"},
|
||||
"NEO/BTC": {"quote": "BTC"},
|
||||
"HELLO-WORLD": {"quote": "BTC"},
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
default_conf["stake_currency"] = ""
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
"XRP/BTC": {"quote": "BTC"},
|
||||
"NEO/BTC": {"quote": "BTC"},
|
||||
"HELLO-WORLD": {"quote": "BTC"},
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
|
||||
Exchange(default_conf)
|
||||
assert type(api_mock).load_markets.call_count == 1
|
||||
|
||||
|
||||
def test_validate_pairs_stakecompatibility_fail(default_conf, mocker):
|
||||
default_conf["exchange"]["pair_whitelist"].append("HELLO-WORLD")
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = get_mock_coro(
|
||||
return_value={
|
||||
"ETH/BTC": {"quote": "BTC"},
|
||||
"LTC/BTC": {"quote": "BTC"},
|
||||
"XRP/BTC": {"quote": "BTC"},
|
||||
"NEO/BTC": {"quote": "BTC"},
|
||||
"HELLO-WORLD": {"quote": "USDT"},
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
|
||||
with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("timeframe", [("5m"), ("1m"), ("15m"), ("1h")])
|
||||
def test_validate_timeframes(default_conf, mocker, timeframe):
|
||||
default_conf["timeframe"] = timeframe
|
||||
@@ -884,7 +761,6 @@ def test_validate_timeframes(default_conf, mocker, timeframe):
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
Exchange(default_conf)
|
||||
@@ -902,7 +778,6 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
with pytest.raises(
|
||||
@@ -932,7 +807,6 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
@@ -954,7 +828,6 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs", MagicMock())
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
@@ -976,7 +849,6 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
||||
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
mocker.patch(f"{EXMS}.validate_required_startup_candles")
|
||||
@@ -993,7 +865,6 @@ def test_validate_pricing(default_conf, mocker):
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.name", "Binance")
|
||||
@@ -1028,7 +899,6 @@ def test_validate_ordertypes(default_conf, mocker):
|
||||
type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True})
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -1087,7 +957,6 @@ def test_validate_ordertypes_stop_advanced(default_conf, mocker, exchange_name,
|
||||
type(api_mock).has = PropertyMock(return_value={"createMarketOrder": True})
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
@@ -1112,7 +981,6 @@ def test_validate_order_types_not_in_config(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
|
||||
mocker.patch(f"{EXMS}.reload_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
@@ -1128,7 +996,6 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
|
||||
mocker.patch(f"{EXMS}._init_ccxt", api_mock)
|
||||
mocker.patch(f"{EXMS}.validate_timeframes")
|
||||
mocker.patch(f"{EXMS}._load_async_markets")
|
||||
mocker.patch(f"{EXMS}.validate_pairs")
|
||||
mocker.patch(f"{EXMS}.validate_pricing")
|
||||
mocker.patch(f"{EXMS}.validate_stakecurrency")
|
||||
|
||||
@@ -2217,24 +2084,29 @@ def test___now_is_time_to_refresh(default_conf, mocker, exchange_name, time_mach
|
||||
assert exchange._now_is_time_to_refresh(pair, "5m", candle_type) is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
@pytest.mark.parametrize("candle_type", ["mark", ""])
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type):
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
|
||||
ohlcv = [
|
||||
[
|
||||
dt_ts(), # unix timestamp ms
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
]
|
||||
]
|
||||
pair = "ETH/BTC"
|
||||
calls = 0
|
||||
now = dt_now()
|
||||
|
||||
async def mock_candle_hist(pair, timeframe, candle_type, since_ms):
|
||||
return pair, timeframe, candle_type, ohlcv, True
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
ohlcv = [
|
||||
[
|
||||
dt_ts(now + timedelta(minutes=5 * (calls + i))), # unix timestamp ms
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
]
|
||||
for i in range(2)
|
||||
]
|
||||
return (pair, timeframe, candle_type, ohlcv, True)
|
||||
|
||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||
# one_call calculation * 1.8 should do 2 calls
|
||||
@@ -2245,7 +2117,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_
|
||||
)
|
||||
|
||||
assert exchange._async_get_candle_history.call_count == 2
|
||||
# Returns twice the above OHLCV data
|
||||
# Returns twice the above OHLCV data after truncating the open candle.
|
||||
assert len(ret) == 2
|
||||
assert log_has_re(r"Downloaded data for .* with length .*\.", caplog)
|
||||
|
||||
@@ -4162,7 +4034,6 @@ def test_merge_ft_has_dict(default_conf, mocker):
|
||||
EXMS,
|
||||
_init_ccxt=MagicMock(return_value=MagicMock()),
|
||||
_load_async_markets=MagicMock(),
|
||||
validate_pairs=MagicMock(),
|
||||
validate_timeframes=MagicMock(),
|
||||
validate_stakecurrency=MagicMock(),
|
||||
validate_pricing=MagicMock(),
|
||||
@@ -4197,7 +4068,6 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
|
||||
EXMS,
|
||||
_init_ccxt=MagicMock(return_value=MagicMock()),
|
||||
_load_async_markets=MagicMock(),
|
||||
validate_pairs=MagicMock(),
|
||||
validate_timeframes=MagicMock(),
|
||||
validate_pricing=MagicMock(),
|
||||
markets=PropertyMock(return_value=markets),
|
||||
@@ -4477,7 +4347,6 @@ def test_get_markets(
|
||||
EXMS,
|
||||
_init_ccxt=MagicMock(return_value=MagicMock()),
|
||||
_load_async_markets=MagicMock(),
|
||||
validate_pairs=MagicMock(),
|
||||
validate_timeframes=MagicMock(),
|
||||
validate_pricing=MagicMock(),
|
||||
markets=PropertyMock(return_value=markets_static),
|
||||
|
||||
@@ -689,13 +689,29 @@ def test_process_trade_creation(
|
||||
assert trade.open_date is not None
|
||||
assert trade.exchange == "binance"
|
||||
assert trade.open_rate == ticker_usdt.return_value[ticker_side]
|
||||
assert pytest.approx(trade.amount) == 60 / ticker_usdt.return_value[ticker_side]
|
||||
# Trade opens with 0 amount. Only trade filling will set the amount
|
||||
assert pytest.approx(trade.amount) == 0
|
||||
assert pytest.approx(trade.amount_requested) == 60 / ticker_usdt.return_value[ticker_side]
|
||||
|
||||
assert log_has(
|
||||
f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
|
||||
"with stake_amount: 60.0 ...",
|
||||
caplog,
|
||||
)
|
||||
mocker.patch("freqtrade.freqtradebot.FreqtradeBot._check_and_execute_exit")
|
||||
|
||||
# Fill trade.
|
||||
freqtrade.process()
|
||||
trades = Trade.get_open_trades()
|
||||
assert len(trades) == 1
|
||||
trade = trades[0]
|
||||
assert trade is not None
|
||||
assert trade.is_open
|
||||
assert trade.open_date is not None
|
||||
assert trade.exchange == "binance"
|
||||
assert trade.open_rate == limit_order[entry_side(is_short)]["price"]
|
||||
# Filled trade has amount set to filled order amount
|
||||
assert pytest.approx(trade.amount) == limit_order[entry_side(is_short)]["filled"]
|
||||
|
||||
|
||||
def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> None:
|
||||
@@ -1685,7 +1701,7 @@ def test_handle_trade_roi(
|
||||
create_order=MagicMock(
|
||||
side_effect=[
|
||||
open_order,
|
||||
{"id": 1234553382},
|
||||
{"id": 1234553382, "amount": open_order["amount"]},
|
||||
]
|
||||
),
|
||||
get_fee=fee,
|
||||
@@ -2205,7 +2221,6 @@ def test_manage_open_orders_buy_exception(
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
validate_pairs=MagicMock(),
|
||||
fetch_ticker=ticker_usdt,
|
||||
fetch_order=MagicMock(side_effect=ExchangeError),
|
||||
cancel_order=cancel_order_mock,
|
||||
@@ -2884,7 +2899,7 @@ def test_execute_trade_exit_up(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
_dry_is_price_crossed=MagicMock(return_value=False),
|
||||
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
|
||||
)
|
||||
patch_whitelist(mocker, default_conf_usdt)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
@@ -2976,7 +2991,7 @@ def test_execute_trade_exit_down(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
_dry_is_price_crossed=MagicMock(return_value=False),
|
||||
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
|
||||
)
|
||||
patch_whitelist(mocker, default_conf_usdt)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
@@ -2999,7 +3014,7 @@ def test_execute_trade_exit_down(
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS),
|
||||
)
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
assert rpc_mock.call_count == 3
|
||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||
assert {
|
||||
"type": RPCMessageType.EXIT,
|
||||
@@ -3063,7 +3078,7 @@ def test_execute_trade_exit_custom_exit_price(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
_dry_is_price_crossed=MagicMock(return_value=False),
|
||||
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
|
||||
)
|
||||
config = deepcopy(default_conf_usdt)
|
||||
config["custom_price_max_distance_ratio"] = 0.1
|
||||
|
||||
@@ -1109,7 +1109,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
||||
EXMS,
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
_dry_is_price_crossed=MagicMock(return_value=False),
|
||||
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
|
||||
)
|
||||
patch_whitelist(mocker, default_conf_usdt)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
@@ -1136,7 +1136,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
||||
trade=trade, limit=trade.stop_loss, exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)
|
||||
)
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
# assert rpc_mock.call_count == 2
|
||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
assert {
|
||||
@@ -1169,7 +1169,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
||||
"cumulative_profit": 0.0,
|
||||
"stake_amount": pytest.approx(60),
|
||||
"is_final_exit": False,
|
||||
"final_profit_ratio": None,
|
||||
"final_profit_ratio": ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
|
||||
@@ -941,7 +941,7 @@ def test_backtest_one_detail(default_conf_usdt, mocker, testdatadir, use_detail)
|
||||
"use_detail,exp_funding_fee, exp_ff_updates",
|
||||
[
|
||||
(True, -0.018054162, 11),
|
||||
(False, -0.01780296, 5),
|
||||
(False, -0.01780296, 6),
|
||||
],
|
||||
)
|
||||
def test_backtest_one_detail_futures(
|
||||
@@ -1051,8 +1051,8 @@ def test_backtest_one_detail_futures(
|
||||
@pytest.mark.parametrize(
|
||||
"use_detail,entries,max_stake,ff_updates,expected_ff",
|
||||
[
|
||||
(True, 50, 3000, 54, -1.18038144),
|
||||
(False, 6, 360, 10, -0.14679994),
|
||||
(True, 50, 3000, 55, -1.18038144),
|
||||
(False, 6, 360, 11, -0.14679994),
|
||||
],
|
||||
)
|
||||
def test_backtest_one_detail_futures_funding_fees(
|
||||
|
||||
@@ -13,6 +13,12 @@ from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFu
|
||||
from tests.conftest import EXMS, get_args, log_has_re, patch_exchange
|
||||
|
||||
|
||||
IGNORE_BIASED_INDICATORS_CAPTION = (
|
||||
"Any indicators in 'biased_indicators' which are used within "
|
||||
"set_freqai_targets() can be ignored."
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lookahead_conf(default_conf_usdt, tmp_path):
|
||||
default_conf_usdt["user_data_dir"] = tmp_path
|
||||
@@ -133,6 +139,58 @@ def test_lookahead_helper_start(lookahead_conf, mocker) -> None:
|
||||
text_table_mock.reset_mock()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"indicators, expected_caption_text",
|
||||
[
|
||||
(
|
||||
["&indicator1", "indicator2"],
|
||||
IGNORE_BIASED_INDICATORS_CAPTION,
|
||||
),
|
||||
(
|
||||
["indicator1", "&indicator2"],
|
||||
IGNORE_BIASED_INDICATORS_CAPTION,
|
||||
),
|
||||
(
|
||||
["&indicator1", "&indicator2"],
|
||||
IGNORE_BIASED_INDICATORS_CAPTION,
|
||||
),
|
||||
(["indicator1", "indicator2"], None),
|
||||
([], None),
|
||||
],
|
||||
ids=(
|
||||
"First of two biased indicators starts with '&'",
|
||||
"Second of two biased indicators starts with '&'",
|
||||
"Both biased indicators start with '&'",
|
||||
"No biased indicators start with '&'",
|
||||
"Empty biased indicators list",
|
||||
),
|
||||
)
|
||||
def test_lookahead_helper_start__caption_based_on_indicators(
|
||||
indicators, expected_caption_text, lookahead_conf, mocker
|
||||
):
|
||||
"""Test that the table caption is only populated if a biased_indicator starts with '&'."""
|
||||
|
||||
single_mock = MagicMock()
|
||||
lookahead_analysis = LookaheadAnalysis(
|
||||
lookahead_conf,
|
||||
{"name": "strategy_test_v3_with_lookahead_bias"},
|
||||
)
|
||||
lookahead_analysis.current_analysis.false_indicators = indicators
|
||||
single_mock.return_value = lookahead_analysis
|
||||
text_table_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
"freqtrade.optimize.analysis.lookahead_helpers.LookaheadAnalysisSubFunctions",
|
||||
initialize_single_lookahead_analysis=single_mock,
|
||||
text_table_lookahead_analysis_instances=text_table_mock,
|
||||
)
|
||||
|
||||
LookaheadAnalysisSubFunctions.start(lookahead_conf)
|
||||
|
||||
text_table_mock.assert_called_once_with(
|
||||
lookahead_conf, [lookahead_analysis], caption=expected_caption_text
|
||||
)
|
||||
|
||||
|
||||
def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf):
|
||||
analysis = Analysis()
|
||||
analysis.has_bias = True
|
||||
@@ -199,6 +257,53 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
assert len(data) == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"caption",
|
||||
[
|
||||
"",
|
||||
"A test caption",
|
||||
None,
|
||||
False,
|
||||
],
|
||||
ids=(
|
||||
"Pass empty string",
|
||||
"Pass non-empty string",
|
||||
"Pass None",
|
||||
"Don't pass caption",
|
||||
),
|
||||
)
|
||||
def test_lookahead_helper_text_table_lookahead_analysis_instances__caption(
|
||||
caption,
|
||||
lookahead_conf,
|
||||
mocker,
|
||||
):
|
||||
"""Test that the caption is passed in the table kwargs when calling print_rich_table()."""
|
||||
|
||||
print_rich_table_mock = MagicMock()
|
||||
mocker.patch(
|
||||
"freqtrade.optimize.analysis.lookahead_helpers.print_rich_table",
|
||||
print_rich_table_mock,
|
||||
)
|
||||
lookahead_analysis = LookaheadAnalysis(
|
||||
lookahead_conf,
|
||||
{
|
||||
"name": "strategy_test_v3_with_lookahead_bias",
|
||||
"location": Path(lookahead_conf["strategy_path"], f"{lookahead_conf['strategy']}.py"),
|
||||
},
|
||||
)
|
||||
kwargs = {}
|
||||
if caption is not False:
|
||||
kwargs["caption"] = caption
|
||||
|
||||
LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
lookahead_conf, [lookahead_analysis], **kwargs
|
||||
)
|
||||
|
||||
assert print_rich_table_mock.call_args[-1]["table_kwargs"]["caption"] == (
|
||||
caption if caption is not False else None
|
||||
)
|
||||
|
||||
|
||||
def test_lookahead_helper_export_to_csv(lookahead_conf):
|
||||
import pandas as pd
|
||||
|
||||
|
||||
@@ -293,20 +293,25 @@ def test_store_backtest_candles(testdatadir, mocker):
|
||||
candle_dict = {"DefStrat": {"UNITTEST/BTC": pd.DataFrame()}}
|
||||
|
||||
# mock directory exporting
|
||||
store_backtest_analysis_results(testdatadir, candle_dict, {}, "2022_01_01_15_05_13")
|
||||
store_backtest_analysis_results(testdatadir, candle_dict, {}, {}, "2022_01_01_15_05_13")
|
||||
|
||||
assert dump_mock.call_count == 2
|
||||
assert dump_mock.call_count == 3
|
||||
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith("_signals.pkl")
|
||||
assert str(dump_mock.call_args_list[1][0][0]).endswith("_rejected.pkl")
|
||||
assert str(dump_mock.call_args_list[2][0][0]).endswith("_exited.pkl")
|
||||
|
||||
dump_mock.reset_mock()
|
||||
# mock file exporting
|
||||
filename = Path(testdatadir / "testresult")
|
||||
store_backtest_analysis_results(filename, candle_dict, {}, "2022_01_01_15_05_13")
|
||||
assert dump_mock.call_count == 2
|
||||
store_backtest_analysis_results(filename, candle_dict, {}, {}, "2022_01_01_15_05_13")
|
||||
assert dump_mock.call_count == 3
|
||||
assert isinstance(dump_mock.call_args_list[0][0][0], Path)
|
||||
# result will be testdatadir / testresult-<timestamp>_signals.pkl
|
||||
assert str(dump_mock.call_args_list[0][0][0]).endswith("_signals.pkl")
|
||||
assert str(dump_mock.call_args_list[1][0][0]).endswith("_rejected.pkl")
|
||||
assert str(dump_mock.call_args_list[2][0][0]).endswith("_exited.pkl")
|
||||
|
||||
dump_mock.reset_mock()
|
||||
|
||||
|
||||
@@ -315,7 +320,7 @@ def test_write_read_backtest_candles(tmp_path):
|
||||
|
||||
# test directory exporting
|
||||
sample_date = "2022_01_01_15_05_13"
|
||||
store_backtest_analysis_results(tmp_path, candle_dict, {}, sample_date)
|
||||
store_backtest_analysis_results(tmp_path, candle_dict, {}, {}, sample_date)
|
||||
stored_file = tmp_path / f"backtest-result-{sample_date}_signals.pkl"
|
||||
with stored_file.open("rb") as scp:
|
||||
pickled_signal_candles = joblib.load(scp)
|
||||
@@ -330,7 +335,7 @@ def test_write_read_backtest_candles(tmp_path):
|
||||
|
||||
# test file exporting
|
||||
filename = tmp_path / "testresult"
|
||||
store_backtest_analysis_results(filename, candle_dict, {}, sample_date)
|
||||
store_backtest_analysis_results(filename, candle_dict, {}, {}, sample_date)
|
||||
stored_file = tmp_path / f"testresult-{sample_date}_signals.pkl"
|
||||
with stored_file.open("rb") as scp:
|
||||
pickled_signal_candles = joblib.load(scp)
|
||||
|
||||
@@ -149,7 +149,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
# Different from "filled" response:
|
||||
response_unfilled.update(
|
||||
{
|
||||
"amount": 91.07468124,
|
||||
"amount": 0.0,
|
||||
"open_trade_value": 0.0,
|
||||
"stoploss_entry_dist": 0.0,
|
||||
"stoploss_entry_dist_ratio": 0.0,
|
||||
"profit_ratio": 0.0,
|
||||
"profit_pct": 0.0,
|
||||
"profit_abs": 0.0,
|
||||
@@ -762,7 +765,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
freqtradebot.enter_positions()
|
||||
# make an limit-buy open trade
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == "3")).first()
|
||||
filled_amount = trade.amount / 2
|
||||
filled_amount = trade.amount_requested / 2
|
||||
# Fetch order - it's open first, and closed after cancel_order is called.
|
||||
mocker.patch(
|
||||
f"{EXMS}.fetch_order",
|
||||
@@ -799,7 +802,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == "3")).first()
|
||||
amount = trade.amount
|
||||
amount = trade.amount_requested
|
||||
# make an limit-sell open order trade
|
||||
mocker.patch(
|
||||
f"{EXMS}.fetch_order",
|
||||
@@ -832,7 +835,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
||||
assert cancel_order_mock.call_count == 0
|
||||
|
||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == "4")).first()
|
||||
amount = trade.amount
|
||||
amount = trade.amount_requested
|
||||
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
||||
mocker.patch(
|
||||
f"{EXMS}.fetch_order",
|
||||
|
||||
@@ -365,13 +365,18 @@ def test_sync_wallet_dry(mocker, default_conf_usdt, fee):
|
||||
assert bal["NEO"].total == 10
|
||||
assert bal["XRP"].total == 10
|
||||
assert bal["LTC"].total == 2
|
||||
assert bal["USDT"].total == 922.74
|
||||
usdt_bal = bal["USDT"]
|
||||
assert usdt_bal.free == 922.74
|
||||
assert usdt_bal.total == 942.74
|
||||
assert usdt_bal.used == 20.0
|
||||
# sum of used and free should be total.
|
||||
assert usdt_bal.total == usdt_bal.free + usdt_bal.used
|
||||
|
||||
assert freqtrade.wallets.get_starting_balance() == default_conf_usdt["dry_run_wallet"]
|
||||
total = freqtrade.wallets.get_total("LTC")
|
||||
free = freqtrade.wallets.get_free("LTC")
|
||||
used = freqtrade.wallets.get_used("LTC")
|
||||
assert free != 0
|
||||
assert used != 0
|
||||
assert free + used == total
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user